diff --git a/.editorconfig b/.editorconfig index d72a75e..ee76204 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,7 +19,7 @@ insert_final_newline = true [*.{bat, cmd}] end_of_line = crlf -[*.{java, gradle, groovy, kt, sh}] +[*.{java, gradle, groovy, kt, sh, xml}] indent_size = 4 [*.md] diff --git a/.gitattributes b/.gitattributes index 07962a1..eaae227 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,6 +22,7 @@ *.less text *.sql text *.properties text +*.md text # unix style *.sh text eol=lf diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..36b705c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,36 @@ +name: CI + +# 在master分支发生push事件时触发。 +on: + push: + branches: + - master + +env: # 设置环境变量 + TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间) + +jobs: + build: # 自定义名称 + runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + # 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions + - name: Checkout + uses: actions/checkout@master + + # 指定 nodejs 版本 + - name: Use Nodejs ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + # 部署 + - name: Deploy + env: # 设置环境变量 + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} + run: npm install && npm run deploy diff --git a/.gitignore b/.gitignore index 4a99239..7d98dac 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ hs_err_pid* # maven plugin temp files .flattened-pom.xml -package-lock.json # ------------------------------- javascript ------------------------------- @@ -41,6 +40,8 @@ build dist _book _jsdoc +.temp +.deploy*/ # temp files *.log @@ -48,7 +49,11 @@ npm-debug.log* yarn-debug.log* yarn-error.log* bundle*.js +.DS_Store +Thumbs.db +db.json book.pdf +package-lock.json # ------------------------------- intellij ------------------------------- diff --git a/README.md b/README.md index 4f375fb..af71637 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,597 @@

- logo + logo

- license - build + + + star + + + + fork + + + + build + + + + code style + +

-

algorithm-tutorial

+

ALGORITHM

-> 算法、数据结构学习要点: -> -> 三分学,七分练 +> 💾 algorithm 是一个数据结构与算法学习笔记。 > -> 坚持 + 坚持 + 坚持 +> 掌握数据结构与算法,你看待问题的深度,解决问题的角度就会完全不一样。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/algorithm-tutorial/) | [Gitee](https://gitee.com/turnon/algorithm-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/algorithm-tutorial/) | [Gitee Pages](http://turnon.gitee.io/algorithm-tutorial/) -## 内容 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200702071922.png) - -- [算法概述](docs/overview.md) -- [数组](docs/array.md) -- [链表](docs/list.md) -- [栈](docs/stack.md) -- [队列](docs/queue.md) -- 递归 -- [排序](docs/sort.md) -- 查找 -- 跳表 -- [散列表](docs/hash.md) -- [树](docs/tree.md) -- [图](docs/graph.md) -- [堆](docs/heap.md) -- [字典树](docs/trie.md) -- 字符串 -- 贪心算法 -- 分治算法 -- 回溯算法 -- 动态规划 -- [算法代码模板](docs/algorithm-template.md) - -## 刷题 +## 📖 内容 -### 数组 +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) -- [三数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/三数之和.java) -- [两数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/两数之和.java) -- [二维数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/二维数组.java) -- [删除排序数组中的重复项](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/删除排序数组中的重复项.java) -- [加一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/加一.java) -- [在排序数组中查找元素的第一个和最后一个位置](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找元素的第一个和最后一个位置.java) -- [在排序数组中查找数字 I](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找数字I.java) -- [存在重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/存在重复元素.java) -- [对角线遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/对角线遍历.java) -- [寻找数组的中心索引](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/寻找数组的中心索引.java) -- [将数组分成和相等的三个部分](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/将数组分成和相等的三个部分.java) -- [数组二分查找](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组二分查找.java) -- [数组拆分 1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组拆分1.java) -- [旋转数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转数组.java) -- [旋转矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转矩阵.java) -- [最大连续 1 的个数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/最大连续1的个数.java) -- [杨辉三角](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角.java) -- [杨辉三角 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角2.java) -- [模拟 ArrayList1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList1.java) -- [模拟 ArrayList2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList2.java) -- [移动零](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移动零.java) -- [移除元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移除元素.java) -- [至少是其他数字两倍的最大数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/至少是其他数字两倍的最大数.java) -- [螺旋矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/螺旋矩阵.java) -- [长度最小的子数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/长度最小的子数组.java) -- [零矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/零矩阵.java) +- 综合 + - [数据结构和算法指南](docs/01.数据结构和算法/00.综合/01.数据结构和算法指南.md) + - [复杂度分析](docs/01.数据结构和算法/00.综合/02.复杂度分析.md) - 关键词:**`时间复杂度`**、**`空间复杂度`**、**`大 O 表示法`**、**`复杂度量级`** +- 线性表 + - [数组和链表](docs/01.数据结构和算法/01.线性表/01.数组和链表.md) - 关键词:**`线性表`**、**`一维数组`**、**`多维数组`**、**`随机访问`**、**`单链表`**、**`双链表`**、**`循环链表`** + - [栈和队列](docs/01.数据结构和算法/01.线性表/02.栈和队列.md) - 关键词:**`先进后出`**、**`后进先出`**、**`循环队列`** + - [线性表的查找](docs/01.数据结构和算法/01.线性表/11.线性表的查找.md) + - [线性表的排序](docs/01.数据结构和算法/01.线性表/12.线性表的排序.md) +- 树 + - [树和二叉树](docs/01.数据结构和算法/02.树/01.树和二叉树.md) + - [堆](docs/01.数据结构和算法/02.树/02.堆.md) + - [B+树](docs/01.数据结构和算法/02.树/03.B+树.md) + - [LSM 树](docs/01.数据结构和算法/02.树/04.LSM树.md) + - [字典树](docs/01.数据结构和算法/02.树/05.字典树.md) + - [红黑树](docs/01.数据结构和算法/02.树/06.红黑树.md) +- [哈希表](docs/01.数据结构和算法/03.哈希表.md) - 关键词:**`哈希函数`**、**`装载因子`**、**`哈希冲突`**、**`开放寻址法`**、**`拉链法`** +- [跳表](docs/01.数据结构和算法/04.跳表.md) - 关键词:**`多级索引`** +- [图](docs/01.数据结构和算法/05.图.md) + +## 💻 刷题 ### 链表 -- [两数相加](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/两数相加.java) -- [二进制链表转整数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/二进制链表转整数.java) -- [删除排序链表中的重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/删除排序链表中的重复元素.java) -- [单链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/单链表示例.java) -- [双链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/双链表示例.java) -- [反转链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/反转链表.java) -- [合并 K 个排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表.java) -- [合并 K 个排序链表解法 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表解法2.java) -- [合并两个有序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并两个有序链表.java) -- [回文链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/回文链表.java) -- [排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/排序链表.java) -- [环形链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/环形链表.java) -- [相交链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/相交链表.java) -- [移除重复节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除重复节点.java) -- [移除链表元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除链表元素.java) -- [返回倒数第 k 个节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/返回倒数第k个节点.java) -- [链表的中间结点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/链表的中间结点.java) - -### 栈 - -- [三合一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/三合一.java) -- [基本计算器](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/基本计算器.java) -- [最小栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈.java) -- [最小栈 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈2.java) -- [有效的括号](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/有效的括号.java) -- [栈排序](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/栈排序.java) -- [棒球比赛](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/棒球比赛.java) -- [比较含退格的字符串](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/比较含退格的字符串.java) -- [用栈实现队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用栈实现队列.java) -- [用队列实现栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用队列实现栈.java) - -### 队列 - -- [动态扩容数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/动态扩容数组实现的队列.java) -- [数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/数组实现的队列.java) -- [最近的请求次数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/最近的请求次数.java) -- [设计循环队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/设计循环队列.java) -- [链表实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/链表实现的队列.java) - -### 字符串 - -- [二进制求和](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java) -- [实现 strStr()](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java) -- [最长公共前缀](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java) -- [反转字符串](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java) -- [反转字符串中的单词](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java) -- [反转字符串中的单词 III](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java) +#### 基础操作 -### 树 +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [1290. 二进制链表转整数](https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/) | 💚 | ✔️ | + +#### 双指针技巧 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | 💚 | ✔️ | +| [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | 💛 | ✔️ | +| [160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | 💚 | ✔️ | +| [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | 💛 | ✔️ | +| [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | 💚 | ✔️ | +| [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | ❤️ | ✔️ | +| [86. 分隔链表](https://leetcode.cn/problems/partition-list/) | 💛 | ✔️ | +| [876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | 💚 | ✔️ | +| [面试题 02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) | 💚 | ✔️ | +| [面试题 02.01. 移除重复节点](https://leetcode.cn/problems/remove-duplicate-node-lcci/) | 💚 | ✔️ | +| [203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | 💚 | ✔️ | +| [328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | 💛 | ✔️ | +| [LCR 136. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) | 💚 | ✔️ | +| [83. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | 💚 | ✔️ | +| [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | 💛 | ✔️ | +| [2. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | 💛 | ✔️ | +| [445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | 💛 | ✔️ | + +#### 单链表反转 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------ | ---- | ------ | +| [61. 旋转链表](https://leetcode.cn/problems/rotate-list/) | 💛 | ✔️ | +| [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | 💚 | ✔️ | +| [92. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | 💛 | ✔️ | +| [25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | ❤️ | ✔️ | + +#### 分治 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------- | ---- | ------ | +| [148. 排序链表](https://leetcode.cn/problems/sort-list/) | 💛 | ❌ | + +#### 回文链表 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------- | ---- | ------ | +| [234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | 💚 | ✔️ | + +### 数组 + +#### 基础 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | 💚 | ✔️ | +| [747. 至少是其他数字两倍的最大数](https://leetcode.cn/problems/largest-number-at-least-twice-of-others/) | 💚 | ✔️ | + +#### 双指针技巧 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [27. 移除元素](https://leetcode.cn/problems/remove-element/) | 💚 | ✔️ | +| [283. 移动零](https://leetcode.cn/problems/move-zeroes/) | 💚 | ✔️ | +| [LCR 179. 查找总价格为目标值的两个商品](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) | 💚 | ✔️ | +| [1. 两数之和](https://leetcode.cn/problems/two-sum/) | 💚 | ✔️ | +| [67. 二进制求和](https://leetcode.cn/problems/add-binary/) | 💚 | ✔️ | +| [167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/)
[LCR 006. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/kLl5u1/) | 💛 | ✔️ | +| [26. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | 💚 | ✔️ | +| [80. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | 💛 | ✔️ | +| [344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | 💚 | ✔️ | +| [125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | 💚 | ✔️ | +| [5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 💛 | ✔️ | +| [75. 颜色分类](https://leetcode.cn/problems/sort-colors/) | 💛 | ✔️ | +| [88. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | 💚 | ✔️ | +| [977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | 💚 | ✔️ | +| [1329. 将矩阵按对角线排序](https://leetcode.cn/problems/sort-the-matrix-diagonally/) | 💛 | ✔️ | +| [1260. 二维网格迁移](https://leetcode.cn/problems/shift-2d-grid/) | 💚 | ✔️ | +| [867. 转置矩阵](https://leetcode.cn/problems/transpose-matrix/) | 💚 | ✔️ | +| [14. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | 💚 | ✔️ | +| [15. 三数之和](https://leetcode.cn/problems/3sum/) | 💛 | ❗ | +| [56. 合并区间](https://leetcode.cn/problems/merge-intervals/) | 💛 | ✔️ | + +#### 二维数组遍历 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | 💛 | ✔️ | +| [48. 旋转图像](https://leetcode.cn/problems/rotate-image/) | 💛 | ✔️ | +| [54. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)
[LCR 146. 螺旋遍历二维数组](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) | 💛 | ✔️ | +| [59. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | 💛 | ✔️ | +| [498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | 💛 | ❌ | +| [面试题 01.08. 零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) | 💛 | ✔️ | -- [N 叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N叉树的最大深度.java) +#### 滑动窗口算法 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [3. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | 💛 | ✔️ | +| [438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | 💛 | ✔️ | +| [567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | 💛 | ✔️ | +| [76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | ❤️ | ✔️ | +| [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | 💛 | ✔️ | +| [713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | 💛 | ✔️ | +| [1004. 最大连续 1 的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | 💛 | ✔️ | +| [424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | 💛 | ✔️ | +| [217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | 💚 | ✔️ | +| [219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | 💛 | ✔️ | +| [220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | ❤️ | ❌ | +| [209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | 💛 | ✔️ | +| [395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) | 💛 | ❌ | + +#### 二分查找算法 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------------------------------------------------------- | :--- | ------ | +| [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | 💛 | ✔️ | +| [35. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | 💚 | ✔️ | +| [704. 二分查找](https://leetcode.cn/problems/binary-search/) | 💚 | ✔️ | +| [LCR 172. 统计目标成绩的出现次数](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) | 💚 | ✔️ | +| [875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | 💛 | ❗ | +| [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | 💛 | ✔️ | +| [410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | ❤️ | ❌ | + +#### 前缀和数组 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | 💚 | ✔️ | +| [724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | 💚 | ✔️ | +| [1013. 将数组分成和相等的三个部分](https://leetcode.cn/problems/partition-array-into-three-parts-with-equal-sum/) | 💚 | ✔️ | +| [304. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) | 💛 | ❌ | + +#### 差分数组 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | ---- | ------ | +| [1094. 拼车](https://leetcode.cn/problems/car-pooling/) | 💛 | ✔️ | +| [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | 💛 | ✔️ | + +### 栈和队列 + +#### 队列 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------- | ---- | ------ | +| [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | 💚 | ✔️ | +| [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) | 💚 | ❗ | +| [622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | 💛 | ❌ | +| [641. 设计循环双端队列](https://leetcode.cn/problems/design-circular-deque/) | 💛 | ❌ | +| [1670. 设计前中后队列](https://leetcode.cn/problems/design-front-middle-back-queue/) | 💛 | ❌ | +| [2073. 买票需要的时间](https://leetcode.cn/problems/time-needed-to-buy-tickets/) | 💚 | ✔️ | +| [373. 查找和最小的 K 对数字](https://leetcode.cn/problems/find-k-pairs-with-smallest-sums/) | 💛 | ❌ | +| [378. 有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) | 💛 | ❌ | + +#### 栈 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------- | ---- | ------ | +| [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | 💚 | ✔️ | +| [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | 💚 | ✔️ | +| [682. 棒球比赛](https://leetcode.cn/problems/baseball-game/) | 💚 | ✔️ | +| [844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | 💚 | ✔️ | +| [71. 简化路径](https://leetcode.cn/problems/simplify-path/) | 💛 | ✔️ | +| [143. 重排链表](https://leetcode.cn/problems/reorder-list/) | 💛 | ✔️ | +| [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | 💛 | ✔️ | +| [388. 文件的最长绝对路径](https://leetcode.cn/problems/longest-absolute-file-path/) | 💛 | ❌ | +| [155. 最小栈](https://leetcode.cn/problems/min-stack/) | 💛 | ✔️ | +| [面试题 03.05. 栈排序](https://leetcode.cn/problems/sort-of-stacks-lcci/) | 💛 | ✔️ | +| [895. 最大频率栈](https://leetcode.cn/problems/maximum-frequency-stack/) | ❤️ | ❌ | + +#### 单调栈 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | 💚 | ✔️ | +| [503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | 💛 | ✔️ | +| [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/)
[剑指 Offer II 038. 每日温度](https://leetcode.cn/problems/iIQa4I/) | 💛 | ✔️ | +| [1019. 链表中的下一个更大节点](https://leetcode.cn/problems/next-greater-node-in-linked-list/) | 💛 | ✔️ | +| [1944. 队列中可以看到的人数](https://leetcode.cn/problems/number-of-visible-people-in-a-queue/) | ❤️ | ❌ | +| [1475. 商品折扣后的最终价格](https://leetcode.cn/problems/final-prices-with-a-special-discount-in-a-shop/) | 💛 | ✔️ | +| [901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | 💛 | ❌ | +| [402. 移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | 💛 | ❌ | +| [853. 车队](https://leetcode.cn/problems/car-fleet/) | 💛 | ❌ | +| [581. 最短无序连续子数组](https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/) | 💛 | ❌ | + +#### 单调队列 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [LCR 184. 设计自助结算系统](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) | 💛 | ❌ | +| [239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | ❤️ | ❌ | +| [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | 💛 | ❌ | +| [862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | ❤️ | ❌ | +| [918. 环形子数组的最大和](https://labuladong.online/algo/problem-set/monotonic-queue/#slug_maximum-sum-circular-subarray) | 💛 | ❌ | + +### 树 #### 二叉树 -- [二叉树中的最大路径和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树中的最大路径和.java) -- [二叉树的中序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的中序遍历.java) -- [二叉树的前序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的前序遍历.java) -- [二叉树的后序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的后序遍历.java) -- [二叉树的层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历.java) -- [二叉树的层次遍历 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历2.java) -- [二叉树的序列化与反序列化](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的序列化与反序列化.java) -- [二叉树的所有路径](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的所有路径.java) -- [二叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最大深度.java) -- [二叉树的最小深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最小深度.java) -- [二叉树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最近公共祖先.java) -- [二叉树的锯齿形层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的锯齿形层次遍历.java) -- [从先序遍历还原二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/从先序遍历还原二叉树.java) -- [叶子相似的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/叶子相似的树.java) -- [填充每个节点的下一个右侧节点指针](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针.java) -- [填充每个节点的下一个右侧节点指针 II](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针II.java) -- [对称二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/对称二叉树.java) -- [平衡二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/平衡二叉树.java) -- [相同的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/相同的树.java) -- [翻转二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/翻转二叉树.java) -- [路径总和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/路径总和.java) +| 题目 | 难度 | 掌握度 | +| ---------------------------------------------------------------------------------------------------- | ---- | ------ | +| [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | 💚 | ✔️ | +| [111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | 💚 | ✔️ | +| [543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | 💚 | ✔️ | +| [114. 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | 💛 | ✔️ | +| [226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | 💚 | ✔️ | +| [654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) | 💛 | ✔️ | +| [297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | ❤️ | ❗ | +| [222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) | 💚 | ✔️ | + +#### DFS + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------- | ---- | ------ | +| [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | 💚 | ✔️ | +| [94. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | 💚 | ✔️ | +| [145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | 💚 | ✔️ | +| [872. 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) | 💚 | ✔️ | + +#### 用「遍历」思维解题 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------- | ---- | ------ | +| [257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) | 💚 | ✔️ | +| [129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | 💛 | ✔️ | +| [199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | 💛 | ✔️ | +| [988. 从叶结点开始的最小字符串](https://leetcode.cn/problems/smallest-string-starting-from-leaf/) | 💛 | ✔️ | +| [1022. 从根到叶的二进制数之和](https://leetcode.cn/problems/sum-of-root-to-leaf-binary-numbers/) | 💚 | ✔️ | +| [1457. 二叉树中的伪回文路径](https://leetcode.cn/problems/pseudo-palindromic-paths-in-a-binary-tree/) | 💛 | ✔️ | +| [404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) | 💚 | ✔️ | +| [623. 在二叉树中增加一行](https://leetcode.cn/problems/add-one-row-to-tree/) | 💛 | ✔️ | +| [508. 出现次数最多的子树元素和](https://leetcode.cn/problems/most-frequent-subtree-sum/) | 💛 | ✔️ | +| [563. 二叉树的坡度](https://leetcode.cn/problems/binary-tree-tilt/) | 💚 | ✔️ | +| [814. 二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) | 💛 | ✔️ | +| [1325. 删除给定值的叶子节点](https://leetcode.cn/problems/delete-leaves-with-a-given-value/) | 💛 | ✔️ | + +#### 用「分解」思维解题 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | 💛 | ✔️ | +| [106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | 💛 | ✔️ | +| [889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | 💛 | ✔️ | +| [331. 验证二叉树的前序序列化](https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/) | 💛 | ❌ | +| [894. 所有可能的真二叉树](https://leetcode.cn/problems/all-possible-full-binary-trees/) | 💛 | ❌ | +| [998. 最大二叉树 II](https://leetcode.cn/problems/maximum-binary-tree-ii/) | 💛 | ❌ | +| [1110. 删点成林](https://leetcode.cn/problems/delete-nodes-and-return-forest/) | 💛 | ❌ | +| [100. 相同的树](https://leetcode.cn/problems/same-tree/) | 💛 | ✔️ | +| [101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | 💛 | ✔️ | +| [951. 翻转等价二叉树](https://leetcode.cn/problems/flip-equivalent-binary-trees/) | 💛 | ✔️ | +| [124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | ❤️ | ❌ | +| [236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | 💛 | ❌ | + +#### 用「层序遍历」思维解题 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | 💛 | ✔️ | +| [107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | 💛 | ✔️ | +| [103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | 💛 | ✔️ | +| [116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | 💛 | ✔️ | +| [117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | 💛 | ✔️ | +| [662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | 💛 | ✔️ | +| [515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) | 💛 | ✔️ | +| [637. 二叉树的层平均值](https://leetcode.cn/problems/average-of-levels-in-binary-tree/) | 💚 | ✔️ | +| [958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | 💛 | ✔️ | +| [1161. 最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) | 💛 | ✔️ | +| [1302. 层数最深叶子节点的和](https://leetcode.cn/problems/deepest-leaves-sum/) | 💛 | ✔️ | +| [1609. 奇偶树](https://leetcode.cn/problems/even-odd-tree/) | 💛 | ✔️ | +| [919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | 💛 | ✔️ | +| [863. 二叉树中所有距离为 K 的结点](https://leetcode.cn/problems/all-nodes-distance-k-in-binary-tree/) | 💛 | ❌ | +| [LCR 149. 彩灯装饰记录 I](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) | 💛 | ✔️ | +| [LCR 150. 彩灯装饰记录 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) | 💚 | ✔️ | +| [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | 💛 | ✔️ | #### 二叉搜索树 -- [二叉搜索树中的插入操作](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树中的插入操作.java) -- [二叉搜索树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树的最近公共祖先.java) -- [二叉搜索树节点最小距离](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树节点最小距离.java) -- [将有序数组转换为二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/将有序数组转换为二叉搜索树.java) -- [验证二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/验证二叉搜索树.java) +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) | 💛 | ✔️ | +| [230. 二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | 💛 | ✔️ | +| [538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | 💛 | ✔️ | +| [450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | 💛 | ✔️ | +| [700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | 💚 | ✔️ | +| [701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | 💛 | ✔️ | +| [98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | 💛 | ✔️ | +| [96. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | 💛 | ❌ | +| [95. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | 💛 | ❌ | +| [108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | 💚 | ✔️ | +| [783. 二叉搜索树节点最小距离](https://leetcode.cn/problems/minimum-distance-between-bst-nodes/) | 💚 | ✔️ | +| [235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | 💛 | ❌ | +| [1373. 二叉搜索子树的最大键值和](https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/) | ❤️ | ❌ | + +#### N 叉树 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------- | :--: | ------ | +| [429. N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) | 💛 | ✔️ | +| [559. N 叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/) | 💚 | ✔️ | +| [589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | 💚 | ✔️ | +| [590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | 💚 | ✔️ | + +### 图 + +#### BFS/DFS + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------ | ---- | ------ | +| [797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | 💛 | ❗ | + +#### 环检测及拓扑排序算法 + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------- | ---- | ------ | +| [207. 课程表](https://leetcode.cn/problems/course-schedule/) | 💛 | ❌ | +| [210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | 💛 | ❌ | + +#### 二分图判定算法 + +| 题目 | 难度 | 掌握度 | +| :---------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/)
[LCR 106. 判断二分图](https://leetcode.cn/problems/vEAB3K/) | 💛 | ❌ | +| [886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | 💛 | ❗ | + +#### 并查集算法 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------------- | ---- | ------ | +| [130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | 💛 | ❌ | +| [684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) | 💛 | ✔️ | +| [990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | 💛 | ✔️ | + +#### Dijkstra 算法 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [743. 网络延迟时间](https://leetcode.cn/problems/network-delay-time/) | 💛 | ❌ | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | 💛 | ❌ | +| [1514. 概率最大的路径](https://leetcode.cn/problems/path-with-maximum-probability/) | 💛 | ❌ | +| [787. K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops/) | 💛 | ❌ | +| [1368. 使网格图至少有一条有效路径的最小代价](https://leetcode.cn/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/) | ❤️ | ❌ | + +### DFS / 回溯算法 + +#### 排列、组合、子集问题 + +子集、组合、排列相关问题,都可以考虑使用回溯算法求解。 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------- | ---- | ------ | +| [46. 全排列](https://leetcode.cn/problems/permutations/) | 💛 | ✔️ | +| [47. 全排列 II](https://leetcode.cn/problems/permutations-ii/) | 💛 | ✔️ | +| [78. 子集](https://leetcode.cn/problems/subsets/) | 💛 | ✔️ | +| [90. 子集 II](https://leetcode.cn/problems/subsets-ii/) | 💛 | ✔️ | +| [77. 组合](https://leetcode.cn/problems/combinations/) | 💛 | ✔️ | +| [39. 组合总和](https://leetcode.cn/problems/combination-sum/) | 💛 | ✔️ | +| [40. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | 💛 | ✔️ | +| [216. 组合总和 III](https://leetcode.cn/problems/combination-sum-iii/) | 💛 | ✔️ | + +#### 岛屿问题 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------------------- | ---- | ------ | +| [200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | 💛 | ❗ | +| [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | 💛 | ❗ | +| [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | 💛 | ❗ | +| [695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | 💛 | ❗ | +| [1905. 统计子岛屿](https://leetcode.cn/problems/count-sub-islands/) | 💛 | ❌ | + +#### 数独、N 皇后问题 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------- | ---- | ------ | +| [37. 解数独](https://leetcode.cn/problems/sudoku-solver/) | ❤️ | ❌ | +| [51. N 皇后](https://leetcode.cn/problems/n-queens/) | ❤️ | ❌ | +| [52. N皇后 II](https://leetcode.cn/problems/n-queens-ii/) | ❤️ | ❌ | + +#### 练习 + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------------------------------------- | ---- | ------ | +| [967. 连续差相同的数字](https://leetcode.cn/problems/numbers-with-same-consecutive-differences/) | 💛 | ❌ | +| [491. 非递减子序列](https://leetcode.cn/problems/non-decreasing-subsequences/) | 💛 | ❌ | +| [980. 不同路径 III](https://leetcode.cn/problems/unique-paths-iii/) | ❤️ | ❌ | +| [526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | 💛 | ❌ | +| [131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | 💛 | ❌ | +| [93. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | 💛 | ❌ | +| [89. 格雷编码](https://leetcode.cn/problems/gray-code/) | 💛 | ❌ | +| [17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | 💛 | ❌ | +| [79. 单词搜索](https://leetcode.cn/problems/word-search/) | 💛 | ❌ | + +### BFS + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------------------------------------- | :--: | ------ | +| [752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | 💛 | ❌ | +| [773. 滑动谜题](https://leetcode.cn/problems/sliding-puzzle/) | ❤️ | ❌ | +| [919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | 💛 | ✔️ | +| [841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | 💛 | ✔️ | +| [433. 最小基因变化](https://leetcode.cn/problems/minimum-genetic-mutation/) | 💛 | ❗ | +| [1926. 迷宫中离入口最近的出口](https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/) | 💛 | ✔️ | +| [1091. 二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) | 💛 | ✔️ | +| [994. 腐烂的橘子](https://leetcode.cn/problems/rotting-oranges/) | 💛 | ✔️ | +| [365. 水壶问题](https://leetcode.cn/problems/water-and-jug-problem/) | 💛 | ❌ | +| [721. 账户合并](https://leetcode.cn/problems/accounts-merge/) | 💛 | ❌ | +| [127. 单词接龙](https://leetcode.cn/problems/word-ladder/) | ❤️ | ❌ | + +### 动态规划 + +#### 斐波那契 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------- | :--: | :----: | +| [509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | 💚 | ✔️ | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | 💚 | ✔️ | +| [70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | 💚 | ✔️ | +| [746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | 💚 | ✔️ | +| [198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | 💛 | ✔️ | +| [740. 删除并获得点数](https://leetcode.cn/problems/delete-and-earn/) | 💛 | ✔️ | + +#### 一维 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------ | :--: | :----: | +| [2140. 解决智力问题](https://leetcode.cn/problems/solving-questions-with-brainpower/) | 💛 | ❌ | +| [2466. 统计构造好字符串的方案数](https://leetcode.cn/problems/count-ways-to-build-good-strings/) | 💛 | ❌ | +| [91. 解码方法](https://leetcode.cn/problems/decode-ways/) | 💛 | ❌ | +| [983. 最低票价](https://leetcode.cn/problems/minimum-cost-for-tickets/) | 💛 | ❌ | +| [264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | 💛 | ❗ | +| [1201. 丑数 III](https://leetcode.cn/problems/ugly-number-iii/) | 💛 | ❌ | +| [313. 超级丑数](https://leetcode.cn/problems/super-ugly-number/) | 💛 | ❌ | + +#### 矩阵 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | :--: | :----: | +| [118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | 💚 | ✔️ | +| [119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | 💚 | ✔️ | +| [62. 不同路径](https://leetcode.cn/problems/unique-paths/) | 💛 | ✔️ | +| [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | 💛 | ✔️ | +| [64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | 💛 | ✔️ | +| [120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) | 💛 | ✔️ | +| [931. 下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum/) | 💛 | ✔️ | +| [221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | 💛 | ✔️ | + +#### 字符串 + +| 题目 | 难度 | 掌握度 | +| ---------------------------------------------------------------------------------------------------------- | :--: | :----: | +| [5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 💛 | ✔️ | +| [139. 单词拆分](https://leetcode.cn/problems/word-break/) | 💛 | ❌ | +| [72. 编辑距离](https://leetcode.cn/problems/edit-distance/) | 💛 | ❗ | +| [583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | 💛 | ❌ | +| [712. 两个字符串的最小ASCII删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) | 💛 | ❌ | +| [516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | 💛 | ❌ | +| [115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | ❤️ | ❌ | + +#### 最长递增/公共子序列 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------------------------- | :--: | :----: | +| [300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | 💛 | ❌ | +| [673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | 💛 | ❌ | +| [646. 最长数对链](https://leetcode.cn/problems/maximum-length-of-pair-chain/) | 💛 | ✔️ | +| [1218. 最长定差子序列](https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/) | 💛 | ❌ | +| [1027. 最长等差数列](https://leetcode.cn/problems/longest-arithmetic-subsequence/) | 💛 | ❌ | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | 💛 | ❗ | +| [1035. 不相交的线](https://leetcode.cn/problems/uncrossed-lines/) | 💛 | ❌ | +| [1312. 让字符串成为回文串的最少插入次数](https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/) | ❤️ | ❌ | + +#### 背包问题 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | ---- | ------ | +| [416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | 💛 | ❌ | +| [322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | 💛 | ❌ | +| [518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | 💛 | ❌ | + +#### 买卖股票的最佳时间/状态机 + +#### 其他 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------- | ---- | ------ | +| [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | 💛 | ❌ | +| [354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | ❤️ | ❌ | + +### 贪心算法 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------- | ---- | ------ | +| [561. 数组拆分](https://leetcode.cn/problems/array-partition/) | 💚 | ❌ | +| [55. 跳跃游戏](https://leetcode.cn/problems/jump-game/) | 💛 | ❌ | +| [45. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | 💛 | ❌ | + +### 分治算法 + +| 题目 | 掌握度 | +| --------------------------------------------------------------------------- | ------ | +| [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | ✔️ | + +### 数学 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------ | ---- | ------ | +| [66. 加一](https://leetcode.cn/problems/plus-one/) | 💚 | ✔️ | +| [263. 丑数](https://leetcode.cn/problems/ugly-number/) | 💚 | ✔️ | ## 📚 资料 - **书籍** - - 刷题必备 + - **刷题必备** - 《剑指 offer》 - 《编程之美》 - - 《编程之法:面试和算法心得》 + - 《编程之法:面试和算法心得》 - 《算法谜题》 都是思维题 - - 基础 - - 《[编程珠玑(第 2 版)](https://www.amazon.cn/gp/product/B00SFZH0DC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00SFZH0DC&linkCode=as2&tag=vastwork-23)》 - - 《[编程珠玑(续)](https://www.amazon.cn/gp/product/B0150BMQDM/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B0150BMQDM&linkCode=as2&tag=vastwork-23)》 - - 《[数据结构与算法分析 : C++描述(第 4 版)](https://www.amazon.cn/gp/product/B01LDG2DSG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01LDG2DSG&linkCode=as2&tag=vastwork-23)》 - - 《[数据结构与算法分析 : C 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B002WC7NGS/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B002WC7NGS&linkCode=as2&tag=vastwork-23)》 - - 《[数据结构与算法分析 : Java 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B01CNP0CG6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01CNP0CG6&linkCode=as2&tag=vastwork-23)》 - - 《[算法(第 4 版)](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23)》 - - 算法设计 - - 《[算法设计与分析基础(第 3 版)](https://www.amazon.cn/gp/product/B00S4HCQUI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00S4HCQUI&linkCode=as2&tag=vastwork-23)》 + - **基础** + - [《编程珠玑(第 2 版)》](https://www.amazon.cn/gp/product/B00SFZH0DC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00SFZH0DC&linkCode=as2&tag=vastwork-23) + - [《编程珠玑(续)》](https://www.amazon.cn/gp/product/B0150BMQDM/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B0150BMQDM&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : C++描述(第 4 版)》](https://www.amazon.cn/gp/product/B01LDG2DSG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01LDG2DSG&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : C 语言描述(第 2 版)》](https://www.amazon.cn/gp/product/B002WC7NGS/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B002WC7NGS&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : Java 语言描述(第 2 版)》](https://www.amazon.cn/gp/product/B01CNP0CG6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01CNP0CG6&linkCode=as2&tag=vastwork-23) + - [《算法(第 4 版)》](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23) + - **算法设计** + - [《算法设计与分析基础(第 3 版)》](https://www.amazon.cn/gp/product/B00S4HCQUI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00S4HCQUI&linkCode=as2&tag=vastwork-23) - 《Algorithm Design Manual》 - 算法设计手册 红皮书 - [《算法导论》](https://www.amazon.cn/gp/product/B00AK7BYJY/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00AK7BYJY&linkCode=as2&tag=vastwork-23) - 是一本对算法介绍比较全面的经典书籍 - 《Algorithms on Strings,Trees and Sequences》 - 《Advanced Data Structures》 - 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600 块 - **学习网站** + - https://labuladong.online/algo/ - https://github.com/TheAlgorithms/Java - https://github.com/nonstriater/Learn-Algorithms - https://github.com/trekhleb/javascript-algorithms @@ -209,6 +617,7 @@ - [算法设计与分析 Design and Analysis of Algorithms](https://class.coursera.org/algorithms-001/lecture) 由北大教授 Wanling Qu 在 coursera 讲授的一门算法课程。首先介绍一些与算法有关的基础知识,然后阐述经典的算法设计思想和分析技术,主要涉及的算法设计技术是:分治策略、动态规划、贪心法、回溯与分支限界等。每个视频都配有相应的讲义(pdf 文件)以便阅读和复习。 - [算法面试通关 40 讲](https://time.geekbang.org/course/intro/100019701) - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) + - [Data Structures - Computer Science Course for Beginners](https://www.youtube.com/watch?v=zg9ih6SVACc) - 高赞 YouTube 视频教程 ## 🚪 传送 diff --git "a/assets/\346\225\260\346\215\256\347\273\223\346\236\204.eddx" "b/assets/\346\225\260\346\215\256\347\273\223\346\236\204.eddx" deleted file mode 100644 index 574dc17..0000000 Binary files "a/assets/\346\225\260\346\215\256\347\273\223\346\236\204.eddx" and /dev/null differ diff --git "a/assets/\346\240\221.eddx" "b/assets/\346\240\221.eddx" deleted file mode 100644 index 574dc17..0000000 Binary files "a/assets/\346\240\221.eddx" and /dev/null differ diff --git "a/assets/\347\256\227\346\263\225.xmind" "b/assets/\347\256\227\346\263\225.xmind" deleted file mode 100644 index e05f4bd..0000000 Binary files "a/assets/\347\256\227\346\263\225.xmind" and /dev/null differ diff --git "a/assets/\351\223\276\350\241\250.eddx" "b/assets/\351\223\276\350\241\250.eddx" deleted file mode 100644 index d580a50..0000000 Binary files "a/assets/\351\223\276\350\241\250.eddx" and /dev/null differ diff --git a/codes/algorithm/pom.xml b/codes/algorithm/pom.xml index de0b922..307f4bd 100644 --- a/codes/algorithm/pom.xml +++ b/codes/algorithm/pom.xml @@ -3,14 +3,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.github.dunwu - dunwu-parent - 0.5.6 - - io.github.dunwu.algorithm algorithm + 1.0.0 jar 算法示例 数据示例源码 @@ -24,20 +19,42 @@ - io.github.dunwu - dunwu-tool-core + cn.hutool + hutool-all + 5.8.29 + + + org.projectlombok + lombok + 1.18.36 + provided ch.qos.logback logback-classic + 1.2.9 org.junit.jupiter junit-jupiter + 5.8.2 org.assertj assertj-core + 3.26.3 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + none + + + + diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Parklot.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Parklot.java deleted file mode 100644 index 2451364..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Parklot.java +++ /dev/null @@ -1,157 +0,0 @@ -package io.github.dunwu.algorithm; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -/** - * @author Zhang Peng - * @since 2020-05-13 - */ -public class Parklot { - - private volatile AtomicInteger space; - private volatile AtomicLong sum; - private volatile int MAX = 1000; - - public boolean enter(Car car) { - if (space.get() >= MAX) { - // reject - return false; - } - - // TODO 判断车类型 - - car.enter(); - space.getAndIncrement(); - return true; - } - - public long exit(Car car) { - if (space.get() >= MAX) { - // reject - return 0L; - } - - // TODO 判断车类型 - car.exit(); - long money = car.money(); - // 扣费 - space.getAndDecrement(); - sum.getAndAdd(money); - return money; - } - - public void getSum() { - - } - - public interface Parking { - - enum Type { - car, - truck - } - - int getPrice(); - - int getMax(); - - void enter(); - - void exit(); - - LocalDateTime getBeginTime(); - - LocalDateTime getEndTime(); - - default long money() { - if (getEndTime() == null) { - return 0; - } - long l2 = getEndTime().toEpochSecond(ZoneOffset.UTC); - long l1 = getBeginTime().toEpochSecond(ZoneOffset.UTC); - long time = l2 - l1; - long hours = TimeUnit.NANOSECONDS.toHours(time); - long total = getPrice() * hours; - return Math.min(total, getMax()); - } - - } - - public abstract class Car implements Parking { - - private static final int price = 5; - private static final int max = 60; - private LocalDateTime beginTime; - private LocalDateTime endTime; - - @Override - public int getPrice() { - return price; - } - - @Override - public int getMax() { - return max; - } - - @Override - public void enter() { - beginTime = LocalDateTime.now(); - } - - @Override - public void exit() { - endTime = LocalDateTime.now(); - } - - @Override - public LocalDateTime getBeginTime() { - return beginTime; - } - - @Override - public LocalDateTime getEndTime() { - return endTime; - } - - } - - public class LittleCar extends Car { - - private static final int price = 5; - private static final int max = 60; - - @Override - public int getPrice() { - return price; - } - - @Override - public int getMax() { - return max; - } - - } - - public class Truck extends Car implements Parking { - - private static final int price = 10; - private static final int max = 120; - - @Override - public int getPrice() { - return price; - } - - @Override - public int getMax() { - return max; - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Test2.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Test2.java deleted file mode 100644 index e918626..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/Test2.java +++ /dev/null @@ -1,123 +0,0 @@ -package io.github.dunwu.algorithm; - -/** - * @author Zhang Peng - * @since 2020-05-13 - */ -public class Test2 { - - public static void main(String[] args) { - ListNode l1 = new ListNode(2); - l1.next = new ListNode(4); - l1.next.next = new ListNode(3); - - ListNode l2 = new ListNode(5); - l2.next = new ListNode(6); - l2.next.next = new ListNode(4); - - ListNode result = add(l1, l2); - ListNode temp = result; - while (temp != null) { - System.out.println(temp.val); - temp = temp.next; - } - - System.out.println("result = " + listNodeToNum(result)); - } - - public static ListNode add(ListNode l1, ListNode l2) { - ListNode n1 = l1; - ListNode n2 = l2; - ListNode resultNode = new ListNode(-1); - ListNode temp = resultNode; - boolean flag = false; - while (n1 != null && n2 != null) { - int value = n1.val + n2.val; - if (flag) { - value++; - } - int num = 0; - if (value >= 10) { - num = value % 10; - flag = true; - } else { - num = value; - flag = false; - } - - n1 = n1.next; - n2 = n2.next; - temp.next = new ListNode(num); - temp = temp.next; - } - - if (n1 != null) { - while (n1 != null) { - int num = 0; - if (flag) { - num = 1 + n1.val; - } else { - num = n1.val; - } - n1 = n1.next; - temp = new ListNode(num); - temp = temp.next; - } - } - - if (n2 != null) { - while (n2 != null) { - int num = 0; - if (flag) { - num = 1 + n2.val; - } else { - num = n2.val; - } - n2 = n2.next; - temp = new ListNode(num); - temp = temp.next; - } - } - - return resultNode.next; - } - - public static int listNodeToNum(ListNode head) { - if (head == null) { - return 0; - } - - int result = 0; - int pos = 0; - ListNode node = head; - while (node != null) { - result += getBase(pos) * node.val; - node = node.next; - pos++; - } - - return result; - } - - public static int getBase(int pos) { - if (pos <= 0) { - return 1; - } else { - pos--; - return 10 * getBase(pos); - } - } - - public static class ListNode { - - public int val; - public ListNode next; - - public ListNode(int val) { - this.val = val; - this.next = null; - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/ArrayDemo.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/ArrayDemo.java deleted file mode 100644 index 62c1db5..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/ArrayDemo.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dunwu.algorithm.array; - -/** - * @author Zhang Peng - * @since 2020-01-20 - */ -public class ArrayDemo { - - public static int maxSubArray(int[] nums) { - int len = nums.length; - - int maxSum = nums[0]; - for (int i = 1; i < len; i++) { - if (nums[i - 1] > 0) nums[i] += nums[i - 1]; - maxSum = Math.max(nums[i], maxSum); - } - return maxSum; - } - - public static void main(String[] args) { - int max = maxSubArray(new int[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 }); - System.out.println("max = " + max); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" new file mode 100644 index 0000000..7e1cb56 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.base; + +import org.junit.jupiter.api.Assertions; + +/** + * 485. 最大连续 1 的个数 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 最大连续1的个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.findMaxConsecutiveOnes(new int[] { 1, 1, 0, 1, 1, 1 })); + Assertions.assertEquals(2, s.findMaxConsecutiveOnes(new int[] { 1, 0, 1, 1, 0, 1 })); + } + + static class Solution { + + public int findMaxConsecutiveOnes(int[] nums) { + int max = 0; + int cnt = 0; + for (int num : nums) { + if (num == 1) { + cnt++; + max = Math.max(max, cnt); + } else { + cnt = 0; + } + } + return max; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" new file mode 100644 index 0000000..cd5ceb9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.base; + +import org.junit.jupiter.api.Assertions; + +/** + * 747. 至少是其他数字两倍的最大数 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 至少是其他数字两倍的最大数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.dominantIndex(new int[] { 3, 6, 1, 0 })); + Assertions.assertEquals(-1, s.dominantIndex(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s.dominantIndex(new int[] { 1, 0 })); + } + + static class Solution { + + public int dominantIndex(int[] nums) { + int second = -1, max = 0; + for (int i = 1; i < nums.length; i++) { + if (nums[i] > nums[max]) { + second = max; + max = i; + } else if (second == -1 || nums[i] > nums[second]) { + second = i; + } + } + return nums[max] >= 2 * nums[second] ? max : -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" new file mode 100644 index 0000000..1a47983 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 704. 二分查找 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 二分查找 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 9)); + Assertions.assertEquals(-1, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 2)); + } + + static class Solution { + + public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..0c3678b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 410. 分割数组的最大值 + * + * @author Zhang Peng + * @date 2025-10-16 + */ +public class 分割数组的最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(18, s.splitArray(new int[] { 7, 2, 5, 10, 8 }, 2)); + Assertions.assertEquals(9, s.splitArray(new int[] { 1, 2, 3, 4, 5 }, 2)); + Assertions.assertEquals(4, s.splitArray(new int[] { 1, 4, 4 }, 3)); + } + + static class Solution { + + public int splitArray(int[] nums, int k) { + return shipWithinDays(nums, k); + } + + public int shipWithinDays(int[] weights, int days) { + int max = 0, sum = 0; + for (int weight : weights) { + max = Math.max(max, weight); + sum += weight; + } + + int left = max, right = sum; + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(weights, mid) <= days) { + // 需要让 f(x) 的返回值大一些 + right = mid - 1; + } else if (f(weights, mid) > days) { + // 需要让 f(x) 的返回值小一些 + left = mid + 1; + } + } + return left; + } + + // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 + // f(x) 随着 x 的增加单调递减 + int f(int[] weights, int x) { + int days = 0; + for (int i = 0; i < weights.length; ) { + // 尽可能多装货物 + int cap = x; + while (i < weights.length) { + if (cap < weights[i]) break; + else cap -= weights[i]; + i++; + } + days++; + } + return days; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" new file mode 100644 index 0000000..04d948d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 1011. 在 D 天内送达包裹的能力 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 在D天内送达包裹的能力 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, s.shipWithinDays(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 5)); + Assertions.assertEquals(6, s.shipWithinDays(new int[] { 3, 2, 2, 4, 1, 4 }, 3)); + Assertions.assertEquals(3, s.shipWithinDays(new int[] { 1, 2, 3, 1, 1 }, 4)); + } + + static class Solution { + + public int shipWithinDays(int[] weights, int days) { + int max = 0, sum = 0; + for (int weight : weights) { + max = Math.max(max, weight); + sum += weight; + } + + int left = max, right = sum; + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(weights, mid) <= days) { + // 需要让 f(x) 的返回值大一些 + right = mid - 1; + } else if (f(weights, mid) > days) { + // 需要让 f(x) 的返回值小一些 + left = mid + 1; + } + } + return left; + } + + // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 + // f(x) 随着 x 的增加单调递减 + int f(int[] weights, int x) { + int days = 0; + for (int i = 0; i < weights.length; ) { + // 尽可能多装货物 + int cap = x; + while (i < weights.length) { + if (cap < weights[i]) break; + else cap -= weights[i]; + i++; + } + days++; + } + return days; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" new file mode 100644 index 0000000..fb14865 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 34.在排序数组中查找元素的第一个和最后一个位置 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 在排序数组中查找元素的第一个和最后一个位置 { + + public static void main(String[] args) { + Solution s = new Solution(); + + Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); + Assertions.assertEquals(0, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); + Assertions.assertEquals(5, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); + Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); + Assertions.assertEquals(1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); + + Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); + Assertions.assertEquals(0, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); + Assertions.assertEquals(5, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); + Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); + Assertions.assertEquals(2, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); + + Assertions.assertArrayEquals(new int[] { 3, 4 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 8)); + Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 6)); + Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] {}, 0)); + Assertions.assertArrayEquals(new int[] { 0, 0 }, s.searchRange(new int[] { 1 }, 1)); + } + + static class Solution { + + public int[] searchRange(int[] nums, int target) { + int left = searchLeft(nums, target); + int right = searchRight(nums, target); + return new int[] { left, right }; + } + + public int searchLeft(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + res = mid; + right = mid - 1; + } + } + return res; + } + + public int searchRight(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] == target) { + res = mid; + left = mid + 1; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" new file mode 100644 index 0000000..bdc2bb9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 35. 搜索插入位置 + * + * @author Zhang Peng + * @since 2020-07-29 + */ +public class 搜索插入位置 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(0, s.searchInsert(new int[] { 1 }, 1)); + Assertions.assertEquals(2, s.searchInsert(new int[] { 1, 3, 5, 6 }, 5)); + Assertions.assertEquals(1, s.searchInsert(new int[] { 1, 3, 5, 6 }, 2)); + Assertions.assertEquals(4, s.searchInsert(new int[] { 1, 3, 5, 6 }, 7)); + Assertions.assertEquals(0, s.searchInsert(new int[] { 1, 3, 5, 6 }, 0)); + } + + static class Solution { + + public int searchInsert(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } + } + return left; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" new file mode 100644 index 0000000..c7b5f17 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 875. 爱吃香蕉的珂珂 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 爱吃香蕉的珂珂 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.minEatingSpeed(new int[] { 3, 6, 7, 11 }, 8)); + Assertions.assertEquals(30, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 5)); + Assertions.assertEquals(23, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 6)); + Assertions.assertEquals(2, s.minEatingSpeed(new int[] { 312884470 }, 312884469)); + Assertions.assertEquals(3, s.minEatingSpeed(new int[] { 805306368, 805306368, 805306368 }, 1000000000)); + Assertions.assertEquals(14, s.minEatingSpeed( + new int[] { 332484035, 524908576, 855865114, 632922376, 222257295, 690155293, 112677673, 679580077, + 337406589, 290818316, 877337160, 901728858, 679284947, 688210097, 692137887, 718203285, 629455728, + 941802184 }, 823855818)); + } + + static class Solution { + + public int minEatingSpeed(int[] piles, int h) { + int left = 1, right = 1_000_000_000; + + // right 是闭区间,所以这里改成 <= + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(piles, mid) <= h) { + // right 是闭区间,所以这里用 mid - 1 + right = mid - 1; + } else if (f(piles, mid) > h) { + left = mid + 1; + } + } + return left; + } + + long f(int[] nums, int x) { + long h = 0; + for (int num : nums) { + h += num / x; + if (num % x > 0) { h++; } + } + return h; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" new file mode 100644 index 0000000..c0266ae --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * LCR 172. 统计目标成绩的出现次数 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 统计目标成绩的出现次数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.countTarget(new int[] { 2, 2, 3, 4, 4, 4, 5, 6, 6, 8 }, 4)); + Assertions.assertEquals(0, s.countTarget(new int[] { 1, 2, 3, 5, 7, 9 }, 6)); + } + + static class Solution { + + public int countTarget(int[] scores, int target) { + int leftBound = searchLeft(scores, target); + if (leftBound == -1) { return 0; } + int cnt = 1; + for (int i = leftBound + 1; i < scores.length; i++) { + if (scores[i] == target) { + cnt++; + } + } + return cnt; + } + + public int searchLeft(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + right = mid - 1; + res = mid; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList1.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" similarity index 98% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList1.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" index 841235f..1787652 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList1.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.array; +package io.github.dunwu.algorithm.array.demo; import java.util.Arrays; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList2.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList2.java" similarity index 99% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList2.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList2.java" index 22250c6..d38d8d9 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\250\241\346\213\237ArrayList2.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList2.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.array; +package io.github.dunwu.algorithm.array.demo; public class 模拟ArrayList2 { diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" new file mode 100644 index 0000000..2a10b4a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 151. 反转字符串中的单词 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 反转字符串中的单词 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("blue is sky the", s.reverseWords("the sky is blue")); + Assertions.assertEquals("world hello", s.reverseWords(" hello world ")); + Assertions.assertEquals("example good a", s.reverseWords("a good example")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals("blue is sky the", s2.reverseWords("the sky is blue")); + Assertions.assertEquals("world hello", s2.reverseWords(" hello world ")); + Assertions.assertEquals("example good a", s2.reverseWords("a good example")); + } + + // 利用库函数 + static class Solution { + + public String reverseWords(String s) { + String[] arr = s.trim().split(" "); + StringBuilder sb = new StringBuilder(); + for (int i = arr.length - 1; i >= 0; i--) { + if (arr[i].equals("")) { + continue; + } + sb.append(arr[i]).append(" "); + } + return sb.toString().trim(); + } + + } + + // 双指针 + static class Solution2 { + + public String reverseWords(String s) { + // 删除首尾空格 + s = s.trim(); + int l = s.length() - 1, r = l; + StringBuilder res = new StringBuilder(); + while (l >= 0) { + // 左指针偏移,直到遇到空格 + while (l >= 0 && s.charAt(l) != ' ') { l--; } + // 添加单词 + res.append(s.substring(l + 1, r + 1)).append(' '); + // 左指针偏移,直到遇到非空格 + while (l >= 0 && s.charAt(l) == ' ') { l--; } + // 右指针对齐左指针 + r = l; + } + return res.toString().trim(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" new file mode 100644 index 0000000..9d3c672 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 498. 对角线遍历 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 对角线遍历 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input = { { 1, 2 }, { 3, 4 } }; + int[] expect = { 1, 2, 3, 4 }; + + int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + int[] expect2 = { 1, 2, 4, 7, 5, 3, 6, 8, 9 }; + + int[][] input3 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; + int[] expect3 = { 1, 2, 5, 9, 6, 3, 4, 7, 10, 13, 14, 11, 8, 12, 15, 16 }; + + Assertions.assertArrayEquals(expect, s.findDiagonalOrder(input)); + Assertions.assertArrayEquals(expect2, s.findDiagonalOrder(input2)); + Assertions.assertArrayEquals(expect3, s.findDiagonalOrder(input3)); + } + + static class Solution { + + // 1. 同一对角线上的元素,满足 i + j = k + // 2. k 的大小,满足递增,从 0 到 m + n - 2 + // 3. 由于,i + j = k -> i = k - j + // i = m - 1 时最大,j 最小;而 k - (m - 1) 必须大于 0 => minJ = max(0, k - (m - 1)) + // i = 0 时最小,j 最大,但不能超过 n - 1 => maxJ = Math.max(k, n -1) + public int[] findDiagonalOrder(int[][] mat) { + + // base case + if (mat == null || mat.length == 0) { return new int[0]; } + + int idx = 0; + int m = mat.length, n = mat[0].length; + int[] res = new int[m * n]; + for (int k = 0; k < m + n - 1; k++) { + int minJ = Math.max(k - (m - 1), 0); + int maxJ = Math.min(k, n - 1); + if (k % 2 == 0) { + for (int j = minJ; j <= maxJ; j++) { + res[idx++] = mat[k - j][j]; + } + } else { + for (int j = maxJ; j >= minJ; j--) { + res[idx++] = mat[k - j][j]; + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" new file mode 100644 index 0000000..b4bdd6d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.matrix; + +import cn.hutool.core.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +/** + * LCR 006. 两数之和 II - 输入有序数组 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 旋转图像 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + s.rotate(matrix); + int[][] expect = { { 7, 4, 1 }, { 8, 5, 2 }, { 9, 6, 3 } }; + Assertions.assertTrue(ArrayUtil.equals(expect, matrix)); + + int[][] matrix2 = { { 5, 1, 9, 11 }, { 2, 4, 8, 10 }, { 13, 3, 6, 7 }, { 15, 14, 12, 16 } }; + s.rotate(matrix2); + int[][] expect2 = { { 15, 13, 2, 5 }, { 14, 3, 4, 1 }, { 12, 6, 8, 9 }, { 16, 7, 10, 11 } }; + Assertions.assertTrue(ArrayUtil.equals(expect2, matrix2)); + } + + static class Solution { + + public void rotate(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return; } + int n = matrix.length; + // 沿对角线置换 + for (int i = 0; i < n; i++) { + for (int j = 0; j < i; j++) { + int temp = matrix[i][j]; + matrix[i][j] = matrix[j][i]; + matrix[j][i] = temp; + } + } + + for (int i = 0; i < n; i++) { + int left = 0, right = n - 1; + while (left < right) { + int temp = matrix[i][left]; + matrix[i][left] = matrix[i][right]; + matrix[i][right] = temp; + left++; + right--; + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" new file mode 100644 index 0000000..e0085df --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + List output = s.spiralOrder(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output.toArray()); + List output2 = s.spiralOrder(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2.toArray()); + } + + static class Solution { + + public List spiralOrder(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return new LinkedList<>(); + } + + int m = matrix.length, n = matrix[0].length; + int up = 0, down = m - 1, left = 0, right = n - 1; + List res = new LinkedList<>(); + while (res.size() < m * n) { + // 向右 + if (up <= down) { + for (int i = left; i <= right; i++) { + res.add(matrix[up][i]); + } + up++; + } + // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向下 + if (left <= right) { + for (int i = up; i <= down; i++) { + res.add(matrix[i][right]); + } + right--; + } + // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向左 + if (up <= down) { + for (int i = right; i >= left; i--) { + res.add(matrix[down][i]); + } + down--; + } + // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向上 + if (left <= right) { + for (int i = down; i >= up; i--) { + res.add(matrix[i][left]); + } + left++; + } + // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" new file mode 100644 index 0000000..785f236 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋矩阵2 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] output = s.generateMatrix(3); + Assertions.assertArrayEquals(new int[][] { { 1, 2, 3 }, { 8, 9, 4 }, { 7, 6, 5 } }, output); + int[][] output2 = s.generateMatrix(1); + Assertions.assertArrayEquals(new int[][] { { 1 } }, output2); + } + + static class Solution { + + public int[][] generateMatrix(int n) { + int cnt = 0; + int[][] res = new int[n][n]; + int left = 0, right = n - 1, top = 0, bottom = n - 1; + while (cnt < n * n) { + + // 向右 + if (top <= bottom) { + for (int i = left; i <= right; i++) { + res[top][i] = ++cnt; + } + top++; + } + + // 向下 + if (left <= right) { + for (int i = top; i <= bottom; i++) { + res[i][right] = ++cnt; + } + right--; + } + + // 向左 + if (top <= bottom) { + for (int i = right; i >= left; i--) { + res[bottom][i] = ++cnt; + } + bottom--; + } + + // 向上 + if (left <= right) { + for (int i = bottom; i >= top; i--) { + res[i][left] = ++cnt; + } + left++; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" new file mode 100644 index 0000000..c39ed1a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋遍历二维数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[] output = s.spiralArray(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output); + int[] output2 = s.spiralArray(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2); + } + + static class Solution { + + public int[] spiralArray(int[][] array) { + List list = spiralOrder(array); + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } + + public List spiralOrder(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return new LinkedList<>(); + } + + int m = matrix.length, n = matrix[0].length; + int up = 0, down = m - 1; + int left = 0, right = n - 1; + List res = new LinkedList<>(); + while (res.size() < m * n) { + // 向右 + if (up <= down) { + for (int i = left; i <= right; i++) { + res.add(matrix[up][i]); + } + up++; + } + // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向下 + if (left <= right) { + for (int i = up; i <= down; i++) { + res.add(matrix[i][right]); + } + right--; + } + // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向左 + if (up <= down) { + for (int i = right; i >= left; i--) { + res.add(matrix[down][i]); + } + down--; + } + // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向上 + if (left <= right) { + for (int i = down; i >= up; i--) { + res.add(matrix[i][left]); + } + left++; + } + // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" new file mode 100644 index 0000000..6c7d535 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" @@ -0,0 +1,54 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 08. 零矩阵 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 零矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = { + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 } + }; + int[][] expect = { + { 1, 0, 1 }, + { 0, 0, 0 }, + { 1, 0, 1 } + }; + s.setZeroes(input); + Assertions.assertArrayEquals(expect, input); + } + + static class Solution { + + public void setZeroes(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + LinkedList queue = new LinkedList<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == 0) { + queue.offer(new int[] { i, j }); + } + } + } + + while (!queue.isEmpty()) { + int[] point = queue.poll(); + int x = point[0], y = point[1]; + for (int i = 0; i < n; i++) { matrix[x][i] = 0; } + for (int i = 0; i < m; i++) { matrix[i][y] = 0; } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" new file mode 100644 index 0000000..61ece6f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 303. 区域和检索 - 数组不可变 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 二维区域和检索_矩阵不可变 { + + public static void main(String[] args) { + NumMatrix numMatrix = new NumMatrix(new int[][] { + { 3, 0, 1, 4, 2 }, + { 5, 6, 3, 2, 1 }, + { 1, 2, 0, 1, 5 }, + { 4, 1, 0, 1, 7 }, + { 1, 0, 3, 0, 5 } + }); + Assertions.assertEquals(8, numMatrix.sumRegion(2, 1, 4, 3)); + Assertions.assertEquals(11, numMatrix.sumRegion(1, 1, 2, 2)); + Assertions.assertEquals(12, numMatrix.sumRegion(1, 2, 2, 4)); + } + + static class NumMatrix { + + // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和 + private int[][] preSum; + + public NumMatrix(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + if (m == 0 || n == 0) return; + // 构造前缀和矩阵 + preSum = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 计算每个矩阵 [0, 0, i, j] 的元素和 + preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1]; + } + } + } + + // 计算子矩阵 [x1, y1, x2, y2] 的元素和 + public int sumRegion(int x1, int y1, int x2, int y2) { + // 目标矩阵之和由四个相邻矩阵运算获得 + return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" new file mode 100644 index 0000000..276664c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 303. 区域和检索 - 数组不可变 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 区域和检索_数组不可变 { + + public static void main(String[] args) { + NumArray numArray = new NumArray(new int[] { -2, 0, 3, -5, 2, -1 }); + Assertions.assertEquals(1, numArray.sumRange(0, 2)); + Assertions.assertEquals(-1, numArray.sumRange(2, 5)); + Assertions.assertEquals(-3, numArray.sumRange(0, 5)); + } + + static class NumArray { + + private int[] preSum; + + public NumArray(int[] nums) { + preSum = new int[nums.length + 1]; + for (int i = 1; i <= nums.length; i++) { + preSum[i] = preSum[i - 1] + nums[i - 1]; + } + } + + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" new file mode 100644 index 0000000..c7d8c6b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 724. 寻找数组的中心索引 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 寻找数组的中心索引 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 })); + Assertions.assertEquals(-1, s.pivotIndex(new int[] { 1, 2, 3 })); + Assertions.assertEquals(0, s.pivotIndex(new int[] { 2, 1, -1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 })); + Assertions.assertEquals(-1, s2.pivotIndex(new int[] { 1, 2, 3 })); + Assertions.assertEquals(0, s2.pivotIndex(new int[] { 2, 1, -1 })); + } + + static class Solution { + + public int pivotIndex(int[] nums) { + for (int mid = 0; mid < nums.length; mid++) { + int leftSum = 0, rightSum = 0; + for (int i = 0; i < mid; i++) { + leftSum += nums[i]; + } + for (int i = mid + 1; i < nums.length; i++) { + rightSum += nums[i]; + } + if (leftSum == rightSum) { + return mid; + } + } + return -1; + } + + } + + static class Solution2 { + + public int pivotIndex(int[] nums) { + int total = 0; + for (int num : nums) { + total += num; + } + + int leftSum = 0; + for (int i = 0; i < nums.length; i++) { + int rightSum = total - leftSum - nums[i]; + if (leftSum == rightSum) { + return i; + } + leftSum += nums[i]; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" new file mode 100644 index 0000000..43dca82 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1013.将数组分成和相等的三个部分 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 将数组分成和相等的三个部分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1 })); + Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 3, 3, 6, 5, -2, 2, 5, 1, -9, 4 })); + Assertions.assertFalse(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, 7, 9, -1, 2, 0, 1 })); + } + + static class Solution { + + public boolean canThreePartsEqualSum(int[] arr) { + int n = arr.length; + NumArray preSum = new NumArray(arr); + for (int i = 1; i < n; i++) { + for (int j = i + 1; j < n; j++) { + int leftSum = preSum.sumRange(0, i - 1); + int midSum = preSum.sumRange(i, j - 1); + int rightSum = preSum.sumRange(j, n - 1); + if (leftSum == midSum && midSum == rightSum) { + return true; + } + } + } + return false; + } + + static class NumArray { + + private final int[] preSum; + + public NumArray(int[] arr) { + preSum = new int[arr.length + 1]; + preSum[0] = 0; + for (int i = 1; i <= arr.length; i++) { + preSum[i] = preSum[i - 1] + arr[i - 1]; + } + } + + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" new file mode 100644 index 0000000..6ea1646 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1094. 屁车 + * + * @author Zhang Peng + * @date 2025-10-17 + */ +public class 拼车 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = { { 2, 1, 5 }, { 3, 3, 7 } }; + Assertions.assertFalse(s.carPooling(input, 3)); + int[][] input2 = { { 1, 2, 10 }, { 2, 2, 15 } }; + Assertions.assertTrue(s.carPooling(input2, 5)); + int[][] input3 = { { 2, 1, 5 }, { 3, 5, 7 } }; + Assertions.assertTrue(s.carPooling(input3, 3)); + } + + static class Solution { + + public boolean carPooling(int[][] trips, int capacity) { + // 最多有 1000 个车站 + int[] nums = new int[1001]; + // 构造差分解法 + Difference df = new Difference(nums); + + for (int[] trip : trips) { + // 乘客数量 + int val = trip[0]; + // 第 trip[1] 站乘客上车 + int i = trip[1]; + // 第 trip[2] 站乘客已经下车, + // 即乘客在车上的区间是 [trip[1], trip[2] - 1] + int j = trip[2] - 1; + // 进行区间操作 + df.increment(i, j, val); + } + + int[] res = df.result(); + + // 客车自始至终都不应该超载 + for (int i = 0; i < res.length; i++) { + if (capacity < res[i]) { + return false; + } + } + return true; + } + + // 差分数组工具类 + class Difference { + + // 差分数组 + private int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" new file mode 100644 index 0000000..bb51080 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1109. 航班预订统计 + * + * @author Zhang Peng + * @date 2025-10-17 + */ +public class 航班预订统计 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] bookings = { { 1, 2, 10 }, { 2, 3, 20 }, { 2, 5, 25 } }; + Assertions.assertArrayEquals(new int[] { 10, 55, 45, 25, 25 }, s.corpFlightBookings(bookings, 5)); + + int[][] bookings2 = { { 1, 2, 10 }, { 2, 2, 15 } }; + Assertions.assertArrayEquals(new int[] { 10, 25 }, s.corpFlightBookings(bookings2, 2)); + } + + static class Solution { + + public int[] corpFlightBookings(int[][] bookings, int n) { + int[] nums = new int[n]; + Difference df = new Difference(nums); + for (int[] booking : bookings) { + int first = booking[0], last = booking[1], seat = booking[2]; + df.increment(first - 1, last - 1, seat); + } + return df.result(); + } + + // 差分数组工具类 + static class Difference { + + // 差分数组 + private final int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" new file mode 100644 index 0000000..7f21032 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 二分查找模板 + * + * @author Zhang Peng + * @date 2025-12-08 + */ +public class 二分查找模板 { + + int binary_search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 直接返回 + return mid; + } + } + // 直接返回 + return -1; + } + + int left_bound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 别返回,锁定左侧边界 + right = mid - 1; + } + } + // 判断 target 是否存在于 nums 中 + if (left < 0 || left >= nums.length) { + return -1; + } + // 判断一下 nums[left] 是不是 target + return nums[left] == target ? left : -1; + } + + int right_bound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 别返回,锁定右侧边界 + left = mid + 1; + } + } + // 由于 while 的结束条件是 right == left - 1,且现在在求右边界 + // 所以用 right 替代 left - 1 更好记 + if (right < 0 || right >= nums.length) { + return -1; + } + return nums[right] == target ? right : -1; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" new file mode 100644 index 0000000..7c37f55 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 前缀和数组代码模板 + * + * @author Zhang Peng + * @date 2025-10-20 + */ +public class 前缀和数组代码模板 { + + /** + * 一维前缀和 + */ + static class NumArray { + + // 前缀和数组 + private final int[] preSum; + + // 输入一个数组,构造前缀和 + public NumArray(int[] nums) { + // preSum[0] = 0,便于计算累加和 + preSum = new int[nums.length + 1]; + // 计算 nums 的累加和 + for (int i = 1; i < preSum.length; i++) { + preSum[i] = preSum[i - 1] + nums[i - 1]; + } + } + + // 查询闭区间 [left, right] 的累加和 + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + + /** + * 二维前缀和 + */ + static class NumMatrix { + + // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和 + private int[][] preSum; + + public NumMatrix(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + if (m == 0 || n == 0) return; + // 构造前缀和矩阵 + preSum = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 计算每个矩阵 [0, 0, i, j] 的元素和 + preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1]; + } + } + } + + // 计算子矩阵 [x1, y1, x2, y2] 的元素和 + public int sumRegion(int x1, int y1, int x2, int y2) { + // 目标矩阵之和由四个相邻矩阵运算获得 + return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" new file mode 100644 index 0000000..bb6a01c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 差分数组代码模板 + * + * @author Zhang Peng + * @date 2025-10-20 + */ +public class 差分数组代码模板 { + + // 差分数组工具类 + static class Difference { + + // 差分数组 + private final int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" new file mode 100644 index 0000000..66a46d4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 滑动窗口模板 + * + * @author Zhang Peng + * @date 2025-11-20 + */ +public class 滑动窗口模板 { + + // 滑动窗口算法伪码框架 + // void slidingWindow(String s) { + // // 用合适的数据结构记录窗口中的数据,根据具体场景变通 + // // 比如说,我想记录窗口中元素出现的次数,就用 map + // // 如果我想记录窗口中的元素和,就可以只用一个 int + // Object window = ... + // + // int left = 0, right = 0; + // while (right < s.length()) { + // // c 是将移入窗口的字符 + // char c = s[right]; + // window.add(c) + // // 增大窗口 + // right++; + // // 进行窗口内数据的一系列更新 + // // ... + // + // // *** debug 输出的位置 *** + // // 注意在最终的解法代码中不要 print + // // 因为 IO 操作很耗时,可能导致超时 + // printf("window: [%d, %d)\n", left, right); + // // *********************** + // + // // 判断左侧窗口是否要收缩 + // while (left < right && window needs shrink){ + // // d 是将移出窗口的字符 + // char d = s[left]; + // window.remove(d) + // // 缩小窗口 + // left++; + // // 进行窗口内数据的一系列更新 + // ... + // } + // } + // } +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..bb147b7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * 三数之和 + * + * @author Zhang Peng + * @since 2020-01-18 + */ +public class 三数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> input = s.threeSum(new int[] { -1, 0, 1, 2, -1, -4 }); + List> expect = new ArrayList<>(); + expect.add(Arrays.asList(-1, -1, 2)); + expect.add(Arrays.asList(-1, 0, 1)); + Assertions.assertArrayEquals(expect.toArray(), input.toArray()); + } + + static class Solution { + + public List> threeSum(int[] nums) { + if (nums == null || nums.length < 3) { return new ArrayList<>(); } + + // 数组排序 + Arrays.sort(nums); + + List> res = new ArrayList<>(); + for (int i = 0; i < nums.length; i++) { + + // 跳过重复元素 + if (i > 0 && nums[i] == nums[i - 1]) { continue; } + + // 双指针,目标是找到 nums[l] + nums[r] = -nums[i] + int target = -nums[i]; + int l = i + 1, r = nums.length - 1; + + while (l < r) { + int sum = nums[l] + nums[r]; + if (sum == target) { + res.add(Arrays.asList(nums[i], nums[l], nums[r])); + l++; + r--; + // 跳过重复元素 + while (l < r && nums[l] == nums[l - 1]) l++; + while (l < r && nums[r] == nums[r + 1]) r--; + } else if (sum > target) { + r--; + } else if (sum < target) { + l++; + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..27e751f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1. 两数之和 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 两数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 3, 2, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 3, 3 }, 6)); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 3, 2, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 3, 3 }, 6)); + } + + /** + * 两次 for 循环暴力求解,时间复杂度 o(n^2) + */ + static class Solution { + + public int[] twoSum(int[] nums, int target) { + int n = nums.length; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (nums[i] + nums[j] == target) { + return new int[] { i, j }; + } + } + } + return new int[0]; + } + + } + + /** + * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target + */ + static class Solution2 { + + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(nums.length); + for (int i = 0; i < nums.length; i++) { + int diff = target - nums[i]; + if (map.containsKey(diff)) { + return new int[] { map.get(diff), i }; + } else { + map.put(nums[i], i); + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" new file mode 100644 index 0000000..2585dda --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 167. 两数之和 II - 输入有序数组 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 两数之和2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 3 }, s.twoSum(new int[] { 2, 3, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { -1, 0 }, -1)); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 3 }, s2.twoSum(new int[] { 2, 3, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { -1, 0 }, -1)); + } + + /** + * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target + */ + static class Solution { + + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(nums.length); + for (int i = 0; i < nums.length; i++) { + int diff = target - nums[i]; + if (map.containsKey(diff)) { + return new int[] { map.get(diff), i + 1 }; + } else { + map.put(nums[i], i + 1); + } + } + return new int[0]; + } + + } + + /** + * 双指针 + */ + static class Solution2 { + + public int[] twoSum(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left < right) { + if (nums[left] + nums[right] == target) { + return new int[] { left + 1, right + 1 }; + } else if (nums[left] + nums[right] < target) { + left++; + } else { + right--; + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" new file mode 100644 index 0000000..1e7c9e1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" @@ -0,0 +1,92 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 1260. 二维网格迁移 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 二维网格迁移 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] grid1 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + List> res1 = s.shiftGrid(grid1, 1); + Assertions.assertNotNull(res1); + Assertions.assertArrayEquals(new Integer[] { 9, 1, 2 }, res1.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 3, 4, 5 }, res1.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 6, 7, 8 }, res1.get(2).toArray(new Integer[0])); + + int[][] grid2 = new int[][] { { 3, 8, 1, 9 }, { 19, 7, 2, 5 }, { 4, 6, 11, 10 }, { 12, 0, 21, 13 } }; + List> res2 = s.shiftGrid(grid2, 4); + Assertions.assertNotNull(res2); + Assertions.assertArrayEquals(new Integer[] { 12, 0, 21, 13 }, res2.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 3, 8, 1, 9 }, res2.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 19, 7, 2, 5 }, res2.get(2).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 4, 6, 11, 10 }, res2.get(3).toArray(new Integer[0])); + + int[][] grid3 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + List> res3 = s.shiftGrid(grid3, 9); + Assertions.assertNotNull(res3); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, res3.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 4, 5, 6 }, res3.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 7, 8, 9 }, res3.get(2).toArray(new Integer[0])); + + int[][] grid4 = new int[][] { { 1 }, { 2 }, { 3 }, { 4 }, { 7 }, { 6 }, { 5 } }; + List> res4 = s.shiftGrid(grid4, 23); + Assertions.assertNotNull(res4); + } + + static class Solution { + + public List> shiftGrid(int[][] grid, int k) { + for (int i = 0; i < k; i++) { + shift(grid); + } + + int m = grid.length, n = grid[0].length; + List> res = new ArrayList<>(); + for (int i = 0; i < m; i++) { + List list = new ArrayList<>(); + res.add(list); + for (int j = 0; j < n; j++) { + list.add(grid[i][j]); + } + } + return res; + } + + public void shift(int[][] grid) { + int m = grid.length, n = grid[0].length; + int last = get(grid, m * n - 1); + for (int i = m * n - 1; i > 0; i--) { + int prev = get(grid, i - 1); + set(grid, i, prev); + } + set(grid, 0, last); + } + + public int get(int[][] grid, int index) { + int n = grid[0].length; + int i = index / n; + int j = index % n; + return grid[i][j]; + } + + public void set(int[][] grid, int index, int val) { + int n = grid[0].length; + int i = index / n; + int j = index % n; + grid[i][j] = val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" new file mode 100644 index 0000000..812604b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 67. 二进制求和 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 二进制求和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("100", s.addBinary("11", "1")); + Assertions.assertEquals("10101", s.addBinary("1010", "1011")); + } + + static class Solution { + + public String addBinary(String a, String b) { + int i = a.length() - 1; + int j = b.length() - 1; + int carry = 0; + StringBuilder sb = new StringBuilder(); + while (i >= 0 || j >= 0) { + int numA = i < 0 ? 0 : a.charAt(i--) - '0'; + int numB = j < 0 ? 0 : b.charAt(j--) - '0'; + int sum = numA + numB + carry; + if (sum > 1) { + carry = 1; + sb.append(sum % 2); + } else { + carry = 0; + sb.append(sum); + } + } + if (carry > 0) { + sb.append(carry); + } + return sb.reverse().toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" new file mode 100644 index 0000000..ce195a8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 26. 删除有序数组中的重复项 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 删除排序数组中的重复项 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 1, 2 })); + Assertions.assertEquals(5, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 2, 2, 3, 3, 4 })); + Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 2 })); + Assertions.assertEquals(1, s.removeDuplicates(new int[] { 2, 2 })); + } + + static class Solution { + + public int removeDuplicates(int[] nums) { + int slow = 0, fast = 1; + while (fast < nums.length) { + if (nums[fast] != nums[slow]) { + slow++; + nums[slow] = nums[fast]; + } + fast++; + } + return slow + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" new file mode 100644 index 0000000..ad47707 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 80. 删除有序数组中的重复项 II + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 删除排序数组中的重复项2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.removeDuplicates(new int[] { 1, 1, 1, 2, 2, 3 })); + Assertions.assertEquals(7, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 1, 2, 3, 3 })); + } + + static class Solution { + + public int removeDuplicates(int[] nums) { + int slow = 0, fast = 1; + int cnt = 1; + while (fast < nums.length) { + if (nums[fast] != nums[slow]) { + cnt = 1; + slow++; + nums[slow] = nums[fast]; + } else { + if (cnt < 2) { + slow++; + nums[slow] = nums[fast]; + } + cnt++; + } + fast++; + } + // System.out.printf("slow: %d, fast: %d, nums: %s\n", slow, fast, + // JSONUtil.toJsonStr(ArrayUtil.sub(nums, 0, slow + 1))); + return slow + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..75e2bc1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 344. 反转字符串 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 反转字符串 { + + public static void main(String[] args) { + Solution s = new Solution(); + char[] arr1 = new char[] { 'h', 'e', 'l', 'l', 'o' }; + s.reverseString(arr1); + Assertions.assertArrayEquals(new char[] { 'o', 'l', 'l', 'e', 'h' }, arr1); + + char[] arr2 = new char[] { 'H', 'a', 'n', 'n', 'a', 'h' }; + s.reverseString(arr2); + Assertions.assertArrayEquals(new char[] { 'h', 'a', 'n', 'n', 'a', 'H' }, arr2); + } + + static class Solution { + + public void reverseString(char[] s) { + int left = 0, right = s.length - 1; + while (left < right) { + char temp = s[left]; + s[left] = s[right]; + s[right] = temp; + left++; + right--; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" new file mode 100644 index 0000000..7465ea6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 88. 合并两个有序数组 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 合并两个有序数组 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] nums1 = new int[] { 1, 2, 3, 0, 0, 0 }; + int[] nums2 = new int[] { 2, 5, 6 }; + s.merge(nums1, 3, nums2, 3); + Assertions.assertArrayEquals(new int[] { 1, 2, 2, 3, 5, 6 }, nums1); + + int[] nums3 = new int[] { 1 }; + int[] nums4 = new int[] {}; + s.merge(nums3, 1, nums4, 0); + Assertions.assertArrayEquals(new int[] { 1 }, nums3); + + int[] nums5 = new int[] { 0 }; + int[] nums6 = new int[] { 1 }; + s.merge(nums5, 0, nums6, 1); + Assertions.assertArrayEquals(new int[] { 1 }, nums5); + + int[] nums7 = new int[] { 4, 5, 6, 0, 0, 0 }; + int[] nums8 = new int[] { 1, 2, 3 }; + s.merge(nums7, 3, nums8, 3); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 6 }, nums7); + } + + static class Solution { + + public void merge(int[] nums1, int m, int[] nums2, int n) { + // 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿) + int i = m - 1, j = n - 1; + // 生成排序的结果(类似拉链的拉锁) + int p = nums1.length - 1; + // 从后向前生成结果数组,类似合并两个有序链表的逻辑 + while (i >= 0 && j >= 0) { + if (nums1[i] >= nums2[j]) { + nums1[p] = nums1[i]; + i--; + } else { + nums1[p] = nums2[j]; + j--; + } + p--; + } + // 可能其中一个数组的指针走到尽头了,而另一个还没走完 + while (i >= 0) { + nums1[p--] = nums1[i--]; + } + while (j >= 0) { + nums1[p--] = nums2[j--]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" new file mode 100644 index 0000000..d424a3e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 56. 合并区间 + * + * @author Zhang Peng + * @since 2020-07-29 + */ +public class 合并区间 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input = new int[][] { { 1, 4 }, { 2, 3 } }; + int[][] expect = new int[][] { { 1, 4 } }; + Assertions.assertArrayEquals(expect, s.merge(input)); + + int[][] input2 = new int[][] { { 1, 3 }, { 2, 6 }, { 8, 10 }, { 15, 18 } }; + int[][] expect2 = new int[][] { { 1, 6 }, { 8, 10 }, { 15, 18 } }; + Assertions.assertArrayEquals(expect2, s.merge(input2)); + + int[][] input3 = new int[][] { { 1, 4 }, { 4, 5 } }; + int[][] expect3 = new int[][] { { 1, 5 } }; + Assertions.assertArrayEquals(expect3, s.merge(input3)); + } + + static class Solution { + + public int[][] merge(int[][] intervals) { + + // base case + if (intervals == null || intervals.length <= 1) { return intervals; } + + // 先按区间下限排序 + Arrays.sort(intervals, (a, b) -> a[0] - b[0]); + + // 设置双指针,扫描 intervals + List merged = new ArrayList<>(); + for (int[] interval : intervals) { + int l = interval[0], r = interval[1]; + int last = merged.size() - 1; + if (last == -1 || merged.get(last)[1] < l) { + merged.add(new int[] { l, r }); + } else { + l = merged.get(last)[0]; + r = Math.max(merged.get(last)[1], r); + merged.set(last, new int[] { l, r }); + } + } + return merged.toArray(new int[merged.size()][2]); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" new file mode 100644 index 0000000..cabacab --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * 1329. 将矩阵按对角线排序 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 将矩阵按对角线排序 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input1 = { { 3, 3, 1, 1 }, { 2, 2, 1, 2 }, { 1, 1, 1, 2 } }; + int[][] expected1 = { { 1, 1, 1, 1 }, { 1, 2, 2, 2 }, { 1, 2, 3, 3 } }; + int[][] output1 = s.diagonalSort(input1); + Assertions.assertArrayEquals(expected1, output1); + + int[][] input2 = { { 11, 25, 66, 1, 69, 7 }, { 23, 55, 17, 45, 15, 52 }, { 75, 31, 36, 44, 58, 8 }, + { 22, 27, 33, 25, 68, 4 }, { 84, 28, 14, 11, 5, 50 } }; + int[][] expected2 = { { 5, 17, 4, 1, 52, 7 }, { 11, 11, 25, 45, 8, 69 }, { 14, 23, 25, 44, 58, 15 }, + { 22, 27, 31, 36, 50, 66 }, { 84, 28, 75, 33, 55, 68 } }; + int[][] output2 = s.diagonalSort(input2); + Assertions.assertArrayEquals(expected2, output2); + } + + static class Solution { + + public int[][] diagonalSort(int[][] mat) { + + int m = mat.length, n = mat[0].length; + + // 在同一个对角线上的元素,其横纵坐标之差是相同的 + // 存储所有对角线的元素列表,利用 PriorityQueue 自动对对角线元素排序 + Map> map = new HashMap<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + // 横纵坐标之差可以作为一条对角线的 ID + int diff = i - j; + if (!map.containsKey(diff)) { + map.put(diff, new PriorityQueue<>(Comparator.comparingInt(a -> mat[a[0]][a[1]]))); + } + map.get(diff).add(new int[] { i, j }); + } + } + + // 把排序结果回填二维矩阵 + int[][] res = new int[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + int diff = i - j; + PriorityQueue queue = map.get(diff); + int[] point = queue.poll(); + res[i][j] = mat[point[0]][point[1]]; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" new file mode 100644 index 0000000..008fb47 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 14. 最长公共前缀 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 最长公共前缀 { + + public static void main(String[] args) { + + Solution s = new Solution(); + String[] input1 = { "flower", "flow", "flight" }; + String expect1 = "fl"; + String output1 = s.longestCommonPrefix(input1); + Assertions.assertEquals(expect1, output1); + + String[] input2 = { "dog", "racecar", "car" }; + String expect2 = ""; + String output2 = s.longestCommonPrefix(input2); + Assertions.assertEquals(expect2, output2); + } + + static class Solution { + + public String longestCommonPrefix(String[] strs) { + int m = strs.length; + // 以第一行的列数为基准 + int n = strs[0].length(); + for (int col = 0; col < n; col++) { + for (int row = 1; row < m; row++) { + String cur = strs[row], prev = strs[row - 1]; + // 判断每个字符串的 col 索引是否都相同 + if (col >= cur.length() || col >= prev.length() || + cur.charAt(col) != prev.charAt(col)) { + // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 + return strs[row].substring(0, col); + } + } + } + return strs[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" new file mode 100644 index 0000000..c42a437 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 5. 最长回文子串 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长回文子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("bab", s.longestPalindrome("babad")); + Assertions.assertEquals("bb", s.longestPalindrome("cbbd")); + Assertions.assertEquals("a", s.longestPalindrome("a")); + Assertions.assertEquals("bb", s.longestPalindrome("bb")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals("aba", s2.longestPalindrome("babad")); + Assertions.assertEquals("bb", s2.longestPalindrome("cbbd")); + Assertions.assertEquals("a", s2.longestPalindrome("a")); + Assertions.assertEquals("bb", s2.longestPalindrome("bb")); + } + + /** + * 双指针判断回文串 + 暴力解决,时间复杂度 o(n^2) + */ + static class Solution { + + public String longestPalindrome(String s) { + String res = s.substring(0, 1); + for (int i = 0; i < s.length(); i++) { + for (int j = i + 1; j < s.length(); j++) { + if (isPalindrome(s, i, j)) { + int len = j - i + 1; + if (len > res.length()) { + res = s.substring(i, j + 1); + } + } + } + } + return res; + } + + public boolean isPalindrome(String s, int left, int right) { + while (left < right) { + if (s.charAt(left) != s.charAt(right)) { + return false; + } + left++; + right--; + } + return true; + } + + } + + static class Solution2 { + + public String longestPalindrome(String s) { + String res = ""; + for (int i = 0; i < s.length(); i++) { + String s1 = palindrome(s, i, i); + String s2 = palindrome(s, i, i + 1); + res = res.length() > s1.length() ? res : s1; + res = res.length() > s2.length() ? res : s2; + } + return res; + } + + public String palindrome(String s, int l, int r) { + while (l >= 0 && r < s.length() + && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + return s.substring(l + 1, r); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" new file mode 100644 index 0000000..e9324ca --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 977. 有序数组的平方 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 有序数组的平方 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] input1 = { -4, -1, 0, 3, 10 }; + int[] expect1 = { 0, 1, 9, 16, 100 }; + int[] output1 = s.sortedSquares(input1); + Assertions.assertArrayEquals(expect1, output1); + + int[] input2 = { -7, -3, 2, 3, 11 }; + int[] expect2 = { 4, 9, 9, 49, 121 }; + int[] output2 = s.sortedSquares(input2); + Assertions.assertArrayEquals(expect2, output2); + } + + public static class Solution { + + public int[] sortedSquares(int[] nums) { + int p = nums.length - 1; + int i = 0, j = nums.length - 1; + int[] res = new int[nums.length]; + while (i <= j) { + if (Math.abs(nums[i]) > Math.abs(nums[j])) { + res[p] = nums[i] * nums[i]; + i++; + } else { + res[p] = nums[j] * nums[j]; + j--; + } + p--; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" new file mode 100644 index 0000000..59232f3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.PriorityQueue; + +/** + * 378. 有序矩阵中第 K 小的元素 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 有序矩阵中第K小的元素 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] matrix = { { 1, 5, 9 }, { 10, 11, 13 }, { 12, 13, 15 } }; + Assertions.assertEquals(13, s.kthSmallest(matrix, 8)); + + int[][] matrix2 = { { -5 } }; + Assertions.assertEquals(-5, s.kthSmallest(matrix2, 1)); + + int[][] matrix3 = { { 1, 2 }, { 1, 3 } }; + Assertions.assertEquals(1, s.kthSmallest(matrix3, 2)); + + int[][] matrix4 = { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; + Assertions.assertEquals(3, s.kthSmallest(matrix4, 8)); + } + + static class Solution { + + public int kthSmallest(int[][] matrix, int k) { + // 存储二元组 (matrix[i][j], i, j) + // i, j 记录当前元素的索引位置,用于生成下一个节点 + PriorityQueue pq = new PriorityQueue<>((a, b) -> { + // 按照元素大小升序排序 + return a[0] - b[0]; + }); + + // 初始化优先级队列,把每一行的第一个元素装进去 + for (int i = 0; i < matrix.length; i++) { + pq.offer(new int[] { matrix[i][0], i, 0 }); + } + + int res = -1; + while (!pq.isEmpty() && k > 0) { + int[] cur = pq.poll(); + res = cur[0]; + k--; + + // 链表中的下一个节点加入优先级队列 + int i = cur[1], j = cur[2]; + if (j + 1 < matrix[i].length) { + pq.add(new int[] { matrix[i][j + 1], i, j + 1 }); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" new file mode 100644 index 0000000..89cfd6a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import cn.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +/** + * 373. 查找和最小的 K 对数字 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 查找和最小的K对数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> expectList1 = new ArrayList<>(); + expectList1.add(Arrays.asList(1, 2)); + expectList1.add(Arrays.asList(1, 4)); + expectList1.add(Arrays.asList(1, 6)); + List> list1 = s.kSmallestPairs(new int[] { 1, 7, 11 }, new int[] { 2, 4, 6 }, 3); + System.out.println(JSONUtil.toJsonStr(list1)); + + List> list2 = s.kSmallestPairs(new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, 2); + System.out.println(JSONUtil.toJsonStr(list2)); + } + + static class Solution { + + public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { + PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a[0] + a[1]))); + for (int i = 0; i < nums1.length; i++) { + for (int j = 0; j < nums2.length; j++) { + queue.offer(new int[] { nums1[i], nums2[j] }); + } + } + + List> res = new ArrayList<>(); + for (int i = 0; i < k; i++) { + int[] element = queue.poll(); + res.add(Arrays.asList(element[0], element[1])); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" new file mode 100644 index 0000000..626bd5b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * LCR 179. 查找总价格为目标值的两个商品 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 查找总价格为目标值的两个商品 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 3, 15 }, s.twoSum(new int[] { 3, 9, 12, 15 }, 18)); + Assertions.assertArrayEquals(new int[] { 27, 34 }, s.twoSum(new int[] { 8, 21, 27, 34, 52, 66 }, 61)); + } + + static class Solution { + + public int[] twoSum(int[] nums, int target) { + Set set = new HashSet<>(); + for (int num : nums) { + int diff = target - num; + if (set.contains(diff)) { + return new int[] { num, diff }; + } else { + set.add(num); + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" new file mode 100644 index 0000000..b682999 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 283. 移动零 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 移动零 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] arr1 = { 0, 1, 0, 3, 12 }; + s.moveZeroes(arr1); + Assertions.assertArrayEquals(new int[] { 1, 3, 12, 0, 0 }, arr1); + + int[] arr2 = { 0, 0, 1 }; + s.moveZeroes(arr2); + Assertions.assertArrayEquals(new int[] { 1, 0, 0 }, arr2); + + int[] arr3 = { 0 }; + s.moveZeroes(arr3); + Assertions.assertArrayEquals(new int[] { 0 }, arr3); + } + + public static class Solution { + + public void moveZeroes(int[] nums) { + // slow 指针维护所有不为 0 的元素 + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != 0) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + // 后续补零 + for (int i = slow; i < nums.length; i++) { + nums[i] = 0; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" new file mode 100644 index 0000000..52992a1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 27. 移除元素 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 移除元素 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] arr1 = { 3, 2, 2, 3 }; + Assertions.assertEquals(2, s.removeElement(arr1, 3)); + + int[] arr2 = { 0, 1, 2, 2, 3, 0, 4, 2 }; + Assertions.assertEquals(5, s.removeElement(arr2, 2)); + + int[] arr3 = { 1 }; + Assertions.assertEquals(0, s.removeElement(arr3, 1)); + } + + static class Solution { + + public int removeElement(int[] nums, int val) { + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != val) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + return slow; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" new file mode 100644 index 0000000..92104fe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 1329. 将矩阵按对角线排序 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 转置矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + int[][] expect1 = { { 1, 4, 7 }, { 2, 5, 8 }, { 3, 6, 9 } }; + int[][] output1 = s.transpose(input1); + Assertions.assertArrayEquals(expect1, output1); + + int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 } }; + int[][] expect2 = { { 1, 4 }, { 2, 5 }, { 3, 6 } }; + int[][] output2 = s.transpose(input2); + Assertions.assertArrayEquals(expect2, output2); + } + + public static class Solution { + + public int[][] transpose(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + int[][] res = new int[n][m]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + res[j][i] = matrix[i][j]; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" new file mode 100644 index 0000000..d8d6d83 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import cn.hutool.json.JSONUtil; +import org.junit.jupiter.api.Assertions; + +/** + * 75. 颜色分类 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 颜色分类 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] nums1 = { 2, 0, 2, 1, 1, 0 }; + s.sortColors(nums1); + Assertions.assertArrayEquals(new int[] { 0, 0, 1, 1, 2, 2 }, nums1); + + int[] nums2 = { 2, 0, 1 }; + s.sortColors(nums2); + Assertions.assertArrayEquals(new int[] { 0, 1, 2 }, nums2); + } + + static class Solution { + + public void sortColors(int[] nums) { + moveToTail(nums, 1); + System.out.println("nums = " + JSONUtil.toJsonStr(nums)); + moveToTail(nums, 2); + System.out.println("nums = " + JSONUtil.toJsonStr(nums)); + } + + public void moveToTail(int[] nums, int val) { + if (nums == null || nums.length == 0) { return; } + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != val) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + for (int i = slow; i < nums.length; i++) { + nums[i] = val; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" new file mode 100644 index 0000000..4c1597e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 125. 验证回文串 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 验证回文串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isPalindrome("A man, a plan, a canal: Panama")); + Assertions.assertFalse(s.isPalindrome("race a car")); + Assertions.assertTrue(s.isPalindrome(" ")); + Assertions.assertTrue(s.isPalindrome("ab_a")); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.isPalindrome("A man, a plan, a canal: Panama")); + Assertions.assertFalse(s2.isPalindrome("race a car")); + Assertions.assertTrue(s2.isPalindrome(" ")); + Assertions.assertTrue(s2.isPalindrome("ab_a")); + } + + static class Solution { + + public boolean isPalindrome(String s) { + String format = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); + return isPalindrome(format, 0, format.length() - 1); + } + + public boolean isPalindrome(String s, int left, int right) { + while (left < right) { + if (s.charAt(left) != s.charAt(right)) { + return false; + } + left++; + right--; + } + return true; + } + + } + + static class Solution2 { + + public boolean isPalindrome(String s) { + int left = 0, right = s.length() - 1; + while (left < right) { + if (!Character.isLetterOrDigit(s.charAt(left))) { + left++; + continue; + } + if (!Character.isLetterOrDigit(s.charAt(right))) { + right--; + continue; + } + + char l = Character.toLowerCase(s.charAt(left)); + char r = Character.toLowerCase(s.charAt(right)); + if (l != r) { + return false; + } + left++; + right--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..6a6d857 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 713. 乘积小于 K 的子数组 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 乘积小于K的子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(8, s.numSubarrayProductLessThanK(new int[] { 10, 5, 2, 6 }, 100)); + Assertions.assertEquals(0, s.numSubarrayProductLessThanK(new int[] { 1, 2, 3 }, 0)); + } + + static class Solution { + + public int numSubarrayProductLessThanK(int[] nums, int k) { + int left = 0, right = 0; + // 滑动窗口,初始化为乘法单位元 + int windowProduct = 1; + // 记录符合条件的子数组个数 + int count = 0; + + while (right < nums.length) { + // 扩大窗口,并更新窗口数据 + windowProduct = windowProduct * nums[right]; + right++; + + while (left < right && windowProduct >= k) { + // 缩小窗口,并更新窗口数据 + windowProduct = windowProduct / nums[left]; + left++; + } + // 现在必然是一个合法的窗口,但注意思考这个窗口中的子数组个数怎么计算: + // 比方说 left = 1, right = 4 划定了 [1, 2, 3] 这个窗口(right 是开区间) + // 但不止 [left..right] 是合法的子数组,[left+1..right], [left+2..right] 等都是合法子数组 + // 所以我们需要把 [3], [2,3], [1,2,3] 这 right - left 个子数组都加上 + count += right - left; + } + + return count; + } + + } + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" new file mode 100644 index 0000000..22f6ea9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 567. 字符串的排列 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 字符串的排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.checkInclusion("ab", "eidbaooo")); + Assertions.assertFalse(s.checkInclusion("ab", "eidboaoo")); + } + + static class Solution { + + public boolean checkInclusion(String t, String s) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1); + + int left = 0, right = 0; + int valid = 0; + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { valid++; } + } + + // 判断左侧窗口是否要收缩 + while (right - left >= t.length()) { + // 在这里判断是否找到了合法的子串 + if (valid == need.size()) { return true; } + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { valid--; } + window.put(d, window.get(d) - 1); + } + } + } + // 未找到符合条件的子串 + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" new file mode 100644 index 0000000..8bf319d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * 217. 存在重复元素 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 存在重复元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 2, 3, 1 })); + Assertions.assertFalse(s.containsDuplicate(new int[] { 1, 2, 3, 4 })); + Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 1, 1, 3, 3, 4, 3, 2, 4, 2 })); + } + + static class Solution { + + public boolean containsDuplicate(int[] nums) { + Set set = new HashSet<>(); + for (int num : nums) { + if (set.contains(num)) { + return true; + } + set.add(num); + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..2e6b279 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; + +/** + * 219. 存在重复元素 II + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 存在重复元素2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1 }, 3)); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 0, 1, 1 }, 1)); + Assertions.assertFalse(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1, 2, 3 }, 2)); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 99, 99 }, 2)); + } + + static class Solution { + + public boolean containsNearbyDuplicate(int[] nums, int k) { + + // base case + if (nums == null || nums.length < 2) { return false; } + + int left = 0, right = 0; + HashSet window = new HashSet<>(); + // 滑动窗口算法框架,维护一个大小为 k 的窗口 + while (right < nums.length) { + // 扩大窗口 + if (window.contains(nums[right])) { return true; } + window.add(nums[right]); + right++; + + if (right - left > k) { + // 当窗口的大小大于 k 时,缩小窗口 + window.remove(nums[left]); + left++; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" new file mode 100644 index 0000000..8265957 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.TreeSet; + +/** + * 220. 存在重复元素 III + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 存在重复元素3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 3, 1 }, 3, 0)); + Assertions.assertFalse(s.containsNearbyAlmostDuplicate(new int[] { 1, 5, 9, 1, 5, 9 }, 2, 3)); + Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 2, 3, 4, 5 }, 3, 0)); + } + + static class Solution { + + public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { + TreeSet window = new TreeSet<>(); + int left = 0, right = 0; + while (right < nums.length) { + // 为了防止 i == j,所以在扩大窗口之前先判断是否有符合题意的索引对 (i, j) + // 查找略大于 nums[right] 的那个元素 + Integer ceiling = window.ceiling(nums[right]); + if (ceiling != null && (long) ceiling - nums[right] <= t) { + return true; + } + // 查找略小于 nums[right] 的那个元素 + Integer floor = window.floor(nums[right]); + if (floor != null && (long) nums[right] - floor <= t) { + return true; + } + + // 扩大窗口 + window.add(nums[right]); + right++; + + if (right - left > k) { + // 缩小窗口 + window.remove(nums[left]); + left++; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" new file mode 100644 index 0000000..d39d1c8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 1658. 将 x 减到 0 的最小操作数 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 将x减到0的最小操作数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minOperations(new int[] { 1, 1, 4, 2, 3 }, 5)); + Assertions.assertEquals(-1, s.minOperations(new int[] { 5, 6, 7, 8, 9 }, 4)); + Assertions.assertEquals(5, s.minOperations(new int[] { 3, 2, 20, 1, 1, 3 }, 10)); + Assertions.assertEquals(16, s.minOperations(new int[] { 8828, 9581, 49, 9818, 9974, 9869, 9991, + 10000, 10000, 10000, 9999, 9993, 9904, 8819, 1231, 6309 }, 134365)); + } + + static class Solution { + + // 【思路】 + // 从边缘删除掉和为 x 的元素,那剩下来的是什么?剩下来的是不是就是 nums 中的一个子数组? + // 让你尽可能少地从边缘删除元素说明什么?是不是就是说剩下来的这个子数组大小尽可能的大? + // 所以,这道题等价于让你寻找 nums 中元素和为 sum(nums) - x 的最长子数组。 + + // 1、当窗口内元素之和小于目标和 target 时,扩大窗口,让窗口内元素和增加。 + // 2、当窗口内元素之和大于目标和 target 时,缩小窗口,让窗口内元素和减小。 + // 3、当窗口内元素之和等于目标和 target 时,找到一个符合条件的子数组,我们想找的是最长的子数组长度。 + public int minOperations(int[] nums, int x) { + int n = nums.length, sum = 0; + for (int num : nums) { sum += num; } + // 滑动窗口需要寻找的子数组目标和 + int target = sum - x; + + int left = 0, right = 0; + // 记录窗口内所有元素和 + int windowSum = 0; + // 记录目标子数组的最大长度 + int maxLen = Integer.MIN_VALUE; + // 开始执行滑动窗口框架 + while (right < nums.length) { + // 扩大窗口 + windowSum += nums[right]; + right++; + + while (windowSum > target && left < right) { + // 缩小窗口 + windowSum -= nums[left]; + left++; + } + // 寻找目标子数组 + if (windowSum == target) { + maxLen = Math.max(maxLen, right - left); + } + } + // 目标子数组的最大长度可以推导出需要删除的字符数量 + return maxLen == Integer.MIN_VALUE ? -1 : n - maxLen; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" new file mode 100644 index 0000000..2a6bb20 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 438. 找到字符串中所有字母异位词 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 找到字符串中所有字母异位词 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 0, 6 }, s.findAnagrams("cbaebabacd", "abc").toArray()); + Assertions.assertArrayEquals(new Integer[] { 0, 1, 2 }, s.findAnagrams("abab", "ab").toArray()); + } + + static class Solution { + + public List findAnagrams(String s, String t) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) { + need.put(c, need.getOrDefault(c, 0) + 1); + } + + int left = 0, right = 0; + int valid = 0; + // 记录结果 + List res = new ArrayList<>(); + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { + valid++; + } + } + // 判断左侧窗口是否要收缩 + while (right - left >= t.length()) { + // 当窗口符合条件时,把起始索引加入 res + if (valid == need.size()) { + res.add(left); + } + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { + valid--; + } + window.put(d, window.get(d) - 1); + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" new file mode 100644 index 0000000..52f0955 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 3. 无重复字符的最长子串 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 无重复字符的最长子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.lengthOfLongestSubstring("abcabcbb")); + Assertions.assertEquals(1, s.lengthOfLongestSubstring("bbbbb")); + Assertions.assertEquals(3, s.lengthOfLongestSubstring("pwwkew")); + Assertions.assertEquals(2, s.lengthOfLongestSubstring("aab")); + } + + static class Solution { + + public int lengthOfLongestSubstring(String s) { + Map window = new HashMap<>(); + + int left = 0, right = 0; + // 记录结果 + int res = 0; + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + window.put(c, window.getOrDefault(c, 0) + 1); + // 判断左侧窗口是否要收缩 + while (window.get(c) > 1) { + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + window.put(d, window.get(d) - 1); + } + // 在这里更新答案 + res = Math.max(res, right - left); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" new file mode 100644 index 0000000..58f6ba0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 424. 替换后的最长重复字符 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 替换后的最长重复字符 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.characterReplacement("ABAB", 2)); + Assertions.assertEquals(4, s.characterReplacement("AABABBA", 1)); + Assertions.assertEquals(4, s.characterReplacement("AAAA", 2)); + } + + static class Solution { + + public int characterReplacement(String s, int k) { + int left = 0, right = 0; + // 统计窗口中每个字符的出现次数 + int[] windowCharCount = new int[26]; + // 记录窗口中字符的最多重复次数 + // 记录这个值的意义在于,最划算的替换方法肯定是把其他字符替换成出现次数最多的那个字符 + int windowMaxCount = 0; + // 记录结果长度 + int res = 0; + + // 开始滑动窗口模板 + while (right < s.length()) { + // 扩大窗口 + int c = s.charAt(right) - 'A'; + windowCharCount[c]++; + windowMaxCount = Math.max(windowMaxCount, windowCharCount[c]); + right++; + + // 这个 while 换成 if 也可以 + while (right - left - windowMaxCount > k) { + // 杂牌字符数量 right - left - windowMaxCount 多于 k + // 此时,k 次替换已经无法把窗口内的字符都替换成相同字符了 + // 必须缩小窗口 + windowCharCount[s.charAt(left) - 'A']--; + left++; + } + // 经过收缩后,此时一定是一个合法的窗口 + res = Math.max(res, right - left); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" new file mode 100644 index 0000000..531e688 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 1004. 最大连续1的个数 III + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 最大连续1的个数3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.longestOnes(new int[] { 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, 2)); + Assertions.assertEquals(10, + s.longestOnes(new int[] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, 3)); + } + + static class Solution { + + public int longestOnes(int[] nums, int k) { + int cnt = 0, len = 0; + int left = 0, right = 0; + while (right < nums.length) { + if (nums[right] == 0) { cnt++; } + right++; + + while (cnt > k) { + if (nums[left] == 0) { cnt--; } + left++; + } + len = Math.max(len, right - left); + } + return len; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" new file mode 100644 index 0000000..81b42a2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.window; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 76. 最小覆盖子串 + * + * @author Zhang Peng + * @date 2025-01-10 + */ +@Slf4j +public class 最小覆盖子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("BANC", s.minWindow("ADOBECODEBANC", "ABC")); + Assertions.assertEquals("a", s.minWindow("a", "a")); + Assertions.assertEquals("", s.minWindow("a", "aa")); + } + + static class Solution { + + public String minWindow(String s, String t) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) { + need.put(c, need.getOrDefault(c, 0) + 1); + } + + int valid = 0; + int left = 0, right = 0; + // 记录最小覆盖子串的起始索引及长度 + int start = 0, len = Integer.MAX_VALUE; + while (right < s.length()) { + // c 是将移入窗口的字符 + char c = s.charAt(right); + // 扩大窗口 + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { valid++; } + } + + // 判断左侧窗口是否要收缩 + while (valid == need.size()) { + + // 在这里更新最小覆盖子串 + if (right - left < len) { + start = left; + len = right - left; + } + // d 是将移出窗口的字符 + char d = s.charAt(left); + // 缩小窗口 + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { valid--; } + window.put(d, window.get(d) - 1); + } + } + } + // 返回最小覆盖子串 + return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" new file mode 100644 index 0000000..5a8994b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 395. 至少有 K + * 个重复字符的最长子串 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 至少有K个重复字符的最长子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.longestSubstring("aaabb", 3)); + Assertions.assertEquals(5, s.longestSubstring("ababbc", 2)); + Assertions.assertEquals(6, s.longestSubstring("aaabbb", 3)); + } + + public static class Solution { + + public int longestSubstring(String s, int k) { + int len = 0; + for (int i = 1; i <= 26; i++) { + // 限制窗口中只能有 i 种不同字符 + len = Math.max(len, longestKLetterSubstring(s, k, i)); + } + return len; + } + + // 寻找 s 中含有 count 种字符,且每种字符出现次数都大于 k 的子串 + public int longestKLetterSubstring(String s, int k, int count) { + + // 记录答案 + int res = 0; + // 快慢指针维护滑动窗口,左闭右开区间 + int left = 0, right = 0; + // 题目说 s 中只有小写字母,所以用大小 26 的数组记录窗口中字符出现的次数 + int[] windowCount = new int[26]; + // 记录窗口中存在几种不同的字符(字符种类) + int windowUniqueCount = 0; + // 记录窗口中有几种字符的出现次数达标(大于等于 k) + int windowValidCount = 0; + // 滑动窗口代码模板 + while (right < s.length()) { + // 移入字符,扩大窗口 + int c = s.charAt(right) - 'a'; + if (windowCount[c] == 0) { + // 窗口中新增了一种字符 + windowUniqueCount++; + } + windowCount[c]++; + if (windowCount[c] == k) { + // 窗口中新增了一种达标的字符 + windowValidCount++; + } + right++; + + // 当窗口中字符种类大于 count 时,缩小窗口 + while (windowUniqueCount > count) { + // 移出字符,缩小窗口 + int d = s.charAt(left) - 'a'; + if (windowCount[d] == k) { + // 窗口中减少了一种达标的字符 + windowValidCount--; + } + windowCount[d]--; + if (windowCount[d] == 0) { + // 窗口中减少了一种字符 + windowUniqueCount--; + } + left++; + } + + // 当窗口中字符种类为 count 且每个字符出现次数都满足 k 时,更新答案 + if (windowValidCount == count) { + res = Math.max(res, right - left); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..b636134 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 209. 长度最小的子数组 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 长度最小的子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minSubArrayLen(7, new int[] { 2, 3, 1, 2, 4, 3 })); + Assertions.assertEquals(1, s.minSubArrayLen(4, new int[] { 1, 4, 4 })); + Assertions.assertEquals(0, s.minSubArrayLen(11, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 })); + } + + public static class Solution { + + public int minSubArrayLen(int target, int[] nums) { + int left = 0, right = 0; + // 维护窗口内元素之和 + int windowSum = 0; + int res = Integer.MAX_VALUE; + + while (right < nums.length) { + // 扩大窗口 + windowSum += nums[right]; + right++; + while (windowSum >= target && left < right) { + // 已经达到 target,缩小窗口,同时更新答案 + res = Math.min(res, right - left); + windowSum -= nums[left]; + left++; + } + } + return res == Integer.MAX_VALUE ? 0 : res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\211\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\211\346\225\260\344\271\213\345\222\214.java" deleted file mode 100644 index 5219810..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\211\346\225\260\344\271\213\345\222\214.java" +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 - *

- * 注意:答案中不可以包含重复的三元组。 - *

- * 示例:给定数组 nums = [-1, 0, 1, 2, -1, -4], - *

- * 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ] - * - * @author Zhang Peng - * @see 三数之和 - * @since 2020-01-18 - */ -public class 三数之和 { - - public static List> threeSum(int[] nums) { - List> list = new ArrayList<>(); - - if (nums == null || nums.length < 3) return list; - - int len = nums.length; - Arrays.sort(nums); - - for (int i = 0; i < len; i++) { - if (nums[i] > 0) break; - - // 去重 - if (i > 0 && nums[i] == nums[i - 1]) continue; - - int L = i + 1; - int R = len - 1; - while (L < R) { - int sum = nums[i] + nums[L] + nums[R]; - if (sum == 0) { - list.add(Arrays.asList(nums[i], nums[L], nums[R])); - while (L < R && nums[L] == nums[L + 1]) L++; - while (L < R && nums[R] == nums[R - 1]) R--; - L++; - R--; - } else if (sum < 0) { - L++; - } else if (sum > 0) { - R--; - } - } - } - - return list; - } - - public static void main(String[] args) { - List> list = threeSum(new int[] { -1, 0, 1, 2, -1, -4 }); - Assertions.assertEquals(Arrays.asList(-1, 0, 1), list.get(1)); - Assertions.assertEquals(Arrays.asList(-1, -1, 2), list.get(0)); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\244\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\244\346\225\260\344\271\213\345\222\214.java" deleted file mode 100644 index 9f8ad23..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\270\244\346\225\260\344\271\213\345\222\214.java" +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 两数之和 { - - public static void main(String[] args) { - Assertions.assertArrayEquals(new int[] { 1, 2 }, twoSumInSorted(new int[] { 2, 7, 11, 15 }, 9)); - Assertions.assertArrayEquals(new int[] { 1, 3 }, twoSumInSorted(new int[] { 2, 3, 4 }, 6)); - Assertions.assertArrayEquals(new int[] { 1, 2 }, twoSumInSorted(new int[] { 0, 0, 3, 4 }, 0)); - - Assertions.assertArrayEquals(new int[] { 0, 1 }, - twoSum_method1(new int[] { 2, 7, 11, 15 }, 9)); - Assertions.assertArrayEquals(new int[] { 1, 2 }, - twoSum_method1(new int[] { 3, 2, 4 }, 6)); - Assertions.assertArrayEquals(new int[] { -1, -1 }, - twoSum_method1(new int[] { 3, 2, 4 }, 9)); - - Assertions.assertArrayEquals(new int[] { 0, 1 }, - twoSum_method2(new int[] { 2, 7, 11, 15 }, 9)); - Assertions.assertArrayEquals(new int[] { 1, 2 }, - twoSum_method2(new int[] { 3, 2, 4 }, 6)); - Assertions.assertArrayEquals(new int[] { -1, -1 }, - twoSum_method2(new int[] { 3, 2, 4 }, 9)); - } - - /** - * 题目:1. 两数之和 - *

- * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 - *

- * 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 - */ - public static int[] twoSumInSorted(int[] nums, int target) { - final int[] notFound = new int[] { -1, -1 }; - if (nums == null || nums.length < 2) { - return notFound; - } - - int left = 0, right = nums.length - 1; - while (left <= right) { - int v = nums[left] + nums[right]; - if (v == target) { - return new int[] { left + 1, right + 1 }; - } else if (v > target) { - right--; - } else { - left++; - } - } - return notFound; - } - - /** - * 题目:1. 两数之和 - *

- * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 - *

- * 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 - */ - public static int[] twoSum_method1(int[] nums, int target) { - final int[] notFound = new int[] { -1, -1 }; - if (nums == null || nums.length < 2) { - return notFound; - } - - for (int i = 0; i < nums.length; i++) { - for (int j = i + 1; j < nums.length; j++) { - if (nums[i] + nums[j] == target) { - return new int[] { i, j }; - } - } - } - return notFound; - } - - /** - * 题目:1. 两数之和 - *

- * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 - *

- * 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 - */ - public static int[] twoSum_method2(int[] nums, int target) { - final int[] notFound = new int[] { -1, -1 }; - if (nums == null || nums.length < 2) { - return notFound; - } - - Map map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int temp = target - nums[i]; - if (map.containsKey(temp)) { - return new int[] { map.get(temp), i }; - } else { - map.put(nums[i], i); - } - } - - return notFound; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\272\214\347\273\264\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\272\214\347\273\264\346\225\260\347\273\204.java" deleted file mode 100644 index 5bd729c..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\344\272\214\347\273\264\346\225\260\347\273\204.java" +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.algorithm.array; - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 二维数组 { - - public static void main(String[] args) { - System.out.println("Example I:"); - int[][] a = new int[2][5]; - printArray(a); - System.out.println("Example II:"); - int[][] b = new int[2][]; - printArray(b); - System.out.println("Example III:"); - b[0] = new int[3]; - b[1] = new int[5]; - printArray(b); - } - - private static void printArray(int[][] a) { - for (int i = 0; i < a.length; ++i) { - System.out.println(a[i]); - } - for (int i = 0; i < a.length; ++i) { - for (int j = 0; a[i] != null && j < a[i].length; ++j) { - System.out.print(a[i][j] + " "); - } - System.out.println(); - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" deleted file mode 100644 index f8f7cd2..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【删除排序数组中的重复项】 - -// -// 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 -// -// 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 -// -// 示例 1: -// -// 给定数组 nums = [1,1,2], -// -// 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 示例 2: -// -// 给定 nums = [0,0,1,1,1,2,2,3,3,4], -// -// 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 说明: -// -// 为什么返回数值是整数,但输出的答案是数组呢? -// -// 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 -// -// 你可以想象内部操作如下: -// -// // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 -// int len = removeDuplicates(nums); -// -// // 在函数里修改输入数组对于调用者是可见的。 -// // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 -// for (int i = 0; i < len; i++) { -// print(nums[i]); -// } - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 删除排序数组中的重复项 { - - public static void main(String[] args) { - int[] nums1 = { 1, 1, 2 }; - Assertions.assertEquals(2, 删除排序数组中的重复项.removeDuplicates(nums1)); - - int[] nums2 = { 0, 0, 1, 1, 1, 2, 2, 3, 3, 4 }; - Assertions.assertEquals(5, 删除排序数组中的重复项.removeDuplicates(nums2)); - - int[] nums3 = { 1, 2 }; - Assertions.assertEquals(2, 删除排序数组中的重复项.removeDuplicates(nums3)); - - int[] nums4 = { 2, 2 }; - Assertions.assertEquals(1, 删除排序数组中的重复项.removeDuplicates(nums4)); - } - - public static int removeDuplicates(int[] nums) { - int left = 0; - int right = nums.length - 1; - - while (left <= right) { - for (int i = left + 1; i <= right; i++) { - if (nums[i] == nums[left]) { - remove(nums, i); - right--; - i--; - } - } - left++; - } - - return right + 1; - } - - private static void remove(int[] nums, int pos) { - for (int i = pos; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\212\240\344\270\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\212\240\344\270\200.java" deleted file mode 100644 index dba8a10..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\212\240\344\270\200.java" +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【加一】 - -// -// 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 -// -// 最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。 -// -// 你可以假设除了整数 0 之外,这个整数不会以零开头。 -// -// 示例 1: -// -// 输入: [1,2,3] -// 输出: [1,2,4] -// 解释: 输入数组表示数字 123。 -// 示例 2: -// -// 输入: [4,3,2,1] -// 输出: [4,3,2,2] -// 解释: 输入数组表示数字 4321。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 加一 { - - public static void main(String[] args) { - int[] nums1 = { 1, 2, 3 }; - int[] nums2 = { 4, 3, 2, 1 }; - int[] nums3 = { 9, 9, 9, 9 }; - - int[] expected1 = { 1, 2, 4 }; - int[] expected2 = { 4, 3, 2, 2 }; - int[] expected3 = { 1, 0, 0, 0, 0 }; - - Assertions.assertArrayEquals(expected1, 加一.plusOne(nums1)); - Assertions.assertArrayEquals(expected2, 加一.plusOne(nums2)); - Assertions.assertArrayEquals(expected3, 加一.plusOne(nums3)); - } - - public static int[] plusOne(int[] digits) { - int n = digits.length; - for (int i = n - 1; i >= 0; i--) { - if (digits[i] < 9) { - digits[i]++; - return digits; - } - - digits[i] = 0; - } - - int[] newNumber = new int[n + 1]; - newNumber[0] = 1; - - return newNumber; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\220\210\345\271\266\345\214\272\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\220\210\345\271\266\345\214\272\351\227\264.java" deleted file mode 100644 index 4e64e04..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\220\210\345\271\266\345\214\272\351\227\264.java" +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; - -/** - * @author Zhang Peng - * @since 2020-07-29 - */ -public class 合并区间 { - - public static void main(String[] args) { - int[][] array = new int[][] { - { 1, 4 }, { 2, 3 } - }; - int[][] exprect = new int[][] { - { 1, 4 } - }; - Assertions.assertArrayEquals(exprect, merge(array)); - - // int[][] array = new int[][] { - // { 1, 3 }, { 2, 6 }, { 8, 10 }, { 15, 18 } - // }; - // int[][] exprect = new int[][] { - // { 1, 6 }, { 8, 10 }, { 15, 18 } - // }; - // Assertions.assertArrayEquals(exprect, merge(array)); - - int[][] array2 = new int[][] { - { 1, 4 }, { 4, 5 } - }; - int[][] exprect2 = new int[][] { - { 1, 5 } - }; - Assertions.assertArrayEquals(exprect2, merge(array2)); - } - - public static int[][] merge(int[][] intervals) { - Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]); - - int len = intervals.length; - int[][] res = new int[len][2]; - int cnt = 0; - for (int[] interval : intervals) { - boolean merged = false; - for (int i = 0; i < cnt; i++) { - if (interval[0] >= res[i][0] && interval[1] <= res[i][1]) { - merged = true; - continue; - } - if (interval[0] <= res[i][1]) { - if (interval[1] >= res[i][1]) { - res[i][1] = interval[1]; - merged = true; - continue; - } - } - } - if (!merged) { - res[cnt] = Arrays.copyOf(interval, 2); - cnt++; - } - } - return Arrays.copyOf(res, cnt); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" deleted file mode 100644 index 9072d40..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 在排序数组中查找元素的第一个和最后一个位置 { - - public static void main(String[] args) { - Assertions.assertArrayEquals(new int[] { 3, 4 }, - searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 8)); - Assertions.assertArrayEquals(new int[] { -1, -1 }, - searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 6)); - - Assertions.assertEquals(-1, searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); - Assertions.assertEquals(0, searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); - Assertions.assertEquals(5, searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); - Assertions.assertEquals(-1, searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); - Assertions.assertEquals(1, searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); - - Assertions.assertEquals(-1, searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); - Assertions.assertEquals(0, searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); - Assertions.assertEquals(5, searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); - Assertions.assertEquals(-1, searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); - Assertions.assertEquals(2, searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); - } - - /** - * 题目:34. - * 在排序数组中查找元素的第一个和最后一个位置 - *

- * 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 - *

- * 如果数组中不存在目标值,返回 [-1, -1]。 - */ - public static int[] searchRange(int[] nums, int target) { - final int[] notFoundResult = { -1, -1 }; - if (nums == null || nums.length == 0) { return notFoundResult; } - - int begin = searchLeft(nums, target); - if (begin == nums.length || nums[begin] != target) { return notFoundResult; } - int end = searchRight(nums, target); - return new int[] { begin, end }; - } - - public static int searchLeft(int[] nums, int target) { - if (nums == null || nums.length == 0) { return -1; } - - int left = 0, right = nums.length - 1; - while (left <= right) { - int mid = left + (right - left) / 2; - if (nums[mid] < target) { - left = mid + 1; - } else if (nums[mid] > target) { - right = mid - 1; - } else if (nums[mid] == target) { - right = mid - 1; - } - } - - if (left >= nums.length || nums[left] != target) { - return -1; - } - return left; - } - - public static int searchRight(int[] nums, int target) { - if (nums == null || nums.length == 0) { return -1; } - - int left = 0, right = nums.length - 1; - while (left <= right) { - int mid = left + (right - left) / 2; - if (nums[mid] > target) { - right = mid - 1; - } else if (nums[mid] < target) { - left = mid + 1; - } else if (nums[mid] == target) { - left = mid + 1; - } - } - - if (right < 0 || nums[right] != target) { - return -1; - } - return right; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.java" deleted file mode 100644 index d3c4f6c..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.java" +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 在排序数组中查找数字I { - - public static void main(String[] args) { - Assertions.assertEquals(2, count(8, new Integer[] { 7, 8, 5, 10, 7, 8 })); - Assertions.assertEquals(0, count(6, new Integer[] { 5, 7, 7, 8, 8, 10 })); - Assertions.assertEquals(2, count("abc", new String[] { "abc", "xyz", "lmn", "abc" })); - } - - /** - * 题目:面试题53 - I. - * 在排序数组中查找数字I - *

- * 统计一个元素在数组中出现的次数。 - */ - public static int count(T target, T[] array) { - Arrays.sort(array); - - int count = 0; - for (T i : array) { - if (target.equals(i)) { - count++; - continue; - } - - if (count != 0) { break; } - } - return count; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" deleted file mode 100644 index 266542f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 存在重复元素 { - - public static void main(String[] args) { - Assertions.assertTrue(containsDuplicate(new Integer[] { 1, 2, 3, 1 })); - Assertions.assertFalse(containsDuplicate(new Integer[] { 1, 2, 3, 4 })); - Assertions.assertTrue(containsDuplicate(new Integer[] { 1, 1, 1, 3, 3, 4, 3, 2, 4, 2 })); - } - - /** - * 题目:217. 存在重复元素 - *

- * 给定一个数组,判断是否存在重复元素。 - *

- * 如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。 - * - * @param array 数组 - * @return true/false - */ - public static boolean containsDuplicate(T[] array) { - if (array == null || array.length <= 1) { - return false; - } - - Set set = new HashSet<>(); - set.addAll(Arrays.asList(array)); - - return set.size() != array.length; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" deleted file mode 100644 index 3921f19..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【对角线遍历】 -// -// 给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。 -// -// 示例: -// -// 输入: -// [ -// [ 1, 2, 3 ], -// [ 4, 5, 6 ], -// [ 7, 8, 9 ] -// ] -// -// 输出: [1,2,4,7,5,3,6,8,9] -// -// 说明: -// -// 给定矩阵中的元素总数不会超过 100000 。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 对角线遍历 { - - public static void main(String[] args) { - int[][] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; - int[] expected = { 1, 2, 4, 7, 5, 3, 6, 8, 9 }; - Assertions.assertArrayEquals(expected, 对角线遍历.findDiagonalOrder(matrix)); - } - - public static int[] findDiagonalOrder(int[][] matrix) { - if (matrix.length == 0) { - return new int[0]; - } - - int x = 0, y = 0; - final int M = matrix.length; - final int N = matrix[0].length; - int[] arr = new int[M * N]; - for (int i = 0; i < arr.length; i++) { - arr[i] = matrix[x][y]; - if ((x + y) % 2 == 0) { - if (y == N - 1) { - x++; - } else if (x == 0) { - y++; - } else { - x--; - y++; - } - } else { - if (x == M - 1) { - y++; - } else if (y == 0) { - x++; - } else { - x++; - y--; - } - } - } - return arr; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" deleted file mode 100644 index faeb40a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【寻找数组的中心索引】 -// -// 给定一个整数类型的数组 nums,请编写一个能够返回数组“中心索引”的方法。 -// -// 我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。 -// -// 如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。 -// -// 示例 1: -// -// 输入: -// nums = [1, 7, 3, 6, 5, 6] -// 输出: 3 -// 解释: -// 索引3 (nums[3] = 6) 的左侧数之和(1 + 7 + 3 = 11),与右侧数之和(5 + 6 = 11)相等。 -// 同时, 3 也是第一个符合要求的中心索引。 -// 示例 2: -// -// 输入: -// nums = [1, 2, 3] -// 输出: -1 -// 解释: -// 数组中不存在满足此条件的中心索引。 -// 说明: -// -// nums 的长度范围为 [0, 10000]。 -// 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 寻找数组的中心索引 { - - public static void main(String[] args) { - Assertions.assertEquals(3, pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 })); - Assertions.assertEquals(-1, pivotIndex(new int[] { 1, 2, 3 })); - } - - public static int pivotIndex(int[] nums) { - int result = 0; - for (int i = 0; i < nums.length; i++) { - int sum1 = 0; - int sum2 = 0; - for (int a = 0; a < result; a++) { - sum1 += nums[a]; - } - - for (int b = result + 1; b < nums.length; b++) { - sum2 += nums[b]; - } - - if (sum1 == sum2) { - return result; - } - - result++; - } - return -1; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" deleted file mode 100644 index 941ac0f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 将数组分成和相等的三个部分 { - - public static void main(String[] args) { - Assertions.assertTrue(canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1 })); - Assertions.assertTrue(canThreePartsEqualSum(new int[] { 3, 3, 6, 5, -2, 2, 5, 1, -9, 4 })); - Assertions.assertFalse(canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, 7, 9, -1, 2, 0, 1 })); - } - - /** - * 题目:1013. - * 将数组分成和相等的三个部分 - *

- * 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。 - *

- * 形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + - * A[A.length - 1]) 就可以将数组三等分。 - */ - public static boolean canThreePartsEqualSum(int[] array) { - return false; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" deleted file mode 100644 index 66715a0..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 搜索插入位置 - * @since 2020-07-29 - */ -public class 搜索插入位置 { - - public static void main(String[] args) { - Assertions.assertEquals(0, searchInsert(new int[] { 1 }, 1)); - Assertions.assertEquals(2, searchInsert(new int[] { 1, 3, 5, 6 }, 5)); - Assertions.assertEquals(1, searchInsert(new int[] { 1, 3, 5, 6 }, 2)); - Assertions.assertEquals(4, searchInsert(new int[] { 1, 3, 5, 6 }, 7)); - Assertions.assertEquals(0, searchInsert(new int[] { 1, 3, 5, 6 }, 0)); - } - - public static int searchInsert(int[] nums, int target) { - if (nums == null || nums.length == 0) return 0; - if (nums[0] >= target) return 0; - if (nums[nums.length - 1] < target) return nums.length; - for (int i = 1; i < nums.length; i++) { - if (nums[i] >= target) { - return i; - } - } - return nums.length; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\344\272\214\345\210\206\346\237\245\346\211\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\344\272\214\345\210\206\346\237\245\346\211\276.java" deleted file mode 100644 index fb81cc9..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\344\272\214\345\210\206\346\237\245\346\211\276.java" +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 数组二分查找 { - - public static void main(String[] args) { - Assertions.assertEquals(5, binarySearch(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); - Assertions.assertEquals(0, binarySearch(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); - Assertions.assertEquals(2, binarySearch(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); - } - - /** - * 数组二分查找,要求传入的数组是有序排列 - *

- * 参考:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/ - */ - public static int binarySearch(int[] nums, int target) { - if (nums == null || nums.length == 0) { return -1; } - - int left = 0, right = nums.length - 1; - while (left <= right) { - int mid = left + (right - left) / 2; // 防止 mid 溢出 - if (nums[mid] == target) { - return mid; - } else if (nums[mid] < target) { - left = mid + 1; - } else if (nums[mid] > target) { - right = mid - 1; - } - } - - return -1; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\346\213\206\345\210\2061.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\346\213\206\345\210\2061.java" deleted file mode 100644 index 1f2380b..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\225\260\347\273\204\346\213\206\345\210\2061.java" +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【数组拆分 I】 -// -// 给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。 -// -// 示例 1: -// -// 输入: [1,4,3,2] -// -// 输出: 4 -// 解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4). -// 提示: -// -// n 是正整数,范围在 [1, 10000]. -// 数组中的元素范围在 [-10000, 10000]. - -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 数组拆分1 { - - public static void main(String[] args) { - int[] nums1 = { 1, 4, 3, 2 }; - Assertions.assertEquals(4, 数组拆分1.arrayPairSum(nums1)); - } - - public static int arrayPairSum(int[] nums) { - Arrays.sort(nums); - int result = 0; - for (int i = 0; i < nums.length; i += 2) { - result += nums[i]; - } - return result; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\346\225\260\347\273\204.java" deleted file mode 100644 index 7108f7a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\346\225\260\347\273\204.java" +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【旋转数组】 - -// -// 给定一个数组,将数组中的元素向右移动 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) 的原地算法。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 旋转数组 { - - public static void main(String[] args) { - int[] nums1 = { 1, 2, 3, 4, 5, 6, 7 }; - int[] expected1 = { 5, 6, 7, 1, 2, 3, 4 }; - 旋转数组.rotate(nums1, 3); - Assertions.assertArrayEquals(expected1, nums1); - - int[] nums2 = { -1, -100, 3, 99 }; - int[] expected2 = { 3, 99, -1, -100 }; - 旋转数组.rotate(nums2, 2); - Assertions.assertArrayEquals(expected2, nums2); - } - - public static void rotate(int[] nums, int k) { - int i = 0; - while (i < k) { - int j = nums.length - 1; - int temp = nums[nums.length - 1]; - while (j > 0) { - nums[j] = nums[j - 1]; - j--; - } - nums[0] = temp; - // System.out.println(ArrayUtil.getArrayString(nums, 0, nums.length - 1)); - i++; - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\347\237\251\351\230\265.java" deleted file mode 100644 index d040256..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\227\213\350\275\254\347\237\251\351\230\265.java" +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 旋转矩阵 { - - public static void main(String[] args) { - int[][] array = { - { 1, 2, 3 }, - { 4, 5, 6 }, - { 7, 8, 9 } - }; - int[][] array2 = { - { 7, 4, 1 }, - { 8, 5, 2 }, - { 9, 6, 3 } - }; - rotate(array); - Assertions.assertArrayEquals(array2, array); - } - - /** - * @see 07. 旋转矩阵 - */ - public static void rotate(int[][] matrix) { - int row = matrix.length; - int column = matrix[0].length; - int[][] array = new int[row][column]; - for (int i = 0; i < row; i++) { - for (int j = 0; j < column; j++) { - array[j][row - i - 1] = matrix[i][j]; - } - } - for (int i = 0; i < row; i++) { - for (int j = 0; j < column; j++) { - matrix[i][j] = array[i][j]; - } - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" deleted file mode 100644 index 589c482..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【最大连续1的个数】 - -// -// 给定一个二进制数组, 计算其中最大连续1的个数。 -// -// 示例 1: -// -// 输入: [1,1,0,1,1,1] -// 输出: 3 -// 解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3. -// 注意: -// -// 输入的数组只包含 0 和1。 -// 输入数组的长度是正整数,且不超过 10,000。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 最大连续1的个数 { - - public static void main(String[] args) { - Assertions.assertEquals(3, 最大连续1的个数.findMaxConsecutiveOnes(new int[] { 1, 1, 0, 1, 1, 1 })); - } - - public static int findMaxConsecutiveOnes(int[] nums) { - int max = 0; - int count = 0; - for (int i = 0; i < nums.length; i++) { - if (nums[i] == 1) { - count++; - } else { - if (count > max) { - max = count; - } - count = 0; - } - } - - if (count > max) { - max = count; - } - return max; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\222.java" deleted file mode 100644 index 51ca60f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\222.java" +++ /dev/null @@ -1,83 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -// 【杨辉三角】 -// -// 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 -// -// 在杨辉三角中,每个数是它左上方和右上方的数的和。 -// -// 示例: -// -// 输入: 5 -// 输出: -// [ -// [1], -// [1,1], -// [1,2,1], -// [1,3,3,1], -// [1,4,6,4,1] -// ] - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 杨辉三角 { - - public static void main(String[] args) { - List> lists = 杨辉三角.generate(5); - printPascalsTriangle(lists); - } - - public static List> generate(int numRows) { - List> result = new ArrayList<>(); - - if (numRows <= 0) { - - } else if (numRows == 1) { - result.add(Arrays.asList(1)); - } else if (numRows == 2) { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - } else { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - for (int i = 2; i < numRows; i++) { - List current = result.get(i - 1); - List next = new ArrayList<>(); - - for (int j = 0; j <= i; j++) { - if (j == 0 || j == i) { - next.add(1); - } else { - int x = current.get(j - 1); - int y = current.get(j); - next.add(x + y); - } - } - - result.add(next); - } - } - - return result; - } - - static void printPascalsTriangle(List> lists) { - System.out.printf("【%d层杨辉三角】\n", lists.size()); - for (List list : lists) { - for (Integer num : list) { - System.out.print(num + "\t"); - } - System.out.println(); - } - System.out.println(); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\2222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\2222.java" deleted file mode 100644 index da70a72..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\346\235\250\350\276\211\344\270\211\350\247\2222.java" +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import io.github.dunwu.algorithm.util.ArrayUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -// 【杨辉三角 II】 -// -// 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。 -// -// 在杨辉三角中,每个数是它左上方和右上方的数的和。 -// -// 示例: -// -// 输入: 3 -// 输出: [1,3,3,1] -// 进阶: -// -// 你可以优化你的算法到 O(k) 空间复杂度吗? - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 杨辉三角2 { - - public static void main(String[] args) { - List list = 杨辉三角2.getRow(3); - System.out.println(ArrayUtil.getArrayString(list.toArray(), 0, list.size() - 1)); - } - - public static List getRow(int rowIndex) { - List> result = new ArrayList<>(); - - int rows = rowIndex + 1; - if (rows <= 0) { - - } else if (rows == 1) { - result.add(Arrays.asList(1)); - } else if (rows == 2) { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - } else { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - for (int i = 2; i < rows; i++) { - List current = result.get(i - 1); - List next = new ArrayList<>(); - - for (int j = 0; j <= i; j++) { - if (j == 0 || j == i) { - next.add(1); - } else { - int x = current.get(j - 1); - int y = current.get(j); - next.add(x + y); - } - } - - result.add(next); - } - } - - return result.get(rowIndex); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\345\212\250\351\233\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\345\212\250\351\233\266.java" deleted file mode 100644 index cb26e3d..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\345\212\250\351\233\266.java" +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【移动零】 - -// -// 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 -// -// 示例: -// -// 输入: [0,1,0,3,12] -// 输出: [1,3,12,0,0] -// 说明: -// -// 必须在原数组上操作,不能拷贝额外的数组。 -// 尽量减少操作次数。 - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 移动零 { - - public static void main(String[] args) { - int[] nums1 = { 0, 1, 0, 3, 12 }; - 移动零.moveZeroes(nums1); - Assertions.assertArrayEquals(new int[] { 1, 3, 12, 0, 0 }, nums1); - - int[] nums2 = { 0, 0, 1 }; - 移动零.moveZeroes(nums2); - Assertions.assertArrayEquals(new int[] { 1, 0, 0 }, nums2); - } - - public static void moveZeroes(int[] nums) { - int i = 0; - int right = nums.length - 1; - while (i <= right) { - if (nums[i] == 0) { - move(nums, i); - right--; - } else { - i++; - } - } - } - - private static void move(int[] nums, int pos) { - int temp = nums[pos]; - for (int i = pos; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - nums[nums.length - 1] = temp; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\351\231\244\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\351\231\244\345\205\203\347\264\240.java" deleted file mode 100644 index f9fd8ab..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\347\247\273\351\231\244\345\205\203\347\264\240.java" +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【移除元素】 - -// -// 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 -// -// 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 -// -// 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 -// -// 示例 1: -// -// 给定 nums = [3,2,2,3], val = 3, -// -// 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 示例 2: -// -// 给定 nums = [0,1,2,2,3,0,4,2], val = 2, -// -// 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 -// -// 注意这五个元素可为任意顺序。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 说明: -// -// 为什么返回数值是整数,但输出的答案是数组呢? -// -// 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 -// -// 你可以想象内部操作如下: -// -// // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 -// int len = removeElement(nums, val); -// -// // 在函数里修改输入数组对于调用者是可见的。 -// // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 -// for (int i = 0; i < len; i++) { -// print(nums[i]); -// } - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 移除元素 { - - public static void main(String[] args) { - int[] nums1 = { 3, 2, 2, 3 }; - Assertions.assertEquals(2, 移除元素.removeElement(nums1, 3)); - } - - public static int removeElement(int[] nums, int val) { - int end = 0; - final int n = nums.length; - for (int i = 0; i < n; i++) { - if (nums[i] != val) { - nums[end] = nums[i]; - end++; - } - } - return end; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" deleted file mode 100644 index 332bf0c..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" +++ /dev/null @@ -1,67 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -// 【至少是其他数字两倍的最大数】 -// -// 在一个给定的数组nums中,总是存在一个最大元素 。 -// -// 查找数组中的最大元素是否至少是数组中每个其他数字的两倍。 -// -// 如果是,则返回最大元素的索引,否则返回-1。 -// -// 示例 1: -// -// 输入: nums = [3, 6, 1, 0] -// 输出: 1 -// 解释: 6是最大的整数, 对于数组中的其他整数, -// 6大于数组中其他元素的两倍。6的索引是1, 所以我们返回1. -// -// -// 示例 2: -// -// 输入: nums = [1, 2, 3, 4] -// 输出: -1 -// 解释: 4没有超过3的两倍大, 所以我们返回 -1. -// -// -// 提示: -// -// nums 的长度范围在[1, 50]. -// 每个 nums[i] 的整数范围在 [0, 99]. - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 至少是其他数字两倍的最大数 { - - public static void main(String[] args) { - int[] nums1 = { 3, 6, 1, 0 }; - int[] nums2 = { 1, 2, 3, 4 }; - - Assertions.assertEquals(1, 至少是其他数字两倍的最大数.dominantIndex(nums1)); - Assertions.assertEquals(-1, 至少是其他数字两倍的最大数.dominantIndex(nums2)); - } - - public static int dominantIndex(int[] nums) { - int index = 0; - while (index < nums.length) { - boolean isMatch = true; - int max = nums[index]; - for (int i = 0; i < nums.length; i++) { - if (index != i && max < nums[i] * 2) { - isMatch = false; - break; - } - } - if (isMatch) { - return index; - } else { - index++; - } - } - return -1; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\236\272\346\227\213\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\236\272\346\227\213\347\237\251\351\230\265.java" deleted file mode 100644 index 95a3576..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\350\236\272\346\227\213\347\237\251\351\230\265.java" +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.List; - -// 【螺旋矩阵】 -// -// 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 -// -// 示例 1: -// -// 输入: -// [ -// [ 1, 2, 3 ], -// [ 4, 5, 6 ], -// [ 7, 8, 9 ] -// ] -// 输出: [1,2,3,6,9,8,7,4,5] -// 示例 2: -// -// 输入: -// [ -// [1, 2, 3, 4], -// [5, 6, 7, 8], -// [9,10,11,12] -// ] -// 输出: [1,2,3,4,8,12,11,10,9,5,6,7] - -/** - * @author Zhang Peng - * @since 2018-11-04 - */ -public class 螺旋矩阵 { - - public static void main(String[] args) { - int[] nums1 = { 1, 2, 3, 4, 5, 6, 7 }; - int[] expected1 = { 5, 6, 7, 1, 2, 3, 4 }; - 旋转数组.rotate(nums1, 3); - Assertions.assertArrayEquals(expected1, nums1); - - int[] nums2 = { -1, -100, 3, 99 }; - int[] expected2 = { 3, 99, -1, -100 }; - 旋转数组.rotate(nums2, 2); - Assertions.assertArrayEquals(expected2, nums2); - } - - public static List spiralOrder(int[][] matrix) { - ArrayList list = new ArrayList<>(); - if (matrix.length == 0) { - return list; - } - - final int M = matrix.length; - final int N = matrix[0].length; - final int MAX = M * N; - int x = 0, y = 0; - int XMIN = 0, YMIN = 0; - int XMAX = M - 1, YMAX = N - 1; - for (int index = 0; index < MAX; index++) { - list.add(matrix[x][y]); - - if (x == XMIN && y != YMAX) { - y++; - } else if (y == YMAX && x != XMAX) { - x++; - } else if (x == XMAX && y != YMIN) { - y--; - } else if (y == YMIN && x != XMIN + 1) { - x--; - } else if (x == XMIN + 1 && y == YMIN) { - XMIN = XMIN + 1; - YMIN = YMIN + 1; - XMAX = XMAX - 1; - YMAX = YMAX - 1; - x = XMIN; - y = YMIN; - } - } - - return list; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" deleted file mode 100644 index 6664fa0..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.algorithm.array; - -// 【长度最小的子数组】 - -// -// 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。 -// -// 示例: -// -// 输入: s = 7, nums = [2,3,1,2,4,3] -// 输出: 2 -// 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。 -// 进阶: -// -// 如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。 - -/** - * @author Zhang Peng - * @since 2018-11-05 - */ -public class 长度最小的子数组 { - - public static void main(String[] args) { - 长度最小的子数组.minSubArrayLen(7, new int[] { 2, 3, 1, 2, 4, 3 }); - 长度最小的子数组.minSubArrayLen(11, new int[] { 2, 3, 1, 2, 4, 3 }); - } - - public static int minSubArrayLen(int s, int[] nums) { - if (nums == null || nums.length == 0) { - return 0; - } - - int j = 0, i = 0, sum = 0, min = Integer.MAX_VALUE; - - while (i < nums.length) { - sum += nums[i++]; - - while (sum >= s) { - min = Math.min(min, i - j); - sum -= nums[j++]; - } - } - - return min == Integer.MAX_VALUE ? 0 : min; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\233\266\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\233\266\347\237\251\351\230\265.java" deleted file mode 100644 index be09756..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/\351\233\266\347\237\251\351\230\265.java" +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.dunwu.algorithm.array; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-05 - */ -public class 零矩阵 { - - public static void main(String[] args) { - int[][] array = { - { 1, 1, 1 }, - { 1, 0, 1 }, - { 1, 1, 1 } - }; - int[][] array2 = { - { 1, 0, 1 }, - { 0, 0, 0 }, - { 1, 0, 1 } - }; - setZeroes(array); - // setZeroForElement(array, 1, 1); - Assertions.assertArrayEquals(array2, array); - } - - /** - * @see 08. 零矩阵 - */ - public static void setZeroes(int[][] matrix) { - int row = matrix.length; - int column = matrix[0].length; - List list = new ArrayList<>(); - for (int i = 0; i < row; i++) { - for (int j = 0; j < column; j++) { - if (matrix[i][j] == 0) { - list.add(new Point(i, j)); - } - } - } - - list.forEach(p -> { - setZeroForElement(matrix, p.i, p.j); - }); - } - - public static void setZeroForElement(int[][] matrix, int x, int y) { - int row = matrix.length; - int column = matrix[0].length; - for (int i = 0; i < row; i++) { - matrix[i][y] = 0; - } - for (int j = 0; j < column; j++) { - matrix[x][j] = 0; - } - } - - static class Point { - - public int i; - public int j; - - public Point(int i, int j) { - this.i = i; - this.j = j; - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java new file mode 100644 index 0000000..04a66a6 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过 BFS 解最短路径类型问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +package io.github.dunwu.algorithm.bfs; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..0448d03 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.bfs.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * BFS 算法模板 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class BFS算法模板 { + + private Graph graph; + + // 从 s 开始 BFS 遍历图的所有节点,且记录遍历的步数 + // 当走到目标节点 target 时,返回步数 + int bfs(int s, int target) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + // 记录从 s 开始走到当前节点的步数 + int step = 0; + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + int cur = q.poll(); + System.out.println("visit " + cur + " at step " + step); + // 判断是否到达终点 + if (cur == target) { + return step; + } + // 将邻居节点加入队列,向四周扩散搜索 + for (Edge e : graph.neighbors(cur)) { + if (!visited[e.to]) { + q.offer(e.to); + visited[e.to] = true; + } + } + } + step++; + } + // 如果走到这里,说明在图中没有找到目标节点 + return -1; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" new file mode 100644 index 0000000..ca60ee9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1091. 二进制矩阵中的最短路径 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class 二进制矩阵中的最短路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.shortestPathBinaryMatrix(new int[][] { { 0, 1 }, { 1, 0 } })); + Assertions.assertEquals(4, s.shortestPathBinaryMatrix(new int[][] { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 1, 0 } })); + Assertions.assertEquals(-1, s.shortestPathBinaryMatrix(new int[][] { { 1, 0, 0 }, { 1, 1, 0 }, { 1, 1, 0 } })); + } + + static class Solution { + + // 八个方向偏移量(上、下、左、右、左上、右下、左下、右上) + private final int[][] directions = { + { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, + { -1, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 } + }; + + public int shortestPathBinaryMatrix(int[][] grid) { + + int m = grid.length, n = grid[0].length; + if (grid[0][0] == 1 || grid[m - 1][n - 1] == 1) { + return -1; + } + + // 需要记录走过的路径,避免死循环 + boolean[][] visited = new boolean[m][n]; + LinkedList queue = new LinkedList<>(); + + // 初始化队列,从 (0, 0) 出发 + visited[0][0] = true; + queue.offer(new int[] { 0, 0 }); + + int step = 1; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] cur = queue.poll(); + int x = cur[0], y = cur[1]; + if (grid[x][y] != 0) { return -1; } + // 到达底部,返回步骤数 + if (x == m - 1 && y == n - 1) { return step; } + + for (int[] d : directions) { + int nextX = x + d[0], nextY = y + d[1]; + if (nextX < 0 || nextX >= m || nextY < 0 || nextY >= n) { continue; } + if (visited[nextX][nextY] || grid[nextX][nextY] != 0) { continue; } + visited[nextX][nextY] = true; + queue.offer(new int[] { nextX, nextY }); + } + } + step++; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" new file mode 100644 index 0000000..77990db --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 919. 完全二叉树插入器 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 完全二叉树插入器 { + + public static void main(String[] args) { + CBTInserter c = new CBTInserter(TreeNode.buildTree(1, 2)); + Assertions.assertEquals(1, c.insert(3)); + Assertions.assertEquals(2, c.insert(4)); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4), c.get_root()); + } + + static class CBTInserter { + + private final TreeNode root; + // 这个队列只记录完全二叉树底部可以进行插入的节点 + private final LinkedList queue; + + public CBTInserter(TreeNode root) { + this.root = root; + this.queue = new LinkedList<>(); + LinkedList tmp = new LinkedList<>(); + tmp.offer(root); + while (!tmp.isEmpty()) { + int size = tmp.size(); + for (int i = 0; i < size; i++) { + TreeNode node = tmp.poll(); + if (node == null) { continue; } + if (node.left != null) { tmp.offer(node.left); } + if (node.right != null) { tmp.offer(node.right); } + if (node.left == null || node.right == null) { + // 找到完全二叉树底部可以进行插入的节点 + queue.offer(node); + } + } + } + } + + public int insert(int val) { + TreeNode node = new TreeNode(val); + TreeNode cur = queue.peek(); + queue.offer(node); + if (cur.left == null) { + cur.left = node; + } else if (cur.right == null) { + cur.right = node; + queue.poll(); + } + return cur.val; + } + + public TreeNode get_root() { + return this.root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" new file mode 100644 index 0000000..8a4c291 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" @@ -0,0 +1,100 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 297. 二叉树的序列化与反序列化 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 打开转盘锁 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + String[] deadends = new String[] { "0201", "0101", "0102", "1212", "2002" }; + Assertions.assertEquals(6, s.openLock(deadends, "0202")); + + String[] deadends2 = new String[] { "8888" }; + Assertions.assertEquals(1, s.openLock(deadends2, "0009")); + + String[] deadends3 = new String[] { "8887", "8889", "8878", "8898", "8788", "8988", "7888", "9888" }; + Assertions.assertEquals(-1, s.openLock(deadends3, "8888")); + } + + static class Solution { + + public int openLock(String[] deadends, String target) { + int step = 0; + + Set blackSet = new HashSet<>(); + Collections.addAll(blackSet, deadends); + + if (blackSet.contains("0000")) { return -1; } + + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + visited.add("0000"); + queue.offer("0000"); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + String cur = queue.poll(); + if (cur.equals(target)) { + return step; + } + + for (String neighbour : neighbours(cur)) { + if (!visited.contains(neighbour) && !blackSet.contains(neighbour)) { + visited.add(neighbour); + queue.offer(neighbour); + } + } + } + step++; + } + return -1; + } + + public String plus(String s, int i) { + char[] ch = s.toCharArray(); + if (ch[i] == '9') { + ch[i] = '0'; + } else { + ch[i] += 1; + } + return new String(ch); + } + + public String minus(String s, int i) { + char[] ch = s.toCharArray(); + if (ch[i] == '0') { + ch[i] = '9'; + } else { + ch[i] -= 1; + } + return new String(ch); + } + + public List neighbours(String s) { + List neighbours = new ArrayList<>(); + for (int i = 0; i < s.length(); i++) { + neighbours.add(plus(s, i)); + neighbours.add(minus(s, i)); + } + return neighbours; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" new file mode 100644 index 0000000..c52c342 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 433. 最小基因变化 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 最小基因变化 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, + s.minMutation("AACCGGTT", "AACCGGTA", new String[] { "AACCGGTA" })); + Assertions.assertEquals(2, + s.minMutation("AACCGGTT", "AAACGGTA", new String[] { "AACCGGTA", "AACCGCTA", "AAACGGTA" })); + Assertions.assertEquals(3, + s.minMutation("AAAAACCC", "AACCCCCC", new String[] { "AAAACCCC", "AAACCCCC", "AACCCCCC" })); + Assertions.assertEquals(-1, + s.minMutation("AACCGGTT", "AACCGGTA", new String[] {})); + Assertions.assertEquals(-1, + s.minMutation("AAAAAAAA", "CCCCCCCC", + new String[] { "AAAAAAAA", "AAAAAAAC", "AAAAAACC", "AAAAACCC", "AAAACCCC", "AACACCCC", "ACCACCCC", + "ACCCCCCC", "CCCCCCCA" })); + } + + static class Solution { + + final char[] AGCT = new char[] { 'A', 'C', 'G', 'T' }; + + public int minMutation(String startGene, String endGene, String[] bank) { + if (startGene.equals(endGene)) { return 0; } + + int step = 0; + Set banks = new HashSet<>(Arrays.asList(bank)); + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(startGene); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + String curGene = queue.poll(); + if (curGene.equals(endGene)) { return step; } + for (String newGene : neighbours(curGene)) { + if (!visited.contains(newGene) && banks.contains(newGene)) { + queue.offer(newGene); + visited.add(newGene); + } + } + } + step++; + } + return -1; + } + + // 当前基因的每个位置都可以变异为 A/G/C/T,穷举所有可能的结构 + public List neighbours(String gene) { + List res = new LinkedList<>(); + char[] ch = gene.toCharArray(); + for (int i = 0; i < ch.length; i++) { + char c = ch[i]; + for (char option : AGCT) { + ch[i] = option; + res.add(new String(ch)); + } + ch[i] = c; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" new file mode 100644 index 0000000..a70a284 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 365. 水壶问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class 水壶问题 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canMeasureWater(3, 5, 4)); + Assertions.assertFalse(s.canMeasureWater(2, 6, 5)); + Assertions.assertTrue(s.canMeasureWater(1, 2, 3)); + } + + static class Solution { + + public boolean canMeasureWater(int x, int y, int t) { + // BFS 算法的队列 + LinkedList q = new LinkedList<>(); + // 用来记录已经遍历过的状态,把元组转化成数字方便存储哈希集合 + // 转化方式是 (x, y) -> (x * (y + 1) + y),和二维数组坐标转一维坐标是一样的原理 + // 因为水桶 2 的取值是 [0, y],所以需要额外加一,请类比二维数组坐标转一维坐标 + // 且考虑到题目输入的数据规模较大,相乘可能导致 int 溢出,所以使用 long 类型 + HashSet visited = new HashSet<>(); + // 添加初始状态,两个桶都没有水 + q.offer(new int[] { 0, 0 }); + visited.add((long) 0 * (0 + 1) + 0); + + while (!q.isEmpty()) { + int[] curState = q.poll(); + if (curState[0] == t || curState[1] == t + || curState[0] + curState[1] == t) { + // 如果任意一个桶的水量等于目标水量,就返回 true + return true; + } + // 计算出所有可能的下一个状态 + List nextStates = new LinkedList<>(); + // 把 1 桶灌满 + nextStates.add(new int[] { x, curState[1] }); + // 把 2 桶灌满 + nextStates.add(new int[] { curState[0], y }); + // 把 1 桶倒空 + nextStates.add(new int[] { 0, curState[1] }); + // 把 2 桶倒空 + nextStates.add(new int[] { curState[0], 0 }); + // 把 1 桶的水灌进 2 桶,直到 1 桶空了或者 2 桶满了 + nextStates.add(new int[] { + curState[0] - Math.min(curState[0], y - curState[1]), + curState[1] + Math.min(curState[0], y - curState[1]) + }); + // 把 2 桶的水灌进 1 桶,直到 2 桶空了或者 1 桶满了 + nextStates.add(new int[] { + curState[0] + Math.min(curState[1], x - curState[0]), + curState[1] - Math.min(curState[1], x - curState[0]) + }); + + // 把所有可能的下一个状态都放进队列里 + for (int[] nextState : nextStates) { + // 把二维坐标转化为数字,方便去重 + long hash = (long) nextState[0] * (y + 1) + nextState[1]; + if (visited.contains(hash)) { + // 如果这个状态之前遍历过,就跳过,避免队列永远不空陷入死循环 + continue; + } + q.offer(nextState); + visited.add(hash); + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" new file mode 100644 index 0000000..0b5751b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" @@ -0,0 +1,93 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 994. 腐烂的橘子 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 腐烂的橘子 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input1 = { { 2, 1, 1 }, { 1, 1, 0 }, { 0, 1, 1 } }; + Assertions.assertEquals(4, s.orangesRotting(input1)); + + int[][] input2 = { { 2, 1, 1 }, { 0, 1, 1 }, { 1, 0, 1 } }; + Assertions.assertEquals(-1, s.orangesRotting(input2)); + + int[][] input3 = { { 0, 2 } }; + Assertions.assertEquals(0, s.orangesRotting(input3)); + + int[][] input4 = { { 1 } }; + Assertions.assertEquals(-1, s.orangesRotting(input4)); + + int[][] input5 = { { 1, 2 } }; + Assertions.assertEquals(1, s.orangesRotting(input5)); + } + + static class Solution { + + // 四个方向偏移量(上、下、左、右) + private static final int[][] directions = { { 0, 1 }, { 0, -1 }, { -1, 0 }, { 1, 0 } }; + + public int orangesRotting(int[][] grid) { + + int m = grid.length, n = grid[0].length; + + int freshCount = 0; + boolean[][] visited = new boolean[m][n]; + LinkedList queue = new LinkedList<>(); + + // 把所有腐烂的橘子加入队列,作为 BFS 的起点 + for (int x = 0; x < m; x++) { + for (int y = 0; y < n; y++) { + if (grid[x][y] == 1) { + freshCount++; + } else if (grid[x][y] == 2) { + queue.offer(new int[] { x, y }); + visited[x][y] = true; + } + } + } + if (freshCount == 0) { return 0; } + if (queue.isEmpty()) { return -1; } + + int step = 1; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] point = queue.poll(); + int x = point[0], y = point[1]; + for (int[] d : directions) { + int nextX = x + d[0], nextY = y + d[1]; + // 超出边界,跳过 + if (nextX < 0 || nextX >= m || nextY < 0 || nextY >= n) { continue; } + // 已访问,跳过(避免死循环) + if (visited[nextX][nextY]) { continue; } + // 遇到空格,跳过 + if (grid[nextX][nextY] == 0) { continue; } + // 遇到新鲜橘子,被传播腐烂 + if (grid[nextX][nextY] == 1) { + grid[nextX][nextY] = 2; + freshCount--; + // 新鲜橘子数为 0,返回结果 + if (freshCount == 0) { return step; } + } + visited[nextX][nextY] = true; + queue.offer(new int[] { nextX, nextY }); + } + } + step++; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" new file mode 100644 index 0000000..3afee28 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" @@ -0,0 +1,112 @@ +package io.github.dunwu.algorithm.bfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 721. 账户合并 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 账户合并 { + + public static void main(String[] args) { + + Solution solution = new Solution(); + + String[][] input1 = { + { "John", "johnsmith@mail.com", "john00@mail.com" }, + { "John", "johnnybravo@mail.com" }, + { "John", "johnsmith@mail.com", "john_newyork@mail.com" }, + { "Mary", "mary@mail.com" } + }; + String[][] expect1 = { + { "John", "johnnybravo@mail.com" }, + { "John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com" }, + { "Mary", "mary@mail.com" } + }; + List> output1 = solution.accountsMerge(ArrayUtil.toStringMatrixList(input1)); + Assertions.assertArrayEquals(expect1, ArrayUtil.toStringMatrixArray(output1)); + + String[][] input2 = { + { "Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co" }, + { "Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co" }, + { "Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co" }, + { "Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co" }, + { "Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co" } + }; + String[][] expect2 = { + { "Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co" }, + { "Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co" }, + { "Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co" }, + { "Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co" }, + { "Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co" } + }; + List> output2 = solution.accountsMerge(ArrayUtil.toStringMatrixList(input2)); + Assertions.assertArrayEquals(expect2, ArrayUtil.toStringMatrixArray(output2)); + } + + static class Solution { + + public List> accountsMerge(List> accounts) { + // key: email, value: 出现该 email 的 account 的索引列表 + HashMap> emailToIdx = new HashMap<>(); + for (int i = 0; i < accounts.size(); i++) { + List account = accounts.get(i); + for (int j = 1; j < account.size(); j++) { + String email = account.get(j); + List indexes = emailToIdx.getOrDefault(email, new ArrayList<>()); + indexes.add(i); + emailToIdx.put(email, indexes); + } + } + + // 计算合并后的账户 + List> res = new ArrayList<>(); + HashSet visitedEmails = new HashSet<>(); + + for (String email : emailToIdx.keySet()) { + if (visitedEmails.contains(email)) { + continue; + } + // 合并账户,用 BFS 算法穷举所有和 email 相关联的邮箱 + LinkedList mergedEmail = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(email); + visitedEmails.add(email); + // BFS 算法框架 + while (!queue.isEmpty()) { + String curEmail = queue.poll(); + mergedEmail.addLast(curEmail); + List indexes = emailToIdx.get(curEmail); + for (int index : indexes) { + List account = accounts.get(index); + for (int j = 1; j < account.size(); j++) { + String nextEmail = account.get(j); + if (!visitedEmails.contains(nextEmail)) { + queue.offer(nextEmail); + visitedEmails.add(nextEmail); + } + } + } + } + String userName = accounts.get(emailToIdx.get(email).get(0)).get(0); + // mergedEmail 是 userName 的所有邮箱 + Collections.sort(mergedEmail); + mergedEmail.addFirst(userName); + res.add(mergedEmail); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" new file mode 100644 index 0000000..4782ed7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1926. 迷宫中离入口最近的出口 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 迷宫中离入口最近的出口 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + char[][] maze1 = { { '+', '+', '.', '+' }, { '.', '.', '.', '+' }, { '+', '+', '+', '.' } }; + int[] entrance1 = { 1, 2 }; + Assertions.assertEquals(1, s.nearestExit(maze1, entrance1)); + + char[][] maze2 = { { '+', '+', '+' }, { '.', '.', '.' }, { '+', '+', '+' } }; + int[] entrance2 = { 1, 0 }; + Assertions.assertEquals(2, s.nearestExit(maze2, entrance2)); + + char[][] maze3 = { { '.', '+' } }; + int[] entrance3 = { 0, 0 }; + Assertions.assertEquals(-1, s.nearestExit(maze3, entrance3)); + + char[][] maze4 = { + { '+', '.', '+', '+', '+', '+', '+' }, + { '+', '.', '+', '.', '.', '.', '+' }, + { '+', '.', '+', '.', '+', '.', '+' }, + { '+', '.', '.', '.', '+', '.', '+' }, + { '+', '+', '+', '+', '+', '+', '.' } + }; + int[] entrance4 = { 0, 1 }; + Assertions.assertEquals(-1, s.nearestExit(maze4, entrance4)); + } + + static class Solution { + + public int nearestExit(char[][] maze, int[] entrance) { + + int m = maze.length, n = maze[0].length; + final int[][] directions = { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }; + + // BFS 算法的队列和 visited 数组 + LinkedList queue = new LinkedList<>(); + boolean[][] visited = new boolean[m][n]; + queue.offer(entrance); + visited[entrance[0]][entrance[1]] = true; + // 启动 BFS 算法从 entrance 开始像四周扩散 + int step = 0; + while (!queue.isEmpty()) { + int size = queue.size(); + step++; + // 扩散当前队列中的所有节点 + for (int i = 0; i < size; i++) { + int[] point = queue.poll(); + // 每个节点都会尝试向上下左右四个方向扩展一步 + for (int[] d : directions) { + int x = point[0] + d[0], y = point[1] + d[1]; + if (x < 0 || x >= m || y < 0 || y >= n) { continue; } + if (visited[x][y] || maze[x][y] == '+') { continue; } + // 走到边界(出口) + if (x == 0 || x == m - 1 || y == 0 || y == n - 1) { return step; } + visited[x][y] = true; + queue.offer(new int[] { x, y }); + } + } + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" new file mode 100644 index 0000000..3464b06 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 841. 钥匙和房间 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 钥匙和房间 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List> input1 = new LinkedList<>(); + input1.add(Collections.singletonList(1)); + input1.add(Collections.singletonList(2)); + input1.add(Collections.singletonList(3)); + input1.add(new LinkedList<>()); + Assertions.assertTrue(s.canVisitAllRooms(input1)); + } + + static class Solution { + + public boolean canVisitAllRooms(List> rooms) { + // base case + if (rooms == null || rooms.size() == 0) { return true; } + + // 记录访问过的房间 + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + // 在队列中加入起点,启动 BFS + queue.offer(0); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + Integer cur = queue.poll(); + if (!visited.contains(cur)) { + visited.add(cur); + for (int room : rooms.get(cur)) { + queue.offer(room); + } + } + } + } + return visited.size() == rooms.size(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" new file mode 100644 index 0000000..7d4ebd1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Iterator; +import java.util.LinkedHashMap; + +/** + * 146. LRU 缓存 + * + * @author Zhang Peng + * @date 2025-10-31 + */ +public class LRU缓存 { + + public static void main(String[] args) { + + LRUCache lRUCache = new LRUCache(2); + lRUCache.put(1, 1); // 缓存是 {1=1} + lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} + Assertions.assertEquals(1, lRUCache.get(1)); + lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} + Assertions.assertEquals(-1, lRUCache.get(2)); + lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} + Assertions.assertEquals(-1, lRUCache.get(1)); + Assertions.assertEquals(3, lRUCache.get(3)); + Assertions.assertEquals(4, lRUCache.get(4)); + } + + static class LRUCache { + + private int capacity = 0; + private LinkedHashMap cache = null; + + public LRUCache(int capacity) { + this.capacity = capacity; + this.cache = new LinkedHashMap<>(capacity); + } + + public int get(int key) { + Integer val = cache.get(key); + if (val != null) { + cache.remove(key); + cache.put(key, val); + } + return val == null ? -1 : val; + } + + public void put(int key, int value) { + if (cache.containsKey(key)) { + cache.remove(key); + } else { + if (capacity <= cache.size()) { + Iterator iterator = cache.keySet().iterator(); + cache.remove(iterator.next()); + } + } + cache.put(key, value); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" new file mode 100644 index 0000000..67de9b1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" @@ -0,0 +1,89 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * 224. 基本计算器 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 基本计算器 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(23, s.calculate("(1+(4+5+2)-3)+(6+8)")); + Assertions.assertEquals(12, s.calculate("1+(4+5+2)")); + Assertions.assertEquals(2147483647, s.calculate("2147483647")); + Assertions.assertEquals(2, s.calculate("1 + 1")); + Assertions.assertEquals(3, s.calculate("2 - 1 + 2")); + } + + static class Solution { + + public int calculate(String s) { + Stack stack = new Stack<>(); + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '(') { + stack.push(i); + } else if (s.charAt(i) == ')') { + map.put(stack.pop(), i); + } + } + return calculate(s, 0, s.length() - 1, map); + } + + public int calculate(String s, int start, int end, Map map) { + int num = 0; + char sign = '+'; + Stack stack = new Stack<>(); + for (int i = start; i <= end; i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + (c - '0'); + } + if (c == '(') { + num = calculate(s, i + 1, map.get(i) - 1, map); + i = map.get(i); + } + + if (c == '+' || c == '-' || c == '*' || c == '/' || i == end) { + int pre = 0; + switch (sign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + pre = stack.pop(); + stack.push(pre * num); + break; + case '/': + pre = stack.pop(); + stack.push(pre / num); + break; + default: + break; + } + sign = c; + num = 0; + } + } + + int result = 0; + while (!stack.isEmpty()) { + result += stack.pop(); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" new file mode 100644 index 0000000..6fe15e1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 227. 基本计算器 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 基本计算器2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2147483647, s.calculate("2147483647")); + Assertions.assertEquals(2, s.calculate("1 + 1")); + Assertions.assertEquals(3, s.calculate("2 - 1 + 2")); + Assertions.assertEquals(7, s.calculate("3+2*2")); + } + + static class Solution { + + public int calculate(String s) { + return calculate(s, 0, s.length() - 1); + } + + public int calculate(String s, int start, int end) { + int num = 0; + char sign = '+'; + Stack stack = new Stack<>(); + for (int i = start; i <= end; i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + (c - '0'); + } + if (c == '+' || c == '-' || c == '*' || c == '/' || i == s.length() - 1) { + int pre = 0; + switch (sign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + pre = stack.pop(); + stack.push(pre * num); + break; + case '/': + pre = stack.pop(); + stack.push(pre / num); + break; + default: + break; + } + sign = c; + num = 0; + } + } + + int result = 0; + while (!stack.isEmpty()) { + result += stack.pop(); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" new file mode 100644 index 0000000..58424c4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.TreeMap; + +/** + * 729. 我的日程安排表 I + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 我的日程安排表 { + + public static void main(String[] args) { + MyCalendar s = new MyCalendar(); + Assertions.assertTrue(s.book(10, 20)); + Assertions.assertFalse(s.book(15, 25)); + Assertions.assertTrue(s.book(20, 30)); + } + + static class MyCalendar { + + private TreeMap calendar = null; + + public MyCalendar() { + calendar = new TreeMap<>(); + } + + public boolean book(int start, int end) { + Integer earlier = calendar.floorKey(start); + Integer later = calendar.ceilingKey(start); + if (later != null && later < end) { + return false; + } + if (earlier != null && start < calendar.get(earlier)) { + return false; + } + calendar.put(start, end); + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" new file mode 100644 index 0000000..e1f68e8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; + +/** + * 950. 按递增顺序显示卡牌 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 按递增顺序显示卡牌 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, 13, 3, 11, 5, 17, 7 } + , s.deckRevealedIncreasing(new int[] { 17, 13, 11, 2, 3, 5, 7 })); + } + + static class Solution { + + public int[] deckRevealedIncreasing(int[] deck) { + int n = deck.length; + LinkedList res = new LinkedList<>(); + Arrays.sort(deck); + for (int i = n - 1; i >= 0; i--) { + if (!res.isEmpty()) { + res.addFirst(res.removeLast()); + } + res.addFirst(deck[i]); + } + int[] arr = new int[n]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" new file mode 100644 index 0000000..2f0b46d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1700. 无法吃午餐的学生数量 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 无法吃午餐的学生数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(0, s.countStudents(new int[] { 1, 1, 0, 0 }, new int[] { 0, 1, 0, 1 })); + Assertions.assertEquals(3, s.countStudents(new int[] { 1, 1, 1, 0, 0, 1 }, new int[] { 1, 0, 0, 0, 1, 1 })); + } + + static class Solution { + + public int countStudents(int[] students, int[] sandwiches) { + int total = students.length; + LinkedList studentQueue = new LinkedList<>(); + for (int s : students) { + studentQueue.addLast(s); + } + int matchNum = 0; + while (matchNum < sandwiches.length) { + int notMatchNum = 0; + int size = studentQueue.size(); + while (notMatchNum < size) { + Integer s = studentQueue.removeFirst(); + if (s == sandwiches[matchNum]) { + matchNum++; + break; + } else { + studentQueue.addLast(s); + notMatchNum++; + } + } + if (notMatchNum == size) { + break; + } + } + return total - matchNum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" new file mode 100644 index 0000000..5e32109 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" @@ -0,0 +1,31 @@ +package io.github.dunwu.algorithm.design; + +import java.util.Arrays; + +/** + * 计数器模板 + * + * @author Zhang Peng + * @date 2025-10-31 + */ +public class 计算器 { + + public static void main(String[] args) { + Solution s = new Solution(); + System.out.println("args = " + Arrays.toString(args)); + } + + static class Solution { + + public int toNum(String s) { + if (s == null || s.length() == 0) { return 0; } + int num = 0; + for (char c : s.toCharArray()) { + num = num * 10 + (c - '0'); + } + return num; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216.java" deleted file mode 100644 index 60f82e0..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216.java" +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.dunwu.algorithm.dfs; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-04 - * @see 51. N皇后 - */ -public class N皇后 { - - int[] cols; - int[] first; - int[] second; - int[] queens; - List> output = new ArrayList<>(); - - public static void main(String[] args) { - N皇后 demo = new N皇后(); - List> result = demo.solveNQueens(5); - result.forEach(System.out::println); - } - - public List> solveNQueens(int n) { - queens = new int[n]; - cols = new int[n]; - first = new int[2 * n]; - second = new int[2 * n]; - backtrack(n, 0); - return output; - } - - public void backtrack(int n, int row) { - if (row >= n) { return; } - for (int col = 0; col < n; col++) { - if (cols[col] == 1 || first[row + col] == 1 || second[row - col + n - 1] == 1) { continue;} - - queens[row] = col; - cols[col] = 1; - first[row + col] = 1; - second[row - col + n - 1] = 1; - - backtrack(n, row + 1); - if (row == n - 1) { - output.add(addSolution(n)); - } - - queens[row] = 0; - cols[col] = 0; - first[row + col] = 0; - second[row - col + n - 1] = 0; - } - } - - public List addSolution(int n) { - List res = new ArrayList<>(); - for (int i = 0; i < n; i++) { - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < n; j++) { - if (i == queens[j]) { - sb.append("Q"); - } else { - sb.append("."); - } - } - res.add(sb.toString()); - } - return res; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216II.java" deleted file mode 100644 index 181842a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/N\347\232\207\345\220\216II.java" +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.dunwu.algorithm.dfs; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @see 52. N皇后II - * @since 2020-07-04 - */ -public class N皇后II { - - int[] cols; - int[] first; - int[] second; - int[] queens; - List> output = new ArrayList<>(); - - public static void main(String[] args) { - N皇后II demo = new N皇后II(); - int result = demo.totalNQueens(4); - Assertions.assertEquals(2, result); - } - - public int totalNQueens(int n) { - List> lists = solveNQueens(n); - return lists.size(); - } - - public List> solveNQueens(int n) { - queens = new int[n]; - cols = new int[n]; - first = new int[2 * n]; - second = new int[2 * n]; - backtrack(n, 0); - return output; - } - - public void backtrack(int n, int row) { - if (row >= n) { return; } - for (int col = 0; col < n; col++) { - if (cols[col] == 1 || first[row + col] == 1 || second[row - col + n - 1] == 1) { continue;} - - queens[row] = col; - cols[col] = 1; - first[row + col] = 1; - second[row - col + n - 1] = 1; - - backtrack(n, row + 1); - if (row == n - 1) { - output.add(addSolution(n)); - } - - queens[row] = 0; - cols[col] = 0; - first[row + col] = 0; - second[row - col + n - 1] = 0; - } - } - - public List addSolution(int n) { - List res = new ArrayList<>(); - for (int i = 0; i < n; i++) { - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < n; j++) { - if (i == queens[j]) { - sb.append("Q"); - } else { - sb.append("."); - } - } - res.add(sb.toString()); - } - return res; - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java new file mode 100644 index 0000000..6b44e2d --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java @@ -0,0 +1,7 @@ +/** + * DFS 解岛屿数类型问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +package io.github.dunwu.algorithm.dfs.island; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" new file mode 100644 index 0000000..c6bafac --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 200. 岛屿数量 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 岛屿数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + + char[][] input = { + { '1', '1', '1', '1', '0' }, + { '1', '1', '0', '1', '0' }, + { '1', '1', '0', '0', '0' }, + { '0', '0', '0', '0', '0' } + }; + Assertions.assertEquals(1, s.numIslands(input)); + + char[][] input2 = { + { '1', '1', '0', '0', '0' }, + { '1', '1', '0', '0', '0' }, + { '0', '0', '1', '0', '0' }, + { '0', '0', '0', '1', '1' } + }; + Assertions.assertEquals(3, s.numIslands(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0) { return 0; } + int res = 0; + int m = grid.length, n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == '1') { + // 每发现一个岛屿,岛屿数量加一 + res++; + // 然后使用 DFS 将岛屿淹了 + dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(char[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == '0') { return; } + + grid[x][y] = '0'; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" new file mode 100644 index 0000000..df3719e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 695. 岛屿的最大面积 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 岛屿的最大面积 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0 }, + { 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 } + }; + Assertions.assertEquals(6, s.maxAreaOfIsland(input)); + + int[][] input2 = { { 0, 0, 0, 0, 0, 0, 0, 0 } }; + Assertions.assertEquals(0, s.maxAreaOfIsland(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int maxAreaOfIsland(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) return 0; + + int res = 0; + int m = grid.length, n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + int size = dfs(grid, i, j); + res = Math.max(res, size); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public int dfs(int[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return 0; } + if (grid[x][y] == 0) { return 0; } + + int cnt = 1; + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + cnt += dfs(grid, i, j); + } + return cnt; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" new file mode 100644 index 0000000..535ae08 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" @@ -0,0 +1,93 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1905. 统计子岛屿 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 统计子岛屿 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] gridA1 = { + { 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 1, 0, 1, 1 } + }; + int[][] gridB1 = { + { 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1 }, + { 0, 1, 0, 0, 0 }, + { 1, 0, 1, 1, 0 }, + { 0, 1, 0, 1, 0 } + }; + Assertions.assertEquals(3, s.countSubIslands(gridA1, gridB1)); + + int[][] gridA2 = { + { 1, 0, 1, 0, 1 }, + { 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1 }, + { 1, 0, 1, 0, 1 } + }; + int[][] gridB2 = { + { 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1 }, + { 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0 }, + { 1, 0, 0, 0, 1 } + }; + Assertions.assertEquals(2, s.countSubIslands(gridA2, gridB2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int countSubIslands(int[][] grid1, int[][] grid2) { + int m = grid1.length, n = grid1[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid1[i][j] == 0 && grid2[i][j] == 1) { + // 这个岛屿肯定不是子岛,淹掉 + dfs(grid2, i, j); + } + } + } + + int res = 0; + // 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid2[i][j] == 1) { + res++; + dfs(grid2, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(int[][] grid, int x, int y) { + // base case + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == 0) { return; } + + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" new file mode 100644 index 0000000..20975ca --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1254. 统计封闭岛屿的数目 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 统计封闭岛屿的数目 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0, 1, 1, 0 }, + { 1, 0, 1, 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0, 1, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 0 } + }; + Assertions.assertEquals(2, s.closedIsland(input)); + + int[][] input2 = { + { 1, 1, 1, 1, 1, 1, 1 }, + { 1, 0, 0, 0, 0, 0, 1 }, + { 1, 0, 1, 1, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 1, 1, 0, 1 }, + { 1, 0, 0, 0, 0, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1 } + }; + Assertions.assertEquals(2, s.closedIsland(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int closedIsland(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) { return 0; } + + // 将靠边的岛屿淹没 + int m = grid.length, n = grid[0].length; + for (int j = 0; j < n; j++) { + dfs(grid, 0, j); + dfs(grid, m - 1, j); + } + for (int i = 0; i < m; i++) { + dfs(grid, i, 0); + dfs(grid, i, n - 1); + } + + int res = 0; + // 遍历 grid,剩下的岛屿都是封闭岛屿 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 0) { + res++; + dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(int[][] grid, int x, int y) { + // base case + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == 1) { return; } + + grid[x][y] = 1; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" new file mode 100644 index 0000000..7bdaa0c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" @@ -0,0 +1,80 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1020. 飞地的数量/a> + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 飞地的数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 0, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 0, 1, 1, 0 }, + { 0, 0, 0, 0 } + }; + Assertions.assertEquals(3, s.numEnclaves(input)); + + int[][] input2 = { + { 0, 1, 1, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 0 } + }; + Assertions.assertEquals(0, s.numEnclaves(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int numEnclaves(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) { return 0; } + + int m = grid.length, n = grid[0].length; + for (int j = 0; j < n; j++) { + dfs(grid, 0, j); + dfs(grid, m - 1, j); + } + for (int i = 0; i < m; i++) { + dfs(grid, i, 0); + dfs(grid, i, n - 1); + } + + int res = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + res += dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public int dfs(int[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return 0; } + if (grid[x][y] == 0) { return 0; } + + int cnt = 1; + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + cnt += dfs(grid, i, j); + } + return cnt; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java new file mode 100644 index 0000000..3192edb --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过回溯算法解决排列、组合、子集类型的问题 + * + * @author Zhang Peng + * @date 2025-12-13 + */ +package io.github.dunwu.algorithm.dfs.permutation_combination; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" new file mode 100644 index 0000000..6062839 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 46. 全排列 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 全排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { { 1, 2, 3 }, { 1, 3, 2 }, { 2, 1, 3 }, { 2, 3, 1 }, { 3, 1, 2 }, { 3, 2, 1 } }; + int[][] expect2 = { { 0, 1 }, { 1, 0 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 1, 2, 3 }))); + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 0, 1 }))); + Assertions.assertArrayEquals(new int[][] { { 1 } }, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 1 }))); + } + + static class Solution { + + // 「路径」中的元素会被标记为 true,避免重复使用 + boolean[] visited; + // 记录「路径」 + LinkedList path; + List> res; + + // 主函数,输入一组不重复的数字,返回它们的全排列 + List> permute(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + backtrack(nums); + return res; + } + + // 路径:记录在 path 中 + // 选择列表:nums 中不存在于 path 的那些元素(visited[i] 为 false) + // 结束条件:nums 中的元素全都在 path 中出现 + void backtrack(int[] nums) { + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + if (path.size() == nums.length) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + + for (int i = 0; i < nums.length; i++) { + + // 排除不合法的选择 + // nums[i] 已经在 path 中,跳过 + if (visited[i]) { continue; } + + // 【选择】 + path.add(nums[i]); + visited[i] = true; + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums); + + // 【取消选择】 + path.removeLast(); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" new file mode 100644 index 0000000..038cdda --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 47. 全排列 II + * LCR 084. 全排列 II + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 全排列2 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] expect = { { 1, 1, 2 }, { 1, 2, 1 }, { 2, 1, 1 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.permuteUnique(new int[] { 1, 1, 2 }))); + + int[][] expect2 = { { 1, 2, 3 }, { 1, 3, 2 }, { 2, 1, 3 }, { 2, 3, 1 }, { 3, 1, 2 }, { 3, 2, 1 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.permuteUnique(new int[] { 1, 2, 3 }))); + } + + static class Solution { + + private boolean[] visited; + private List path; + private List> res; + + public List> permuteUnique(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(nums); + backtrack(nums); + return res; + } + + public void backtrack(int[] nums) { + + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + if (path.size() == nums.length) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + } + + for (int i = 0; i < nums.length; i++) { + + // 排除不合法的选择 + if (visited[i]) { continue; } + // 剪枝逻辑,固定相同的元素在排列中的相对位置 + if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } + + // 【选择】 + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" new file mode 100644 index 0000000..ce2377e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 78. 子集 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 子集 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { {}, { 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 3 }, { 2 }, { 2, 3 }, { 3 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.subsets(new int[] { 1, 2, 3 }))); + Assertions.assertArrayEquals(new int[][] { {}, { 0 } }, ArrayUtil.toIntMatrixArray(s.subsets(new int[] { 0 }))); + } + + static class Solution { + + private boolean[] visited; + private List path; + private List> res; + + // 主函数 + public List> subsets(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(nums); + backtrack(nums, 0); + return res; + } + + // 回溯算法核心函数,遍历子集问题的回溯树 + public void backtrack(int[] nums, int start) { + + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + + for (int i = start; i < nums.length; i++) { + + // 排除不合法的选择 + if (visited[i]) { continue; } + + // 【选择】 + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" new file mode 100644 index 0000000..d09fb5e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 90. 子集 II + *

+ * 元素可重复,不可复选 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 子集2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] expect = { {}, { 1 }, { 1, 2 }, { 1, 2, 2 }, { 2 }, { 2, 2 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.subsetsWithDup(new int[] { 1, 2, 2 }))); + + int[][] expect2 = { {}, { 0 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.subsetsWithDup(new int[] { 0 }))); + } + + static class Solution { + + private List> res; + private LinkedList path; + + public List> subsetsWithDup(int[] nums) { + path = new LinkedList<>(); + res = new LinkedList<>(); + // 先排序,让相同的元素靠在一起 + Arrays.sort(nums); + backtrack(nums, 0); + return res; + } + + public void backtrack(int[] nums, int start) { + + // 【结束】 + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + + for (int i = start; i < nums.length; i++) { + + // 剪枝逻辑,值相同的相邻树枝,只遍历第一条 + if (i > start && nums[i] == nums[i - 1]) continue; + + // 【选择】 + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" new file mode 100644 index 0000000..c29d190 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 77. 组合 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.combine(4, 2))); + Assertions.assertArrayEquals(new int[][] { { 1 } }, ArrayUtil.toIntMatrixArray(s.combine(1, 1))); + } + + static class Solution { + + private List path; + private List> res; + + public List> combine(int n, int k) { + path = new LinkedList<>(); + res = new LinkedList<>(); + backtrack(n, k, 1); + return res; + } + + public void backtrack(int n, int k, int s) { + + // 【结束】 + if (path.size() == k) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + } + + for (int i = s; i <= n; i++) { + // 【选择】 + path.add(i); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 通过 start 参数控制树枝的遍历,避免产生重复的子集 + backtrack(n, k, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" new file mode 100644 index 0000000..8deb466 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 39. 组合总和 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List> output = s.combinationSum(new int[] { 2, 3, 6, 7 }, 7); + Assertions.assertArrayEquals(new int[][] { { 2, 2, 3 }, { 7 } }, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.combinationSum(new int[] { 2, 3, 5 }, 8); + Assertions.assertArrayEquals(new int[][] { { 2, 2, 2, 2 }, { 2, 3, 3 }, { 3, 5 } }, + ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private int sum; + private List path; + private List> res; + + public List> combinationSum(int[] candidates, int target) { + sum = 0; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(candidates); + backtrack(candidates, target, 0); + return res; + } + + public void backtrack(int[] nums, int target, int start) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target) { return; } + + for (int i = start; i < nums.length; i++) { + // 【选择】 + sum += nums[i]; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 同一元素可重复使用,注意参数 + backtrack(nums, target, i); + + // 【取消选择】 + path.remove(path.size() - 1); + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" new file mode 100644 index 0000000..e57c58c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 40. 组合总和 II + * LCR 082. 组合总和 II + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + List> output = s.combinationSum2(new int[] { 10, 1, 2, 7, 6, 1, 5 }, 8); + int[][] expect = { { 1, 1, 6 }, { 1, 2, 5 }, { 1, 7 }, { 2, 6 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.combinationSum2(new int[] { 2, 5, 2, 1, 2 }, 5); + int[][] expect2 = { { 1, 2, 2 }, { 5 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private int sum; + private boolean[] visited; + private List path; + private List> res; + + public List> combinationSum2(int[] candidates, int target) { + sum = 0; + visited = new boolean[candidates.length]; + path = new ArrayList<>(); + res = new ArrayList<>(); + Arrays.sort(candidates); + backtrack(candidates, target, 0); + return res; + } + + public void backtrack(int[] nums, int target, int start) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target) { return; } + + for (int i = start; i < nums.length; i++) { + + // 剪枝逻辑 + if (visited[i]) { continue; } + if (i > start && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } + + // 【选择】 + sum += nums[i]; + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, target, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" new file mode 100644 index 0000000..c0128a6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 216. 组合总和 III + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和3 { + + public static void main(String[] args) { + Solution s = new Solution(); + + Assertions.assertArrayEquals(new int[][] { { 1, 2, 4 } }, ArrayUtil.toIntMatrixArray(s.combinationSum3(3, 7))); + + int[][] expect2 = { { 1, 2, 6 }, { 1, 3, 5 }, { 2, 3, 4 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.combinationSum3(3, 9))); + + Assertions.assertArrayEquals(new int[][] {}, ArrayUtil.toIntMatrixArray(s.combinationSum3(4, 1))); + } + + static class Solution { + + private int sum; + private boolean[] visited; + private List path; + private List> res; + + public List> combinationSum3(int k, int n) { + sum = 0; + visited = new boolean[9]; + path = new LinkedList<>(); + res = new LinkedList<>(); + int[] nums = new int[9]; + for (int i = 0; i < 9; i++) { + nums[i] = i + 1; + } + backtrack(nums, n, k, 0); + return res; + } + + public void backtrack(int[] nums, int target, int k, int s) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target && path.size() == k) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target || path.size() > k) { return; } + + for (int i = s; i < nums.length; i++) { + + if (visited[i]) { continue; } + + // 【选择】 + sum += nums[i]; + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 同一元素可重复使用,注意参数 + backtrack(nums, target, k, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" new file mode 100644 index 0000000..9e15542 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" @@ -0,0 +1,96 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 51. N 皇后 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class N皇后 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> output = s.solveNQueens(4); + String[][] expect = { { ".Q..", "...Q", "Q...", "..Q." }, { "..Q.", "Q...", "...Q", ".Q.." } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toStringMatrixArray(output)); + + List> output2 = s.solveNQueens(1); + String[][] expect2 = { { "Q" } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toStringMatrixArray(output2)); + } + + static class Solution { + + private List> res; + + // 输入棋盘边长 n,返回所有合法的放置 + public List> solveNQueens(int n) { + res = new ArrayList<>(); + // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 + char[] arr = new char[n]; + Arrays.fill(arr, '.'); + String str = new String(arr); + List board = new ArrayList<>(); + for (int i = 0; i < n; i++) { + board.add(str); + } + backtrack(board, 0); + return res; + } + + // 路径:board 中小于 row 的那些行都已经成功放置了皇后 + // 选择列表:第 row 行的所有列都是放置皇后的选择 + // 结束条件:row 超过 board 的最后一行 + public void backtrack(List board, int row) { + // 触发结束条件 + if (row == board.size()) { + res.add(new ArrayList<>(board)); + return; + } + + int n = board.get(row).length(); + for (int col = 0; col < n; col++) { + // 排除不合法选择 + if (!isValid(board, row, col)) { + continue; + } + // 做选择 + char[] newRow = board.get(row).toCharArray(); + newRow[col] = 'Q'; + board.set(row, new String(newRow)); + // 进入下一行决策 + backtrack(board, row + 1); + // 撤销选择 + newRow[col] = '.'; + board.set(row, new String(newRow)); + } + } + + // 是否可以在 board[row][col] 放置皇后? + public boolean isValid(List board, int row, int col) { + int n = board.size(); + // 检查列是否有皇后互相冲突 + for (int i = 0; i < row; i++) { + if (board.get(i).charAt(col) == 'Q') { return false; } + } + // 检查右上方是否有皇后互相冲突 + for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + // 检查左上方是否有皇后互相冲突 + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" new file mode 100644 index 0000000..c9a72cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 52. N皇后II + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class N皇后2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.totalNQueens(4)); + Assertions.assertEquals(1, s.totalNQueens(1)); + } + + static class Solution { + + private List> res; + + public int totalNQueens(int n) { + return solveNQueens(n).size(); + } + + // 输入棋盘边长 n,返回所有合法的放置 + public List> solveNQueens(int n) { + res = new ArrayList<>(); + // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 + char[] arr = new char[n]; + Arrays.fill(arr, '.'); + String str = new String(arr); + List board = new ArrayList<>(); + for (int i = 0; i < n; i++) { + board.add(str); + } + backtrack(board, 0); + return res; + } + + // 路径:board 中小于 row 的那些行都已经成功放置了皇后 + // 选择列表:第 row 行的所有列都是放置皇后的选择 + // 结束条件:row 超过 board 的最后一行 + public void backtrack(List board, int row) { + // 触发结束条件 + if (row == board.size()) { + res.add(new ArrayList<>(board)); + return; + } + + int n = board.get(row).length(); + for (int col = 0; col < n; col++) { + // 排除不合法选择 + if (!isValid(board, row, col)) { + continue; + } + // 做选择 + char[] newRow = board.get(row).toCharArray(); + newRow[col] = 'Q'; + board.set(row, new String(newRow)); + // 进入下一行决策 + backtrack(board, row + 1); + // 撤销选择 + newRow[col] = '.'; + board.set(row, new String(newRow)); + } + } + + // 是否可以在 board[row][col] 放置皇后? + public boolean isValid(List board, int row, int col) { + int n = board.size(); + // 检查列是否有皇后互相冲突 + for (int i = 0; i < row; i++) { + if (board.get(i).charAt(col) == 'Q') { return false; } + } + // 检查右上方是否有皇后互相冲突 + for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + // 检查左上方是否有皇后互相冲突 + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + return true; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java new file mode 100644 index 0000000..34ca619 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java @@ -0,0 +1,7 @@ +/** + * 回溯算法解数独、N 皇后问题 + * + * @author Zhang Peng + * @date 2025-12-13 + */ +package io.github.dunwu.algorithm.dfs.sudoku; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" new file mode 100644 index 0000000..3a198c3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" @@ -0,0 +1,249 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 37. 解数独 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 解数独 { + + public static void main(String[] args) { + + char[][] expect = { + { '5', '3', '4', '6', '7', '8', '9', '1', '2' }, + { '6', '7', '2', '1', '9', '5', '3', '4', '8' }, + { '1', '9', '8', '3', '4', '2', '5', '6', '7' }, + { '8', '5', '9', '7', '6', '1', '4', '2', '3' }, + { '4', '2', '6', '8', '5', '3', '7', '9', '1' }, + { '7', '1', '3', '9', '2', '4', '8', '5', '6' }, + { '9', '6', '1', '5', '3', '7', '2', '8', '4' }, + { '2', '8', '7', '4', '1', '9', '6', '3', '5' }, + { '3', '4', '5', '2', '8', '6', '1', '7', '9' } + }; + char[][] input = { + { '5', '3', '.', '.', '7', '.', '.', '.', '.' }, + { '6', '.', '.', '1', '9', '5', '.', '.', '.' }, + { '.', '9', '8', '.', '.', '.', '.', '6', '.' }, + { '8', '.', '.', '.', '6', '.', '.', '.', '3' }, + { '4', '.', '.', '8', '.', '3', '.', '.', '1' }, + { '7', '.', '.', '.', '2', '.', '.', '.', '6' }, + { '.', '6', '.', '.', '.', '.', '2', '8', '.' }, + { '.', '.', '.', '4', '1', '9', '.', '.', '5' }, + { '.', '.', '.', '.', '8', '.', '.', '7', '9' } + }; + char[][] input2 = { + { '5', '3', '.', '.', '7', '.', '.', '.', '.' }, + { '6', '.', '.', '1', '9', '5', '.', '.', '.' }, + { '.', '9', '8', '.', '.', '.', '.', '6', '.' }, + { '8', '.', '.', '.', '6', '.', '.', '.', '3' }, + { '4', '.', '.', '8', '.', '3', '.', '.', '1' }, + { '7', '.', '.', '.', '2', '.', '.', '.', '6' }, + { '.', '6', '.', '.', '.', '.', '2', '8', '.' }, + { '.', '.', '.', '4', '1', '9', '.', '.', '5' }, + { '.', '.', '.', '.', '8', '.', '.', '7', '9' } + }; + + Solution s = new Solution(); + s.solveSudoku(input); + Assertions.assertArrayEquals(expect, input); + + Solution2 s2 = new Solution2(); + s2.solveSudoku(input2); + Assertions.assertArrayEquals(expect, input2); + } + + static class Solution { + + private int n; + // 标记是否已经找到可行解 + boolean found = false; + + public void solveSudoku(char[][] board) { + n = board.length; + backtrack(board, 0); + } + + // 路径:board 中小于 index 的位置所填的数字 + // 选择列表:数字 1~9 + // 结束条件:整个 board 都填满数字 + void backtrack(char[][] board, int index) { + if (found) { + // 已经找到一个可行解,立即结束 + return; + } + + int[] point = point(index); + int row = point[0], col = point[1]; + if (index == n * n) { + // 找到一个可行解,触发 base case + found = true; + return; + } + + if (board[row][col] != '.') { + // 如果有预设数字,不用我们穷举 + backtrack(board, index + 1); + return; + } + + for (char ch = '1'; ch <= '9'; ch++) { + // 剪枝:如果遇到不合法的数字,就跳过 + if (!isValid(board, row, col, ch)) { continue; } + + // 做选择 + board[row][col] = ch; + + backtrack(board, index + 1); + if (found) { + // 如果找到一个可行解,立即结束 + // 不要撤销选择,否则 board[i][j] 会被重置为 '.' + return; + } + + // 撤销选择 + board[row][col] = '.'; + } + } + + // 判断是否可以在 (r, c) 位置放置数字 num + boolean isValid(char[][] board, int row, int col, char num) { + for (int i = 0; i < 9; i++) { + // 判断行是否存在重复 + if (board[row][i] == num) return false; + // 判断列是否存在重复 + if (board[i][col] == num) return false; + // 判断 3 x 3 方框是否存在重复 + if (board[(row / 3) * 3 + i / 3][(col / 3) * 3 + i % 3] == num) { return false; } + } + return true; + } + + public int index(int x, int y) { + return x * n + y; + } + + public int[] point(int index) { + int x = index / n; + int y = index % n; + return new int[] { x, y }; + } + + } + + static class Solution2 { + + private int n; + + // 标记是否已经找到可行解 + private boolean found; + + // 记录每行已经出现的数字 + // 比如 rows[0] = {1, 2, 3} 表示第 0 行已经出现了数字 1, 2, 3 + private final List> rows; + + // 记录每列已经出现的数字 + private final List> cols; + + // 记录每个九宫格已经出现的数字 + private final List> boxes; + + public Solution2() { + found = false; + rows = new ArrayList<>(9); + cols = new ArrayList<>(9); + boxes = new ArrayList<>(9); + for (int i = 0; i < 9; i++) { + rows.add(new HashSet<>()); + cols.add(new HashSet<>()); + boxes.add(new HashSet<>()); + } + } + + public void solveSudoku(char[][] board) { + n = board.length; + // 将预设数字加入集合 + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] != '.') { + rows.get(i).add(board[i][j]); + cols.get(j).add(board[i][j]); + boxes.get(getBoxIndex(i, j)).add(board[i][j]); + } + } + } + backtrack(board, 0); + } + + // 路径:board 中小于 index 的位置所填的数字 + // 选择列表:数字 1~9 + // 结束条件:整个 board 都填满数字 + public void backtrack(char[][] board, int index) { + + // 已经找到一个可行解,立即结束 + if (found) { return; } + + // 找到一个可行解,触发 base case + if (index == n * n) { + found = true; + return; + } + + int row = index / n; + int col = index % n; + // 如果有预设数字,无需穷举 + if (board[row][col] != '.') { + backtrack(board, index + 1); + return; + } + + for (char ch = '1'; ch <= '9'; ch++) { + + // 【剪枝】如果遇到不合法的数字,就跳过 + if (!isValid(row, col, ch)) { continue; } + + // 【选择】把 ch 填入 board[i][j] + board[row][col] = ch; + rows.get(row).add(ch); + cols.get(col).add(ch); + boxes.get(getBoxIndex(row, col)).add(ch); + + backtrack(board, index + 1); + if (found) { + // 如果找到一个可行解,立即结束 + // 不要撤销选择,否则 board[i][j] 会被重置为 '.' + return; + } + + // 【取消选择】把 board[i][j] 重置为 '.' + board[row][col] = '.'; + rows.get(row).remove(ch); + cols.get(col).remove(ch); + boxes.get(getBoxIndex(row, col)).remove(ch); + } + } + + // 获取 (row, col) 所在的九宫格索引 + public int getBoxIndex(int row, int col) { + return (row / 3) * 3 + (col / 3); + } + + // 判断是否可以在 (row, col) 位置放置数字 num + public boolean isValid(int row, int col, char num) { + // 现在只需要查询三次哈希表即可 + if (rows.get(row).contains(num)) { return false; } + if (cols.get(col).contains(num)) { return false; } + if (boxes.get(getBoxIndex(row, col)).contains(num)) { return false; } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..24b5dd5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,20 @@ +package io.github.dunwu.algorithm.dfs.template; + +/** + * 回溯算法模板 + * + * @author Zhang Peng + * @date 2025-12-11 + */ +public class 回溯算法模板 { + + // 【回溯算法伪代码模板】 + // for 选择 in 选择列表: + // # 做选择 + // 将该选择从选择列表移除 + // 路径.add(选择) + // backtrack(路径, 选择列表) + // # 撤销选择 + // 路径.remove(选择) + // 将该选择再加入选择列表 +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" new file mode 100644 index 0000000..cb4aff8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 526. 优美的排列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 优美的排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.countArrangement(1)); + Assertions.assertEquals(2, s.countArrangement(2)); + } + + static class Solution { + + // 记录所有的「优美排列」的个数 + private int res = 0; + // track 中的元素会被标记为 true,避免重复选择 + private boolean[] visited; + // 记录回溯算法的递归路径,即每个索引选择的元素 + private LinkedList path; + + public int countArrangement(int n) { + res = 0; + visited = new boolean[n + 1]; + path = new LinkedList<>(); + dfs(n, 1); + return res; + } + + // 回溯算法标准框架,站在索引的视角选择元素 + void dfs(int n, int index) { + // base case,到达叶子节点 + if (index > n) { + // 找到一个结果 + res += 1; + return; + } + + // 索引 index 开始选择元素 + for (int val = 1; val <= n; val++) { + // 已经被其他索引选过的元素,不能重复选择 + if (visited[val]) { + continue; + } + if (!(index % val == 0 || val % index == 0)) { + continue; + } + // 【选择】index 选择元素 elem + visited[val] = true; + path.addLast(val); + // 【回溯】 + dfs(n, index + 1); + // 【取消选择】 + path.removeLast(); + visited[val] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" new file mode 100644 index 0000000..7c63a40 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 131. 分割回文串 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 分割回文串 { + + public static void main(String[] args) { + Solution s = new Solution(); + + String[][] expect = new String[][] { { "a", "a", "b" }, { "aa", "b" } }; + List> output = s.partition("aab"); + Assertions.assertEquals(expect.length, output.size()); + + List> output2 = s.partition("a"); + Assertions.assertEquals(1, output2.size()); + } + + static class Solution { + + private List path; + private List> res; + + public List> partition(String s) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(s, 0); + return res; + } + + public void dfs(String s, int start) { + if (start == s.length()) { + // base case,走到叶子节点 + // 即整个 s 被成功分割为若干个回文子串,记下答案 + res.add(new LinkedList(path)); + } + for (int i = start; i < s.length(); i++) { + if (!isPalindrome(s, start, i)) { + // s[start..i] 不是回文串,不能分割 + continue; + } + // s[start..i] 是一个回文串,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + path.add(s.substring(start, i + 1)); + // 进入回溯树的下一层,继续切分 s[i+1..] + dfs(s, i + 1); + // 撤销选择 + path.remove(path.size() - 1); + } + } + + // 用双指针技巧判断 s[low..high] 是否是一个回文串 + public boolean isPalindrome(String s, int low, int high) { + while (low < high) { + if (s.charAt(low) != s.charAt(high)) { return false; } + low++; + high--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" new file mode 100644 index 0000000..ef11cfe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +/** + * 79. 单词搜索 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 单词搜索 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "ABCCED")); + Assertions.assertTrue( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "SEE")); + Assertions.assertFalse( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "ABCB")); + } + + static class Solution { + + boolean found = false; + + public boolean exist(char[][] board, String word) { + found = false; + int m = board.length, n = board[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + dfs(board, i, j, word, 0); + if (found) { return true; } + } + } + return false; + } + + // 从 (i, j) 开始向四周搜索,试图匹配 word[p..] + void dfs(char[][] board, int i, int j, String word, int p) { + if (p == word.length()) { + // 整个 word 已经被匹配完,找到了一个答案 + found = true; + return; + } + if (found) { + // 已经找到了一个答案,不用再搜索了 + return; + } + int m = board.length, n = board[0].length; + if (i < 0 || j < 0 || i >= m || j >= n) { + return; + } + if (board[i][j] != word.charAt(p)) { + return; + } + + // 已经匹配过的字符,我们给它添一个负号作为标记,避免走回头路 + board[i][j] = (char) (-board[i][j]); + // word[p] 被 board[i][j] 匹配,开始向四周搜索 word[p+1..] + dfs(board, i + 1, j, word, p + 1); + dfs(board, i, j + 1, word, p + 1); + dfs(board, i - 1, j, word, p + 1); + dfs(board, i, j - 1, word, p + 1); + board[i][j] = (char) (-board[i][j]); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/recursive/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" similarity index 95% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/recursive/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" index 06cd60a..9799f09 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/recursive/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.recursive; +package io.github.dunwu.algorithm.dfs; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" new file mode 100644 index 0000000..ce16a6a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" @@ -0,0 +1,88 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 93. 复原 IP 地址 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 复原IP地址 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new String[] { "255.255.11.135", "255.255.111.35" }, + s.restoreIpAddresses("25525511135").toArray()); + Assertions.assertArrayEquals(new String[] { "0.0.0.0" }, s.restoreIpAddresses("0000").toArray()); + Assertions.assertArrayEquals(new String[] { "1.0.10.23", "1.0.102.3", "10.1.0.23", "10.10.2.3", "101.0.2.3" }, + s.restoreIpAddresses("101023").toArray()); + } + + static class Solution { + + private LinkedList path; + private LinkedList res; + + public List restoreIpAddresses(String s) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(s, 0); + return res; + } + + public void dfs(String s, int start) { + + // base case,走到叶子节点 + // 即整个 s 被成功分割为合法的四部分,记下答案 + if (start == s.length() && path.size() == 4) { + String ip = String.join(".", path); + res.add(ip); + } + + for (int i = start; i < s.length(); i++) { + + // s[start..i] 不是合法的 ip 数字,不能分割 + if (!isValid(s, start, i)) { continue; } + + // 已经分解成 4 部分了,不能再分解了 + if (path.size() >= 4) { continue; } + + // 【选择】 + // s[start..i] 是一个合法的 ip 数字,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + path.add(s.substring(start, i + 1)); + + // 【回溯】 + dfs(s, i + 1); + + // 【取消选择】 + path.removeLast(); + } + } + + public boolean isValid(String s, int start, int end) { + + int length = end - start + 1; + + if (length == 0 || length > 3) { return false; } + + // 如果只有一位数字,肯定是合法的 + if (length == 1) { return true; } + + // 多于一位数字,但开头是 0,肯定不合法 + if (s.charAt(start) == '0') { return false; } + + // 排除了开头是 0 的情况,那么如果是两位数,肯定是合法的 + if (length <= 2) { return true; } + + // 现在输入的一定是三位数,不能大于 255 + return Integer.parseInt(s.substring(start, start + length)) <= 255; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" new file mode 100644 index 0000000..92162c4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.dfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 526. 优美的排列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 格雷编码 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, ArrayUtil.toIntArray(s.grayCode(1))); + Assertions.assertArrayEquals(new int[] { 0, 1, 3, 2 }, ArrayUtil.toIntArray(s.grayCode(2))); + } + + static class Solution { + + private boolean[] visited; + private LinkedList path; + private LinkedList res; + + public List grayCode(int n) { + visited = new boolean[n]; + path = new LinkedList<>(); + res = null; + dfs(n, 0); + return res; + } + + public void dfs(int n, int root) { + if (res != null) return; + if (path.size() == (1 << n)) { + res = new LinkedList<>(path); + return; + } + if (visited[root]) return; + + visited[root] = true; + path.addLast(root); + + for (int i = 0; i < n; i++) { + int next = flipBit(root, i); + dfs(n, next); + } + + path.removeLast(); + visited[root] = false; + } + + // 把第 i 位取反(0 变 1,1 变 0) + int flipBit(int x, int i) { + return x ^ (1 << i); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" new file mode 100644 index 0000000..def2aea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 17. 电话号码的字母组合 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 电话号码的字母组合 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new String[] { "ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf" }, + s.letterCombinations("23").toArray()); + Assertions.assertArrayEquals(new String[] { "a", "b", "c" }, s.letterCombinations("2").toArray()); + Assertions.assertArrayEquals(new String[] { "t", "u", "v" }, s.letterCombinations("8").toArray()); + } + + static class Solution { + + private StringBuilder sb; + private LinkedList res; + private final String[] options = new String[] { + "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" + }; + + public List letterCombinations(String digits) { + if (digits.isEmpty()) { return new LinkedList<>(); } + sb = new StringBuilder(); + res = new LinkedList<>(); + // 从 digits[0] 开始进行回溯 + dfs(digits, 0); + return res; + } + + // 回溯算法主函数 + public void dfs(String digits, int start) { + // 到达回溯树底部 + if (sb.length() == digits.length()) { + res.add(sb.toString()); + return; + } + + // 回溯算法框架 + int digit = digits.charAt(start) - '0'; + for (char c : options[digit].toCharArray()) { + sb.append(c); + dfs(digits, start + 1); + sb.deleteCharAt(sb.length() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" new file mode 100644 index 0000000..d9a0551 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" @@ -0,0 +1,78 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 967. 连续差相同的数字 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 连续差相同的数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 181, 292, 707, 818, 929 }, s.numsSameConsecDiff(3, 7)); + Assertions.assertArrayEquals(new int[] { 10, 12, 21, 23, 32, 34, 43, 45, 54, 56, 65, 67, 76, 78, 87, 89, 98 }, + s.numsSameConsecDiff(2, 1)); + Assertions.assertArrayEquals(new int[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 }, s.numsSameConsecDiff(2, 0)); + Assertions.assertArrayEquals(new int[] { 13, 20, 24, 31, 35, 42, 46, 53, 57, 64, 68, 75, 79, 86, 97 }, + s.numsSameConsecDiff(2, 2)); + } + + static class Solution { + + // 记录当前路径组成的数字的值 + private int num = 0; + // 记录当前数字的位数 + private int digit = 0; + private List res; + + public int[] numsSameConsecDiff(int n, int k) { + + num = 0; + digit = 0; + res = new LinkedList<>(); + + dfs(n, k); + + int[] arr = new int[res.size()]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + // 回溯算法核心函数 + void dfs(int n, int k) { + // base case,到达叶子节点 + if (digit == n) { + // 找到一个合法的 n 位数 + res.add(num); + return; + } + + // 回溯算法标准框架 + for (int i = 0; i <= 9; i++) { + // 本题的剪枝逻辑 1,第一个数字不能是 0 + if (digit == 0 && i == 0) continue; + // 本题的剪枝逻辑 2,相邻两个数字的差的绝对值必须等于 k + if (digit > 0 && Math.abs(i - num % 10) != k) continue; + + // 做选择,在 track 尾部追加数字 i + digit++; + num = 10 * num + i; + // 进入下一层回溯树 + dfs(n, k); + // 取消选择,删除 track 尾部数字 + num = num / 10; + digit--; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..0c5494d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.dfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 491. 非递减子序列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 非递减子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] expect = new int[][] { + { 4, 6 }, { 4, 6, 7 }, { 4, 6, 7, 7 }, { 4, 7 }, { 4, 7, 7 }, { 6, 7 }, { 6, 7, 7 }, { 7, 7 } + }; + List> output = s.findSubsequences(new int[] { 4, 6, 7, 7 }); + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.findSubsequences(new int[] { 4, 4, 3, 2, 1 }); + Assertions.assertArrayEquals(new int[][] { { 4, 4 } }, ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private List path; + private List> res; + + public List> findSubsequences(int[] nums) { + + // base case + if (nums == null || nums.length == 0) { return new LinkedList<>(); } + + path = new LinkedList<>(); + res = new LinkedList<>(); + + dfs(nums, 0); + return res; + } + + public void dfs(int[] nums, int start) { + if (path.size() >= 2) { + res.add(new LinkedList<>(path)); + } + + // 用哈希集合防止重复选择相同元素 + HashSet visited = new HashSet<>(); + + for (int i = start; i < nums.length; i++) { + + if (!path.isEmpty() && nums[i] < path.get(path.size() - 1)) { continue; } + // 保证不要重复使用相同的元素 + if (visited.contains(nums[i])) { continue; } + + // 【选择】 + visited.add(nums[i]); + path.add(nums[i]); + + // 【递归】 + dfs(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" new file mode 100644 index 0000000..dcb4260 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 264. 丑数II + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(12, s.nthUglyNumber(10)); + Assertions.assertEquals(1, s.nthUglyNumber(1)); + Assertions.assertEquals(15, s.nthUglyNumber(11)); + } + + static class Solution { + + public int nthUglyNumber(int n) { + int[] dp = new int[n + 1]; + dp[1] = 1; + int p2 = 1, p3 = 1, p5 = 1; + for (int index = 2; index <= n; index++) { + int n2 = dp[p2] * 2, n3 = dp[p3] * 3, n5 = dp[p5] * 5; + dp[index] = min(n2, n3, n5); + if (dp[index] == n2) { p2++; } + if (dp[index] == n3) { p3++; } + if (dp[index] == n5) { p5++; } + } + return dp[n]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" new file mode 100644 index 0000000..1ca3fb6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 1201. 丑数 III + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.nthUglyNumber(3, 2, 3, 5)); + Assertions.assertEquals(6, s.nthUglyNumber(4, 2, 3, 4)); + Assertions.assertEquals(10, s.nthUglyNumber(5, 2, 11, 13)); + } + + static class Solution { + + public int nthUglyNumber(int n, int a, int b, int c) { + int[] dp = new int[n + 1]; + int pa = 1, pb = 1, pc = 1; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + int na = pa * a, nb = pb * b, nc = pc * c; + dp[i] = min(na, nb, nc); + if (dp[i] == na) { pa++; } + if (dp[i] == nb) { pb++; } + if (dp[i] == nc) { pc++; } + } + return dp[n]; + } + + int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" new file mode 100644 index 0000000..80c922b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 983. 最低票价 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 最低票价 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(11, s.mincostTickets(new int[] { 1, 4, 6, 7, 8, 20 }, new int[] { 2, 7, 15 })); + Assertions.assertEquals(17, + s.mincostTickets(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31 }, new int[] { 2, 7, 15 })); + } + + // 动态规划 + static class Solution { + + public int mincostTickets(int[] days, int[] costs) { + int n = days.length; + int lastDay = days[n - 1]; + boolean[] isTravel = new boolean[lastDay + 1]; + for (int d : days) { isTravel[d] = true; } + + // dp[i] 表示 1 到 i 天的最小花费 + int[] dp = new int[lastDay + 1]; + dp[0] = 0; + for (int i = 1; i <= lastDay; i++) { + if (!isTravel[i]) { + // 如果第 i 天不在 days 中,则第 i 天和第 i - 1 天花费相同 + dp[i] = dp[i - 1]; + } else { + // 如果第 i 天在 days 中 + // 则求三种不同方案最小值: + dp[i] = min( + // 在第 i 天购买为期 1 天的通行证的最小花费 + costs[0] + dp[i - 1], + // 在第 i - 7 天购买为期 7 天的通行证的最小花费(如果 i - 7 < 0,视为 0,f[0] 花费为 0) + costs[1] + dp[Math.max(0, i - 7)], + // 在第 i - 30 天购买为期 30 天的通行证的最小花费(如果 i - 30 < 0,视为 0,f[0] 花费为 0) + costs[2] + dp[Math.max(0, i - 30)] + ); + } + } + return dp[lastDay]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" new file mode 100644 index 0000000..107feec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 2466. 统计构造好字符串的方案数 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 统计构造好字符串的方案数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(8, s.countGoodStrings(3, 3, 1, 1)); + Assertions.assertEquals(5, s.countGoodStrings(2, 3, 1, 2)); + } + + static class Solution { + + public int countGoodStrings(int low, int high, int zero, int one) { + final int MOD = 1_000_000_007; + int res = 0; + // dp[i] 表示构造长为 i 的字符串的方案数 + int[] dp = new int[high + 1]; + // 构造空串的方案数为 1 + dp[0] = 1; + for (int i = 1; i <= high; i++) { + if (i >= zero) dp[i] = dp[i - zero]; + if (i >= one) dp[i] = (dp[i] + dp[i - one]) % MOD; + if (i >= low) res = (res + dp[i]) % MOD; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" new file mode 100644 index 0000000..5126e91 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 2140. 解决智力问题 + * + * @author Zhang Peng + * @date 2025-11-17 + */ +public class 解决智力问题 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.mostPoints(new int[][] { { 3, 2 }, { 4, 3 }, { 4, 4 }, { 2, 5 } })); + Assertions.assertEquals(7, s.mostPoints(new int[][] { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 } })); + } + + static class Solution { + + long[] memo; + + public long mostPoints(int[][] questions) { + if (questions == null || questions.length == 0) { return 0; } + memo = new long[questions.length + 1]; + Arrays.fill(memo, -1); + return dp(questions, 0); + } + + public long dp(int[][] questions, int i) { + if (i < 0 || i >= questions.length) { return 0L; } + if (memo[i] != -1) { return memo[i]; } + int score = questions[i][0]; + int skip = questions[i][1]; + memo[i] = Math.max( + dp(questions, i + 1), + dp(questions, i + skip + 1) + score + ); + return memo[i]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" new file mode 100644 index 0000000..d813925 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" @@ -0,0 +1,103 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 91. 解码方法 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 解码方法 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.numDecodings("12")); + Assertions.assertEquals(3, s.numDecodings("226")); + Assertions.assertEquals(0, s.numDecodings("06")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(2, s2.numDecodings("12")); + Assertions.assertEquals(3, s2.numDecodings("226")); + Assertions.assertEquals(0, s2.numDecodings("06")); + } + + // 回溯解法 + static class Solution { + + private StringBuilder sb; + private LinkedList res; + + public int numDecodings(String s) { + sb = new StringBuilder(); + res = new LinkedList<>(); + backtrack(s, 0); + return res.size(); + } + + public void backtrack(String s, int start) { + + // base case,走到叶子节点 + // 即整个 s 被成功分割为合法的四部分,记下答案 + if (start == s.length()) { + res.add(sb.toString()); + return; + } + + for (int i = start; i < s.length(); i++) { + + // s[start..i] 不是合法的 ip 数字,不能分割 + char letter = getLetter(s, start, i); + if (letter == '#') { continue; } + + // 【选择】 + // s[start..i] 是一个合法的 ip 数字,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + sb.append(letter); + + // 【回溯】 + backtrack(s, i + 1); + + // 【取消选择】 + sb.deleteCharAt(sb.length() - 1); + } + } + + public char getLetter(String s, int begin, int end) { + int len = end - begin + 1; + if (len <= 0 || len > 2) { return '#'; } + String numStr = s.substring(begin, begin + len); + if (numStr.startsWith("0")) { return '#'; } + int num = Integer.parseInt(numStr); + if (num < 1 || num > 26) { return '#'; } + return (char) ('A' + (num - 1)); + } + + } + + // 动态规划 + static class Solution2 { + + public int numDecodings(String s) { + int n = s.length(); + s = " " + s; + char[] ch = s.toCharArray(); + int[] dp = new int[n + 1]; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + // a : 代表「当前位置」单独形成 item + // b : 代表「当前位置」与「前一位置」共同形成 item + int a = ch[i] - '0', b = (ch[i - 1] - '0') * 10 + (ch[i] - '0'); + // 如果 a 属于有效值,那么 dp[i] 可以由 dp[i - 1] 转移过来 + if (1 <= a && a <= 9) dp[i] = dp[i - 1]; + // 如果 b 属于有效值,那么 dp[i] 可以由 dp[i - 2] 或者 dp[i - 1] & dp[i - 2] 转移过来 + if (10 <= b && b <= 26) dp[i] += dp[i - 2]; + } + return dp[n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" new file mode 100644 index 0000000..feeb72a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 322. 零钱兑换 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 零钱兑换 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.coinChange(new int[] { 1, 2, 5 }, 11)); + Assertions.assertEquals(-1, s.coinChange(new int[] { 2 }, 3)); + Assertions.assertEquals(0, s.coinChange(new int[] { 1 }, 0)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.coinChange(new int[] { 1, 2, 5 }, 11)); + Assertions.assertEquals(-1, s2.coinChange(new int[] { 2 }, 3)); + Assertions.assertEquals(0, s2.coinChange(new int[] { 1 }, 0)); + } + + static class Solution { + + int[] memo; + + public int coinChange(int[] coins, int amount) { + memo = new int[amount + 1]; + // 备忘录初始化为一个不会被取到的特殊值,代表还未被计算 + Arrays.fill(memo, -666); + + return dp(coins, amount); + } + + int dp(int[] coins, int amount) { + if (amount == 0) return 0; + if (amount < 0) return -1; + // 查备忘录,防止重复计算 + if (memo[amount] != -666) { return memo[amount]; } + + int res = Integer.MAX_VALUE; + for (int coin : coins) { + // 计算子问题的结果 + int subProblem = dp(coins, amount - coin); + + // 子问题无解则跳过 + if (subProblem == -1) continue; + // 在子问题中选择最优解,然后加一 + res = Math.min(res, subProblem + 1); + } + // 把计算结果存入备忘录 + memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; + return memo[amount]; + } + + } + + static class Solution2 { + + public int coinChange(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + // 数组大小为 amount + 1,初始值也为 amount + 1 + Arrays.fill(dp, amount + 1); + + // base case + dp[0] = 0; + // 外层 for 循环在遍历所有状态的所有取值 + for (int i = 0; i < dp.length; i++) { + // 内层 for 循环在求所有选择的最小值 + for (int coin : coins) { + // 子问题无解,跳过 + if (i - coin < 0) { + continue; + } + dp[i] = Math.min(dp[i], 1 + dp[i - coin]); + } + } + return (dp[amount] == amount + 1) ? -1 : dp[amount]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java new file mode 100644 index 0000000..1e7cb09 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java @@ -0,0 +1,7 @@ +/** + * 动态规划 - 斐波那契类型 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +package io.github.dunwu.algorithm.dp.fib; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" new file mode 100644 index 0000000..e0d3ca1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 746. 使用最小花费爬楼梯 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 使用最小花费爬楼梯 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, s.minCostClimbingStairs(new int[] { 10, 15, 20 })); + Assertions.assertEquals(6, s.minCostClimbingStairs(new int[] { 1, 100, 1, 1, 1, 100, 1, 1, 100, 1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(15, s2.minCostClimbingStairs(new int[] { 10, 15, 20 })); + Assertions.assertEquals(6, s2.minCostClimbingStairs(new int[] { 1, 100, 1, 1, 1, 100, 1, 1, 100, 1 })); + } + + static class Solution { + + public int minCostClimbingStairs(int[] cost) { + if (cost == null || cost.length == 0) { return 0; } + int N = cost.length; + int[] dp = new int[N + 1]; + dp[0] = dp[1] = 0; + for (int i = 2; i <= N; i++) { + dp[i] = Math.min( + dp[i - 1] + cost[i - 1], + dp[i - 2] + cost[i - 2] + ); + } + return dp[N]; + } + + } + + static class Solution2 { + + public int minCostClimbingStairs(int[] cost) { + if (cost == null || cost.length == 0) { return 0; } + int pre1 = 0, pre2 = 0; + for (int i = 2; i <= cost.length; i++) { + int tmp = Math.min( + pre1 + cost[i - 1], + pre2 + cost[i - 2] + ); + pre2 = pre1; + pre1 = tmp; + } + return pre1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" new file mode 100644 index 0000000..2442b5b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 740. 删除并获得点数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 删除并获得点数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.deleteAndEarn(new int[] { 3, 4, 2 })); + // 删除 4 获得 4 个点数,因此 3 也被删除。 + // 之后,删除 2 获得 2 个点数。总共获得 6 个点数。 + Assertions.assertEquals(9, s.deleteAndEarn(new int[] { 2, 2, 3, 3, 3, 4 })); + // 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 + // 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 + // 总共获得 9 个点数。 + } + + static class Solution { + + public int deleteAndEarn(int[] nums) { + + if (nums == null || nums.length == 0) { return 0; } + + int n = nums.length; + int max = Integer.MIN_VALUE; + for (int i = 1; i < n; i++) { + max = Math.max(max, nums[i]); + } + + int[] sums = new int[max + 1]; + for (int num : nums) { + sums[num] += num; + } + return rob(sums); + } + + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { return 0; } + if (nums.length == 1) { return nums[0]; } + int N = nums.length; + int first = nums[0], second = Math.max(nums[0], nums[1]); + for (int i = 2; i < N; i++) { + int tmp = Math.max(second, first + nums[i]); + first = second; + second = tmp; + } + return second; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" new file mode 100644 index 0000000..9acda1e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 198. 打家劫舍 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 打家劫舍 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.rob(new int[] { 1, 2, 3, 1 })); + Assertions.assertEquals(12, s.rob(new int[] { 2, 7, 9, 3, 1 })); + Assertions.assertEquals(1, s.rob(new int[] { 1, 1 })); + } + + static class Solution { + + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { return 0; } + if (nums.length == 1) { return nums[0]; } + int N = nums.length; + int first = nums[0], second = Math.max(nums[0], nums[1]); + for (int i = 2; i < N; i++) { + int tmp = Math.max(second, first + nums[i]); + first = second; + second = tmp; + } + return second; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" new file mode 100644 index 0000000..a2bc7f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" @@ -0,0 +1,110 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 509. 斐波那契数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 斐波那契数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.fib(2)); + Assertions.assertEquals(2, s.fib(3)); + Assertions.assertEquals(3, s.fib(4)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(1, s2.fib(2)); + Assertions.assertEquals(2, s2.fib(3)); + Assertions.assertEquals(3, s2.fib(4)); + + Solution2 s3 = new Solution2(); + Assertions.assertEquals(1, s3.fib(2)); + Assertions.assertEquals(2, s3.fib(3)); + Assertions.assertEquals(3, s3.fib(4)); + } + + /** + * 使用备忘录优化动态规划问题 + */ + static class Solution { + + int fib(int n) { + // 备忘录全初始化为 -1 + // 因为斐波那契数肯定是非负整数,所以初始化为特殊值 -1 表示未计算 + + // 因为数组的索引从 0 开始,所以需要 n + 1 个空间 + // 这样才能把 `f(0) ~ f(n)` 都记录到 memo 中 + int[] memo = new int[n + 1]; + Arrays.fill(memo, -1); + + return dp(memo, n); + } + + // 带着备忘录进行递归 + int dp(int[] memo, int n) { + // base case + if (n == 0 || n == 1) { + return n; + } + // 已经计算过,不用再计算了 + if (memo[n] != -1) { + return memo[n]; + } + // 在返回结果之前,存入备忘录 + memo[n] = dp(memo, n - 1) + dp(memo, n - 2); + return memo[n]; + } + + } + + /** + * DP Table 解决动态规划问题 + */ + static class Solution2 { + + int fib(int n) { + if (n == 0 || n == 1) { + return n; + } + + int[] dp = new int[n + 1]; + dp[0] = 0; + dp[1] = 1; + for (int i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + } + + /** + * 在 DP Table 基础上优化空间复杂度 + */ + static class Solution3 { + + int fib(int n) { + if (n == 0 || n == 1) { + return n; + } + + // 分别代表 dp[i - 1] 和 dp[i - 2] + int dp_i_1 = 1, dp_i_2 = 0; + for (int i = 2; i <= n; i++) { + // dp[i] = dp[i - 1] + dp[i - 2]; + int dp_i = dp_i_1 + dp_i_2; + dp_i_2 = dp_i_1; + dp_i_1 = dp_i; + } + return dp_i_1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" new file mode 100644 index 0000000..2b7f0bf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 70. 爬楼梯 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class 爬楼梯 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertEquals(1, s.climbStairs(0)); + Assertions.assertEquals(1, s.climbStairs(1)); + Assertions.assertEquals(2, s.climbStairs(2)); + Assertions.assertEquals(3, s.climbStairs(3)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(1, s2.climbStairs(0)); + Assertions.assertEquals(1, s2.climbStairs(1)); + Assertions.assertEquals(2, s2.climbStairs(2)); + Assertions.assertEquals(3, s2.climbStairs(3)); + } + + static class Solution { + + int[] memo = null; + + public int climbStairs(int n) { + memo = new int[n + 1]; + Arrays.fill(memo, -1); + return dp(n); + } + + // 爬第n阶楼梯的方法数量,等于 2 部分之和 + // + // 爬上 n−1 阶楼梯的方法数量。因为再爬1阶就能到第n阶 + // 爬上 n−2 阶楼梯的方法数量,因为再爬2阶就能到第n阶 + public int dp(int n) { + if (n == 0 || n == 1) return 1; + if (memo[n] != -1) return memo[n]; + memo[n] = dp(n - 1) + dp(n - 2); + return memo[n]; + } + + } + + // 空间复杂度 o(1) + static class Solution2 { + + public int climbStairs(int n) { + if (n == 0 || n == 1) return 1; + int pre1 = 1, pre2 = 1; + for (int i = 2; i <= n; i++) { + int tmp = pre1 + pre2; + pre2 = pre1; + pre1 = tmp; + } + return pre1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" new file mode 100644 index 0000000..87a3c49 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 1137. 第 N 个泰波那契数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 第N个泰波那契数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.tribonacci(4)); + Assertions.assertEquals(1389537, s.tribonacci(25)); + } + + static class Solution { + + public int tribonacci(int n) { + if (n == 0) return 0; + if (n == 1 || n == 2) return 1; + + int[] dp = new int[n + 1]; + dp[0] = 0; + dp[1] = 1; + dp[2] = 1; + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]; + } + return dp[n]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java new file mode 100644 index 0000000..60b8318 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java @@ -0,0 +1,7 @@ +/** + * 动态规划 - 矩阵 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +package io.github.dunwu.algorithm.dp.matrix; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..9d2e036 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.List; + +/** + * 120. 三角形最小路径和 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class 三角形最小路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> input = ArrayUtil.toIntMatrixList(new int[][] { { 2 }, { 3, 4 }, { 6, 5, 7 }, { 4, 1, 8, 3 } }); + Assertions.assertEquals(11, s.minimumTotal(input)); + List> input2 = ArrayUtil.toIntMatrixList(new int[][] { { -10 } }); + Assertions.assertEquals(-10, s.minimumTotal(input2)); + } + + static class Solution { + + public int minimumTotal(List> triangle) { + + // base case + if (triangle == null || triangle.size() == 0) { return 0; } + if (triangle.size() == 1) { return triangle.get(0).get(0); } + + // 状态定义 + int n = triangle.size(); + int[][] dp = new int[n][n]; + + // 初始状态 + dp[0][0] = triangle.get(0).get(0); + + // 状态转移方程 + int min = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + dp[i][0] = triangle.get(i).get(0) + dp[i - 1][0]; + for (int j = 1; j < i; j++) { + dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j); + } + dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i); + } + + for (int j = 0; j < n; j++) { + min = Math.min(min, dp[n - 1][j]); + } + return min; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" new file mode 100644 index 0000000..b1965bc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 931. 下降路径最小和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 下降路径最小和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(13, s.minFallingPathSum(new int[][] { { 2, 1, 3 }, { 6, 5, 4 }, { 7, 8, 9 } })); + Assertions.assertEquals(-59, s.minFallingPathSum(new int[][] { { -19, 57 }, { -40, -5 } })); + } + + static class Solution { + + public int minFallingPathSum(int[][] matrix) { + + // base case + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return 0; } + if (matrix.length == 1) { return matrix[0][0]; } + + // 状态定义 + int n = matrix.length; + int[][] dp = new int[n][n]; + + // 初始状态、边界状态 + for (int j = 0; j < n; j++) { + dp[0][j] = matrix[0][j]; + } + + // 状态转移 + for (int i = 1; i < n; i++) { + dp[i][0] = Math.min(dp[i - 1][0], dp[i - 1][1]) + matrix[i][0]; + for (int j = 1; j < n - 1; j++) { + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i - 1][j + 1]) + matrix[i][j]; + } + dp[i][n - 1] = Math.min(dp[i - 1][n - 1], dp[i - 1][n - 2]) + matrix[i][n - 1]; + } + + int min = Integer.MAX_VALUE; + for (int j = 0; j < n; j++) { + min = Math.min(min, dp[n - 1][j]); + } + return min; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" new file mode 100644 index 0000000..aa700cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 62. 不同路径 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +public class 不同路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(28, s.uniquePaths(3, 7)); + Assertions.assertEquals(3, s.uniquePaths(3, 2)); + } + + static class Solution { + + public int uniquePaths(int m, int n) { + + // 状态定义 + int[][] dp = new int[m][n]; + + // 初始状态、边界状态 + for (int i = 0; i < m; i++) { dp[i][0] = 1; } + for (int j = 0; j < n; j++) { dp[0][j] = 1; } + + // 状态转移方程 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" new file mode 100644 index 0000000..50295cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 63. 不同路径 II + * + * @author Zhang Peng + * @date 2025-11-12 + */ +public class 不同路径2 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = new int[][] { { 0, 0, 0 }, { 0, 1, 0 }, { 0, 0, 0 } }; + Assertions.assertEquals(2, s.uniquePathsWithObstacles(input1)); + int[][] input2 = new int[][] { { 0, 1 }, { 0, 0 } }; + Assertions.assertEquals(1, s.uniquePathsWithObstacles(input2)); + int[][] input3 = new int[][] { { 1, 0 } }; + Assertions.assertEquals(0, s.uniquePathsWithObstacles(input3)); + int[][] input4 = new int[][] { { 0, 1, 0, 0 } }; + Assertions.assertEquals(0, s.uniquePathsWithObstacles(input4)); + } + + static class Solution { + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + + // base case + if (obstacleGrid == null || obstacleGrid.length == 0) { return 0; } + int m = obstacleGrid.length, n = obstacleGrid[0].length; + // 起点、终点有障碍,注定无法到达 + if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) { return 0; } + + // 状态定义 + int[][] dp = new int[m][n]; + + // 初始状态、边界状态 + dp[0][0] = 1; + for (int i = 1; i < m; i++) { dp[i][0] = (obstacleGrid[i][0] == 1) ? 0 : dp[i - 1][0]; } + for (int j = 1; j < n; j++) { dp[0][j] = (obstacleGrid[0][j] == 1) ? 0 : dp[0][j - 1]; } + + // 状态转移方程 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + if (obstacleGrid[i][j] == 1) { + dp[i][j] = 0; + } else { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" new file mode 100644 index 0000000..2d4926a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" @@ -0,0 +1,80 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 221. 最大正方形 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最大正方形 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + char[][] input1 = new char[][] { + { '1', '0', '1', '0', '0' }, + { '1', '0', '1', '1', '1' }, + { '1', '1', '1', '1', '1' }, + { '1', '0', '0', '1', '0' } + }; + Assertions.assertEquals(4, s.maximalSquare(input1)); + + char[][] input2 = new char[][] { { '0', '1' }, { '1', '0' } }; + Assertions.assertEquals(1, s.maximalSquare(input2)); + + char[][] input3 = new char[][] { { '0' } }; + Assertions.assertEquals(0, s.maximalSquare(input3)); + + char[][] input4 = new char[][] { + { '1', '0', '1', '1', '0', '1' }, + { '1', '1', '1', '1', '1', '1' }, + { '0', '1', '1', '0', '1', '1' }, + { '1', '1', '1', '0', '1', '0' }, + { '0', '1', '1', '1', '1', '1' }, + { '1', '1', '0', '1', '1', '1' } + }; + Assertions.assertEquals(4, s.maximalSquare(input4)); + } + + static class Solution { + + public int maximalSquare(char[][] matrix) { + + // base case + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { return 0; } + + // 状态定义 + int m = matrix.length, n = matrix[0].length; + int[][] dp = new int[m][n]; + + // 状态转移方程 + int max = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (i == 0 || j == 0) { + dp[i][j] = matrix[i][j] == '1' ? 1 : 0; + } else { + if (matrix[i][j] == '1') { + dp[i][j] = min( + dp[i - 1][j], + dp[i][j - 1], + dp[i - 1][j - 1] + ) + 1; + } + } + max = Math.max(dp[i][j], max); + } + } + return max * max; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..b20d79b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 64. 最小路径和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最小路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(7, s.minPathSum(new int[][] { { 1, 3, 1 }, { 1, 5, 1 }, { 4, 2, 1 } })); + Assertions.assertEquals(12, s.minPathSum(new int[][] { { 1, 2, 3 }, { 4, 5, 6 } })); + } + + static class Solution { + + public int minPathSum(int[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } + int m = grid.length, n = grid[0].length; + int[][] dp = new int[m][n]; + dp[0][0] = grid[0][0]; + for (int i = 1; i < m; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } + for (int j = 1; j < n; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java similarity index 72% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/package-info.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java index 567166a..48674a2 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/package-info.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java @@ -4,4 +4,4 @@ * @author Zhang Peng * @since 2020-03-06 */ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" new file mode 100644 index 0000000..af5cc5f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * 121. 买卖股票的最佳时机 + * + * @author Zhang Peng + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.maxProfit(new int[] { 7, 1, 5, 3, 6, 4 })); + Assertions.assertEquals(0, s.maxProfit(new int[] { 7, 6, 4, 3, 1 })); + } + + static class Solution { + + public int maxProfit(int[] prices) { + int min = prices[0]; + int max = 0; + for (int i = 1; i < prices.length; i++) { + if (prices[i] <= min) { + min = prices[i]; + } else { + max = Math.max(max, prices[i] - min); + } + } + return max; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" similarity index 81% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272II.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" index 821d79a..98b90e7 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272II.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" @@ -1,13 +1,14 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.state; import org.junit.jupiter.api.Assertions; /** + * 122. 买卖股票的最佳时机 II + * * @author Zhang Peng - * @see 122. 买卖股票的最佳时机 II * @since 2020-07-05 */ -public class 买卖股票的最佳时机II { +public class 买卖股票的最佳时机2 { public static void main(String[] args) { int[] prices = { 7, 1, 5, 3, 6, 4 }; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" index e53c7e8..916188a 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.state; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" index 63bab9e..f2bb072 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.state; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" similarity index 96% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" index 89d7b43..134b337 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.state; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" similarity index 96% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" index a06adee..9836d29 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.state; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" new file mode 100644 index 0000000..a3df26e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 583. 两个字符串的删除操作 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 两个字符串的删除操作 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minDistance("sea", "eat")); + } + + static class Solution { + + public int minDistance(String word1, String word2) { + int lcs = longestCommonSubsequence(word1, word2); + return word1.length() + word2.length() - lcs - lcs; + } + + public int longestCommonSubsequence(String text1, String text2) { + int[][] memo = new int[text1.length()][text2.length()]; + for (int i = 0; i < text1.length(); i++) { + Arrays.fill(memo[i], -1); + } + return dp(memo, text1, 0, text2, 0); + } + + public int dp(int[][] memo, String text1, int i, String text2, int j) { + if (i < 0 || j < 0 || i >= text1.length() || j >= text2.length()) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + + if (text1.charAt(i) == text2.charAt(j)) { + memo[i][j] = 1 + dp(memo, text1, i + 1, text2, j + 1); + } else { + memo[i][j] = max( + dp(memo, text1, i + 1, text2, j), + dp(memo, text1, i, text2, j + 1), + dp(memo, text1, i + 1, text2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" new file mode 100644 index 0000000..18a5317 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +/** + * 712. 两个字符串的最小ASCII删除和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 两个字符串的最小ASCII删除和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(231, s.minimumDeleteSum("sea", "eat")); + Assertions.assertEquals(403, s.minimumDeleteSum("delete", "leet")); + } + + static class Solution { + + public int minimumDeleteSum(String s1, String s2) { + int m = s1.length(), n = s2.length(); + int[][] dp = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + dp[i][0] = dp[i - 1][0] + s1.codePointAt(i - 1); + } + for (int j = 1; j <= n; j++) { + dp[0][j] = dp[0][j - 1] + s2.codePointAt(j - 1); + } + for (int i = 1; i <= m; i++) { + int code1 = s1.codePointAt(i - 1); + for (int j = 1; j <= n; j++) { + int code2 = s2.codePointAt(j - 1); + if (code1 == code2) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j] + code1, dp[i][j - 1] + code2); + } + } + } + return dp[m][n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" new file mode 100644 index 0000000..97e11ea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" @@ -0,0 +1,112 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 139. 单词拆分 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 单词拆分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.wordBreak("leetcode", Arrays.asList("leet", "code"))); + Assertions.assertTrue(s.wordBreak("applepenapple", Arrays.asList("apple", "pen"))); + Assertions.assertFalse(s.wordBreak("catsandog", Arrays.asList("cats", "dog", "sand", "and", "cat"))); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.wordBreak("leetcode", Arrays.asList("leet", "code"))); + Assertions.assertTrue(s2.wordBreak("applepenapple", Arrays.asList("apple", "pen"))); + Assertions.assertFalse(s2.wordBreak("catsandog", Arrays.asList("cats", "dog", "sand", "and", "cat"))); + } + + // 回溯解决方案 + static class Solution { + + // 记录是否找到一个合法的答案 + boolean found = false; + // 记录回溯算法的路径 + private LinkedList path; + + public boolean wordBreak(String s, List wordDict) { + found = false; + path = new LinkedList<>(); + backtrack(wordDict, s, 0); + return found; + } + + public void backtrack(List wordDict, String target, int start) { + + // 找到一个合法答案 + if (start == target.length()) { found = true; } + // 如果已经找到答案,就不要再递归搜索了 + if (found) { return; } + + // 回溯算法框架 + for (String word : wordDict) { + + int len = word.length(); + + // 无效情况,剪枝 + if (start + len > target.length()) { return; } + if (!target.substring(start, start + len).equals(word)) { continue; } + + // 【选择】 + path.add(word); + // 【回溯】 + backtrack(wordDict, target, start + len); + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + + static class Solution2 { + + // 备忘录,-1 代表未计算,0 代表无法凑出,1 代表可以凑出 + private int[] memo; + // 用哈希集合方便快速判断是否存在 + HashSet wordDict; + + public boolean wordBreak(String s, List wordDict) { + this.wordDict = new HashSet<>(wordDict); + this.memo = new int[s.length()]; + Arrays.fill(memo, -1); + return dp(s, 0); + } + + public boolean dp(String s, int index) { + // base case + if (index == s.length()) { return true; } + // 避免冗余 + if (memo[index] != -1) { return memo[index] == 0 ? false : true; } + + // 遍历 s[i..] 的所有前缀 + for (int len = 1; index + len <= s.length(); len++) { + // 看看哪些前缀存在 wordDict 中 + String prefix = s.substring(index, index + len); + if (wordDict.contains(prefix)) { + // 找到一个单词匹配 s[i..i+len) + // 只要 s[i+len..] 可以被拼出,s[i..] 就能被拼出 + if (dp(s, index + len)) { + memo[index] = 1; + return true; + } + } + } + // s[i..] 无法被拼出 + memo[index] = 0; + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..4104ba8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +/** + * 516. 最长回文子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长回文子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.longestPalindromeSubseq("bbbab")); + Assertions.assertEquals(2, s.longestPalindromeSubseq("v")); + } + + static class Solution { + + public int longestPalindromeSubseq(String s) { + int n = s.length(); + int[][] dp = new int[n][n]; + for (int i = n - 1; i >= 0; i--) { + dp[i][i] = 1; + for (int j = i + 1; j < n; j++) { + if (s.charAt(i) == s.charAt(j)) { + dp[i][j] = dp[i + 1][j - 1] + 2; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); + } + } + } + return dp[0][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" new file mode 100644 index 0000000..2dc83a4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 72. 编辑距离 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 编辑距离 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.minDistance("horse", "ros")); + Assertions.assertEquals(5, s.minDistance("intention", "execution")); + } + + static class Solution { + + int[][] memo; + + public int minDistance(String word1, String word2) { + memo = new int[word1.length()][word2.length()]; + for (int i = 0; i < word1.length(); i++) { + Arrays.fill(memo[i], Integer.MAX_VALUE); + } + return dp(word1, 0, word2, 0); + } + + public int dp(String word1, int i, String word2, int j) { + if (i >= word1.length()) { return word2.length() - j; } + if (j >= word2.length()) { return word1.length() - i; } + if (memo[i][j] != Integer.MAX_VALUE) { + return memo[i][j]; + } + if (word1.charAt(i) == word2.charAt(j)) { + memo[i][j] = dp(word1, i + 1, word2, j + 1); + } else { + memo[i][j] = min( + dp(word1, i + 1, word2, j), + dp(word1, i, word2, j + 1), + dp(word1, i + 1, word2, j + 1) + ) + 1; + } + return memo[i][j]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" new file mode 100644 index 0000000..290ae9d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 不相交的线 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.maxUncrossedLines(new int[] { 1, 4, 2 }, new int[] { 1, 2, 4 })); + } + + static class Solution { + + int[][] memo; + + public int maxUncrossedLines(int[] nums1, int[] nums2) { + memo = new int[nums1.length + 1][nums2.length + 1]; + for (int i = 0; i <= nums1.length; i++) { + Arrays.fill(memo[i], -1); + } + return dp(nums1, 0, nums2, 0); + } + + public int dp(int[] nums1, int i, int[] nums2, int j) { + if (i < 0 || i >= nums1.length || j < 0 || j >= nums2.length) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + if (nums1[i] == nums2[j]) { + memo[i][j] = dp(nums1, i + 1, nums2, j + 1) + 1; + } else { + memo[i][j] = max( + dp(nums1, i, nums2, j + 1), + dp(nums1, i + 1, nums2, j), + dp(nums1, i + 1, nums2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..ad63c86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 392. 判断子序列 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 判断子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSubsequence("abc", "ahbgdc")); + Assertions.assertFalse(s.isSubsequence("axc", "ahbgdc")); + Assertions.assertTrue(s.isSubsequence("", "ahbgdc")); + Assertions.assertFalse(s.isSubsequence("aaaaaa", "bbaaaa")); + } + + static class Solution { + + public boolean isSubsequence(String s, String t) { + int m = s.length(), n = t.length(); + + // dp[i][j] 表示 s 的前 i 个字符是否是 t 的前 j 个字符的子序列 + boolean[][] dp = new boolean[m + 1][n + 1]; + + // 初始化:空字符串是任何字符串的子序列 + for (int j = 0; j <= n; j++) { + dp[0][j] = true; + } + + // 动态规划填表 + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 字符匹配,取决于前一个状态 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 字符不匹配,只能尝试在 t 中继续寻找 + dp[i][j] = dp[i][j - 1]; + } + } + } + + return dp[m][n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" similarity index 86% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" index b5cb9e1..22b2183 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" @@ -1,10 +1,11 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.subseq; import org.junit.jupiter.api.Assertions; /** + * 53. 最大子序和 + * * @author Zhang Peng - * @see 53. 最大子序和 * @since 2020-07-06 */ public class 最大子序和 { diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" similarity index 84% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" index 1aef32a..d7ff0fe 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" @@ -1,10 +1,11 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp.subseq; import org.junit.jupiter.api.Assertions; /** + * 300. 最长上升子序列 + * * @author Zhang Peng - * @see 300. 最长上升子序列 * @since 2020-07-06 */ public class 最长上升子序列 { diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..cf7c08e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 1143. 最长公共子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长公共子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.longestCommonSubsequence("abcde", "ace")); + Assertions.assertEquals(3, s.longestCommonSubsequence("abc", "abc")); + Assertions.assertEquals(0, s.longestCommonSubsequence("abc", "def")); + } + + static class Solution { + + private int[][] memo; + + public int longestCommonSubsequence(String text1, String text2) { + int m = text1.length(), n = text2.length(); + memo = new int[m + 1][n + 1]; + for (int i = 0; i <= m; i++) { + Arrays.fill(memo[i], -1); + } + return dp(text1, 0, text2, 0); + } + + public int dp(String text1, int i, String text2, int j) { + if (i < 0 || i >= text1.length() || j < 0 || j >= text2.length()) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + if (text1.charAt(i) == text2.charAt(j)) { + memo[i][j] = dp(text1, i + 1, text2, j + 1) + 1; + } else { + memo[i][j] = max( + dp(text1, i + 1, text2, j), + dp(text1, i, text2, j + 1), + dp(text1, i + 1, text2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..d7ae1d4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1218. 最长定差子序列 + * + * @author Zhang Peng + * @date 2025-11-14 + */ +public class 最长定差子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.longestSubsequence(new int[] { 1, 2, 3, 4 }, 1)); + Assertions.assertEquals(1, s.longestSubsequence(new int[] { 1, 3, 5, 7 }, 1)); + Assertions.assertEquals(4, s.longestSubsequence(new int[] { 1, 5, 7, 8, 5, 3, 4, 2, 1 }, -2)); + Assertions.assertEquals(2, s.longestSubsequence(new int[] { 3, 4, -3, -2, -4 }, -5)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(4, s2.longestSubsequence(new int[] { 1, 2, 3, 4 }, 1)); + Assertions.assertEquals(1, s2.longestSubsequence(new int[] { 1, 3, 5, 7 }, 1)); + Assertions.assertEquals(4, s2.longestSubsequence(new int[] { 1, 5, 7, 8, 5, 3, 4, 2, 1 }, -2)); + Assertions.assertEquals(2, s2.longestSubsequence(new int[] { 3, 4, -3, -2, -4 }, -5)); + } + + static class Solution { + + public int longestSubsequence(int[] arr, int diff) { + int n = arr.length; + Map map = new HashMap<>(); + int[][] dp = new int[n][2]; + dp[0][1] = 1; + map.put(arr[0], 0); + for (int i = 1; i < n; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); + dp[i][1] = 1; + int prev = arr[i] - diff; + if (map.containsKey(prev)) dp[i][1] = Math.max(dp[i][1], dp[map.get(prev)][1] + 1); + map.put(arr[i], i); + } + return Math.max(dp[n - 1][0], dp[n - 1][1]); + } + + } + + static class Solution2 { + + public int longestSubsequence(int[] arr, int diff) { + int res = 1; + Map map = new HashMap<>(); + for (int val : arr) { + map.put(val, map.getOrDefault(val - diff, 0) + 1); + res = Math.max(res, map.get(val)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" new file mode 100644 index 0000000..ae2ef0d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * 646. 最长数对链 + * + * @author Zhang Peng + * @date 2025-11-14 + */ +public class 最长数对链 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 } }; + Assertions.assertEquals(2, s.findLongestChain(input1)); + + int[][] input2 = new int[][] { { 1, 2 }, { 7, 8 }, { 4, 5 } }; + Assertions.assertEquals(3, s.findLongestChain(input2)); + } + + static class Solution { + + public int findLongestChain(int[][] pairs) { + Arrays.sort(pairs, Comparator.comparingInt(pair -> pair[0])); + int n = pairs.length; + int[] dp = new int[n]; + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) { + if (pairs[i][0] > pairs[j][1]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + return dp[n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" new file mode 100644 index 0000000..e0b1b49 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1027. 最长等差数列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长等差数列 { + + public static void main(String[] args) { + Solution s = new Solution(); + // Assertions.assertEquals(4, s.longestArithSeqLength(new int[] { 3, 6, 9, 12 })); + // Assertions.assertEquals(3, s.longestArithSeqLength(new int[] { 9, 4, 7, 2, 10 })); + Assertions.assertEquals(4, s.longestArithSeqLength(new int[] { 20, 1, 15, 3, 10, 5, 8 })); + } + + static class Solution { + + public int longestArithSeqLength(int[] nums) { + int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE; + for (int num : nums) { + min = Math.min(min, num); + max = Math.max(max, num); + } + + int res = 1; + int maxDiff = max - min; + for (int diff = -maxDiff; diff <= maxDiff; diff++) { + res = Math.max(longestSubsequence(nums, diff), res); + } + return res; + } + + public int longestSubsequence(int[] arr, int diff) { + int res = 1; + Map map = new HashMap<>(); + for (int val : arr) { + map.put(val, map.getOrDefault(val - diff, 0) + 1); + res = Math.max(res, map.get(val)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..ad8cb65 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长递增子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.lengthOfLIS(new int[] { 10, 9, 2, 5, 3, 7, 101, 18 })); + Assertions.assertEquals(4, s.lengthOfLIS(new int[] { 0, 1, 0, 3, 2, 3 })); + Assertions.assertEquals(1, s.lengthOfLIS(new int[] { 7, 7, 7, 7, 7, 7, 7 })); + } + + static class Solution { + + public int lengthOfLIS(int[] nums) { + int n = nums.length; + int[] dp = new int[n]; + int max = 1; + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) { + // 枚举区间 [0,i) 的所有数 nums[j],如果满足 nums[j]300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长递增子序列的个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.findNumberOfLIS(new int[] { 1, 3, 5, 4, 7 })); + Assertions.assertEquals(5, s.findNumberOfLIS(new int[] { 2, 2, 2, 2, 2 })); + Assertions.assertEquals(3, s.findNumberOfLIS(new int[] { 1, 2, 4, 3, 5, 4, 7, 2 })); + } + + static class Solution { + + public int findNumberOfLIS(int[] nums) { + + int n = nums.length; + int[] dp = new int[n]; + int[] cnt = new int[n]; + int max = 1; + for (int i = 0; i < n; i++) { + dp[i] = cnt[i] = 1; + for (int j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + if (dp[i] < dp[j] + 1) { + dp[i] = dp[j] + 1; + cnt[i] = cnt[j]; + } else if (dp[i] == dp[j] + 1) { + cnt[i] += cnt[j]; + } + } + } + max = Math.max(max, dp[i]); + } + int res = 0; + for (int i = 0; i < n; i++) { + if (dp[i] == max) res += cnt[i]; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" new file mode 100644 index 0000000..654e676 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.dp.template; + +/** + * 动态规划模板 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 动态规划模板 { + // 自顶向下递归的动态规划 + // def dp(状态1, 状态2, ...): + // for 选择 in 所有可能的选择: + // # 此时的状态已经因为做了选择而改变 + // result = 求最值(result, dp(状态1, 状态2, ...)) + // return result + + // 自底向上迭代的动态规划 + // 初始化 base case + // dp[0][0][...] = base case + // # 进行状态转移 + // for 状态1 in 状态1的所有取值: + // for 状态2 in 状态2的所有取值: + // for ... + // dp[状态1][状态2][...] = 求最值(选择1,选择2...) +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" new file mode 100644 index 0000000..ded4738 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" @@ -0,0 +1,22 @@ +package io.github.dunwu.algorithm.dp.template; + +/** + * 动态规划解背包问题模板 + * + * @author Zhang Peng + * @date 2025-12-17 + */ +public class 动态规划解背包问题模板 { + + // int[][] dp[N+1][W+1] + // dp[0][..] = 0 + // dp[..][0] = 0 + // + // for i in [1..N]: + // for w in [1..W]: + // dp[i][w] = max( + // 把物品 i 装进背包, + // 不把物品 i 装进背包 + // ) + // return dp[N][W] +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" index 6734c61..3f7dff2 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.dynamic; +package io.github.dunwu.algorithm.dp; import org.junit.jupiter.api.Assertions; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" new file mode 100644 index 0000000..f478807 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 416. 分割等和子集 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 分割等和子集 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canPartition(new int[] { 1, 5, 11, 5 })); + Assertions.assertFalse(s.canPartition(new int[] { 1, 2, 3, 5 })); + } + + static class Solution { + + public boolean canPartition(int[] wights) { + + int sum = 0; + for (int weight : wights) { + sum += weight; + } + + // 和为奇数时,不可能划分成两个和相等的集合 + if (sum % 2 != 0) return false; + + // 初始化为背包问题 + int W = sum / 2; + int N = wights.length; + + // base case + boolean[][] dp = new boolean[N + 1][W + 1]; + for (int i = 0; i <= N; i++) + dp[i][0] = true; + + for (int i = 1; i <= N; i++) { + for (int w = 1; w <= W; w++) { + if (w - wights[i - 1] < 0) { + dp[i][w] = dp[i - 1][w]; + } else { + dp[i][w] = dp[i - 1][w] + || dp[i - 1][w - wights[i - 1]]; + } + } + } + return dp[N][W]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" new file mode 100644 index 0000000..e7af1a6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 53. 最大子数组和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最大子数组和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.maxSubArray(new int[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 })); + } + + static class Solution { + + public int maxSubArray(int[] nums) { + int n = nums.length; + if (n == 0) return 0; + int[] dp = new int[n]; + // base case + // 第一个元素前面没有子数组 + dp[0] = nums[0]; + // 状态转移方程 + int res = Integer.MIN_VALUE; + for (int i = 1; i < n; i++) { + dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); + res = Math.max(res, dp[i]); + System.out.printf("nums[%d] = %d, dp[%d] = %d\n", i, nums[i], i, dp[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" new file mode 100644 index 0000000..4ace68d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" @@ -0,0 +1,54 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 118. 杨辉三角 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 杨辉三角 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> expect = new ArrayList<>(); + expect.add(Arrays.asList(1)); + expect.add(Arrays.asList(1, 1)); + expect.add(Arrays.asList(1, 2, 1)); + expect.add(Arrays.asList(1, 3, 3, 1)); + expect.add(Arrays.asList(1, 4, 6, 4, 1)); + List> lists = s.generate(5); + Assertions.assertArrayEquals(expect.toArray(), lists.toArray()); + } + + static class Solution { + + public List> generate(int row) { + int[][] matrix = new int[row][row]; + matrix[0][0] = 1; + List> res = new ArrayList<>(); + res.add(Collections.singletonList(1)); + for (int i = 1; i < row; i++) { + List list = new ArrayList<>(); + for (int j = 0; j <= i; j++) { + if (j == 0) { + matrix[i][j] = matrix[i - 1][j]; + } else { + matrix[i][j] = matrix[i - 1][j] + matrix[i - 1][j - 1]; + } + list.add(matrix[i][j]); + } + res.add(list); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" new file mode 100644 index 0000000..a6a3faa --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 119. 杨辉三角 II + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 杨辉三角2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 3, 1 }, s.getRow(3).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, s.getRow(0).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 1 }, s.getRow(1).toArray()); + } + + static class Solution { + + public List getRow(int rowIndex) { + int row = rowIndex + 1; + int[][] matrix = new int[row][row]; + matrix[0][0] = 1; + List> res = new ArrayList<>(); + res.add(Collections.singletonList(1)); + for (int i = 1; i < row; i++) { + List list = new ArrayList<>(); + for (int j = 0; j <= i; j++) { + if (j == 0) { + matrix[i][j] = matrix[i - 1][j]; + } else { + matrix[i][j] = matrix[i - 1][j] + matrix[i - 1][j - 1]; + } + list.add(matrix[i][j]); + } + res.add(list); + } + return res.get(rowIndex); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" new file mode 100644 index 0000000..c0b0b76 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 518. 零钱兑换 II + * + * @author Zhang Peng + * @date 2025-11-11 + */ +public class 零钱兑换2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.change(5, new int[] { 1, 2, 5 })); + Assertions.assertEquals(0, s.change(3, new int[] { 2 })); + Assertions.assertEquals(1, s.change(10, new int[] { 10 })); + } + + static class Solution { + + public int change(int amount, int[] coins) { + int n = coins.length; + int[][] dp = new int[n + 1][amount + 1]; + // base case + for (int i = 0; i <= n; i++) + dp[i][0] = 1; + + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= amount; j++) + if (j - coins[i - 1] >= 0) { + dp[i][j] = dp[i - 1][j] + + dp[i][j - coins[i - 1]]; + } else { dp[i][j] = dp[i - 1][j]; } + } + return dp[n][amount]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/MaxSubArray.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/MaxSubArray.java deleted file mode 100644 index 130756a..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/MaxSubArray.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import cn.hutool.core.util.ArrayUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Zhang Peng - * @since 2020-03-06 - */ -public class MaxSubArray { - - private static final Logger log = LoggerFactory.getLogger(MaxSubArray.class); - - public static int maxSubArray(int[] nums) { - int[] result = new int[nums.length]; - result[0] = nums[0]; - int max = nums[0]; - for (int i = 1; i < nums.length; i++) { - result[i] = Math.max(result[i - 1] + nums[i], nums[i]); - if (max < result[i]) { - max = result[i]; - } - - if (log.isDebugEnabled()) { - log.debug(ArrayUtil.toString(result)); - } - } - return max; - } - - public static void main(String[] args) { - int[] array = new int[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; - int max = MaxSubArray.maxSubArray(array); - System.out.println("max = " + max); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" deleted file mode 100644 index e1dd46e..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" +++ /dev/null @@ -1,101 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * @author Zhang Peng - * @see 120. 三角形最小路径和 - * @since 2020-07-04 - */ -public class 三角形最小路径和 { - - public static void main(String[] args) { - List> triangle = new ArrayList<>(); - triangle.add(Collections.singletonList(2)); - triangle.add(Arrays.asList(3, 4)); - triangle.add(Arrays.asList(6, 5, 7)); - triangle.add(Arrays.asList(4, 1, 8, 3)); - System.out.println("args = " + minimumTotal(triangle)); - Assertions.assertEquals(11, minimumTotal(triangle)); - Assertions.assertEquals(11, minimumTotal2(triangle)); - Assertions.assertEquals(11, minimumTotal3(triangle)); - Assertions.assertEquals(11, minimumTotal4(triangle)); - } - - // 回溯法,自上而下 - // 时间复杂度:O(2^(M*N)) - public static int minimumTotal(List> triangle) { - return backtrack(triangle, triangle.size(), 0, 0); - } - - private static int backtrack(List> triangle, int row, int x, int y) { - if (x == row - 1) return triangle.get(x).get(y); - int left = backtrack(triangle, row, x + 1, y); - int right = backtrack(triangle, row, x + 1, y + 1); - return triangle.get(x).get(y) + Math.min(left, right); - } - - // 回溯法 + 剪枝,自上而下 - // 针对 minimumTotal 加入记忆缓存 memory 存储计算结果,避免递归中的重复计算 - // 时间复杂度:< O(2^(M*N)) - // 空间复杂度:O(M*M) - public static int minimumTotal2(List> triangle) { - int level = triangle.size(); - int[][] memory = new int[level][level]; // 存储每个节点能得到的最优解 - return backtrack2(triangle, memory, triangle.size(), 0, 0); - } - - private static int backtrack2(List> triangle, int[][] memory, int row, int x, int y) { - if (memory[x][y] != 0) { return memory[x][y]; } - if (x == row - 1) return memory[x][y] = triangle.get(x).get(y); - int left = backtrack2(triangle, memory, row, x + 1, y); - int right = backtrack2(triangle, memory, row, x + 1, y + 1); - memory[x][y] = triangle.get(x).get(y) + Math.min(left, right); - return memory[x][y]; - } - - // 动态规划,自下而上 - // 时间复杂度:O(M^2) - // 空间复杂度:O(M^2) - public static int minimumTotal3(List> triangle) { - // 判空 - if (triangle == null || triangle.size() == 0) return 0; - int level = triangle.size(); - // 横竖维度都加1,可以不用考虑最后一行的初始化 - // 由于是三角形二维数组,可视为横竖维度都是行数 - int[][] memory = new int[level + 1][level + 1]; - for (int i = level - 1; i >= 0; i--) { - for (int j = 0; j < triangle.get(i).size(); j++) { - if (memory[i][j] == 0) { - memory[i][j] = Math.min(memory[i + 1][j], memory[i + 1][j + 1]) + triangle.get(i).get(j); - } - } - } - return memory[0][0]; - } - - // 动态规划,自下而上 + 空间优化 - // 时间复杂度:O(M^2) - // 空间复杂度:O(M^2) - public static int minimumTotal4(List> triangle) { - // 判空 - if (triangle == null || triangle.size() == 0) return 0; - int level = triangle.size(); - // 横竖维度都加1,可以不用考虑最后一行的初始化 - // 由于是三角形二维数组,可视为横竖维度都是行数 - int[] memory = new int[level + 1]; - for (int i = level - 1; i >= 0; i--) { - List rows = triangle.get(i); - for (int j = 0; j < rows.size(); j++) { - memory[j] = Math.min(memory[j], memory[j + 1]) + rows.get(j); - } - } - return memory[0]; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" deleted file mode 100644 index e8723ba..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 121. 买卖股票的最佳时机 - * @since 2020-07-05 - */ -public class 买卖股票的最佳时机 { - - public static void main(String[] args) { - int[] prices = { 7, 1, 5, 3, 6, 4 }; - int[] prices2 = { 7, 6, 4, 3, 1 }; - Assertions.assertEquals(5, maxProfit(prices)); - Assertions.assertEquals(0, maxProfit(prices2)); - } - - public static int maxProfit(int[] prices) { - if (prices == null || prices.length == 0) return 0; - int n = prices.length; - int max = 0; - - // 定义二维数组 - // 一维表示第 i 天 - // 二维表示交易状态:0 表示没有买卖;1 表示买入;2 表示卖出 - int[][] mp = new int[n][3]; - mp[0][0] = 0; // 无 - mp[0][1] = -prices[0]; // 买 - mp[0][2] = 0; // 当天买进卖出,净赚0 - for (int i = 1; i < n; i++) { - mp[i][0] = mp[i - 1][0]; // 一直不买 - mp[i][1] = Math.max(mp[i - 1][1], mp[i - 1][0] - prices[i]); // 昨天买或今天买 - mp[i][2] = mp[i - 1][1] + prices[i]; // 昨天还有股,今天卖出 - for (int j = 0; j <= 2; j++) { - if (max < mp[i][j]) { - max = mp[i][j]; - } - } - } - return max; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" deleted file mode 100644 index 96d0bb2..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 392. 判断子序列 - * @since 2020-07-06 - */ -public class 判断子序列 { - - public static void main(String[] args) { - Assertions.assertTrue(isSubsequence("abc", "ahbgdc")); - Assertions.assertFalse(isSubsequence("axc", "ahbgdc")); - Assertions.assertTrue(isSubsequence("", "ahbgdc")); - Assertions.assertFalse(isSubsequence("aaaaaa", "bbaaaa")); - } - - public static boolean isSubsequence(String s, String t) { - if (s == null || s.length() == 0) return true; - if (s.length() > t.length()) return false; - char[] source = s.toCharArray(); - char[] target = t.toCharArray(); - int i = 0, j = 0; - while (i < source.length && j < target.length) { - if (target[j] != source[i]) { - j++; - } else { - if (i == source.length - 1) { - return true; - } - i++; - j++; - } - } - return false; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\210\254\346\245\274\346\242\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\210\254\346\245\274\346\242\257.java" deleted file mode 100644 index 4f33ee3..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\210\254\346\245\274\346\242\257.java" +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 79. 单词搜索 - * @since 2020-07-04 - */ -public class 爬楼梯 { - - public static void main(String[] args) { - Assertions.assertEquals(1, climbStairs(0)); - Assertions.assertEquals(1, climbStairs(1)); - Assertions.assertEquals(2, climbStairs(2)); - Assertions.assertEquals(3, climbStairs(3)); - - Assertions.assertEquals(1, climbStairs2(0)); - Assertions.assertEquals(1, climbStairs2(1)); - Assertions.assertEquals(2, climbStairs2(2)); - Assertions.assertEquals(3, climbStairs2(3)); - - Assertions.assertEquals(1, climbStairs3(0)); - Assertions.assertEquals(1, climbStairs3(1)); - Assertions.assertEquals(2, climbStairs3(2)); - Assertions.assertEquals(3, climbStairs3(3)); - } - - // 使用递归(回溯方式) - // 时间复杂度:O(2^N) - public static int climbStairs(int n) { - return (n <= 1) ? 1 : climbStairs(n - 1) + climbStairs(n - 2); - } - - // 使用动态规划 - // 时间复杂度:O(N) - // 空间复杂度:O(N) - public static int climbStairs2(int n) { - if (n <= 1) return 1; - int[] mem = new int[n + 1]; - mem[0] = 1; - mem[1] = 1; - for (int i = 2; i < n + 1; i++) { - mem[i] = mem[i - 1] + mem[i - 2]; - } - return mem[n]; - } - // 优化 climbStairs2 动态规划方案 - // 时间复杂度:O(N) - // 空间复杂度:O(3) - public static int climbStairs3(int n) { - if (n <= 1) return 1; - int res = 0; - int prevStep1 = 1; - int prevStep2 = 1; - for (int i = 2; i < n + 1; i++) { - res = prevStep1 + prevStep2; - prevStep2 = prevStep1; - prevStep1 = res; - } - return res; - } -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\274\226\350\276\221\350\267\235\347\246\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\274\226\350\276\221\350\267\235\347\246\273.java" deleted file mode 100644 index 868165e..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\347\274\226\350\276\221\350\267\235\347\246\273.java" +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 72. 编辑距离 - * @since 2020-07-06 - */ -public class 编辑距离 { - - public static void main(String[] args) { - Assertions.assertEquals(3, minDistance("horse", "ros")); - Assertions.assertEquals(5, minDistance("intention", "execution")); - } - - public static int minDistance(String word1, String word2) { - int m = word1.length(); - int n = word2.length(); - int[][] dp = new int[m + 1][n + 1]; - for (int i = 0; i < m + 1; i++) dp[i][0] = i; - for (int j = 0; j < n + 1; j++) dp[0][j] = j; - - for (int i = 1; i < m + 1; i++) { - for (int j = 1; j < n + 1; j++) { - if (word1.charAt(i - 1) == word2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - int m1 = Math.min(dp[i - 1][j], dp[i][j - 1]); - int m2 = Math.min(m1, dp[i - 1][j - 1]); - dp[i][j] = 1 + m2; - } - } - } - return dp[m][n]; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\351\233\266\351\222\261\345\205\221\346\215\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\351\233\266\351\222\261\345\205\221\346\215\242.java" deleted file mode 100644 index 20c3454..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dynamic/\351\233\266\351\222\261\345\205\221\346\215\242.java" +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.dunwu.algorithm.dynamic; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 322. 零钱兑换 - * @since 2020-07-06 - */ -public class 零钱兑换 { - - public static void main(String[] args) { - int[] nums = { 1, 2, 5 }; - Assertions.assertEquals(3, coinChange(nums, 11)); - Assertions.assertEquals(-1, coinChange(new int[] { 2 }, 3)); - } - - - public static int coinChange(int[] coins, int amount) { - return coinChange(coins, amount, 0); - } - - public static int coinChange(int[] coins, int amount, int idxCoin) { - if (amount == 0) { return 0; } - if (idxCoin < coins.length && amount > 0) { - int maxVal = amount / coins[idxCoin]; - int minCost = Integer.MAX_VALUE; - for (int x = 0; x <= maxVal; x++) { - if (amount >= x * coins[idxCoin]) { - int res = coinChange(coins, amount - x * coins[idxCoin], idxCoin + 1); - if (res != -1) { minCost = Math.min(minCost, res + x); } - } - } - return (minCost == Integer.MAX_VALUE) ? -1 : minCost; - } - return -1; - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java new file mode 100644 index 0000000..c4ee513 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java @@ -0,0 +1,16 @@ +package io.github.dunwu.algorithm.graph; + +/** + * 存储相邻节点及边的权重 + */ +public class Edge { + + public int to; + public int weight; + + public Edge(int to, int weight) { + this.to = to; + this.weight = weight; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java new file mode 100644 index 0000000..eaf8c0c --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.graph; + +import java.util.List; + +public interface Graph { + + // 添加一条边(带权重) + void addEdge(int from, int to, int weight); + + // 删除一条边 + void removeEdge(int from, int to); + + // 判断两个节点是否相邻 + boolean hasEdge(int from, int to); + + // 返回一条边的权重 + int weight(int from, int to); + + // 返回某个节点的所有邻居节点和对应权重 + List neighbors(int v); + + // 返回节点总数 + int size(); + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java new file mode 100644 index 0000000..3b78a8f --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java @@ -0,0 +1,17 @@ +package io.github.dunwu.algorithm.graph; + +// 图结构的 BFS 遍历,从节点 s 开始进行 BFS,且记录遍历步数(从起点 s 到当前节点的边的条数) +// 每个节点自行维护 State 类,记录从 s 走来的遍历步数 +public class State { + + // 当前节点 ID + public int node; + // 从起点 s 到当前节点的遍历步数 + public int step; + + public State(int node, int step) { + this.node = node; + this.step = step; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java new file mode 100644 index 0000000..46dec8a --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java @@ -0,0 +1,11 @@ +package io.github.dunwu.algorithm.graph; + +/** + * 图节点 + */ +public class Vertex { + + public int id; + public Vertex[] neighbors; + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" new file mode 100644 index 0000000..a78231e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" @@ -0,0 +1,139 @@ +package io.github.dunwu.algorithm.graph.bipartite; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 785. 判断二分图 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 判断二分图 { + + public static void main(String[] args) { + + int[][] input = new int[][] { { 1, 2, 3 }, { 0, 2 }, { 0, 1, 3 }, { 0, 2 } }; + int[][] input2 = new int[][] { { 1, 3 }, { 0, 2 }, { 1, 3 }, { 0, 2 } }; + + Solution s = new Solution(); + Assertions.assertFalse(s.isBipartite(input)); + Assertions.assertFalse(s.isBipartite(input2)); + + Solution2 s2 = new Solution2(); + Assertions.assertFalse(s2.isBipartite(input)); + Assertions.assertFalse(s2.isBipartite(input2)); + } + + // 二分图算法(DFS 版本) + static class Solution { + + // 记录图是否符合二分图性质 + private boolean ok = true; + // 记录图中节点的颜色,false 和 true 代表两种不同颜色 + private boolean[] color; + // 记录图中节点是否被访问过 + private boolean[] visited; + + // 主函数,输入邻接表,判断是否是二分图 + public boolean isBipartite(int[][] graph) { + int n = graph.length; + color = new boolean[n]; + visited = new boolean[n]; + // 因为图不一定是联通的,可能存在多个子图 + // 所以要把每个节点都作为起点进行一次遍历 + // 如果发现任何一个子图不是二分图,整幅图都不算二分图 + for (int v = 0; v < n; v++) { + if (!visited[v]) { + dfs(graph, v); + } + } + return ok; + } + + // DFS 遍历框架 + private void dfs(int[][] graph, int v) { + // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了 + if (!ok) return; + + visited[v] = true; + for (int w : graph[v]) { + if (!visited[w]) { + // 相邻节点 w 没有被访问过 + // 那么应该给节点 w 涂上和节点 v 不同的颜色 + color[w] = !color[v]; + // 继续遍历 w + dfs(graph, w); + } else { + // 相邻节点 w 已经被访问过 + // 根据 v 和 w 的颜色判断是否是二分图 + if (color[w] == color[v]) { + // 若相同,则此图不是二分图 + ok = false; + } + } + } + } + + } + + // 二分图算法(BFS 版本) + static class Solution2 { + + // 记录图是否符合二分图性质 + private boolean ok = true; + // 记录图中节点的颜色,false 和 true 代表两种不同颜色 + private boolean[] color; + // 记录图中节点是否被访问过 + private boolean[] visited; + + public boolean isBipartite(int[][] graph) { + int n = graph.length; + color = new boolean[n]; + visited = new boolean[n]; + + for (int v = 0; v < n; v++) { + if (!visited[v]) { + // 改为使用 BFS 函数 + bfs(graph, v); + } + } + + return ok; + } + + // 从 start 节点开始进行 BFS 遍历 + private void bfs(int[][] graph, int start) { + Queue q = new LinkedList<>(); + visited[start] = true; + q.offer(start); + + while (!q.isEmpty() && ok) { + int v = q.poll(); + // 从节点 v 向所有相邻节点扩散 + for (int w : graph[v]) { + if (!visited[w]) { + // 相邻节点 w 没有被访问过 + // 那么应该给节点 w 涂上和节点 v 不同的颜色 + color[w] = !color[v]; + // 标记 w 节点,并放入队列 + visited[w] = true; + q.offer(w); + } else { + // 相邻节点 w 已经被访问过 + // 根据 v 和 w 的颜色判断是否是二分图 + if (color[w] == color[v]) { + // 若相同,则此图不是二分图 + ok = false; + return; + } + } + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" new file mode 100644 index 0000000..1dc1d9b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.graph.bipartite; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 886. 可能的二分法 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 可能的二分法 { + + public static void main(String[] args) { + + int[][] input = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 4 } }; + int[][] input2 = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 3 } }; + int[][] input3 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 1, 5 } }; + + Solution s = new Solution(); + Assertions.assertTrue(s.possibleBipartition(4, input)); + Assertions.assertFalse(s.possibleBipartition(3, input2)); + Assertions.assertFalse(s.possibleBipartition(5, input3)); + } + + static class Solution { + + private boolean ok = true; + private boolean[] color; + private boolean[] visited; + + public boolean possibleBipartition(int n, int[][] dislikes) { + // 图节点编号从 1 开始 + color = new boolean[n + 1]; + visited = new boolean[n + 1]; + // 转化成邻接表表示图结构 + List[] graph = buildGraph(n, dislikes); + + for (int v = 1; v <= n; v++) { + if (!visited[v]) { + dfs(graph, v); + } + } + return ok; + } + + // 建图函数 + private List[] buildGraph(int n, int[][] dislikes) { + // 图节点编号为 1...n + List[] graph = new LinkedList[n + 1]; + for (int i = 1; i <= n; i++) { + graph[i] = new LinkedList<>(); + } + for (int[] edge : dislikes) { + int v = edge[1]; + int w = edge[0]; + // 「无向图」相当于「双向图」 + // v -> w + graph[v].add(w); + // w -> v + graph[w].add(v); + } + return graph; + } + + // 和之前判定二分图的 traverse 函数完全相同 + private void dfs(List[] graph, int v) { + if (!ok) return; + visited[v] = true; + for (int w : graph[v]) { + if (!visited[w]) { + color[w] = !color[v]; + dfs(graph, w); + } else { + if (color[w] == color[v]) { + ok = false; + } + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" new file mode 100644 index 0000000..b111b38 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.graph.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 797. 所有可能的路径 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 所有可能的路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = new int[][] { + { 1, 2 }, { 3 }, { 3 }, {} + }; + List> expect = new LinkedList<>(); + expect.add(Arrays.asList(0, 1, 3)); + expect.add(Arrays.asList(0, 2, 3)); + List> output = s.allPathsSourceTarget(input); + for (int i = 0; i < expect.size(); i++) { + Assertions.assertArrayEquals(expect.get(i).toArray(), output.get(i).toArray()); + } + // System.out.println("v = " + output); + } + + static class Solution { + + private List path; + private List> res; + + public List> allPathsSourceTarget(int[][] graph) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(graph, 0); + return res; + } + + // 图的遍历框架 + void dfs(int[][] graph, int s) { + + // 添加节点 s 到路径 + path.add(s); + + int n = graph.length; + if (s == n - 1) { + // 到达终点 + res.add(new LinkedList<>(path)); + path.remove(path.size() - 1); + return; + } + + // 递归每个相邻节点 + for (int v : graph[s]) { + dfs(graph, v); + } + + // 从路径移出节点 s + path.remove(path.size() - 1); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" new file mode 100644 index 0000000..74141ec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.State; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 图的遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class BFS遍历图 { + + // 图结构的 BFS 遍历,从节点 s 开始进行 BFS + void bfs(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + + while (!q.isEmpty()) { + int cur = q.poll(); + System.out.println("visit " + cur); + for (Edge e : graph.neighbors(cur)) { + if (visited[e.to]) { + continue; + } + q.offer(e.to); + visited[e.to] = true; + } + } + } + + // 从 s 开始 BFS 遍历图的所有节点,且记录遍历的步数 + void bfs2(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + // 记录从 s 开始走到当前节点的步数 + int step = 0; + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + int cur = q.poll(); + System.out.println("visit " + cur + " at step " + step); + for (Edge e : graph.neighbors(cur)) { + if (visited[e.to]) { + continue; + } + q.offer(e.to); + visited[e.to] = true; + } + } + step++; + } + } + + // 图结构的 BFS 遍历,从节点 s 开始进行 BFS,且记录遍历步数(从起点 s 到当前节点的边的条数) + // 每个节点自行维护 State 类,记录从 s 走来的遍历步数 + void bfs3(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + + q.offer(new State(s, 0)); + visited[s] = true; + + while (!q.isEmpty()) { + State state = q.poll(); + int node = state.node; + int step = state.step; + System.out.println("visit " + node + " with step " + step); + for (Edge e : graph.neighbors(node)) { + if (visited[e.to]) { + continue; + } + q.offer(new State(e.to, step + 1)); + visited[e.to] = true; + } + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" new file mode 100644 index 0000000..86b8925 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.Vertex; + +/** + * 图的遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class DFS遍历图的所有节点 { + + // 遍历图的所有节点 + void traverse(Graph graph, int s, boolean[] visited) { + // base case + if (s < 0 || s >= graph.size()) { return; } + // 防止死循环 + if (visited[s]) { return; } + // 前序位置 + visited[s] = true; + System.out.println("visit " + s); + for (Edge e : graph.neighbors(s)) { + traverse(graph, e.to, visited); + } + // 后序位置 + } + + // 图的遍历框架 + // 需要一个 visited 数组记录被遍历过的节点 + // 避免走回头路陷入死循环 + void traverse(Vertex v, boolean[] visited) { + // base case + if (v == null) { return; } + // 防止死循环 + if (visited[v.id]) { return; } + // 前序位置 + visited[v.id] = true; + System.out.println("visit " + v.id); + for (Vertex neighbor : v.neighbors) { + traverse(neighbor, visited); + } + // 后序位置 + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" new file mode 100644 index 0000000..0a53f86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.tree.Node; + +import java.util.LinkedList; + +/** + * DFS遍历图的所有路径 + * + * @author Zhang Peng + * @date 2025-12-02 + */ +public class DFS遍历图的所有路径 { + + // onPath 和 path 记录当前递归路径上的节点 + boolean[] onPath = null; + // 多叉树的遍历框架,寻找从根节点到目标节点的路径 + LinkedList path = new LinkedList<>(); + + void traverse(Node root, Node targetNode) { + // base case + if (root == null) { + return; + } + if (root.val == targetNode.val) { + // 找到目标节点 + System.out.println("find path: " + String.join("->", path) + "->" + targetNode); + return; + } + // 前序位置 + path.addLast(String.valueOf(root.val)); + for (Node child : root.children) { + traverse(child, targetNode); + } + // 后序位置 + path.removeLast(); + } + + void traverse(Graph graph, int from, int to) { + if (onPath == null) { onPath = new boolean[graph.size()]; } + // base case + if (from < 0 || from >= graph.size()) { return; } + // 防止死循环(成环) + if (onPath[from]) { return; } + if (from == to) { + // 找到目标节点 + System.out.println("find path: " + String.join("->", path) + "->" + to); + return; + } + + // 前序位置 + onPath[from] = true; + path.add(String.valueOf(from)); + for (Edge e : graph.neighbors(from)) { + traverse(graph, e.to, to); + } + // 后序位置 + path.remove(path.size() - 1); + onPath[from] = false; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" new file mode 100644 index 0000000..8081142 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.Vertex; +import io.github.dunwu.algorithm.tree.Node; + +/** + * DFS遍历图的所有边 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class DFS遍历图的所有边 { + + // 遍历多叉树的树枝 + void traverseBranch(Node root) { + // base case + if (root == null) { return; } + for (Node child : root.children) { + System.out.println("visit branch: " + root.val + " -> " + child.val); + traverseBranch(child); + } + } + + // 遍历图的边 + // 需要一个二维 visited 数组记录被遍历过的边,visited[from][to] 表示边 from->to 已经被遍历过 + void traverseEdges(Vertex v, boolean[][] visited) { + // base case + if (v == null) { return; } + for (Vertex neighbor : v.neighbors) { + // 如果边已经被遍历过,则跳过 + if (visited[v.id][neighbor.id]) { continue; } + // 标记并访问边 + visited[v.id][neighbor.id] = true; + System.out.println("visit edge: " + v.id + " -> " + neighbor.id); + traverseEdges(neighbor, visited); + } + } + + // 从起点 s 开始遍历图的所有边 + void traverseEdges(Graph graph, int s, boolean[][] visited) { + // base case + if (s < 0 || s >= graph.size()) { return; } + for (Edge e : graph.neighbors(s)) { + // 如果边已经被遍历过,则跳过 + if (visited[s][e.to]) { continue; } + // 标记并访问边 + visited[s][e.to] = true; + System.out.println("visit edge: " + s + " -> " + e.to); + traverseEdges(graph, e.to, visited); + } + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java new file mode 100644 index 0000000..518770b --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java @@ -0,0 +1,78 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; + +import java.util.Arrays; +import java.util.PriorityQueue; + +/** + * Dijkstra 算法模板 + * + * @author Zhang Peng + * @date 2025-12-03 + */ +public class Dijkstra { + + // 输入不包含负权重边的加权图 graph 和起点 src + // 返回从起点 src 到其他节点的最小路径权重和 + public int[] dijkstra(Graph graph, int src) { + // 记录从起点 src 到其他节点的最小路径权重和 + // distTo[i] 表示从起点 src 到节点 i 的最小路径权重和 + int[] distTo = new int[graph.size()]; + // 都初始化为正无穷,表示未计算 + Arrays.fill(distTo, Integer.MAX_VALUE); + + // 优先级队列,distFromStart 较小的节点排在前面 + PriorityQueue pq = new PriorityQueue<>((a, b) -> { + return a.distFromStart - b.distFromStart; + }); + + // 从起点 src 开始进行 BFS + pq.offer(new State(src, 0)); + distTo[src] = 0; + + while (!pq.isEmpty()) { + State state = pq.poll(); + int curNode = state.node; + int curDistFromStart = state.distFromStart; + + if (distTo[curNode] < curDistFromStart) { + // 在 Dijkstra 算法中,队列中可能存在重复的节点 state + // 所以要在元素出队时进行判断,去除较差的重复节点 + continue; + } + + for (Edge e : graph.neighbors(curNode)) { + int nextNode = e.to; + int nextDistFromStart = curDistFromStart + e.weight; + + if (distTo[nextNode] <= nextDistFromStart) { + continue; + } + + // 将 nextNode 节点加入优先级队列 + pq.offer(new State(nextNode, nextDistFromStart)); + // 记录 nextNode 节点到起点的最小路径权重和 + distTo[nextNode] = nextDistFromStart; + } + } + + return distTo; + } + + static class State { + + // 当前节点 ID + int node; + // 从起点 s 到当前 node 节点的最小路径权重和 + int distFromStart; + + public State(int node, int distFromStart) { + this.node = node; + this.distFromStart = distFromStart; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" new file mode 100644 index 0000000..bc15937 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.graph.template; + +/** + * 并查集 + * + * @author Zhang Peng + * @date 2025-12-03 + */ +public class 并查集 { + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + // n 为图中节点的个数 + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + // 将节点 p 和节点 q 连通 + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + // 两个连通分量合并成一个连通分量 + count--; + } + + // 判断节点 p 和节点 q 是否连通 + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + // 返回图中的连通分量个数 + public int count() { + return count; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" new file mode 100644 index 0000000..140b44c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" @@ -0,0 +1,138 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; + +import java.util.ArrayList; +import java.util.List; + +/** + * 邻接矩阵实现图 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 邻接矩阵实现图 { + + // 加权有向图的通用实现(邻接矩阵) + static class WeightedDigraph { + + // 邻接矩阵,matrix[from][to] 存储从节点 from 到节点 to 的边的权重 + // 0 表示没有连接 + private int[][] matrix; + + public WeightedDigraph(int n) { + matrix = new int[n][n]; + } + + // 增,添加一条带权重的有向边,复杂度 O(1) + public void addEdge(int from, int to, int weight) { + matrix[from][to] = weight; + } + + // 删,删除一条有向边,复杂度 O(1) + public void removeEdge(int from, int to) { + matrix[from][to] = 0; + } + + // 查,判断两个节点是否相邻,复杂度 O(1) + public boolean hasEdge(int from, int to) { + return matrix[from][to] != 0; + } + + // 查,返回一条边的权重,复杂度 O(1) + public int weight(int from, int to) { + return matrix[from][to]; + } + + // 查,返回某个节点的所有邻居节点,复杂度 O(V) + public List neighbors(int v) { + List res = new ArrayList<>(); + for (int i = 0; i < matrix[v].length; i++) { + if (matrix[v][i] != 0) { + res.add(new Edge(i, matrix[v][i])); + } + } + return res; + } + + public static void main(String[] args) { + WeightedDigraph graph = new WeightedDigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(1, 2, 2); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // false + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " -> " + edge.to + ", wight: " + edge.weight); + }); + // 2 -> 0, wight: 3 + // 2 -> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + } + + } + + // 无向加权图的通用实现 + static class WeightedUndigraph { + + private WeightedDigraph graph; + + public WeightedUndigraph(int n) { + graph = new WeightedDigraph(n); + } + + // 增,添加一条带权重的无向边 + public void addEdge(int from, int to, int weight) { + graph.addEdge(from, to, weight); + graph.addEdge(to, from, weight); + } + + // 删,删除一条无向边 + public void removeEdge(int from, int to) { + graph.removeEdge(from, to); + graph.removeEdge(to, from); + } + + // 查,判断两个节点是否相邻 + public boolean hasEdge(int from, int to) { + return graph.hasEdge(from, to); + } + + // 查,返回一条边的权重 + public int weight(int from, int to) { + return graph.weight(from, to); + } + + // 查,返回某个节点的所有邻居节点 + public List neighbors(int v) { + return graph.neighbors(v); + } + + public static void main(String[] args) { + WeightedUndigraph graph = new WeightedUndigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // true + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " <-> " + edge.to + ", wight: " + edge.weight); + }); + // 2 <-> 0, wight: 3 + // 2 <-> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + System.out.println(graph.hasEdge(1, 0)); // false + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" new file mode 100644 index 0000000..2372c4c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" @@ -0,0 +1,159 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; + +import java.util.ArrayList; +import java.util.List; + +/** + * 邻接表实现图 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 邻接表实现图 { + + /** + * 加权有向图的通用实现(邻接表) + */ + static class WeightedDigraph { + + // 邻接表,graph[v] 存储节点 v 的所有邻居节点及对应权重 + private List[] graph; + + public WeightedDigraph(int n) { + // 我们这里简单起见,建图时要传入节点总数,这其实可以优化 + // 比如把 graph 设置为 Map>,就可以动态添加新节点了 + graph = new List[n]; + for (int i = 0; i < n; i++) { + graph[i] = new ArrayList<>(); + } + } + + // 增,添加一条带权重的有向边,复杂度 O(1) + public void addEdge(int from, int to, int weight) { + graph[from].add(new Edge(to, weight)); + } + + // 删,删除一条有向边,复杂度 O(V) + public void removeEdge(int from, int to) { + for (int i = 0; i < graph[from].size(); i++) { + if (graph[from].get(i).to == to) { + graph[from].remove(i); + break; + } + } + } + + // 查,判断两个节点是否相邻,复杂度 O(V) + public boolean hasEdge(int from, int to) { + for (Edge e : graph[from]) { + if (e.to == to) { + return true; + } + } + return false; + } + + // 查,返回一条边的权重,复杂度 O(V) + public int weight(int from, int to) { + for (Edge e : graph[from]) { + if (e.to == to) { + return e.weight; + } + } + throw new IllegalArgumentException("No such edge"); + } + + // 上面的 hasEdge、removeEdge、weight 方法遍历 List 的行为是可以优化的 + // 比如用 Map> 存储邻接表 + // 这样就可以避免遍历 List,复杂度就能降到 O(1) + + // 查,返回某个节点的所有邻居节点,复杂度 O(1) + public List neighbors(int v) { + return graph[v]; + } + + public static void main(String[] args) { + WeightedDigraph graph = new WeightedDigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(1, 2, 2); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // false + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " -> " + edge.to + ", wight: " + edge.weight); + }); + // 2 -> 0, wight: 3 + // 2 -> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + } + + } + + /** + * 无向加权图的通用实现 + */ + static class WeightedUndigraph { + + private WeightedDigraph graph; + + public WeightedUndigraph(int n) { + graph = new WeightedDigraph(n); + } + + // 增,添加一条带权重的无向边 + public void addEdge(int from, int to, int weight) { + graph.addEdge(from, to, weight); + graph.addEdge(to, from, weight); + } + + // 删,删除一条无向边 + public void removeEdge(int from, int to) { + graph.removeEdge(from, to); + graph.removeEdge(to, from); + } + + // 查,判断两个节点是否相邻 + public boolean hasEdge(int from, int to) { + return graph.hasEdge(from, to); + } + + // 查,返回一条边的权重 + public int weight(int from, int to) { + return graph.weight(from, to); + } + + // 查,返回某个节点的所有邻居节点 + public List neighbors(int v) { + return graph.neighbors(v); + } + + public static void main(String[] args) { + WeightedUndigraph graph = new WeightedUndigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // true + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " <-> " + edge.to + ", wight: " + edge.weight); + }); + // 2 <-> 0, wight: 3 + // 2 <-> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + System.out.println(graph.hasEdge(1, 0)); // false + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" new file mode 100644 index 0000000..62c8c0d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" @@ -0,0 +1,139 @@ +package io.github.dunwu.algorithm.graph.topological_sort; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 207. 课程表 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 课程表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canFinish(2, new int[][] { { 1, 0 } })); + Assertions.assertFalse(s.canFinish(2, new int[][] { { 1, 0 }, { 0, 1 } })); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.canFinish(2, new int[][] { { 1, 0 } })); + Assertions.assertFalse(s2.canFinish(2, new int[][] { { 1, 0 }, { 0, 1 } })); + } + + // 环检测算法(DFS 版本) + static class Solution { + + // 记录一次递归堆栈中的节点 + boolean[] onPath; + // 记录节点是否被遍历过 + boolean[] visited; + // 记录图中是否有环 + boolean hasCycle = false; + + public boolean canFinish(int numCourses, int[][] prerequisites) { + List[] graph = buildGraph(numCourses, prerequisites); + visited = new boolean[numCourses]; + onPath = new boolean[numCourses]; + + // 遍历图中的所有节点 + for (int i = 0; i < numCourses; i++) { + dfs(graph, i); + } + // 只要没有循环依赖可以完成所有课程 + return !hasCycle; + } + + public void dfs(List[] graph, int s) { + // 找到环,或已访问,则无需再遍历 + if (onPath[s]) { hasCycle = true; } + if (hasCycle || visited[s]) { return; } + + // 【前序】 + visited[s] = true; + onPath[s] = true; + for (int t : graph[s]) { + dfs(graph, t); + } + // 【后序】 + onPath[s] = false; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + + // 环检测算法(BFS 版本) + static class Solution2 { + + public boolean canFinish(int numCourses, int[][] prerequisites) { + // 建图,有向边代表「被依赖」关系 + List[] graph = buildGraph(numCourses, prerequisites); + // 构建入度数组 + int[] indegree = new int[numCourses]; + for (int[] edge : prerequisites) { + int from = edge[1], to = edge[0]; + // 节点 to 的入度加一 + indegree[to]++; + } + + // 根据入度初始化队列中节点 + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + // 节点 i 没有入度,即没有依赖的节点 + // 可以作为拓扑排序的起点,加入队列 + q.offer(i); + } + } + + // 记录遍历的节点个数 + int count = 0; + // 开始执行 BFS 遍历 + while (!q.isEmpty()) { + // 弹出节点 cur,并将它指向的节点的入度减一 + int cur = q.poll(); + count++; + for (int next : graph[cur]) { + indegree[next]--; + if (indegree[next] == 0) { + // 如果入度变为 0,说明 next 依赖的节点都已被遍历 + q.offer(next); + } + } + } + + // 如果所有节点都被遍历过,说明不成环 + return count == numCourses; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" new file mode 100644 index 0000000..998b686 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" @@ -0,0 +1,156 @@ +package io.github.dunwu.algorithm.graph.topological_sort; + +import org.junit.jupiter.api.Assertions; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 210. 课程表 II + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 课程表2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.findOrder(2, new int[][] { { 1, 0 } })); + Assertions.assertArrayEquals(new int[] { 0, 2, 1, 3 }, + s.findOrder(4, new int[][] { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } })); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.findOrder(2, new int[][] { { 1, 0 } })); + Assertions.assertArrayEquals(new int[] { 0, 2, 1, 3 }, + s2.findOrder(4, new int[][] { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } })); + } + + // 拓扑排序算法(DFS 版本) + static class Solution { + + // 记录后序遍历结果 + private List preorder; + // 记录一次递归堆栈中的节点 + boolean[] onPath; + // 记录节点是否被遍历过 + boolean[] visited; + // 记录图中是否有环 + boolean hasCycle = false; + + public int[] findOrder(int numCourses, int[][] prerequisites) { + List[] graph = buildGraph(numCourses, prerequisites); + preorder = new LinkedList<>(); + visited = new boolean[numCourses]; + onPath = new boolean[numCourses]; + + for (int i = 0; i < numCourses; i++) { + dfs(graph, i); + } + + // 有环图无法进行拓扑排序 + if (hasCycle) { return new int[0]; } + + // 逆后序遍历结果即为拓扑排序结果 + Collections.reverse(preorder); + int[] order = new int[numCourses]; + for (int i = 0; i < numCourses; i++) { + order[i] = preorder.get(i); + } + return order; + } + + public void dfs(List[] graph, int s) { + // 找到环,或已访问,则无需再遍历 + if (onPath[s]) { hasCycle = true; } + if (hasCycle || visited[s]) { return; } + + // 【前序】 + visited[s] = true; + onPath[s] = true; + for (int t : graph[s]) { + dfs(graph, t); + } + // 【后序】 + preorder.add(s); + onPath[s] = false; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + + // 拓扑排序算法(BFS 版本) + static class Solution2 { + + public int[] findOrder(int numCourses, int[][] prerequisites) { + // 建图,和环检测算法相同 + List[] graph = buildGraph(numCourses, prerequisites); + // 计算入度,和环检测算法相同 + int[] indegree = new int[numCourses]; + for (int[] edge : prerequisites) { + int from = edge[1], to = edge[0]; + indegree[to]++; + } + + // 根据入度初始化队列中的节点,和环检测算法相同 + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + q.offer(i); + } + } + + // 记录拓扑排序结果 + int[] res = new int[numCourses]; + // 记录遍历节点的顺序(索引) + int count = 0; + // 开始执行 BFS 算法 + while (!q.isEmpty()) { + int cur = q.poll(); + // 弹出节点的顺序即为拓扑排序结果 + res[count] = cur; + count++; + for (int next : graph[cur]) { + indegree[next]--; + if (indegree[next] == 0) { + q.offer(next); + } + } + } + + // 存在环,拓扑排序不存在 + if (count != numCourses) { + return new int[0]; + } + return res; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" new file mode 100644 index 0000000..8619903 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 684. 冗余连接 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 冗余连接 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 3 } }; + int[][] input2 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 1, 4 }, { 1, 5 } }; + Assertions.assertArrayEquals(new int[] { 2, 3 }, s.findRedundantConnection(input)); + Assertions.assertArrayEquals(new int[] { 1, 4 }, s.findRedundantConnection(input2)); + } + + static class Solution { + + public int[] findRedundantConnection(int[][] edges) { + UF uf = new UF(edges.length + 1); + for (int[] edge : edges) { + int p = edge[0], q = edge[1]; + if (uf.connected(p, q)) { + return new int[] { p, q }; + } else { + uf.union(p, q); + } + } + return new int[0]; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" new file mode 100644 index 0000000..c78af57 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 990. 等式方程的可满足性 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 等式方程的可满足性 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertFalse(s.equationsPossible(new String[] { "a==b", "b!=a" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "b==a", "a==b" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "a==b", "b==c", "a==c" })); + Assertions.assertFalse(s.equationsPossible(new String[] { "a==b", "b!=c", "c==a" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "c==c", "b==d", "x!=z" })); + } + + static class Solution { + + public boolean equationsPossible(String[] equations) { + UF uf = new UF(26); + for (String exp : equations) { + if (exp.contains("==")) { + String[] vals = exp.split("=="); + int a = vals[0].charAt(0) - 'a'; + int b = vals[1].charAt(0) - 'a'; + uf.union(a, b); + } + } + + for (String exp : equations) { + if (exp.contains("!=")) { + String[] vals = exp.split("!="); + int a = vals[0].charAt(0) - 'a'; + int b = vals[1].charAt(0) - 'a'; + if (uf.connected(a, b)) { + return false; + } + } + } + return true; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" new file mode 100644 index 0000000..ae1114a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" @@ -0,0 +1,136 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 130. 被围绕的区域 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 被围绕的区域 { + + public static void main(String[] args) { + Solution s = new Solution(); + + char[][] input = new char[][] { + { 'X', 'X', 'X', 'X' }, + { 'X', 'O', 'O', 'X' }, + { 'X', 'X', 'O', 'X' }, + { 'X', 'O', 'X', 'X' } + }; + char[][] expect = new char[][] { + { 'X', 'X', 'X', 'X' }, + { 'X', 'X', 'X', 'X' }, + { 'X', 'X', 'X', 'X' }, + { 'X', 'O', 'X', 'X' } + }; + s.solve(input); + Assertions.assertArrayEquals(expect, input); + } + + static class Solution { + + private int m; + private int n; + int[][] direct = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; + + public void solve(char[][] board) { + + if (board == null || board.length == 0) return; + m = board.length; + n = board[0].length; + + // 给 dummy 留一个额外位置 + UF uf = new UF(m * n + 1); + int dummy = m * n; + + // 将首列和末列的 O 与 dummy 连通 + for (int i = 0; i < m; i++) { + if (board[i][0] == 'O') { uf.union(index(i, 0), dummy); } + if (board[i][n - 1] == 'O') { uf.union(index(i, n - 1), dummy); } + } + + // 将首行和末行的 O 与 dummy 连通 + for (int j = 0; j < n; j++) { + if (board[0][j] == 'O') { uf.union(index(0, j), dummy); } + if (board[m - 1][j] == 'O') { uf.union(index(m - 1, j), dummy); } + } + + // 方向数组 d 是上下左右搜索的常用手法 + for (int i = 1; i < m - 1; i++) { + for (int j = 1; j < n - 1; j++) { + if (board[i][j] == 'O') { + // 将此 O 与上下左右的 O 连通 + for (int[] d : direct) { + int x = i + d[0], y = j + d[1]; + if (board[x][y] == 'O') { + uf.union(index(x, y), index(i, j)); + } + } + } + } + } + + // 所有不和 dummy 连通的 O,都要被替换 + for (int i = 1; i < m - 1; i++) { + for (int j = 1; j < n - 1; j++) { + int index = index(i, j); + if (!uf.connected(index, dummy)) { + board[i][j] = 'X'; + } + } + } + } + + public int index(int row, int col) { + return row * n + col; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" new file mode 100644 index 0000000..88d7ee5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" @@ -0,0 +1,35 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 561. 数组拆分 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 数组拆分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.arrayPairSum(new int[] { 1, 4, 3, 2 })); + Assertions.assertEquals(9, s.arrayPairSum(new int[] { 6, 2, 6, 5, 1, 2 })); + } + + static class Solution { + + + public int arrayPairSum(int[] nums) { + Arrays.sort(nums); + int sum = 0; + for (int i = 0; i < nums.length; i+=2) { + sum += nums[i]; + } + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" new file mode 100644 index 0000000..bae6a44 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 55. 跳跃游戏 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 跳跃游戏 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.jump(new int[] { 2, 3, 1, 1, 4 })); + } + + static class Solution { + + int[] memo; + + public int jump(int[] nums) { + if (nums.length <= 1) { + return 0; + } + int n = nums.length; + // 备忘录都初始化为 n,相当于 INT_MAX + // 因为从 0 跳到 n - 1 最多 n - 1 步 + memo = new int[n]; + Arrays.fill(memo, n); + + return dp(nums, 0); + } + + int dp(int[] nums, int p) { + int n = nums.length; + if (p >= n - 1) { + return 0; + } + + int steps = nums[p]; + // 你可以选择跳 1 步,2 步... + for (int i = 1; i <= steps; i++) { + // 穷举每一个选择 + // 计算每一个子问题的结果 + int sub = dp(nums, p + i); + // 取其中最小的作为最终结果 + memo[p] = Math.min(memo[p], sub + 1); + } + return memo[p]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" new file mode 100644 index 0000000..d1413bb --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" @@ -0,0 +1,31 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +/** + * 53. 最大子数组和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 跳跃游戏2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canJump(new int[] { 2, 3, 1, 1, 4 })); + } + + static class Solution { + + public boolean canJump(int[] nums) { + int farthest = 0; + for (int i = 0; i < nums.length; i++) { + farthest = Math.max(farthest, i + nums[i]); + if (farthest <= i) return false; + } + return farthest >= nums.length - 1; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java similarity index 96% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java index 788329f..57f5933 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.hashtable; +package io.github.dunwu.algorithm.hash; import java.util.HashSet; diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java new file mode 100644 index 0000000..8cf1e6e --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java @@ -0,0 +1,126 @@ +package io.github.dunwu.algorithm.hash; + +// 不使用任何内建的哈希表库设计一个哈希映射(HashMap)。 +// +// 实现 MyHashMap 类: +// +// MyHashMap() 用空映射初始化对象 +// void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。 +// int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。 +// void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。 +// +// 示例: +// +// 输入: +// ["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] +// [[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] +// 输出: +// [null, null, null, 1, -1, null, 1, null, -1] +// +// 解释: +// MyHashMap myHashMap = new MyHashMap(); +// myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] +// myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) +// myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] +// myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] +// myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] +// +// 提示: +// +// 0 <= key, value <= 106 +// 最多调用 104 次 put、get 和 remove 方法 +// +// 链接:https://leetcode-cn.com/leetbook/read/hash-table/xhqwd3/ + +import java.util.LinkedList; + +/** + * 实现 HashMap,使用拉链表法(基于 LinkedList 实现)决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +public class MyHashMap { + + private final int BUCKET_NUM = 1000; + private final LinkedList[] data; + + public MyHashMap() { + data = new LinkedList[BUCKET_NUM]; + for (int i = 0; i < BUCKET_NUM; ++i) { + data[i] = new LinkedList<>(); + } + } + + public void put(int key, int value) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + pair.value = value; + return; + } + } + data[bucket].add(new Pair(key, value)); + } + + public int get(int key) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + return pair.value; + } + } + return -1; + } + + public void remove(int key) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + data[bucket].remove(pair); + return; + } + } + } + + private int hash(int key) { + return key % BUCKET_NUM; + } + + private static class Pair { + + private final int key; + private int value; + + public Pair(int key, int value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + } + + public static void main(String[] args) { + MyHashMap obj = new MyHashMap(); + obj.put(5, 555); + obj.put(1005, 555); + System.out.println("key = 5, value = " + obj.get(5)); + System.out.println("key = 1005, value = " + obj.get(1005)); + // obj.remove(key); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java new file mode 100644 index 0000000..1a864d5 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java @@ -0,0 +1,90 @@ +// 【设计哈希集合】 +// +// 不使用任何内建的哈希表库设计一个哈希集合 +// +// 具体地说,你的设计应该包含以下的功能 +// +// add(value):向哈希集合中插入一个值。 +// contains(value) :返回哈希集合中是否存在这个值。 +// remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 +// +// 示例: +// +// MyHashSet hashSet = new MyHashSet(); +// hashSet.add(1); +// hashSet.add(2); +// hashSet.contains(1); // 返回 true +// hashSet.contains(3); // 返回 false (未找到) +// hashSet.add(2); +// hashSet.contains(2); // 返回 true +// hashSet.remove(2); +// hashSet.contains(2); // 返回 false (已经被删除) +// +// 注意: +// +// 所有的值都在 [1, 1000000]的范围内。 +// 操作的总数目在[1, 10000]范围内。 +// 不要使用内建的哈希集合库。 + +package io.github.dunwu.algorithm.hash; + +import java.util.LinkedList; + +/** + * 实现 HashMap,使用拉链表法(基于 LinkedList 实现)决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +class MyHashSet { + + private final int BUCKET_NUM = 1000; + + private final LinkedList[] data; + + public MyHashSet() { + data = new LinkedList[BUCKET_NUM]; + for (int i = 0; i < BUCKET_NUM; ++i) { + data[i] = new LinkedList<>(); + } + } + + public void add(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + return; + } + } + data[bucket].add(key); + } + + public int hash(int key) { + return key % BUCKET_NUM; + } + + public int pos(int key) { + return key / BUCKET_NUM; + } + + public void remove(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + data[bucket].remove(item); + return; + } + } + } + + public boolean contains(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + return true; + } + } + return false; + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashset.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java similarity index 60% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashset.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java index 5ebaf01..dc60c47 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashset.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java @@ -1,7 +1,4 @@ -package io.github.dunwu.algorithm.hash; - // 【设计哈希集合】 - // // 不使用任何内建的哈希表库设计一个哈希集合 // @@ -29,52 +26,53 @@ // 操作的总数目在[1, 10000]范围内。 // 不要使用内建的哈希集合库。 -class DesignHashset { +package io.github.dunwu.algorithm.hash; - private int buckets = 1000; +/** + * 实现 HashSet,使用开放寻址法决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +class MyHashSet2 { - private int itemsPerBucket = 1001; + private final int BUCKET_NUM = 1000; - private boolean[][] table; + private final boolean[][] data; - /** - * Initialize your data structure here. - */ - public DesignHashset() { - table = new boolean[buckets][]; + public MyHashSet2() { + data = new boolean[BUCKET_NUM][]; } public void add(int key) { - int hashkey = hash(key); + int bucket = hash(key); - if (table[hashkey] == null) { - table[hashkey] = new boolean[itemsPerBucket]; + if (data[bucket] == null) { + // BUCKET_NUM + 1 是为了防止数组越界 + data[bucket] = new boolean[BUCKET_NUM + 1]; } - table[hashkey][pos(key)] = true; + data[bucket][pos(key)] = true; } public int hash(int key) { - return key % buckets; + return key % BUCKET_NUM; } public int pos(int key) { - return key / buckets; + return key / BUCKET_NUM; } public void remove(int key) { - int hashkey = hash(key); + int bucket = hash(key); - if (table[hashkey] != null) { - table[hashkey][pos(key)] = false; + if (data[bucket] != null) { + data[bucket][pos(key)] = false; } } - /** - * Returns true if this set did not already contain the specified element - */ public boolean contains(int key) { - int hashkey = hash(key); - return table[hashkey] != null && table[hashkey][pos(key)]; + int bucket = hash(key); + return data[bucket] != null && data[bucket][pos(key)]; } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java similarity index 98% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java index 2497364..7d7f733 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.hashtable; +package io.github.dunwu.algorithm.hash; import java.util.ArrayList; import java.util.HashMap; diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java similarity index 95% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java index 885f1df..97af3d7 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.hashtable; +package io.github.dunwu.algorithm.hash; /* https://leetcode.com/problems/to-lower-case/ diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java new file mode 100644 index 0000000..f8868a7 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java @@ -0,0 +1,114 @@ +package io.github.dunwu.algorithm.linkedlist; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class ListNode { + + public int val; + public ListNode next; + + public ListNode(int val) { this.val = val; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListNode)) return false; + ListNode listNode = (ListNode) o; + return val == listNode.val && + Objects.equals(next, listNode.next); + } + + @Override + public int hashCode() { + return Objects.hash(val, next); + } + + public List toList() { + return ListNode.toList(this); + } + + public static ListNode createLinkedList(int[] arr) { + if (arr == null || arr.length == 0) { + return null; + } + ListNode head = new ListNode(arr[0]); + ListNode cur = head; + for (int i = 1; i < arr.length; i++) { + cur.next = new ListNode(arr[i]); + cur = cur.next; + } + return head; + } + + public static void addLast() { + + } + + public static ListNode buildList(int... list) { + ListNode head = new ListNode(-1); + ListNode node = head; + for (int val : list) { + node.next = new ListNode(val); + node = node.next; + } + return head.next; + } + + public static ListNode buildCycleList(int cyclePoint, int[] list) { + ListNode head = new ListNode(-1); + ListNode node = head; + ListNode cycleBeginNode = null; + for (int val : list) { + ListNode item = new ListNode(val); + if (cyclePoint == 0 && cycleBeginNode == null) { + cycleBeginNode = item; + } else { + cyclePoint--; + } + node.next = item; + node = node.next; + } + if (cycleBeginNode != null) { + node.next = cycleBeginNode; + } + return head.next; + } + + public static List toList(ListNode listNode) { + List list = new ArrayList<>(); + while (listNode != null) { + list.add(listNode.val); + listNode = listNode.next; + } + return list; + } + + public static void buildMetPot(ListNode listA, ListNode listB, int skipA, int skipB) { + ListNode pA = listA; + for (int i = 0; i < skipA; i++) { + pA = pA.next; + } + ListNode pB = listB; + for (int i = 0; i < skipB - 1; i++) { + pB = pB.next; + } + pB.next = pA; + } + + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + ListNode head = createLinkedList(arr); + ListNode p = head; + while (p.next != null) { + p = p.next; + } + p.next = new ListNode(6); + while (head != null) { + System.out.println(head.val); + head = head.next; + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" new file mode 100644 index 0000000..2afe676 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" @@ -0,0 +1,34 @@ +package io.github.dunwu.algorithm.linkedlist.base; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 二进制链表转整数 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 二进制链表转整数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.getDecimalValue(ListNode.buildList(1, 0, 1))); + Assertions.assertEquals(0, s.getDecimalValue(ListNode.buildList(0))); + } + + static class Solution { + + public int getDecimalValue(ListNode head) { + int res = 0; + ListNode p = head; + while (p != null) { + res = res * 2 + p.val; + p = p.next; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" new file mode 100644 index 0000000..97faea6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.linkedlist.base; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * LCR 136. 删除链表的节点 + * + * @author Zhang Peng + * @date 2025-12-18 + */ +public class 删除链表的节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(4, 5, 1), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 9)); + Assertions.assertEquals(ListNode.buildList(4, 1, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 5)); + Assertions.assertEquals(ListNode.buildList(4, 5, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 1)); + Assertions.assertEquals(ListNode.buildList(5, 1, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 4)); + } + + static class Solution { + + public ListNode deleteNode(ListNode head, int val) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode pre = dummy; + while (pre != null && pre.next != null) { + ListNode cur = pre.next; + if (cur.val == val) { + pre.next = cur.next; + pre = cur.next; + } else { + pre = pre.next; + } + } + return dummy.next; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java new file mode 100644 index 0000000..9b4c126 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java @@ -0,0 +1,230 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public class DoublyLinkedList { + + private int size = 0; + private Node first = null; + private Node last = null; + + public DoublyLinkedList() { + first = new Node<>(null); + last = new Node<>(null); + first.next = last; + last.prev = first; + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public int indexOf(E element) { + int pos = 0; + + Node p = first.next; + while (p != null) { + if (p.element.equals(element)) { + return pos; + } + p = p.next; + pos++; + } + return -1; + } + + public E get(int index) { + Node node = node(index); + return node == null ? null : node.element; + } + + Node node(int index) { + int i = 0; + + Node p = first; + while (p != null && i != index) { + p = p.next; + i++; + } + return p; + } + + public void addFirst(E element) { + + Node node = new Node<>(element); + Node temp = first.next; + + node.next = temp; + temp.prev = node; + + node.prev = first; + first.next = node; + + size++; + } + + public void addLast(E element) { + + Node node = new Node<>(element); + Node temp = last.prev; + + temp.next = node; + node.prev = temp; + + last.prev = node; + node.next = last; + + size++; + } + + public boolean add(int index, E element) { + + if (index == 0) { + addFirst(element); + return true; + } + + Node p = node(index); + Node node = new Node<>(element); + node.next = p.next; + p.next.prev = node; + + node.prev = p; + p.next = node; + + size++; + return true; + } + + public boolean remove(E e) { + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + Node x = p.next; + if (e.equals(x.element)) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } + return false; + } + + public boolean removeAll(E e) { + + if (first.next == null) { + return false; + } + + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + if (e.equals(p.next.element)) { + p.next = p.next.next; + size--; + } else { + p = p.next; + } + } + } + return true; + } + + public void clear() { + first.next = last; + last.prev = first; + size = 0; + } + + public void printAll() { + Node p = first; + while (p.next != null && p.next != last) { + p = p.next; + System.out.print(p.element + " "); + } + System.out.println(); + } + + public List toList() { + List list = new ArrayList<>(); + Node node = first.next; + while (node != null && node != last) { + list.add(node.element); + node = node.next; + } + return list; + } + + @Getter + @Setter + public static class Node { + + private E element; + private Node next; + private Node prev; + + public Node(E element) { + this.element = element; + this.next = null; + this.prev = null; + } + + public Node(E element, Node prev, Node next) { + this.element = element; + this.prev = prev; + this.next = next; + } + + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3, 4, 5 }; + DoublyLinkedList list = new DoublyLinkedList<>(); + DoublyLinkedList reverseList = new DoublyLinkedList<>(); + for (int num : nums) { + list.addLast(num); + reverseList.addFirst(num); + } + + list.add(5, 999); + System.out.println("【队尾写入链表】"); + list.printAll(); + System.out.println("【队头写入链表】"); + reverseList.printAll(); + list.printAll(); + System.out.println("999 在队列中的位置:" + list.indexOf(999)); + System.out.println("队列中位置 5 的元素值:" + list.get(5)); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBaseLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java similarity index 98% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBaseLinkedList.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java index 126fe30..de8e751 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBaseLinkedList.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.list; +package io.github.dunwu.algorithm.linkedlist.demo; import java.util.Scanner; diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBasedArray.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java similarity index 98% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBasedArray.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java index e7aa484..cd909e4 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/LRUBasedArray.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.list; +package io.github.dunwu.algorithm.linkedlist.demo; import java.util.HashMap; import java.util.Map; diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java new file mode 100644 index 0000000..be76a54 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java @@ -0,0 +1,244 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import java.util.NoSuchElementException; + +public class MyLinkedList { + + // 虚拟头尾节点 + private int size; + final private Node head, tail; + + // 双链表节点 + private static class Node { + + E data; + Node next; + Node prev; + + Node(E data) { + this.data = data; + } + + } + + // 构造函数初始化虚拟头尾节点 + public MyLinkedList() { + this.head = new Node<>(null); + this.tail = new Node<>(null); + head.next = tail; + tail.prev = head; + this.size = 0; + } + + // ***** 增 ***** + + public void addLast(T data) { + Node node = new Node<>(data); + Node temp = tail.prev; + // temp <-> x + temp.next = node; + node.prev = temp; + + node.next = tail; + tail.prev = node; + // temp <-> x <-> tail + size++; + } + + public void addFirst(T e) { + Node x = new Node<>(e); + Node temp = head.next; + // head <-> temp + temp.prev = x; + x.next = temp; + + head.next = x; + x.prev = head; + // head <-> x <-> temp + size++; + } + + public void add(int index, T element) { + checkPositionIndex(index); + if (index == size) { + addLast(element); + return; + } + + // 找到 index 对应的 Node + Node p = getNode(index); + Node temp = p.prev; + // temp <-> p + + // 新要插入的 Node + Node x = new Node<>(element); + + p.prev = x; + temp.next = x; + + x.prev = temp; + x.next = p; + + // temp <-> x <-> p + + size++; + } + + // ***** 删 ***** + + public T removeFirst() { + if (size < 1) { + throw new NoSuchElementException(); + } + // 虚拟节点的存在是我们不用考虑空指针的问题 + Node x = head.next; + Node temp = x.next; + // head <-> x <-> temp + head.next = temp; + temp.prev = head; + + x.prev = null; + x.next = null; + // head <-> temp + + size--; + return x.data; + } + + public T removeLast() { + if (size < 1) { + throw new NoSuchElementException(); + } + Node x = tail.prev; + Node temp = tail.prev.prev; + // temp <-> x <-> tail + + tail.prev = temp; + temp.next = tail; + + x.prev = null; + x.next = null; + // temp <-> tail + + size--; + return x.data; + } + + public T remove(int index) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node x = getNode(index); + Node prev = x.prev; + Node next = x.next; + // prev <-> x <-> next + prev.next = next; + next.prev = prev; + + x.prev = x.next = null; + // prev <-> next + + size--; + + return x.data; + } + + // ***** 查 ***** + + public T get(int index) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node p = getNode(index); + + return p.data; + } + + public T getFirst() { + if (size < 1) { + throw new NoSuchElementException(); + } + + return head.next.data; + } + + public T getLast() { + if (size < 1) { + throw new NoSuchElementException(); + } + + return tail.prev.data; + } + + // ***** 改 ***** + + public T set(int index, T val) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node p = getNode(index); + + T oldVal = p.data; + p.data = val; + + return oldVal; + } + + // ***** 其他工具函数 ***** + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + private Node getNode(int index) { + checkElementIndex(index); + Node p = head.next; + // TODO: 可以优化,通过 index 判断从 head 还是 tail 开始遍历 + for (int i = 0; i < index; i++) { + p = p.next; + } + return p; + } + + private boolean isElementIndex(int index) { + return index >= 0 && index < size; + } + + private boolean isPositionIndex(int index) { + return index >= 0 && index <= size; + } + + // 检查 index 索引位置是否可以存在元素 + private void checkElementIndex(int index) { + if (!isElementIndex(index)) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } + } + + // 检查 index 索引位置是否可以添加元素 + private void checkPositionIndex(int index) { + if (!isPositionIndex(index)) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } + } + + private void display() { + System.out.println("size = " + size); + for (Node p = head.next; p != tail; p = p.next) { + System.out.print(p.data + " <-> "); + } + System.out.println("null"); + System.out.println(); + } + + public static void main(String[] args) { + MyLinkedList list = new MyLinkedList<>(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.addFirst(0); + list.add(2, 100); + + list.display(); + // size = 5 + // 0 <-> 1 <-> 100 <-> 2 -> 3 -> null + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java new file mode 100644 index 0000000..d519205 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java @@ -0,0 +1,264 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public class SinglyLinkedList { + + private int size = 0; + private Node first = null; + + public SinglyLinkedList() { + this.size = 0; + this.first = new Node<>(null); + } + + public SinglyLinkedList(E[] elementArray) { + this.size = 0; + this.first = new Node<>(null); + for (E element : elementArray) { + addLast(element); + } + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public int indexOf(E element) { + int pos = 0; + Node p = first.next; + while (p != null) { + if (p.element.equals(element)) { + return pos; + } + p = p.next; + pos++; + } + return -1; + } + + public E get(int index) { + Node node = node(index); + return node == null ? null : node.element; + } + + Node node(int index) { + int i = 0; + Node p = first; + while (p != null && i != index) { + p = p.next; + i++; + } + return p; + } + + public void addFirst(E element) { + Node node = new Node<>(element); + node.next = first.next; + first.next = node; + size++; + } + + public void addLast(E element) { + Node node = new Node<>(element); + Node p = first; + while (p.next != null) { + p = p.next; + } + p.next = node; + size++; + } + + public boolean add(int index, E element) { + + checkPositionIndex(index); + + if (index == 0) { + addFirst(element); + return true; + } + + Node p = node(index - 1); + Node node = new Node<>(element); + node.next = p.next; + p.next = node; + size++; + return true; + } + + public void removeFirst() { + first = first.next; + size--; + } + + public void removeLast() { + Node p = first; + while (p.next.next != null) { + p = p.next; + } + p.next = null; + size--; + } + + public boolean remove(int index) { + + checkElementIndex(index); + + if (index == 0) { + removeFirst(); + } + + int pos = 0; + Node p = first; + while (pos < index - 1) { + p = p.next; + pos++; + } + p.next = p.next.next; + size--; + return true; + } + + public boolean remove(E e) { + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } else { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element.equals(e)) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } + return false; + } + + public boolean removeAll(E e) { + + if (first.next == null) { + return false; + } + + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + if (p.next.element.equals(e)) { + p.next = p.next.next; + size--; + } else { + p = p.next; + } + } + } + return true; + } + + public void clear() { + first.next = null; + size = 0; + } + + private void checkElementIndex(int index) { + if (index >= 0 && index < size) { return; } + throw new RuntimeException("超出边界!"); + } + + private void checkPositionIndex(int index) { + if (index >= 0 && index <= size) { return; } + throw new RuntimeException("超出边界!"); + } + + public void printAll() { + Node p = first.next; + while (p != null) { + System.out.print(p.element + " "); + p = p.next; + } + System.out.println(); + } + + public List toList() { + List list = new ArrayList<>(); + Node node = first.next; + while (node != null) { + list.add(node.element); + node = node.next; + } + return list; + } + + @Getter + @Setter + private static class Node { + + private E element; + private Node next; + + public Node(E element) { + this.element = element; + this.next = null; + } + + public Node(E element, Node next) { + this.element = element; + this.next = next; + } + + } + + public static void main(String[] args) { + + Integer[] nums = { 1, 2, 3, 4, 5 }; + SinglyLinkedList list = new SinglyLinkedList<>(nums); + SinglyLinkedList reverseList = new SinglyLinkedList<>(); + for (int num : nums) { + reverseList.addFirst(num); + } + + list.add(5, 999); + System.out.println("【队尾写入链表】"); + list.printAll(); + System.out.println("【队头写入链表】"); + reverseList.printAll(); + System.out.println("999 在队列中的位置:" + list.indexOf(999)); + System.out.println("队列中位置 5 的元素值:" + list.get(5)); + + System.out.println("【删除指定位置元素】"); + list.removeLast(); + list.printAll(); + list.remove(new Integer(999)); + list.printAll(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" similarity index 95% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" index 8bef8cf..b3ba9ec 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.list; +package io.github.dunwu.algorithm.linkedlist.demo; import java.util.ArrayList; import java.util.List; @@ -25,7 +25,7 @@ public class 单链表示例 { * * @param value 数据值 */ - public void addHead(E value) { + public void addFirst(E value) { ListNode newNode = new ListNode<>(value, null); newNode.next = this.head.next; this.head.next = newNode; @@ -36,7 +36,7 @@ public void addHead(E value) { * * @param value 数据值 */ - public void addTail(E value) { + public void addLast(E value) { // init new node ListNode newNode = new ListNode<>(value, null); @@ -67,13 +67,13 @@ public void remove(ListNode node) { * @param value 数据值 * @return {@link ListNode} */ - public ListNode removeFirst(E value) { + public E removeFirst(E value) { ListNode prev = this.head; while (prev.next != null) { ListNode curr = prev.next; if (curr.value.equals(value)) { prev.next = curr.next; - return curr; + return curr.value; } prev = prev.next; } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" similarity index 98% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" index 02ede0a..f6db8af 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.list; +package io.github.dunwu.algorithm.linkedlist.demo; /** * @author Zhang Peng diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..9c6a30e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.linkedlist.divide; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 148.排序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 排序链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 4), s.sortList(ListNode.buildList(4, 2, 1, 3))); + Assertions.assertEquals(ListNode.buildList(-1, 0, 3, 4, 5), s.sortList(ListNode.buildList(-1, 5, 3, 4, 0))); + Assertions.assertEquals(ListNode.buildList(), s.sortList(ListNode.buildList())); + } + + public static class Solution { + + public ListNode sortList(ListNode head) { + // 如果链表为空或者只有一个节点,无需排序 + if (head == null || head.next == null) { + return head; + } + // 找到中间节点 head2,并断开 head2 与其前一个节点的连接 + // 比如 head=[4,2,1,3],那么 middleNode 调用结束后 head=[4,2] head2=[1,3] + ListNode mid = middleNode(head); + // 分治 + head = sortList(head); + mid = sortList(mid); + // 合并 + return mergeTwoLists(head, mid); + } + + // 876. 链表的中间结点(快慢指针) + private ListNode middleNode(ListNode head) { + ListNode pre = head; + ListNode slow = head; + ListNode fast = head; + while (fast != null && fast.next != null) { + pre = slow; // 记录 slow 的前一个节点 + slow = slow.next; + fast = fast.next.next; + } + pre.next = null; // 断开 slow 的前一个节点和 slow 的连接 + return slow; + } + + // 21. 合并两个有序链表(双指针) + private ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode dummy = new ListNode(-1); // 用哨兵节点简化代码逻辑 + ListNode cur = dummy; // cur 指向新链表的末尾 + while (list1 != null && list2 != null) { + if (list1.val < list2.val) { + cur.next = list1; // 把 list1 加到新链表中 + list1 = list1.next; + } else { // 注:相等的情况加哪个节点都是可以的 + cur.next = list2; // 把 list2 加到新链表中 + list2 = list2.next; + } + cur = cur.next; + } + cur.next = list1 != null ? list1 : list2; // 拼接剩余链表 + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" new file mode 100644 index 0000000..dd4a500 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.linkedlist.palindrome; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 234. 回文链表 + * 面试题 02.06. 回文链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 回文链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 2, 1); + Assertions.assertTrue(s.isPalindrome(input)); + ListNode input2 = ListNode.buildList(1, 2); + Assertions.assertFalse(s.isPalindrome(input2)); + } + + static class Solution { + + public boolean isPalindrome(ListNode head) { + List list = new ArrayList<>(); + ListNode p = head; + while (p != null) { + list.add(p.val); + p = p.next; + } + + int left = 0, right = list.size() - 1; + while (left < right) { + if (!list.get(left).equals(list.get(right))) { + return false; + } + left++; + right--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..18143c9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 25. K 个一组翻转链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class K个一组翻转链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode output = s.reverseKGroup(ListNode.buildList(1, 2, 3, 4, 5), 2); + Assertions.assertEquals(ListNode.buildList(2, 1, 4, 3, 5), output); + ListNode output2 = s.reverseKGroup(ListNode.buildList(1, 2, 3, 4, 5), 3); + Assertions.assertEquals(ListNode.buildList(3, 2, 1, 4, 5), output2); + } + + static class Solution { + + public ListNode reverseKGroup(ListNode head, int k) { + if (head == null || head.next == null) { return head; } + ListNode p = head; + for (int i = 0; i < k; i++) { + if (p == null) { return head; } + p = p.next; + } + ListNode newHead = reverseN(head, k); + head.next = reverseKGroup(p, k); + return newHead; + } + + public ListNode reverseN(ListNode head, int len) { + if (head == null) { return null; } + ListNode pre = null, cur = head; + for (int i = 0; i < len; i++) { + if (cur == null) { break; } + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + head.next = cur; + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..ac54c4c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 206. 反转链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 反转链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode result = s.reverseList(ListNode.buildList(1, 2, 3, 4)); + Assertions.assertEquals(ListNode.buildList(4, 3, 2, 1), result); + + ListNode result2 = s.reverseList(ListNode.buildList(1, 2)); + Assertions.assertEquals(ListNode.buildList(2, 1), result2); + + ListNode result3 = s.reverseList(ListNode.buildList()); + Assertions.assertEquals(ListNode.buildList(), result3); + } + + static class Solution { + + public ListNode reverseList(ListNode head) { + if (head == null || head.next == null) { return head; } + ListNode pre = null, cur = head; + while (cur != null) { + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" new file mode 100644 index 0000000..74aca5d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 92. 反转链表 II + * + * @author Zhang Peng + * @date 2025-01-20 + */ +public class 反转链表2 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode result = s.reverseBetween(ListNode.buildList(1, 2, 3, 4, 5), 2, 4); + Assertions.assertEquals(ListNode.buildList(1, 4, 3, 2, 5), result); + ListNode result2 = s.reverseBetween(ListNode.buildList(3, 5), 1, 2); + Assertions.assertEquals(ListNode.buildList(5, 3), result2); + } + + static class Solution { + + public ListNode reverseBetween(ListNode head, int m, int n) { + if (m == 1) { + return reverseN(head, n); + } + // 找到第 m 个节点的前驱 + ListNode pre = head; + for (int i = 1; i < m - 1; i++) { + pre = pre.next; + } + // 从第 m 个节点开始反转 + pre.next = reverseN(pre.next, n - m + 1); + return head; + } + + ListNode reverseN(ListNode head, int n) { + if (head == null || head.next == null) { + return head; + } + ListNode pre, cur, nxt; + pre = null; + cur = head; + nxt = head.next; + while (n > 0) { + cur.next = pre; + pre = cur; + cur = nxt; + if (nxt != null) { + nxt = nxt.next; + } + n--; + } + // 此时的 cur 是第 n + 1 个节点,head 是反转后的尾结点 + head.next = cur; + + // 此时的 pre 是反转后的头结点 + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..a27b82a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 61. 旋转链表 + * + * @author Zhang Peng + * @since 2025-11-20 + */ +public class 旋转链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + + ListNode input = ListNode.buildList(1, 2, 3, 4, 5); + ListNode output = s.rotateRight(input, 2); + Assertions.assertEquals(ListNode.buildList(4, 5, 1, 2, 3), output); + + ListNode input2 = ListNode.buildList(0, 1, 2); + ListNode output2 = s.rotateRight(input2, 4); + Assertions.assertEquals(ListNode.buildList(2, 0, 1), output2); + + ListNode input3 = ListNode.buildList(1, 2); + ListNode output3 = s.rotateRight(input3, 1); + Assertions.assertEquals(ListNode.buildList(2, 1), output3); + + ListNode input4 = ListNode.buildList(1, 2); + ListNode output4 = s.rotateRight(input4, 3); + Assertions.assertEquals(ListNode.buildList(2, 1), output4); + } + + static class Solution { + + public ListNode rotateRight(ListNode head, int k) { + if (head == null || head.next == null) { + return head; + } + + ListNode dummy = new ListNode(-1); + dummy.next = head; + + ListNode newLast = lastFromEnd(head, k + 1); + ListNode last = newLast; + while (last.next != null) { + last = last.next; + } + + last.next = head; + dummy.next = newLast.next; + newLast.next = null; + + return dummy.next; + } + + public ListNode lastFromEnd(ListNode head, int k) { + + if (head == null || head.next == null) { + return null; + } + + int i = 0; + ListNode slow = head, fast = head; + while (i < k) { + i++; + if (fast == null) { + fast = head; + } + fast = fast.next; + } + + // fast 先走 k 步后,slow 从 head 开始出发,当 fast 到底,slow 正好是倒数第 k 个节点 + while (fast != null) { + slow = slow.next; + fast = fast.next; + } + return slow; + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" new file mode 100644 index 0000000..69de0e0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 2. 两数相加 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 两数相加 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode output1 = s.addTwoNumbers(ListNode.buildList(2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(7, 0, 8), output1); + + ListNode output2 = s.addTwoNumbers(ListNode.buildList(0), ListNode.buildList(0)); + Assertions.assertEquals(ListNode.buildList(0), output2); + + ListNode output3 = s.addTwoNumbers(ListNode.buildList(9, 9, 9, 9, 9, 9, 9), ListNode.buildList(9, 9, 9, 9)); + Assertions.assertEquals(ListNode.buildList(8, 9, 9, 9, 0, 0, 0, 1), output3); + } + + static class Solution { + + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + // 在两条链表上的指针 + ListNode p1 = l1, p2 = l2; + // 虚拟头结点(构建新链表时的常用技巧) + ListNode dummy = new ListNode(-1); + // 指针 p 负责构建新链表 + ListNode p = dummy; + // 记录进位 + int carry = 0; + // 开始执行加法,两条链表走完且没有进位时才能结束循环 + while (p1 != null || p2 != null || carry > 0) { + + int val = 0; + if (p1 != null) { + val += p1.val; + p1 = p1.next; + } + if (p2 != null) { + val += p2.val; + p2 = p2.next; + } + if (carry > 0) { + val += carry; + } + + carry = val / 10; + val = val % 10; + p.next = new ListNode(val); + p = p.next; + } + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" new file mode 100644 index 0000000..445b9d7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 445. 两数相加 II + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 两数相加2 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode result = s.addTwoNumbers(ListNode.buildList(7, 2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(7, 8, 0, 7), result); + ListNode result2 = s.addTwoNumbers(ListNode.buildList(2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(8, 0, 7), result2); + ListNode result3 = s.addTwoNumbers(ListNode.buildList(0), ListNode.buildList(0)); + Assertions.assertEquals(ListNode.buildList(0), result3); + } + + public static class Solution { + + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + ListNode r1 = reverse(l1); + ListNode r2 = reverse(l2); + ListNode res = doAddTwoNumbers(r1, r2); + return reverse(res); + } + + public ListNode reverse(ListNode head) { + ListNode pre = null, cur = head; + while (cur != null) { + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } + + public ListNode doAddTwoNumbers(ListNode l1, ListNode l2) { + // 在两条链表上的指针 + ListNode p1 = l1, p2 = l2; + // 虚拟头结点(构建新链表时的常用技巧) + ListNode dummy = new ListNode(-1); + // 指针 p 负责构建新链表 + ListNode p = dummy; + // 记录进位 + int carry = 0; + // 开始执行加法,两条链表走完且没有进位时才能结束循环 + while (p1 != null || p2 != null || carry > 0) { + + int val = 0; + if (p1 != null) { + val += p1.val; + p1 = p1.next; + } + if (p2 != null) { + val += p2.val; + p2 = p2.next; + } + if (carry > 0) { + val += carry; + } + + carry = val / 10; + val = val % 10; + p.next = new ListNode(val); + p = p.next; + } + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" new file mode 100644 index 0000000..2ab09c3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" @@ -0,0 +1,101 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 86. 分隔链表 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 分隔链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + Assertions.assertEquals(ListNode.buildList(1, 2, 2, 4, 3, 5), + s.partition(ListNode.buildList(1, 4, 3, 2, 5, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 2), s.partition(ListNode.buildList(2, 1), 2)); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s.partition(ListNode.buildList(3, 1, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 0, 4, 3, 5, 2), + s.partition(ListNode.buildList(1, 4, 3, 0, 5, 2), 2)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(ListNode.buildList(1, 2, 2, 4, 3, 5), + s2.partition(ListNode.buildList(1, 4, 3, 2, 5, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 2), s2.partition(ListNode.buildList(2, 1), 2)); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s2.partition(ListNode.buildList(3, 1, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 0, 4, 3, 5, 2), + s2.partition(ListNode.buildList(1, 4, 3, 0, 5, 2), 2)); + } + + static class Solution { + + public ListNode partition(ListNode head, int x) { + + if (head == null) { return null; } + + ListNode dummy = new ListNode(-1); + dummy.next = head; + + // 找到大于等于 x 的节点的前一个节点 + ListNode l = dummy, r = dummy.next; + while (r != null) { + + while (l.next != null && l.next.val < x) { + l = l.next; + } + if (l.next == null) { + break; + } + + r = l.next; + while (r.next != null && r.next.val >= x) { + r = r.next; + } + if (r.next == null) { + break; + } + + // 替换节点 + ListNode tmp = r.next; + r.next = tmp.next; + tmp.next = l.next; + l.next = tmp; + + l = l.next; + r = r.next; + } + + return dummy.next; + } + + } + + static class Solution2 { + + public ListNode partition(ListNode head, int x) { + ListNode dummy1 = new ListNode(-1); + ListNode dummy2 = new ListNode(-1); + + ListNode d1 = dummy1, d2 = dummy2, p = head; + while (p != null) { + if (p.val < x) { + d1.next = p; + d1 = d1.next; + } else { + d2.next = p; + d2 = d2.next; + } + p = p.next; + } + d2.next = null; + d1.next = dummy2.next; + return dummy1.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" new file mode 100644 index 0000000..8d17469 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 83. 删除排序链表中的重复元素 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除排序链表中的重复元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 1, 2); + Assertions.assertEquals(ListNode.buildList(1, 2), s.deleteDuplicates(input)); + ListNode input2 = ListNode.buildList(1, 1, 2, 3, 3); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s.deleteDuplicates(input2)); + } + + static class Solution { + + public ListNode deleteDuplicates(ListNode head) { + ListNode pre = head, cur = head.next; + while (cur != null) { + pre.next = cur.next; + pre = cur; + cur = cur.next; + } + return head; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..94cc3af --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 82. 删除排序链表中的重复元素 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除排序链表中的重复元素2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input = ListNode.buildList(1, 2, 3, 3, 4, 4, 5); + Assertions.assertEquals(ListNode.buildList(1, 2, 5), s.deleteDuplicates(input)); + + ListNode input2 = ListNode.buildList(1, 1, 1, 2, 3); + Assertions.assertEquals(ListNode.buildList(2, 3), s.deleteDuplicates(input2)); + + ListNode input3 = ListNode.buildList(1, 2, 2); + Assertions.assertEquals(ListNode.buildList(1), s.deleteDuplicates(input3)); + } + + public static class Solution { + + public ListNode deleteDuplicates(ListNode head) { + // 将原链表分解为两条链表 + // 一条链表存放不重复的节点,另一条链表存放重复的节点 + // 运用虚拟头结点技巧,题目说了 node.val <= 100,所以用 101 作为虚拟头结点 + ListNode dummyUniq = new ListNode(101); + ListNode dummyDup = new ListNode(101); + + ListNode pUniq = dummyUniq, pDup = dummyDup; + ListNode p = head; + + while (p != null) { + if ((p.next != null && p.val == p.next.val) || p.val == pDup.val) { + // 发现重复节点,接到重复链表后面 + pDup.next = p; + pDup = pDup.next; + } else { + // 不是重复节点,接到不重复链表后面 + pUniq.next = p; + pUniq = pUniq.next; + } + + p = p.next; + // 将原链表和新链表断开 + pUniq.next = null; + pDup.next = null; + } + + return dummyUniq.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" new file mode 100644 index 0000000..d3e6e42 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 19. 删除链表的倒数第 N 个结点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除链表的倒数第N个结点 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input1 = ListNode.buildList(1, 2, 3, 4, 5); + ListNode output1 = s.removeNthFromEnd(input1, 2); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 5), output1); + + ListNode input2 = ListNode.buildList(1); + ListNode output2 = s.removeNthFromEnd(input2, 1); + Assertions.assertEquals(ListNode.buildList(), output2); + + ListNode input3 = ListNode.buildList(1, 2); + ListNode output3 = s.removeNthFromEnd(input3, 1); + Assertions.assertEquals(ListNode.buildList(1), output3); + } + + static class Solution { + + public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode node = findFromEnd(dummy, n + 1); + node.next = node.next.next; + return dummy.next; + } + + public ListNode findFromEnd(ListNode head, int k) { + ListNode p1 = head; + // p1 先走 k 步 + for (int i = 0; i < k; i++) { + p1 = p1.next; + } + ListNode p2 = head; + // p1 和 p2 同时走 n - k 步 + while (p1 != null) { + p2 = p2.next; + p1 = p1.next; + } + // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点 + return p2; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..419f5e2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 23. 合并 K 个升序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 合并K个升序链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode head1 = ListNode.buildList(1, 4, 5); + ListNode head2 = ListNode.buildList(1, 3, 4); + ListNode head3 = ListNode.buildList(2, 6); + ListNode result = s.mergeKLists(new ListNode[] { head1, head2, head3 }); + Assertions.assertEquals(ListNode.buildList(1, 1, 2, 3, 4, 4, 5, 6), result); + + ListNode[] array2 = new ListNode[] {}; + ListNode result2 = s.mergeKLists(array2); + Assertions.assertEquals(ListNode.buildList(), result2); + } + + static class Solution { + + public ListNode mergeKLists(ListNode[] lists) { + if (lists == null || lists.length == 0) return null; + ListNode l1 = lists[0]; + for (int i = 1; i < lists.length; i++) { + ListNode l2 = lists[i]; + l1 = mergeTwoLists(l1, l2); + } + return l1; + } + + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + // 虚拟头结点 + ListNode dummy = new ListNode(-1), p = dummy; + ListNode p1 = l1, p2 = l2; + + while (p1 != null && p2 != null) { + // 比较 p1 和 p2 两个指针 + // 将值较小的的节点接到 p 指针 + if (p1.val > p2.val) { + p.next = p2; + p2 = p2.next; + } else { + p.next = p1; + p1 = p1.next; + } + // p 指针不断前进 + p = p.next; + } + + if (p1 != null) { p.next = p1; } + if (p2 != null) { p.next = p2; } + + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..8da3bd1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 21. 合并两个有序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 合并两个有序链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode h1 = ListNode.buildList(1, 2, 4); + ListNode h2 = ListNode.buildList(1, 3, 4); + ListNode result = s.mergeTwoLists(h1, h2); + Assertions.assertEquals(ListNode.buildList(1, 1, 2, 3, 4, 4), result); + } + + static class Solution { + + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + // 虚拟头结点 + ListNode dummy = new ListNode(-1), p = dummy; + ListNode p1 = l1, p2 = l2; + + while (p1 != null && p2 != null) { + // 比较 p1 和 p2 两个指针 + // 将值较小的的节点接到 p 指针 + if (p1.val > p2.val) { + p.next = p2; + p2 = p2.next; + } else { + p.next = p1; + p1 = p1.next; + } + // p 指针不断前进 + p = p.next; + } + + if (p1 != null) { p.next = p1; } + if (p2 != null) { p.next = p2; } + + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" new file mode 100644 index 0000000..5d3c5be --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 328. 奇偶链表 + * + * @author Zhang Peng + * @since 2020-07-08 + */ +public class 奇偶链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 3, 5, 2, 4), s.oddEvenList(ListNode.buildList(1, 2, 3, 4, 5))); + Assertions.assertEquals(ListNode.buildList(2, 3, 6, 7, 1, 5, 4), + s.oddEvenList(ListNode.buildList(2, 1, 3, 5, 6, 4, 7))); + } + + static class Solution { + + public ListNode oddEvenList(ListNode head) { + ListNode oddDummy = new ListNode(-1); + ListNode evenDummy = new ListNode(-1); + ListNode p = head, o = oddDummy, e = evenDummy; + int i = 1; + while (p != null) { + if (i % 2 == 0) { + e.next = p; + e = e.next; + } else { + o.next = p; + o = o.next; + } + p = p.next; + i++; + } + e.next = null; + o.next = evenDummy.next; + return oddDummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" new file mode 100644 index 0000000..f73021e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 141. 环形链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 环形链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode head = ListNode.buildList(3, 2, 0, -4); + Assertions.assertFalse(s.hasCycle(head)); + + ListNode head2 = ListNode.buildCycleList(1, new int[] { 3, 2, 0, -4 }); + Assertions.assertTrue(s.hasCycle(head2)); + + ListNode head3 = ListNode.buildCycleList(0, new int[] { 1, 2 }); + Assertions.assertTrue(s.hasCycle(head3)); + + ListNode head4 = ListNode.buildCycleList(1, new int[] { 1 }); + Assertions.assertFalse(s.hasCycle(head4)); + } + + static class Solution { + + public boolean hasCycle(ListNode head) { + // 快慢指针初始化指向 head + ListNode slow = head, fast = head; + // 快指针走到末尾时停止 + while (fast != null && fast.next != null) { + // 慢指针走一步,快指针走两步 + slow = slow.next; + fast = fast.next.next; + // 快慢指针相遇,说明含有环 + if (slow == fast) { + return true; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" new file mode 100644 index 0000000..1c6abd9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 142. 环形链表 II + * + * @author Zhang Peng + * @since 2020-07-08 + */ +public class 环形链表2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input = ListNode.buildList(3, 2, 0, -4); + Assertions.assertNull(s.detectCycle(input)); + + ListNode input2 = ListNode.buildList(1); + Assertions.assertNull(s.detectCycle(input2)); + + ListNode input3 = ListNode.buildCycleList(1, new int[] { 3, 2, 0, -4 }); + Assertions.assertEquals(2, s.detectCycle(input3).val); + + ListNode input4 = ListNode.buildCycleList(0, new int[] { 1, 2 }); + Assertions.assertEquals(1, s.detectCycle(input4).val); + } + + static class Solution { + + public ListNode detectCycle(ListNode head) { + ListNode fast, slow; + fast = slow = head; + while (fast != null && fast.next != null) { + fast = fast.next.next; + slow = slow.next; + if (fast == slow) break; + } + + // fast 遇到空指针说明没有环 + if (fast == null || fast.next == null) { + return null; + } + + // 重新指向头结点 + slow = head; + + // 快慢指针同步前进,相交点就是环起点 + while (slow != fast) { + fast = fast.next; + slow = slow.next; + } + return slow; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" new file mode 100644 index 0000000..e222dfe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 相交链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 相交链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode listA = ListNode.buildList(4, 1, 8, 4, 5); + ListNode listB = ListNode.buildList(5, 6, 1, 8, 4, 5); + ListNode.buildMetPot(listA, listB, 2, 3); + ListNode result = s.getIntersectionNode(listA, listB); + Assertions.assertEquals(8, result.val); + + ListNode listA2 = ListNode.buildList(1, 9, 1, 2, 4); + ListNode listB2 = ListNode.buildList(3, 2, 4); + ListNode.buildMetPot(listA2, listB2, 3, 1); + ListNode result2 = s.getIntersectionNode(listA2, listB2); + Assertions.assertEquals(2, result2.val); + + ListNode listA3 = ListNode.buildList(2, 6, 4); + ListNode listB3 = ListNode.buildList(1, 5); + ListNode result3 = s.getIntersectionNode(listA3, listB3); + Assertions.assertNull(result3); + } + + static class Solution { + + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + // pA 指向 A 链表头结点,pB 指向 B 链表头结点 + ListNode pA = headA, pB = headB; + while (pA != pB) { + // pA 走一步,如果走到 A 链表末尾,转到 B 链表 + if (pA == null) { + pA = headB; + } else { + pA = pA.next; + } + // pB 走一步,如果走到 B 链表末尾,转到 A 链表 + if (pB == null) { + pB = headA; + } else { + pB = pB.next; + } + } + return pA; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" new file mode 100644 index 0000000..0bba21c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * 面试题 02.01. 移除重复节点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 移除重复节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), + s.removeDuplicateNodes(ListNode.buildList(1, 2, 3, 3, 2, 1))); + Assertions.assertEquals(ListNode.buildList(1, 2), + s.removeDuplicateNodes(ListNode.buildList(1, 1, 1, 1, 2))); + } + + static class Solution { + + public ListNode removeDuplicateNodes(ListNode head) { + Set set = new HashSet<>(); + ListNode dummy = new ListNode(-1); + ListNode p = head, n = dummy; + while (p != null) { + if (!set.contains(p.val)) { + n.next = p; + n = n.next; + set.add(p.val); + } + p = p.next; + } + n.next = null; + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" new file mode 100644 index 0000000..6cb6000 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 203. 移除链表元素 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 移除链表元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 4, 5), + s.removeElements(ListNode.buildList(1, 2, 6, 3, 4, 5, 6), 6)); + Assertions.assertEquals(ListNode.buildList(), s.removeElements(ListNode.buildList(), 1)); + Assertions.assertEquals(ListNode.buildList(), s.removeElements(ListNode.buildList(7, 7, 7, 7, 7), 7)); + } + + static class Solution { + + public ListNode removeElements(ListNode head, int val) { + ListNode dummy = new ListNode(0); + ListNode p = head, q = dummy; + while (p != null) { + if (p.val != val) { + q.next = p; + q = q.next; + } + p = p.next; + } + q.next = null; + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" new file mode 100644 index 0000000..808ba2b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 面试题 02. 返回倒数第 k 个节点 + * LCR 140. 训练计划 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 返回倒数第k个节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.kthToLast(ListNode.buildList(1, 2, 3, 4, 5), 2)); + Assertions.assertEquals(1, s.kthToLast(ListNode.buildList(1), 1)); + } + + static class Solution { + + public int kthToLast(ListNode head, int k) { + ListNode slow = head, fast = head; + for (int i = 0; i < k && fast != null; i++) { + fast = fast.next; + } + while (fast != null) { + fast = fast.next; + slow = slow.next; + } + return slow == null ? -1 : slow.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" new file mode 100644 index 0000000..f93a67c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" @@ -0,0 +1,35 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 876. 链表的中间结点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 链表的中间结点 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 3, 4, 5); + Assertions.assertEquals(ListNode.buildList(3, 4, 5), s.middleNode(input)); + ListNode input2 = ListNode.buildList(1, 2, 3, 4, 5, 6); + Assertions.assertEquals(ListNode.buildList(4, 5, 6), s.middleNode(input2)); + } + + static class Solution { + + public ListNode middleNode(ListNode head) { + ListNode slow = head, fast = head; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListNode.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListNode.java deleted file mode 100644 index 2096ad3..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import java.util.Objects; - -public final class ListNode { - - int val; - ListNode next; - - ListNode(int val) { this.val = val; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ListNode)) return false; - ListNode listNode = (ListNode) o; - return val == listNode.val && - Objects.equals(next, listNode.next); - } - - @Override - public int hashCode() { - return Objects.hash(val, next); - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListUtil.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListUtil.java deleted file mode 100644 index 8694c09..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/ListUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class ListUtil { - - private ListUtil() {} - - public static ListNode buildList(int... list) { - ListNode head = new ListNode(-1); - ListNode node = head; - for (int val : list) { - node.next = new ListNode(val); - node = node.next; - } - return head.next; - } - - public static List toList(ListNode result) { - List list = new ArrayList<>(); - while (result != null) { - list.add(result.val); - result = result.next; - } - return list; - } - - public static List getValues(ListNode listNode) { - List list = new ArrayList<>(); - ListNode item = listNode; - while (item != null) { - list.add(item.val); - item = item.next; - } - return list; - } - - public static ListNode buildCycleList(int pos, int[] list) { - ListNode head = new ListNode(-1); - ListNode node = head; - ListNode cycleBeginNode = null; - for (int val : list) { - ListNode item = new ListNode(val); - if (pos == 0) { - cycleBeginNode = item; - } else { - pos--; - } - node.next = item; - node = node.next; - } - if (cycleBeginNode != null) { - node.next = cycleBeginNode; - } - return head.next; - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/SinglyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/SinglyLinkedList.java deleted file mode 100644 index 081fdfd..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/SinglyLinkedList.java +++ /dev/null @@ -1,334 +0,0 @@ -package io.github.dunwu.algorithm.list; - -/** - * 1)单链表的插入、删除、查找操作; 2)链表中存储的是int类型的数据; - *

- * Author:Zheng - */ -public class SinglyLinkedList { - - private Node head = null; - - public Node findByValue(int value) { - Node p = head; - while (p != null && p.data != value) { - p = p.next; - } - - return p; - } - - public Node findByIndex(int index) { - Node p = head; - int pos = 0; - while (p != null && pos != index) { - p = p.next; - ++pos; - } - - return p; - } - - //无头结点 - //表头部插入 - //这种操作将于输入的顺序相反,逆序 - public void insertToHead(int value) { - Node newNode = new Node(value, null); - insertToHead(newNode); - } - - public void insertToHead(Node newNode) { - if (head == null) { - head = newNode; - } else { - newNode.next = head; - head = newNode; - } - } - - //顺序插入 - //链表尾部插入 - public void insertTail(int value) { - - Node newNode = new Node(value, null); - //空链表,可以插入新节点作为head,也可以不操作 - if (head == null) { - head = newNode; - } else { - Node q = head; - while (q.next != null) { - q = q.next; - } - newNode.next = q.next; - q.next = newNode; - } - } - - public void insertAfter(Node p, int value) { - Node newNode = new Node(value, null); - insertAfter(p, newNode); - } - - public void insertAfter(Node p, Node newNode) { - if (p == null) return; - - newNode.next = p.next; - p.next = newNode; - } - - public void insertBefore(Node p, int value) { - Node newNode = new Node(value, null); - insertBefore(p, newNode); - } - - public void insertBefore(Node p, Node newNode) { - if (p == null) return; - if (head == p) { - insertToHead(newNode); - return; - } - - Node q = head; - while (q != null && q.next != p) { - q = q.next; - } - - if (q == null) { - return; - } - - newNode.next = p; - q.next = newNode; - } - - public void deleteByNode(Node p) { - if (p == null || head == null) return; - - if (p == head) { - head = head.next; - return; - } - - Node q = head; - while (q != null && q.next != p) { - q = q.next; - } - - if (q == null) { - return; - } - - q.next = q.next.next; - } - - public void deleteByValue(int value) { - if (head == null) return; - - Node p = head; - Node q = null; - while (p != null && p.data != value) { - q = p; - p = p.next; - } - - if (p == null) return; - - if (q == null) { - head = head.next; - } else { - q.next = q.next.next; - } - - // 可重复删除指定value的代码 - /* - if (head != null && head.data == value) { - head = head.next; - } - - Node pNode = head; - while (pNode != null) { - if (pNode.next.data == data) { - pNode.next = pNode.next.next; - continue; - } - pNode = pNode.next; - } - */ - } - - public void printAll() { - Node p = head; - while (p != null) { - System.out.print(p.data + " "); - p = p.next; - } - System.out.println(); - } - - //判断true or false - public boolean TFResult(Node left, Node right) { - Node l = left; - Node r = right; - - boolean flag = true; - System.out.println("left_:" + l.data); - System.out.println("right_:" + r.data); - while (l != null && r != null) { - if (l.data == r.data) { - l = l.next; - r = r.next; - continue; - } else { - flag = false; - break; - } - } - - System.out.println("什么结果"); - return flag; - /* if (l==null && r==null){ - System.out.println("什么结果"); - return true; - }else{ - return false; - }*/ - } - // 判断是否为回文 - - public boolean palindrome() { - if (head == null) { - return false; - } else { - System.out.println("开始执行找到中间节点"); - Node p = head; - Node q = head; - if (p.next == null) { - System.out.println("只有一个元素"); - return true; - } - while (q.next != null && q.next.next != null) { - p = p.next; - q = q.next.next; - } - - System.out.println("中间节点" + p.data); - System.out.println("开始执行奇数节点的回文判断"); - Node leftLink = null; - Node rightLink = null; - if (q.next == null) { - // p 一定为整个链表的中点,且节点数目为奇数 - rightLink = p.next; - leftLink = inverseLinkList(p).next; - System.out.println("左边第一个节点" + leftLink.data); - System.out.println("右边第一个节点" + rightLink.data); - } else { - //p q 均为中点 - rightLink = p.next; - leftLink = inverseLinkList(p); - } - return TFResult(leftLink, rightLink); - } - } - - //带结点的链表翻转 - public Node inverseLinkList_head(Node p) { - // Head 为新建的一个头结点 - Node Head = new Node(9999, null); - // p 为原来整个链表的头结点,现在Head指向 整个链表 - Head.next = p; - /* - 带头结点的链表翻转等价于 - 从第二个元素开始重新头插法建立链表 - */ - Node Cur = p.next; - p.next = null; - Node next = null; - - while (Cur != null) { - next = Cur.next; - Cur.next = Head.next; - Head.next = Cur; - System.out.println("first " + Head.data); - - Cur = next; - } - - // 返回左半部分的中点之前的那个节点 - // 从此处开始同步像两边比较 - return Head; - } - - //无头结点的链表翻转 - public Node inverseLinkList(Node p) { - - Node pre = null; - Node r = head; - System.out.println("z---" + r.data); - Node next = null; - while (r != p) { - next = r.next; - - r.next = pre; - pre = r; - r = next; - } - - r.next = pre; - // 返回左半部分的中点之前的那个节点 - // 从此处开始同步像两边比较 - return r; - } - - public static Node createNode(int value) { - return new Node(value, null); - } - - public static class Node { - - private int data; - private Node next; - - public Node(int data, Node next) { - this.data = data; - this.next = next; - } - - public int getData() { - return data; - } - - } - - public static void main(String[] args) { - - SinglyLinkedList link = new SinglyLinkedList(); - System.out.println("hello"); - //int data[] = {1}; - //int data[] = {1,2}; - //int data[] = {1,2,3,1}; - //int data[] = {1,2,5}; - //int data[] = {1,2,2,1}; - // int data[] = {1,2,5,2,1}; - int data[] = { 1, 2, 5, 3, 1 }; - - for (int i = 0; i < data.length; i++) { - //link.insertToHead(data[i]); - link.insertTail(data[i]); - } - // link.printAll(); - // Node p = link.inverseLinkList_head(link.head); - // while(p != null){ - // System.out.println("aa"+p.data); - // p = p.next; - // } - - System.out.println("打印原始:"); - link.printAll(); - if (link.palindrome()) { - System.out.println("回文"); - } else { - System.out.println("不是回文"); - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\270\244\346\225\260\347\233\270\345\212\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\270\244\346\225\260\347\233\270\345\212\240.java" deleted file mode 100644 index d48687a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\270\244\346\225\260\347\233\270\345\212\240.java" +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 两数相加 { - - public static void main(String[] args) { - ListNode head1 = ListUtil.buildList(2, 4, 3); - ListNode head2 = ListUtil.buildList(5, 6, 4); - ListNode result = addTwoNumbers(head1, head2); - ListUtil.toList(head1); - ListUtil.toList(head2); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 7, 0, 8 }, list.toArray()); - - head1 = new ListNode(1); - head2 = ListUtil.buildList(9, 9); - result = addTwoNumbers(head1, head2); - ListUtil.toList(head1); - ListUtil.toList(head2); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 0, 0, 1 }, list.toArray()); - } - - /** - * 两数相加 算法实现 - *

- * 给出两个 非空 的链表用来表示两个非负的整数。 - *

- * 其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 - *

- * 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 - *

- * 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 - *

- * 示例: - *

-     * 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
-     * 输出:7 -> 0 -> 8
-     * 原因:342 + 465 = 807
-     * 
- * - * @see 两数相加 - * @see 面试题 02.05. 链表求和 - */ - public static ListNode addTwoNumbers(ListNode l1, ListNode l2) { - - // 如果任意一个表示数的链表为空,直接返回另一个链表 - if (l1 == null) return l2; - if (l2 == null) return l1; - - // 初始化 - int carry = 0; - ListNode x = l1; - ListNode y = l2; - ListNode resultHead = new ListNode(-1); - ListNode currNode = resultHead; - - // 同时遍历两个操作数链表,任意操作数链表的当前位数所对应元素不为 null 则累加 - while (x != null || y != null) { - int value = carry; - - if (x != null) { - value += x.val; - x = x.next; - } - - if (y != null) { - value += y.val; - y = y.next; - } - - carry = value / 10; - currNode.next = new ListNode(value % 10); - currNode = currNode.next; - } - - if (carry != 0) { - currNode.next = new ListNode(carry); - } - return resultHead.next; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" deleted file mode 100644 index 4348aad..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 二进制链表转整数 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 0, 1); - System.out.println(ListUtil.toList(head)); - int result = getDecimalValue(head); - Assertions.assertEquals(5, result); - - head = new ListNode(0); - System.out.println(ListUtil.toList(head)); - result = getDecimalValue(head); - Assertions.assertEquals(0, result); - - head = new ListNode(1); - System.out.println(ListUtil.toList(head)); - result = getDecimalValue(head); - Assertions.assertEquals(1, result); - - head = ListUtil.buildList(1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0); - System.out.println(ListUtil.toList(head)); - result = getDecimalValue(head); - Assertions.assertEquals(18880, result); - } - - /** - * 二进制链表转整数 算法实现 - *

- * 给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。 - *

- * 请你返回该链表所表示数字的 十进制值 。 - *

- * 示例 1: - *

-     * 输入:head = [1,0,1]
-     * 输出:5
-     * 解释:二进制数 (101) 转化为十进制数 (5)
-     * 
- *

- * 示例 2: - *

-     * 输入:head = [0]
-     * 输出:0
-     * 
- *

- * 示例 3: - *

-     * 输入:head = [1]
-     * 输出:1
-     * 
- *

- * 示例 4: - *

-     * 输入:head = [1,0,0,1,0,0,1,1,1,0,0,0,0,0,0]
-     * 输出:18880
-     * 
- *

- * 示例 5: - *

-     * 输入:head = [0,0]
-     * 输出:0
-     * 
- *

- * 提示: - *

-     * 链表不为空。
-     * 链表的结点总数不超过 30。
-     * 每个结点的值不是 0 就是 1。
-     * 
- * - * @see 二进制链表转整数 - */ - public static int getDecimalValue(ListNode head) { - int sum = 0; - while (head != null) { - sum = sum * 2 + head.val; - head = head.next; - } - return sum; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\206\351\232\224\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\206\351\232\224\351\223\276\350\241\250.java" deleted file mode 100644 index 749f5e7..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\206\351\232\224\351\223\276\350\241\250.java" +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @see 86. 分隔链表 - * @since 2020-07-06 - */ -public class 分隔链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 4, 3, 2, 5, 2); - ListNode result = partition(head, 3); - List list = ListUtil.toList(result); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 2, 4, 3, 5 }, list.toArray(new Integer[0])); - - ListNode head2 = ListUtil.buildList(2, 1); - ListNode result2 = partition(head2, 2); - List list2 = ListUtil.toList(result2); - Assertions.assertArrayEquals(new Integer[] { 1, 2 }, list2.toArray(new Integer[0])); - - ListNode head3 = ListUtil.buildList(3, 1, 2); - ListNode result3 = partition(head3, 3); - List list3 = ListUtil.toList(result3); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, list3.toArray(new Integer[0])); - } - - public static ListNode partition(ListNode head, int x) { - return null; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" deleted file mode 100644 index 5873f31..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @see 83. 删除排序链表中的重复元素 - * @since 2020-06-09 - */ -public class 删除排序链表中的重复元素 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 1, 2); - System.out.println(ListUtil.toList(head)); - ListNode result = deleteDuplicates(head); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 2 }, list.toArray(new Integer[0])); - } - - public static ListNode deleteDuplicates(ListNode head) { - ListNode node = head; - while (node != null && node.next != null) { - if (node.val == node.next.val) { - node.next = node.next.next; - } else { - node = node.next; - } - } - return head; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240II.java" deleted file mode 100644 index 1bc2a84..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240II.java" +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 删除排序链表中的重复元素II { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 1, 2); - System.out.println(ListUtil.toList(head)); - ListNode result = deleteDuplicates(head); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 2 }, list.toArray(new Integer[0])); - } - - public static ListNode deleteDuplicates(ListNode head) { - if (head == null) return head; // 若head为空则直接返回null - ListNode dummy = new ListNode(-1); // 建立一个虚拟头结点 - ListNode tail = dummy; // 定义一个尾巴,用于尾插法。 - for (ListNode l = head, r = head; l != null; l = r) { - while (r != null && r.val == l.val) r = r.next; // 只要r不为空并且与l的值相等则一直向后移动 - if (l.next == r) { // 若长度为1,则通过尾插法加入。 - tail.next = l; // 基本的尾插法 - tail = l; - tail.next = null; // 这里记得将尾部的后面置为null,不然可能后面会带着一些其他的节点。 - } - } - return dummy.next; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\215\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\215\350\275\254\351\223\276\350\241\250.java" deleted file mode 100644 index 952f02e..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\217\215\350\275\254\351\223\276\350\241\250.java" +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; -import java.util.Stack; - -/** - * @author Zhang Peng - * @see 206. 反转链表 - * @since 2020-06-09 - */ -public class 反转链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 3, 4); - System.out.println(ListUtil.toList(head)); - ListNode result = reverseList2(head); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 4, 3, 2, 1 }, list.toArray(new Integer[0])); - } - - // 借助栈来实现 - public static ListNode reverseList(ListNode head) { - if (head == null) return null; - Stack stack = new Stack<>(); - ListNode node = head; - while (node != null) { - stack.push(node); - node = node.next; - } - - ListNode dummy = new ListNode(-1); - node = dummy; - while (!stack.isEmpty()) { - node.next = stack.pop(); - node.next.next = null; - node = node.next; - } - return dummy.next; - } - - public static ListNode reverseList2(ListNode head) { - if (head == null) { - return null; - } - - ListNode dummy = new ListNode(-1); - dummy.next = head; - ListNode prev = null; - ListNode curr = head; - while (curr != null) { - ListNode temp = curr.next; - curr.next = prev; - prev = curr; - curr = temp; - } - return prev; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250.java" deleted file mode 100644 index f3feee4..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250.java" +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 合并K个排序链表 { - - public static void main(String[] args) { - ListNode head1 = ListUtil.buildList(1, 4, 5); - ListNode head2 = ListUtil.buildList(1, 3, 4); - ListNode head3 = ListUtil.buildList(2, 6); - ListNode[] array = new ListNode[] { head1, head2, head3 }; - ListNode result = mergeKLists(array); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 1, 2, 3, 4, 4, 5, 6 }, list.toArray(new Integer[0])); - } - - /** - * 23. 合并K个排序链表 算法实现 - *

- * 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 - *

- * 示例: - *

-     * 输入:
-     * [
-     *   1->4->5,
-     *   1->3->4,
-     *   2->6
-     * ]
-     * 输出: 1->1->2->3->4->4->5->6
-     * 
- * - * @see 23. 合并K个排序链表 - */ - public static ListNode mergeKLists(ListNode[] lists) { - if (lists == null || lists.length == 0) { - return null; - } - - ListNode root = new ListNode(-1); - ListNode resultHead = root; - while (true) { - Integer minIndex = null; - Integer minVal = null; - for (int i = 0; i < lists.length; i++) { - if (lists[i] == null) { - continue; - } - - if (minVal == null || lists[i].val < minVal) { - minIndex = i; - minVal = lists[i].val; - } - } - - if (minIndex != null) { - resultHead.next = new ListNode(lists[minIndex].val); - resultHead = resultHead.next; - lists[minIndex] = lists[minIndex].next; - } else { - break; - } - } - - return root.next; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250\350\247\243\346\263\2252.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250\350\247\243\346\263\2252.java" deleted file mode 100644 index 1bfaa86..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266K\344\270\252\346\216\222\345\272\217\351\223\276\350\241\250\350\247\243\346\263\2252.java" +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 合并K个排序链表解法2 { - - public static void main(String[] args) { - ListNode head1 = ListUtil.buildList(1, 4, 5); - ListNode head2 = ListUtil.buildList(1, 3, 4); - ListNode head3 = ListUtil.buildList(2, 6); - ListNode[] array = new ListNode[] { head1, head2, head3 }; - ListNode result = mergeKLists(array); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 1, 2, 3, 4, 4, 5, 6 }, list.toArray(new Integer[0])); - } - - /** - * 23. 合并K个排序链表 算法实现 - *

- * 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 - *

- * 示例: - *

-     * 输入:
-     * [
-     *   1->4->5,
-     *   1->3->4,
-     *   2->6
-     * ]
-     * 输出: 1->1->2->3->4->4->5->6
-     * 
- * - * @see 23. 合并K个排序链表 - */ - public static ListNode mergeKLists(ListNode[] lists) { - if (lists == null || lists.length == 1) { - return lists[0]; - } - - ListNode result = lists[0]; - for (int i = 1; i < lists.length; i++) { - result = 合并两个有序链表.mergeTwoLists(result, lists[i]); - } - return result; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" deleted file mode 100644 index 4b24ebd..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @see 合并两个有序链表 - * @since 2020-06-09 - */ -public class 合并两个有序链表 { - - public static void main(String[] args) { - ListNode head1 = ListUtil.buildList(1, 2, 4); - ListNode head2 = ListUtil.buildList(1, 3, 4); - ListNode result = mergeTwoLists(head1, head2); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 1, 2, 3, 4, 4 }, list.toArray(new Integer[0])); - } - - public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { - ListNode dummy = new ListNode(-1); - ListNode n = dummy; - while (l1 != null && l2 != null) { - if (l1.val <= l2.val) { - n.next = l1; - l1 = l1.next; - } else { - n.next = l2; - l2 = l2.next; - } - n = n.next; - } - - n.next = (l1 != null) ? l1 : l2; - return dummy.next; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\233\236\346\226\207\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\233\236\346\226\207\351\223\276\350\241\250.java" deleted file mode 100644 index a983424..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\233\236\346\226\207\351\223\276\350\241\250.java" +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @see 234. 回文链表 - * @see 面试题 02.06. 回文链表 - * @since 2020-06-09 - */ -public class 回文链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 2, 1); - Assertions.assertTrue(isPalindrome(head)); - - head = ListUtil.buildList(1, 2); - Assertions.assertFalse(isPalindrome(head)); - } - - public static boolean isPalindrome(ListNode head) { - List list = new ArrayList<>(); - ListNode node = head; - while (node != null) { - list.add(node.val); - node = node.next; - } - - // int i = 0, j = list.size() - 1; - for (int i = 0, j = list.size() - 1; i < j; i++, j--) { - if (!list.get(i).equals(list.get(j))) { - return false; - } - } - return true; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\245\207\345\201\266\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\245\207\345\201\266\351\223\276\350\241\250.java" deleted file mode 100644 index 3ab1ae7..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\345\245\207\345\201\266\351\223\276\350\241\250.java" +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import java.util.Arrays; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-08 - */ -public class 奇偶链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 3, 4, 5); - List list = ListUtil.toList(oddEvenList(head)); - System.out.println(list); - // Assertions.assertFalse(); - } - - public static ListNode oddEvenList(ListNode head) { - if (head == null || head.next == null) return head; - - ListNode odd = head, even = head.next, evenHead = even; - - while (even != null && even.next != null) { - odd.next = even.next; - odd = odd.next; - even.next = odd.next; - even = even.next; - } - odd.next = evenHead; - return head; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\346\216\222\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\346\216\222\345\272\217\351\223\276\350\241\250.java" deleted file mode 100644 index fa65a28..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\346\216\222\345\272\217\351\223\276\350\241\250.java" +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @see 148.排序链表 - * @since 2020-06-09 - */ -public class 排序链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(4, 2, 1, 3); - System.out.println(ListUtil.toList(head)); - ListNode result = sortList(head); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 4 }, list.toArray(new Integer[0])); - - head = ListUtil.buildList(-1, 5, 3, 4, 0); - System.out.println(ListUtil.toList(head)); - result = sortList(head); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { -1, 0, 3, 4, 5 }, list.toArray(new Integer[0])); - } - - public static ListNode sortList(ListNode head) { - if (head == null) {return head;} - return mergeSort(head); - } - - static ListNode mergeSort(ListNode head) { - //回归条件 - if (head.next == null) { - return head; - } - //快指针,考虑到链表为2时的情况,fast比slow早一格 - ListNode fast = head.next; - //慢指针 - ListNode slow = head; - //快慢指针开跑 - while (fast != null && fast.next != null) { - fast = fast.next.next; - slow = slow.next; - } - //找到右子链表头元素,复用fast引用 - fast = slow.next; - //将中点后续置空,切割为两个子链表 - slow.next = null; - //递归分解左子链表,得到新链表起点 - head = mergeSort(head); - //递归分解右子链表,得到新链表起点 - fast = mergeSort(fast); - //并归两个子链表 - return merge(head, fast); - } - - static ListNode merge(ListNode left, ListNode right) { - //维护临时序列的头元素 - ListNode head; - if (left.val <= right.val) { - head = left; - left = left.next; - } else { - head = right; - right = right.next; - } - //两个子链表均存在剩余元素 - ListNode temp = head; - while (left != null && right != null) { - //将较小的元素加入临时序列 - if (left.val <= right.val) { - temp.next = left; - left = left.next; - temp = temp.next; - } else { - temp.next = right; - right = right.next; - temp = temp.next; - } - } - //左子序列用完将右子序列余下元素加入临时序列 - if (left == null) { - temp.next = right; - } - //右子序列用完将左子序列余下元素加入临时序列 - if (right == null) { - temp.next = left; - } - return head; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250.java" deleted file mode 100644 index be74796..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250.java" +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 141. 环形链表 - * @since 2020-06-09 - */ -public class 环形链表 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(3, 2, 0, -4); - Assertions.assertFalse(hasCycle(head)); - - head = ListUtil.buildCycleList(1, new int[] { 3, 2, 0, -4 }); - Assertions.assertTrue(hasCycle(head)); - - head = ListUtil.buildCycleList(0, new int[] { 1, 2 }); - Assertions.assertTrue(hasCycle(head)); - } - - public static boolean hasCycle(ListNode head) { - if (head == null || head.next == null) { - return false; - } - - ListNode slow = head; - ListNode fast = head.next; - while (slow != fast) { - if (fast == null || fast.next == null) { - return false; - } - slow = slow.next; - fast = fast.next.next; - } - - return true; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250II.java" deleted file mode 100644 index 7405c45..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\216\257\345\275\242\351\223\276\350\241\250II.java" +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.dunwu.algorithm.list; - -/** - * @author Zhang Peng - * @since 2020-07-08 - */ -public class 环形链表II { - - public static ListNode detectCycle(ListNode head) { - ListNode slow = head; - ListNode fast = head; - while (true) { - if (fast == null || fast.next == null) { - return null; - } - slow = slow.next; - fast = fast.next.next; - if (slow == fast) break; - } - - fast = head; - while (slow != fast) { - slow = slow.next; - fast = fast.next; - } - return fast; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\233\270\344\272\244\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\233\270\344\272\244\351\223\276\350\241\250.java" deleted file mode 100644 index 9eaf090..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\233\270\344\272\244\351\223\276\350\241\250.java" +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.dunwu.algorithm.list; - -/** - * @author Zhang Peng - * @see 相交链表 - * @since 2020-06-09 - */ -public class 相交链表 { - - public static ListNode getIntersectionNode(final ListNode headA, final ListNode headB) { - if (headA == null || headB == null) return null; - ListNode pA = headA, pB = headB; - while (pA != pB) { - pA = pA == null ? headB : pA.next; - pB = pB == null ? headA : pB.next; - } - return pA; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" deleted file mode 100644 index cd353d7..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 移除重复节点 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 3, 3, 2, 1); - ListNode listNode = removeDuplicateNodes(head); - List result = ListUtil.getValues(listNode); - System.out.println(result); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); - } - - /** - * @see 面试题 02.01. 移除重复节点 - */ - public static ListNode removeDuplicateNodes(ListNode head) { - if (head == null) { - return null; - } - - ListNode list = new ListNode(-1); - list.next = null; - - ListNode node = head; - while (node != null) { - if (!exists(list, node.val)) { - addToTail(list, node.val); - } - node = node.next; - } - return list.next; - } - - private static boolean exists(ListNode head, int val) { - ListNode node = head; - while (node != null) { - if (node.val == val) { return true; } - node = node.next; - } - return false; - } - - private static void addToTail(ListNode head, int val) { - if (head == null) { - return; - } - ListNode node = head; - while (node.next != null) { - node = node.next; - } - ListNode newNode = new ListNode(val); - node.next = newNode; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" deleted file mode 100644 index deccb7f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @see 203. 移除链表元素 - * @since 2020-06-09 - */ -public class 移除链表元素 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2); - System.out.println(ListUtil.toList(head)); - ListNode result = removeElementByValue(head, 1); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 2 }, list.toArray(new Integer[0])); - - head = new ListNode(1); - System.out.println(ListUtil.toList(head)); - result = removeElementByValue(head, 1); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] {}, list.toArray(new Integer[0])); - - head = ListUtil.buildList(1, 1); - System.out.println(ListUtil.toList(head)); - result = removeElementByValue(head, 1); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] {}, list.toArray(new Integer[0])); - - head = ListUtil.buildList(1, 2, 6, 3, 4, 5, 6); - System.out.println(ListUtil.toList(head)); - result = removeElementByValue(head, 6); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 4, 5 }, list.toArray(new Integer[0])); - } - - public static ListNode removeElementByValue(ListNode head, int val) { - if (head == null) return null; - - ListNode root = new ListNode(-1); - root.next = head; - ListNode prev = root; - while (prev.next != null) { - if (prev.next.val == val) { - prev.next = prev.next.next; - } else { - prev = prev.next; - } - } - return root.next; - } - - public static ListNode removeElementByIndex(ListNode head, int index) { - if (head == null) { - return null; - } - - ListNode root = new ListNode(-1); - root.next = head; - ListNode node = root; - int pos = 0; - while (node.next != null && pos != index) { - node = node.next; - pos++; - } - - if (node.next != null) { - node.next = node.next.next; - } - - return root.next; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\256\276\350\256\241\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\256\276\350\256\241\351\223\276\350\241\250.java" deleted file mode 100644 index 11e02a1..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\256\276\350\256\241\351\223\276\350\241\250.java" +++ /dev/null @@ -1,132 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-07-08 - */ -public class 设计链表 { - - public static void main(String[] args) { - MyLinkedList list = new MyLinkedList(); - list.addAtHead(1); - list.print(); - list.addAtTail(3); - list.print(); - list.addAtIndex(1, 2); //链表变为1-> 2-> 3 - list.print(); - System.out.println(list.get(1));//返回2 - Assertions.assertEquals(2, list.get(1)); - list.deleteAtIndex(1); //现在链表是1-> 3 - list.print(); - Assertions.assertEquals(3, list.get(1)); - } - - static class MyLinkedList { - - private Node head; - - /** - * Initialize your data structure here. - */ - public MyLinkedList() { - head = new Node(-1); - } - - /** - * Get the value of the index-th node in the linked list. If the index is invalid, return -1. - */ - public int get(int index) { - int i = 0; - Node p = head.next; - while (p.next != null && i < index) { - p = p.next; - i++; - } - return p.val; - } - - /** - * Add a node of value val before the first element of the linked list. After the insertion, the new node will - * be the first node of the linked list. - */ - public void addAtHead(int val) { - Node node = new Node(val); - if (head.next == null) { - head.next = node; - } else { - node.next = head.next; - head.next = node; - } - } - - /** - * Append a node of value val to the last element of the linked list. - */ - public void addAtTail(int val) { - Node p = head; - while (p.next != null) { - p = p.next; - } - p.next = new Node(val); - } - - /** - * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked - * list, the node will be appended to the end of linked list. If index is greater than the length, the node will - * not be inserted. - */ - public void addAtIndex(int index, int val) { - int i = 0; - Node p = head.next; - while (p.next != null && i < index - 1) { - p = p.next; - } - - Node node = new Node(val); - node.next = p.next; - p.next = node; - } - - /** - * Delete the index-th node in the linked list, if the index is valid. - */ - public void deleteAtIndex(int index) { - int i = 0; - Node p = head.next; - while (p.next != null && i < index - 1) { - p = p.next; - } - - if (p.next != null) { - p.next = p.next.next; - } else { - p.next = null; - } - } - - public void print() { - Node p = head; - while (p.next != null) { - p = p.next; - System.out.print(p.val + "\t"); - } - System.out.println(); - } - - static class Node { - - int val; - Node next; - - public Node(int val) { - this.val = val; - next = null; - } - - } - - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" deleted file mode 100644 index f6bfbe5..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 返回倒数第k个节点 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 3, 4, 5); - int val = kthToLast(head, 2); - Assertions.assertEquals(4, val); - } - - /** - * @see 面试题 02.02. 返回倒数第 k 个节点 - */ - public static int kthToLast(ListNode head, int k) { - int length = length(head); - if (k > length) { - return -1; - } - int pos = length - k; - ListNode node = head; - while (node != null && pos > 0) { - node = node.next; - pos--; - } - if (node != null) { - return node.val; - } else { - return -1; - } - } - - public static int length(ListNode head) { - if (head == null) { - return 0; - } - - int count = 1; - ListNode node = head.next; - while (node != null) { - count++; - node = node.next; - } - return count; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" deleted file mode 100644 index 9602f79..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.dunwu.algorithm.list; - -import org.junit.jupiter.api.Assertions; - -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 链表的中间结点 { - - public static void main(String[] args) { - ListNode head = ListUtil.buildList(1, 2, 3, 4, 5, 6); - System.out.println(ListUtil.toList(head)); - ListNode result = middleNode(head); - List list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 4, 5, 6 }, list.toArray(new Integer[0])); - - head = ListUtil.buildList(1, 2, 3, 4, 5); - - System.out.println(ListUtil.toList(head)); - result = middleNode(head); - list = ListUtil.toList(result); - System.out.println(list); - Assertions.assertArrayEquals(new Integer[] { 3, 4, 5 }, list.toArray(new Integer[0])); - } - - /** - * 链表的中间结点 算法实现 - *

- * 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 - *

- * 如果有两个中间结点,则返回第二个中间结点。 - *

- * 示例 1: - *

-     * 输入:[1,2,3,4,5]
-     * 输出:此列表中的结点 3 (序列化形式:[3,4,5])
-     * 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
-     * 注意,我们返回了一个 ListNode 类型的对象 ans,这样:
-     * ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
-     * 
- *

- * 示例 2: - *

-     * 输入:[1,2,3,4,5,6]
-     * 输出:此列表中的结点 4 (序列化形式:[4,5,6])
-     * 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
-     * 
- *

- * 提示:给定链表的结点数介于 1 和 100 之间。 - * - * @see 链表的中间结点 - */ - public static ListNode middleNode(ListNode head) { - // 利用快慢指针,慢指针每次偏移一个节点,快指针每次偏移两个节点 - ListNode slow = head; - ListNode fast = head; - while (fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - return slow; - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/map/LRUCache.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/map/LRUCache.java deleted file mode 100644 index 6723dd3..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/map/LRUCache.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.dunwu.algorithm.map; - -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * @author Zhang Peng - * @since 2020-01-18 - */ -class LRUCache { - - private int capacity; - - // 保持插入顺序 - private Map map; - - public LRUCache(int capacity) { - this.capacity = capacity; - map = new LinkedHashMap<>(capacity); - } - - public int get(int key) { - if (map.containsKey(key)) { - int value = map.get(key); - map.remove(key); - // 保证每次查询后,都在末尾 - map.put(key, value); - return value; - } - return -1; - } - - public void put(int key, int value) { - if (map.containsKey(key)) { - map.remove(key); - } else if (map.size() == capacity) { - Iterator> iterator = map.entrySet().iterator(); - iterator.next(); - iterator.remove(); - } - map.put(key, value); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" new file mode 100644 index 0000000..dace497 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.math; + +import org.junit.jupiter.api.Assertions; + +/** + * 263. 丑数 + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isUgly(6)); + Assertions.assertTrue(s.isUgly(1)); + Assertions.assertFalse(s.isUgly(14)); + } + + static class Solution { + + public boolean isUgly(int n) { + if (n <= 0) { return false; } + while (n % 2 == 0) { n /= 2; } + while (n % 3 == 0) { n /= 3; } + while (n % 5 == 0) { n /= 5; } + return n == 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" new file mode 100644 index 0000000..334b604 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.math; + +import org.junit.jupiter.api.Assertions; + +/** + * 66. 加一 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 加一 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 2, 4 }, s.plusOne(new int[] { 1, 2, 3 })); + Assertions.assertArrayEquals(new int[] { 4, 3, 2, 2 }, s.plusOne(new int[] { 4, 3, 2, 1 })); + Assertions.assertArrayEquals(new int[] { 1, 0, 0, 0, 0 }, s.plusOne(new int[] { 9, 9, 9, 9 })); + } + + static class Solution { + + public int[] plusOne(int[] digits) { + for (int i = digits.length - 1; i >= 0; i--) { + if (digits[i] == 9) { + digits[i] = 0; + } else { + digits[i]++; + return digits; + } + } + int[] res = new int[digits.length + 1]; + res[0] = 1; + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" index dc4f3f8..f476f99 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.queue; +package io.github.dunwu.algorithm.queue.demo; import java.util.Arrays; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" index 1c55892..0e321f7 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.queue; +package io.github.dunwu.algorithm.queue.demo; /** * 用数组实现的队列 diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" similarity index 97% rename from "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" index 5237442..3117ad2 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.queue; +package io.github.dunwu.algorithm.queue.demo; /** * 基于链表实现的队列 diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java new file mode 100644 index 0000000..a13cdb2 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import java.util.LinkedList; + +// 单调队列的实现,可以高效维护最大值和最小值 +class MonotonicQueue> { + + // 常规队列,存储所有元素 + LinkedList q = new LinkedList<>(); + // 元素降序排列的单调队列,头部是最大值 + LinkedList maxq = new LinkedList<>(); + // 元素升序排列的单调队列,头部是最小值 + LinkedList minq = new LinkedList<>(); + + public void push(E elem) { + // 维护常规队列,直接在队尾插入元素 + q.addLast(elem); + + // 维护 maxq,将小于 elem 的元素全部删除 + while (!maxq.isEmpty() && maxq.getLast().compareTo(elem) < 0) { + maxq.pollLast(); + } + maxq.addLast(elem); + + // 维护 minq,将大于 elem 的元素全部删除 + while (!minq.isEmpty() && minq.getLast().compareTo(elem) > 0) { + minq.pollLast(); + } + minq.addLast(elem); + } + + public E max() { + // maxq 的头部是最大元素 + return maxq.getFirst(); + } + + public E min() { + // minq 的头部是最大元素 + return minq.getFirst(); + } + + public E pop() { + // 从标准队列头部弹出需要删除的元素 + E deleteVal = q.pollFirst(); + assert deleteVal != null; + + // 由于 push 的时候会删除元素,deleteVal 可能已经被删掉了 + if (deleteVal.equals(maxq.getFirst())) { + maxq.pollFirst(); + } + if (deleteVal.equals(minq.getFirst())) { + minq.pollFirst(); + } + return deleteVal; + } + + public int size() { + // 标准队列的大小即是当前队列的大小 + return q.size(); + } + + public boolean isEmpty() { + return q.isEmpty(); + } + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..3b9aaf2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 239. 滑动窗口最大值 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 滑动窗口最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 3, 3, 5, 5, 6, 7 }, + s.maxSlidingWindow(new int[] { 1, 3, -1, -3, 5, 3, 6, 7 }, 3)); + Assertions.assertArrayEquals(new int[] { 1 }, s.maxSlidingWindow(new int[] { 1 }, 1)); + } + + static class Solution { + + public int[] maxSlidingWindow(int[] nums, int k) { + MonotonicQueue window = new MonotonicQueue(); + List res = new ArrayList<>(); + + for (int i = 0; i < nums.length; i++) { + if (i < k - 1) { + window.push(nums[i]); + } else { + window.push(nums[i]); + res.add(window.max()); + window.pop(nums[i - (k - 1)]); + } + } + + int[] arr = new int[res.size()]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + static class MonotonicQueue { + + LinkedList q = new LinkedList<>(); + + public void push(int n) { + // 将小于 n 的元素全部删除 + while (!q.isEmpty() && q.getLast() < n) { + q.pollLast(); + } + // 然后将 n 加入尾部 + q.addLast(n); + } + + public int max() { + return q.getFirst(); + } + + public void pop(int n) { + if (n == q.getFirst()) { + q.pollFirst(); + } + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" new file mode 100644 index 0000000..69586cd --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import org.junit.jupiter.api.Assertions; + +/** + * 739. 每日温度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 设计自助结算系统 { + + public static void main(String[] args) { + Checkout c = new Checkout(); + c.add(4); + c.add(7); + Assertions.assertEquals(7, c.get_max()); + Assertions.assertEquals(4, c.remove()); + Assertions.assertEquals(7, c.get_max()); + } + + static class Checkout { + + private MonotonicQueue q; + public Checkout() { + q = new MonotonicQueue<>(); + } + + public int get_max() { + return q.isEmpty() ? -1 : q.max(); + } + + public void add(int value) { + q.push(value); + } + + public int remove() { + if (q.isEmpty()) { return -1; } + return q.pop(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" new file mode 100644 index 0000000..e350c5c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +/** + * 2073. 买票需要的时间 + * + * @author Zhang Peng + * @since 2025-11-26 + */ +public class 买票需要的时间 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.timeRequiredToBuy(new int[] { 2, 3, 2 }, 2)); + Assertions.assertEquals(8, s.timeRequiredToBuy(new int[] { 5, 1, 1, 1 }, 0)); + } + + static class Solution { + + public int timeRequiredToBuy(int[] tickets, int k) { + int i = 0; + int seconds = 0; + while (tickets[k] != 0) { + if (tickets[i] != 0) { + tickets[i]--; + seconds++; + } + i = (i + 1) % tickets.length; + } + return seconds; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" index ee1d7e7..00e74d9 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" @@ -4,22 +4,37 @@ import java.util.Queue; /** + * 933. 最近的请求次数 + * * @author Zhang Peng - * @see 933. 最近的请求次数 * @since 2020-06-10 */ public class 最近的请求次数 { - Queue queue; - - public 最近的请求次数() { - queue = new LinkedList<>(); + public static void main(String[] args) { + RecentCounter recentCounter = new RecentCounter(); + recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1 + recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2 + recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3 + recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3 } - public int ping(int t) { - queue.add(t); - while (queue.peek() < t - 3000) { queue.poll(); } - return queue.size(); + static class RecentCounter { + + private Queue queue; + + public RecentCounter() { + queue = new LinkedList<>(); + } + + public int ping(int t) { + queue.offer(t); + while (queue.peek() < t - 3000) { + queue.poll(); + } + return queue.size(); + } + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" new file mode 100644 index 0000000..7628222 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 225. 用队列实现栈 + * + * @author Zhang Peng + * @since 2020-01-18 + */ +public class 用队列实现栈 { + + public static void main(String[] args) { + MyStack s1 = new MyStack(); + s1.push(1); + s1.push(2); + Assertions.assertEquals(2, s1.top()); + Assertions.assertEquals(2, s1.pop()); + Assertions.assertFalse(s1.empty()); + + int max = 10; + MyStack stack = new MyStack(); + for (int i = 1; i <= max; i++) { + stack.push(i); + } + for (int i = 1; i <= max; i++) { + Assertions.assertEquals(max - i + 1, stack.top()); + Assertions.assertEquals(max - i + 1, stack.pop()); + } + } + + static class MyStack { + + private Integer top; + Queue q; + + public MyStack() { + q = new LinkedList<>(); + } + + public void push(int x) { + q.offer(x); + top = x; + } + + public int pop() { + int size = q.size(); + for (int i = 1; i < size; i++) { + Integer val = q.poll(); + q.offer(val); + top = val; + } + return q.poll(); + } + + public int top() { + return top == null ? 0 : top; + } + + public boolean empty() { + return q.isEmpty(); + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/MyCircularDeque.java "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" similarity index 93% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/MyCircularDeque.java rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" index 8d4bed9..1db2d49 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/MyCircularDeque.java +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" @@ -1,14 +1,15 @@ package io.github.dunwu.algorithm.queue; /** + * 641. 设计循环双端队列 + * * @author Zhang Peng - * @see 641. 设计循环双端队列 * @since 2020-06-10 */ -public class MyCircularDeque { +public class 设计循环双端队列 { public static void main(String[] args) { - MyCircularDeque queue = new MyCircularDeque(3); + 设计循环双端队列 queue = new 设计循环双端队列(3); queue.insertFront(1); queue.insertFront(2); queue.insertFront(3); @@ -43,7 +44,7 @@ public static void main(String[] args) { private int capacity; /** Initialize your data structure here. Set the size of the deque to be k. */ - public MyCircularDeque(int k) { + public 设计循环双端队列(int k) { this.capacity = k + 1; this.data = new int[this.capacity]; } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.java" deleted file mode 100644 index 380f53f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\351\230\237\345\210\227.java" +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.dunwu.algorithm.queue; - -/** - * @author Zhang Peng - * @see 622. 设计循环队列 - * @since 2020-06-10 - */ -public class 设计循环队列 { - - public static void main(String[] args) { - 设计循环队列 queue = new 设计循环队列(3); - queue.enQueue(1); - queue.enQueue(2); - queue.enQueue(3); - queue.enQueue(4); - queue.printAll(); - System.out.println("rear: " + queue.Rear()); - System.out.println("full: " + queue.isFull()); - queue.deQueue(); - queue.enQueue(4); - queue.printAll(); - System.out.println("rear: " + queue.Rear()); - } - - private int[] data; - private int head; - // head表示队头下标,tail表示队尾下标 - private int tail; - private int capacity; - - // 申请一个大小为capacity的数组(由于循环队列会浪费一个空间,所以如果想要存储元素数为capacity,数组维度n=capacity+1) - public 设计循环队列(int k) { - this.capacity = k + 1; - this.data = new int[capacity]; - this.head = 0; - this.tail = 0; - } - - /** Insert an element into the circular queue. Return true if the operation is successful. */ - public boolean enQueue(int value) { - if (isFull()) { - return false; - } - - this.data[tail] = value; - tail = (tail + 1) % capacity; - return true; - } - - /** Delete an element from the circular queue. Return true if the operation is successful. */ - public boolean deQueue() { - if (isEmpty()) { - return false; - } - - head = (head + 1) % capacity; - return true; - } - - /** Get the front item from the queue. */ - public int Front() { - if (isEmpty()) { - return -1; - } - - return data[head]; - } - - /** Get the last item from the queue. */ - public int Rear() { - if (isEmpty()) { - return -1; - } - - int temp = (tail - 1 + capacity) % capacity; - return data[temp]; - } - - /** Checks whether the circular queue is empty or not. */ - public boolean isEmpty() { - if (head == tail) { - return true; - } - return false; - } - - /** Checks whether the circular queue is full or not. */ - public boolean isFull() { - if ((tail + 1) % capacity == head) { - return true; - } - return false; - } - - public void printAll() { - if (head == tail) { - System.out.println("队列已空"); - return; - } - for (int i = head; i != tail; ) { - System.out.print(data[i] + "\t"); - if (i == capacity - 1) { - i = 0; - } else { - i++; - } - } - System.out.println(); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" new file mode 100644 index 0000000..ab5b321 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" @@ -0,0 +1,73 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.PriorityQueue; +import java.util.Set; + +/** + * 313. 超级丑数 + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 超级丑数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(32, s.nthSuperUglyNumber(12, new int[] { 2, 7, 13, 19 })); + Assertions.assertEquals(1, s.nthSuperUglyNumber(1, new int[] { 2, 3, 5 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(32, s2.nthSuperUglyNumber(12, new int[] { 2, 7, 13, 19 })); + Assertions.assertEquals(1, s2.nthSuperUglyNumber(1, new int[] { 2, 3, 5 })); + } + + // 优先队列(堆)方案 + static class Solution { + + public int nthSuperUglyNumber(int n, int[] primes) { + Set set = new HashSet<>(); + PriorityQueue queue = new PriorityQueue<>(); + set.add(1L); + queue.add(1L); + for (int i = 1; i <= n; i++) { + long curVal = queue.poll(); + if (i == n) { return (int) curVal; } + for (int prime : primes) { + long nextVal = curVal * prime; + if (!set.contains(nextVal)) { + set.add(nextVal); + queue.add(nextVal); + } + } + } + return -1; + } + + } + + // 多路归并方案 + static class Solution2 { + + public int nthSuperUglyNumber(int n, int[] nums) { + int m = nums.length; + PriorityQueue q = new PriorityQueue<>((a, b) -> a[0] - b[0]); + for (int i = 0; i < m; i++) { + q.add(new int[] { nums[i], i, 0 }); + } + int[] dp = new int[n]; + dp[0] = 1; + for (int j = 1; j < n; ) { + int[] poll = q.poll(); + int num = poll[0], i = poll[1], idx = poll[2]; + if (num != dp[j - 1]) dp[j++] = num; + q.add(new int[] { dp[idx + 1] * nums[i], i, idx + 1 }); + } + return dp[n - 1]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java index d2c28ca..bfbe8ca 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java @@ -1,6 +1,8 @@ package io.github.dunwu.algorithm.sort; /** + * 排序通用泛型接口 + * * @author Zhang Peng */ public interface Sort { diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java index 3398632..5e2f7ef 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java @@ -1,29 +1,27 @@ package io.github.dunwu.algorithm.sort; import io.github.dunwu.algorithm.util.ArrayUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** * 使用策略模式,对算法进行包装 * * @author Zhang Peng */ +@Slf4j public class SortStrategy { - private static final Logger logger = LoggerFactory.getLogger(SortStrategy.class); - - private Sort sort; + private final Sort sort; public SortStrategy(Sort sort) { this.sort = sort; } public void sort(Integer[] list) { - logger.info(this.sort.getClass().getSimpleName() + " 排序开始:"); - logger.info("排序前: {}", ArrayUtil.getArrayString(list, 0, list.length - 1)); + System.out.printf("=================== %s 排序开始 ===================\n", this.sort.getClass().getSimpleName()); + System.out.printf("【排序前】\n%s\n", ArrayUtil.getArrayString(list, 0, list.length - 1)); this.sort.sort(list); - logger.info("排序后: {}", ArrayUtil.getArrayString(list, 0, list.length - 1)); + System.out.printf("【排序后】\n%s\n", ArrayUtil.getArrayString(list, 0, list.length - 1)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java index be160ef..3c10699 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java @@ -23,8 +23,7 @@ public > void sort(T[] list) { list[j] = temp; } } - - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i + 1)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java index 6452573..7bebb12 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java @@ -12,12 +12,11 @@ public class BubbleSort2 implements Sort { @Override public > void sort(T[] list) { - // 交换标志 - boolean bChange = false; // 要遍历的次数 for (int i = 0; i < list.length - 1; i++) { - bChange = false; + // 交换标志 + boolean changed = false; // 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上 for (int j = list.length - 1; j > i; j--) { // 比较相邻的元素,如果前面的数大于后面的数,则交换 @@ -25,16 +24,16 @@ public > void sort(T[] list) { T temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; - bChange = true; + changed = true; } } // 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序 - if (false == bChange) { + if (!changed) { break; } - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i + 1)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java index a5fc017..d403207 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java @@ -27,7 +27,7 @@ public > void sort(T[] list) { // 筛选 R[0] 结点,得到i-1个结点的堆 adjustHeat(list, 0, i); - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", list.length - i)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", list.length - i)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java index 4847fcb..9d1d49b 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java @@ -12,9 +12,6 @@ public class InsertSort implements Sort { @Override public > void sort(T[] list) { - // 打印第一个元素 - ArrayUtil.debugLogArray(list, 0, 0, String.format("i = %d:\t", 0)); - // 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列 for (int i = 1; i < list.length; i++) { int j = 0; @@ -27,7 +24,7 @@ public > void sort(T[] list) { } list[j + 1] = temp; - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("i = %d:\t", i)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java index 5ac1e6c..56d36ac 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java @@ -9,7 +9,7 @@ public class MergeSort implements Sort { public > void sort(T[] list) { for (int gap = 1; gap < list.length; gap = 2 * gap) { mergeSort(list, gap, list.length); - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("gap = %d", gap)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("gap = %d", gap)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java index e821cb6..03ee815 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java @@ -42,7 +42,7 @@ private > void quickSort(T[] list, int left, int right) // 对数组进行分割,取出下次分割的基准标号 int base = division(list, left, right); - ArrayUtil.debugLogArray(list, left, right, String.format("base = %d: ", list[base])); + ArrayUtil.printArray(list, left, right, String.format("base = %d: ", list[base])); // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序 quickSort(list, left, base - 1); diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java index 128b255..319cc81 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java @@ -30,7 +30,7 @@ public > void sort(T[] list) { list[index] = list[i]; list[i] = temp; - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟:", i + 1)); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java index 08c9f5f..7b32584 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java @@ -27,7 +27,7 @@ public > void sort(T[] list) { list[j + gap] = temp; } - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("gap = %d:", gap)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("gap = %d:", gap)); // 减小增量 gap = gap / 2; } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/StackBasedOnLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/StackBasedOnLinkedList.java deleted file mode 100644 index f45a3de..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/StackBasedOnLinkedList.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.dunwu.algorithm.stack; - -/** - * 基于链表实现的栈。 - *

- * Author: Zheng - */ -public class StackBasedOnLinkedList { - - public static void main(String[] args) { - StackBasedOnLinkedList stack = new StackBasedOnLinkedList(); - stack.push(1); - stack.push(2); - stack.push(3); - stack.printAll(); - System.out.println("pop " + stack.pop()); - System.out.println("pop " + stack.pop()); - System.out.println("pop " + stack.pop()); - } - - private Node top = null; - - public void push(int value) { - Node node = new Node(value, null); - if (top == null) { - top = node; - } else { - node.next = top; - top = node; - } - } - - /** - * 我用-1表示栈中没有数据。 - */ - public int pop() { - if (top == null) return -1; - int val = top.data; - top = top.next; - return val; - } - - public void printAll() { - Node p = top; - while (p != null) { - System.out.print(p.data + " "); - p = p.next; - } - System.out.println(); - } - - private static class Node { - - private int data; - private Node next; - - public Node(int data, Node next) { - this.data = data; - this.next = next; - } - - public int getData() { - return data; - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java new file mode 100644 index 0000000..6a2fefd --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过「单调栈」解决「下一个更大元素」,「上一个更小元素」等类型问题 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +package io.github.dunwu.algorithm.stack.monotonic; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" new file mode 100644 index 0000000..d18dd54 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * 496. 下一个更大元素 I + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 下一个更大元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[] output1 = s.nextGreaterElement(new int[] { 4, 1, 2 }, new int[] { 1, 3, 4, 2 }); + Assertions.assertArrayEquals(new int[] { -1, 3, -1 }, output1); + int[] output2 = s.nextGreaterElement(new int[] { 2, 4 }, new int[] { 1, 2, 3, 4 }); + Assertions.assertArrayEquals(new int[] { 3, -1 }, output2); + } + + // 采用单调栈解决问题,算法复杂度:O(n) + static class Solution { + + public int[] nextGreaterElement(int[] nums1, int[] nums2) { + Stack stack = new Stack<>(); + Map map = new HashMap<>(); + for (int i = nums2.length - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() <= nums2[i]) { + stack.pop(); + } + int largerVal = stack.isEmpty() ? -1 : stack.peek(); + map.put(nums2[i], largerVal); + stack.push(nums2[i]); + } + + for (int i = 0; i < nums1.length; i++) { + nums1[i] = map.get(nums1[i]); + } + return nums1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..b2fe027 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 503. 下一个更大元素 II + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 下一个更大元素2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, -1, 2 }, s.nextGreaterElements(new int[] { 1, 2, 1 })); + Assertions.assertArrayEquals(new int[] { 2, 3, 4, -1, 4 }, s.nextGreaterElements(new int[] { 1, 2, 3, 4, 3 })); + } + + static class Solution { + + public int[] nextGreaterElements(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 2 * n - 1; i >= 0; i--) { + int index = i % n; + // 遍历栈,将小于当前元素的值都踢了 + while (!s.isEmpty() && s.peek() <= nums[index]) { + s.pop(); + } + // nums[i] 下一个更大元素在栈顶 + res[index] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[index]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" new file mode 100644 index 0000000..9243ec1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 1475. 商品折扣后的最终价格 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 商品折扣后的最终价格 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 4, 2, 4, 2, 3 }, s.finalPrices(new int[] { 8, 4, 6, 2, 3 })); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, s.finalPrices(new int[] { 1, 2, 3, 4, 5 })); + Assertions.assertArrayEquals(new int[] { 9, 0, 1, 6 }, s.finalPrices(new int[] { 10, 1, 1, 6 })); + } + + static class Solution { + + public int[] finalPrices(int[] prices) { + int[] res = new int[prices.length]; + Stack stack = new Stack<>(); + for (int i = prices.length - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() > prices[i]) { + stack.pop(); + } + res[i] = stack.isEmpty() ? prices[i] : prices[i] - stack.peek(); + stack.push(prices[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..077f1c8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,89 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Stack; + +/** + * 581. 最短无序连续子数组 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 最短无序连续子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.findUnsortedSubarray(new int[] { 2, 6, 4, 8, 10, 9, 15 })); + Assertions.assertEquals(0, s.findUnsortedSubarray(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s.findUnsortedSubarray(new int[] { 1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(5, s2.findUnsortedSubarray(new int[] { 2, 6, 4, 8, 10, 9, 15 })); + Assertions.assertEquals(0, s2.findUnsortedSubarray(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s2.findUnsortedSubarray(new int[] { 1 })); + } + + // 排序解法 + static class Solution { + + public int findUnsortedSubarray(int[] nums) { + int[] temp = Arrays.copyOf(nums, nums.length); + Arrays.sort(temp); + int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE; + for (int i = 0; i < nums.length; i++) { + if (temp[i] != nums[i]) { + left = i; + break; + } + } + for (int i = nums.length - 1; i >= 0; i--) { + if (temp[i] != nums[i]) { + right = i; + break; + } + } + if (left == Integer.MAX_VALUE && right == Integer.MIN_VALUE) { + // nums 本来就是有序的 + return 0; + } + return right - left + 1; + } + + } + + // 单调栈解法 + static class Solution2 { + + public int findUnsortedSubarray(int[] nums) { + int n = nums.length; + int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE; + // 递增栈,存储元素索引 + Stack incrStk = new Stack<>(); + for (int i = 0; i < n; i++) { + while (!incrStk.isEmpty() && nums[incrStk.peek()] > nums[i]) { + // 弹出的元素都是乱序元素,其中最小的索引就是乱序子数组的左边界 + left = Math.min(left, incrStk.pop()); + } + incrStk.push(i); + } + // 递减栈,存储元素索引 + Stack decrStk = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + while (!decrStk.isEmpty() && nums[decrStk.peek()] < nums[i]) { + // 弹出的元素都是乱序元素,其中最大的索引就是乱序子数组的右边界 + right = Math.max(right, decrStk.pop()); + } + decrStk.push(i); + } + if (left == Integer.MAX_VALUE && right == Integer.MIN_VALUE) { + // 说明单调栈没有弹出任何元素,即 nums 本来就是有序的 + return 0; + } + return right - left + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" new file mode 100644 index 0000000..774fe37 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 739. 每日温度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 每日温度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 1, 4, 2, 1, 1, 0, 0 }, + s.dailyTemperatures(new int[] { 73, 74, 75, 71, 69, 72, 76, 73 })); + Assertions.assertArrayEquals(new int[] { 1, 1, 1, 0 }, + s.dailyTemperatures(new int[] { 30, 40, 50, 60 })); + } + + static class Solution { + + public int[] dailyTemperatures(int[] t) { + int[] res = new int[t.length]; + Stack s = new Stack<>(); + for (int i = t.length - 1; i >= 0; i--) { + while (!s.isEmpty() && s.peek()[1] <= t[i]) { + s.pop(); + } + res[i] = s.isEmpty() ? 0 : s.peek()[0] - i; + s.push(new int[] { i, t[i] }); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" new file mode 100644 index 0000000..aeb64a5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 402. 移掉 K 位数字 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 移掉K位数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("1219", s.removeKdigits("1432219", 3)); + Assertions.assertEquals("200", s.removeKdigits("10200", 1)); + Assertions.assertEquals("0", s.removeKdigits("10", 2)); + } + + static class Solution { + + public String removeKdigits(String num, int k) { + Stack s = new Stack<>(); + for (char c : num.toCharArray()) { + // 单调栈代码模板 + while (!s.isEmpty() && c < s.peek() && k > 0) { + s.pop(); + k--; + } + // 防止 0 作为数字的开头 + if (s.isEmpty() && c == '0') { continue; } + s.push(c); + } + + // 此时栈中元素单调递增,若 k 还没用完的话删掉栈顶元素 + while (!s.isEmpty() && k > 0) { + s.pop(); + k--; + } + // 若最后没剩下数字,就是 0 + if (s.isEmpty()) { return "0"; } + // 将栈中字符转化成字符串 + StringBuilder sb = new StringBuilder(); + while (!s.isEmpty()) { sb.append(s.pop()); } + // 出栈顺序和字符串顺序是反的 + return sb.reverse().toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" new file mode 100644 index 0000000..4852eb3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * 901. 股票价格跨度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 股票价格跨度 { + + public static void main(String[] args) { + StockSpanner stock = new StockSpanner(); + Assertions.assertEquals(1, stock.next(100)); + Assertions.assertEquals(1, stock.next(80)); + Assertions.assertEquals(1, stock.next(60)); + Assertions.assertEquals(2, stock.next(70)); + Assertions.assertEquals(1, stock.next(60)); + Assertions.assertEquals(4, stock.next(75)); + Assertions.assertEquals(6, stock.next(85)); + + StockSpanner2 stock2 = new StockSpanner2(); + Assertions.assertEquals(1, stock2.next(100)); + Assertions.assertEquals(1, stock2.next(80)); + Assertions.assertEquals(1, stock2.next(60)); + Assertions.assertEquals(2, stock2.next(70)); + Assertions.assertEquals(1, stock2.next(60)); + Assertions.assertEquals(4, stock2.next(75)); + Assertions.assertEquals(6, stock2.next(85)); + } + + static class StockSpanner { + + private final List l; + + public StockSpanner() { + l = new ArrayList<>(); + } + + public int next(int price) { + int count = 1; + for (int i = l.size() - 1; i >= 0; i--) { + if (l.get(i) > price) { + break; + } + count++; + } + l.add(price); + return count; + } + + } + + static class StockSpanner2 { + + // int[] 记录 {价格,小于等于该价格的天数} 二元组 + private final Stack s; + + public StockSpanner2() { + s = new Stack<>(); + } + + public int next(int price) { + // 算上当天 + int count = 1; + // 单调栈模板 + while (!s.isEmpty() && s.peek()[0] <= price) { + // 挤掉价格低于 price 的记录 + int[] prev = s.pop(); + count += prev[1]; + } + s.push(new int[] { price, count }); + return count; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" new file mode 100644 index 0000000..8005fcc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Stack; + +/** + * 853. 车队 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 车位 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.carFleet(12, new int[] { 10, 8, 0, 5, 3 }, new int[] { 2, 4, 1, 1, 3 })); + Assertions.assertEquals(1, s.carFleet(10, new int[] { 3 }, new int[] { 3 })); + Assertions.assertEquals(1, s.carFleet(100, new int[] { 0, 2, 4 }, new int[] { 4, 2, 1 })); + } + + static class Solution { + + public int carFleet(int target, int[] position, int[] speed) { + + int n = position.length; + int[][] cars = new int[n][2]; + for (int i = 0; i < n; i++) { + cars[i][0] = position[i]; + cars[i][1] = speed[i]; + } + + // 按照初始位置,从小到大排序 + Arrays.sort(cars, (int[] a, int[] b) -> { + return Integer.compare(a[0], b[0]); + }); + + // 计算每辆车到达终点的时间 + double[] times = new double[n]; + for (int i = 0; i < n; i++) { + int[] car = cars[i]; + times[i] = (double) (target - car[0]) / car[1]; + } + + // 使用单调栈计算车队的数量 + Stack s = new Stack<>(); + for (double t : times) { + while (!s.isEmpty() && t >= s.peek()) { + s.pop(); + } + s.push(t); + } + return s.size(); + + // 避免使用栈模拟,倒序遍历取递增序列就是答案 + // int res = 0; + // double maxTime = 0; + // for (int i = n - 1; i >= 0; i--) { + // if (time[i] > maxTime) { + // maxTime = time[i]; + // res++; + // } + // } + // return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" new file mode 100644 index 0000000..cd746cc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Stack; + +/** + * 1019. 链表中的下一个更大节点 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 链表中的下一个更大节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 5, 5, 0 }, + s.nextLargerNodes(ListNode.buildList(2, 1, 5))); + Assertions.assertArrayEquals(new int[] { 7, 0, 5, 5, 0 }, + s.nextLargerNodes(ListNode.buildList(2, 7, 4, 3, 5))); + } + + static class Solution { + + public int[] nextLargerNodes(ListNode head) { + // 把单链表转化成数组,方便通过索引访问 + ArrayList nums = new ArrayList<>(); + ListNode p = head; + while (p != null) { + nums.add(p.val); + p = p.next; + } + + // 存放答案的数组 + int[] res = new int[nums.size()]; + Stack stack = new Stack<>(); + // 单调栈模板,求下一个更大元素,从后往前遍历 + for (int i = nums.size() - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() <= nums.get(i)) { + stack.pop(); + } + // 本题要求没有下一个更大元素时返回 0 + res[i] = stack.isEmpty() ? 0 : stack.peek(); + stack.push(nums.get(i)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" new file mode 100644 index 0000000..8257b94 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 1944. 队列中可以看到的人数 + * + * @author Zhang Peng + * @date 2025-12-19 + */ +public class 队列中可以看到的人数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(new int[] { 3, 1, 2, 1, 1, 0 }, s.canSeePersonsCount(new int[] { 10, 6, 8, 5, 11, 9 })); + Assertions.assertEquals(new int[] { 4, 1, 1, 1, 0 }, s.canSeePersonsCount(new int[] { 5, 1, 2, 3, 10 })); + } + + static class Solution { + + public int[] canSeePersonsCount(int[] heights) { + int n = heights.length; + int[] res = new int[n]; + // int[] 记录 {身高,小于等于该身高的人数} 二元组 + Stack stk = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + // 记录右侧比自己矮的人 + int count = 0; + // 单调栈模板,计算下一个更大或相等元素(身高) + while (!stk.isEmpty() && heights[i] > stk.peek()) { + stk.pop(); + count++; + } + // 不仅可以看到比自己矮的人,如果后面存在更高的的人,也可以看到这个高人 + res[i] = stk.isEmpty() ? count : count + 1; + stk.push(heights[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..99192ab --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,172 @@ +package io.github.dunwu.algorithm.stack.template; + +import java.util.Stack; + +/** + * 单调栈算法模板 + * + * @author Zhang Peng + * @date 2025-12-19 + */ +public class 单调栈算法模板 { + + /** + * 下一个更大的元素:计算 nums 中每个元素的下一个更大元素 + */ + int[] nextGreaterElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 因为是求 nums[i] 后面的元素,所以倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较小的元素 + while (!s.isEmpty() && s.peek() <= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更大元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更大或相等的元素:计算 nums 中每个元素的下一个更大或相等的元素 + */ + int[] nextGreaterOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + // 把这里改成 < 号 + while (!s.isEmpty() && s.peek() < nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的大于等于 nums[i] 的元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更小的元素:计算 nums 中每个元素的下一个更小的元素 + */ + int[] nextLessElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较大的元素 + while (!s.isEmpty() && s.peek() >= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更小元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更小或相等的元素:计算 nums 中每个元素的下一个更小或相等的元素 + */ + int[] nextLessOrEqualElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较大的元素 + while (!s.isEmpty() && s.peek() > nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更小或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更大元素:计算 nums 中每个元素的上一个更大元素 + */ + int[] prevGreaterElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + // 因为是求 nums[i] 前面的元素,所以正着往栈里放 + for (int i = 0; i < n; i++) { + // 删掉 nums[i] 前面较小的元素 + while (!s.isEmpty() && s.peek() <= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更大元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更大或相等的元素:计算 nums 中每个元素的上一个更大或相等元素 + */ + int[] prevGreaterOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 注意不等号 + while (!s.isEmpty() && s.peek() < nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更大或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更小的元素:计算 nums 中每个元素的上一个更小的元素 + */ + int[] prevLessElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 把 nums[i] 之前的较大元素删除 + while (!s.isEmpty() && s.peek() >= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更小元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更小或相等的元素:计算 nums 中每个元素的上一个更小或相等元素 + */ + int[] prevLessOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 注意不等号 + while (!s.isEmpty() && s.peek() > nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更小或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\344\270\211\345\220\210\344\270\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\344\270\211\345\220\210\344\270\200.java" deleted file mode 100644 index b38b134..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\344\270\211\345\220\210\344\270\200.java" +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dunwu.algorithm.stack; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-06-09 - */ -public class 三合一 { - - int stackSize; - List> stacks; - - public 三合一(int stackSize) { - this.stackSize = stackSize; - this.stacks = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - LinkedList list = new LinkedList<>(); - this.stacks.add(list); - } - } - - public void push(int stackNum, int value) { - LinkedList list = stacks.get(stackNum); - if (list.size() < stackSize) { - list.addLast(value); - } - } - - public int pop(int stackNum) { - LinkedList list = stacks.get(stackNum); - int value = -1; - if (list.size() > 0) { - value = list.getLast(); - list.removeLast(); - } - return value; - } - - public int peek(int stackNum) { - LinkedList list = stacks.get(stackNum); - int value = -1; - if (list.size() > 0) { - value = list.getLast(); - } - return value; - } - - public boolean isEmpty(int stackNum) { - LinkedList list = stacks.get(stackNum); - return list.size() <= 0; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" deleted file mode 100644 index 1e41699..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.dunwu.algorithm.stack; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 224. 基本计算器 - * @since 2020-06-09 - */ -public class 基本计算器 { - - public static void main(String[] args) { - Assertions.assertEquals(23, calculate("(1+(4+5+2)-3)+(6+8)")); - Assertions.assertEquals(3, calculate("2-(5-6)")); - Assertions.assertEquals(12, calculate("1+(4+5+2)")); - Assertions.assertEquals(2147483647, calculate("2147483647")); - Assertions.assertEquals(2, calculate("1 + 1")); - Assertions.assertEquals(3, calculate("2 - 1 + 2")); - } - - public static int calculate(String s) { - int sign = 1; - int current = 0; - int result = 0; - GenericStack stack = new GenericStack<>(); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (Character.isDigit(c)) { - current = current * 10 + (c - '0'); - } else if (c == '+') { - // 累加上一个操作数并重置 - result = result + sign * current; - current = 0; - // 设置下一个操作数的正负号 - sign = 1; - } else if (c == '-') { - // 累加上一个操作数并重置 - result = result + sign * current; - current = 0; - // 设置下一个操作数的正负号 - sign = -1; - } else if (c == '(') { - stack.push(result); - stack.push(sign); - sign = 1; - result = 0; - } else if (c == ')') { - // 累加上一个操作数并重置 - result = result + sign * current; - current = 0; - // 依次取出暂存栈中的正负号和操作数 - sign = stack.pop(); - int temp = stack.pop(); - // 累加 - result = temp + sign * result; - } - } - - if (current != 0) { - result = result + sign * current; - } - return result; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" new file mode 100644 index 0000000..e0bde55 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * 150. 逆波兰表达式求值 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 文件的最长绝对路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(20, s.lengthLongestPath("dir\\n\\tsubdir1\\n\\tsubdir2\\n\\t\\tfile.ext")); + Assertions.assertEquals(32, s.lengthLongestPath( + "dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext")); + Assertions.assertEquals(0, s.lengthLongestPath("a")); + Assertions.assertEquals(12, s.lengthLongestPath("file1.txt\\nfile2.txt\\nlongfile.txt")); + } + + static class Solution { + + public int lengthLongestPath(String input) { + // 这个栈存储之前的父路径。实际上这里只用存父路径的长度就够了,这个优化留给你吧 + Deque stack = new LinkedList<>(); + int maxLen = 0; + for (String part : input.split("\n")) { + int level = part.lastIndexOf("\t") + 1; + // 让栈中只保留当前目录的父路径 + while (level < stack.size()) { + stack.removeLast(); + } + stack.addLast(part.substring(level)); + // 如果是文件,就计算路径长度 + if (part.contains(".")) { + int sum = stack.stream().mapToInt(String::length).sum(); + // 加上父路径的分隔符 + sum += stack.size() - 1; + maxLen = Math.max(maxLen, sum); + } + } + return maxLen; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" new file mode 100644 index 0000000..9f6ec25 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Stack; +import java.util.TreeMap; + +/** + * 895. 最大频率栈 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 最大频率栈 { + + public static void main(String[] args) { + FreqStack s1 = new FreqStack(); + s1.push(5);//堆栈为 [5] + s1.push(7);//堆栈是 [5,7] + s1.push(5);//堆栈是 [5,7,5] + s1.push(7);//堆栈是 [5,7,5,7] + s1.push(4);//堆栈是 [5,7,5,7,4] + s1.push(5);//堆栈是 [5,7,5,7,4,5] + Assertions.assertEquals(5, s1.pop()); //返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,5,7,4] + Assertions.assertEquals(7, s1.pop()); //返回 7 ,因为 5 和 7 出现频率最高,但7最接近顶部。堆栈变成 [5,7,5,4]。 + Assertions.assertEquals(5, s1.pop()); //返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,4]。 + Assertions.assertEquals(4, s1.pop()); //返回 4 ,因为 4, 5 和 7 出现频率最高,但 4 是最接近顶部的。堆栈变成 [5,7]。 + } + + static class FreqStack { + + private HashMap valToFreq; + private TreeMap> freqToValStack; + + public FreqStack() { + valToFreq = new HashMap<>(); + freqToValStack = new TreeMap<>(); + } + + public void push(int val) { + valToFreq.put(val, valToFreq.getOrDefault(val, 0) + 1); + Integer freq = this.valToFreq.get(val); + freqToValStack.putIfAbsent(freq, new Stack<>()); + freqToValStack.get(freq).push(val); + } + + public int pop() { + Integer maxFreq = freqToValStack.lastKey(); + Stack stack = freqToValStack.get(maxFreq); + Integer val = stack.pop(); + if (stack.empty()) { freqToValStack.remove(maxFreq); } + valToFreq.put(val, valToFreq.getOrDefault(val, 0) - 1); + return val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" index 9a4f5ac..6775a69 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" @@ -1,63 +1,60 @@ package io.github.dunwu.algorithm.stack; -import java.util.LinkedList; +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; /** - * @see 面试题 03.02. 栈的最小值 + * 面试题 03.02. 栈的最小值 + * + * @author Zhang Peng + * @date 2025-10-20 */ public class 最小栈 { public static void main(String[] args) { - 最小栈 stack = new 最小栈(); - stack.push(9); - stack.push(2); - stack.push(5); - stack.push(6); - stack.push(3); - stack.push(1); - System.out.println("min = " + stack.getMin()); - System.out.println("pop " + stack.pop()); - System.out.println("pop " + stack.pop()); - System.out.println("pop " + stack.pop()); + MinStack minStack = new MinStack(); + minStack.push(-2); + minStack.push(0); + minStack.push(-3); + Assertions.assertEquals(-3, minStack.getMin()); + minStack.pop(); + Assertions.assertEquals(0, minStack.top()); + Assertions.assertEquals(-2, minStack.getMin()); } - private final LinkedList stack; - private final LinkedList minStack; + static class MinStack { - public 最小栈() { - stack = new LinkedList<>(); - minStack = new LinkedList<>(); - } + Stack stack; + Stack minStack; - public void push(int x) { - if (!minStack.isEmpty()) { - Integer first = minStack.getFirst(); - if (x < first) { - minStack.push(x); + public MinStack() { + stack = new Stack<>(); + minStack = new Stack<>(); + } + + public void push(int val) { + stack.push(val); + if (minStack.isEmpty() || val < minStack.peek()) { + minStack.push(val); + } else { + minStack.push(minStack.peek());; } - stack.push(x); } - } - public int pop() { - int top = stack.pop(); - int val = minStack.peek() ; - if (val == val) { + public void pop() { + stack.pop(); minStack.pop(); } - return val; - } - public int top() { - return stack.getFirst(); - } + public int top() { + return stack.peek(); + } - public int getMin() { - if (minStack.isEmpty()) { - return -1; - } else { - return minStack.getFirst(); + public int getMin() { + return minStack.peek(); } + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\2102.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\2102.java" deleted file mode 100644 index f96f802..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\2102.java" +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dunwu.algorithm.stack; - -import java.util.LinkedList; - -/** - * @author Zhang Peng - * @since 2020-01-18 - */ -public class 最小栈2 { - - // 数据栈 - private LinkedList data; - - // 辅助栈 - private LinkedList helper; - - /** - * initialize your data structure here. - */ - public 最小栈2() { - data = new LinkedList<>(); - helper = new LinkedList<>(); - } - - // 思路 1:数据栈和辅助栈在任何时候都同步 - public void push(int x) { - // 数据栈和辅助栈一定会增加元素 - data.push(x); - if (helper.isEmpty() || helper.peek() >= x) { - helper.push(x); - } else { - helper.push(helper.peek()); - } - } - - public void pop() { - // 两个栈都得 pop - if (!data.isEmpty()) { - helper.pop(); - data.pop(); - } - } - - public int top() { - if (!data.isEmpty()) { - return data.peek(); - } - throw new RuntimeException("栈中元素为空,此操作非法"); - } - - public int getMin() { - if (!helper.isEmpty()) { - return helper.peek(); - } - throw new RuntimeException("栈中元素为空,此操作非法"); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" index b4fb51a..1601e5f 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" @@ -2,52 +2,52 @@ import org.junit.jupiter.api.Assertions; +import java.util.Stack; + /** - * @see 20. 有效的括号 + * 20. 有效的括号 + * * @author Zhang Peng * @since 2020-06-09 */ public class 有效的括号 { public static void main(String[] args) { - Assertions.assertTrue(isValid("()")); - Assertions.assertTrue(isValid("{[]}")); - Assertions.assertFalse(isValid("([)]")); - Assertions.assertFalse(isValid("([)")); + Solution s = new Solution(); + Assertions.assertTrue(s.isValid("()")); + Assertions.assertTrue(s.isValid("{[]}")); + Assertions.assertFalse(s.isValid("([)]")); + Assertions.assertFalse(s.isValid("([)")); + Assertions.assertFalse(s.isValid("((")); + Assertions.assertTrue(s.isValid("(())")); } - public static boolean isValid(String s) { - if (s == null) { - return true; - } - - int length = s.length(); - if (length == 0) return true; - if (length % 2 != 0) return false; - - GenericStack stack = new GenericStack<>(); - for (char c : s.toCharArray()) { - Character top = stack.peek(); - if (top == null) { - stack.push(c); - continue; - } - - if (top == '(' && c == ')') { - stack.pop(); - } else if (top == '[' && c == ']') { - stack.pop(); - } else if (top == '{' && c == '}') { - stack.pop(); - } else { - stack.push(c); + static class Solution { + + public boolean isValid(String s) { + Stack stack = new Stack<>(); + for (char c : s.toCharArray()) { + switch (c) { + case ')': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '(') { return false; } + break; + case ']': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '[') { return false; } + break; + case '}': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '{') { return false; } + break; + default: + stack.push(c); + break; + } } + return stack.isEmpty(); } - if (stack.getSize() == 0) { - return true; - } - return false; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" index 8e9e1ef..40e5a99 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" @@ -1,73 +1,71 @@ package io.github.dunwu.algorithm.stack; -import java.util.LinkedList; +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; /** - * @see 面试题 03.05. 栈排序 + * 面试题 03.05. 栈排序 + * + * @author Zhang Peng + * @date 2025-11-26 */ public class 栈排序 { public static void main(String[] args) { - 栈排序 demo = new 栈排序(); - demo.push(1); - System.out.println(demo.stack1); - demo.push(2); - System.out.println(demo.stack1); - } - - public LinkedList stack1; - public LinkedList stack2; + SortedStack s = new SortedStack(); + s.push(1); + s.push(2); + Assertions.assertEquals(1, s.peek()); + s.pop(); + Assertions.assertEquals(2, s.peek()); - public 栈排序() { - stack1 = new LinkedList<>(); - stack2 = new LinkedList<>(); + SortedStack s2 = new SortedStack(); + s2.pop(); + s2.pop(); + s2.push(1); + s2.pop(); + Assertions.assertTrue(s2.isEmpty()); } - public void push(int val) { - if (isEmpty()) { - stack1.push(val); - return; - } + static class SortedStack { - if (!stack1.isEmpty()) { - move(val); - } + private Stack s; + private Stack t; - stack1.push(val); - while (!stack2.isEmpty()) { - Integer top = stack2.pop(); - stack1.push(top); + public SortedStack() { + s = new Stack<>(); + t = new Stack<>(); } - } - private void move(int val) { - if (stack1.isEmpty()) { - return; + public void push(int val) { + if (s.isEmpty()) { + s.push(val); + return; + } + while (!s.isEmpty() && s.peek() < val) { + t.push(s.pop()); + } + s.push(val); + while (!t.isEmpty()) { + s.push(t.pop()); + } } - int top = peek(); - if (top < val) { - stack2.push(stack1.pop()); - move(val); + public void pop() { + if (!s.isEmpty()) { + s.pop(); + } } - } - public int pop() { - if (stack1.isEmpty()) { - return -1; + public int peek() { + return s.isEmpty() ? -1 : s.peek(); } - return stack1.pop(); - } - public int peek() { - if (stack1.isEmpty()) { - return -1; + public boolean isEmpty() { + return s.isEmpty(); } - return stack1.peek(); - } - public boolean isEmpty() { - return stack1.isEmpty() && stack2.isEmpty(); } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" index 57d7263..3ae17cf 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" @@ -1,42 +1,56 @@ package io.github.dunwu.algorithm.stack; +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + /** + * 682. 棒球比赛 + * * @author Zhang Peng - * @see 682. 棒球比赛 * @since 2020-06-09 */ public class 棒球比赛 { public static void main(String[] args) { - System.out.println(calPoints("5", "2", "C", "D", "+")); - System.out.println(calPoints("5", "-2", "4", "C", "D", "9", "+", "+")); + Solution s = new Solution(); + Assertions.assertEquals(30, s.calPoints(new String[] { "5", "2", "C", "D", "+" })); + Assertions.assertEquals(27, s.calPoints(new String[] { "5", "-2", "4", "C", "D", "9", "+", "+" })); } - public static int calPoints(String... ops) { - int total = 0; - GenericStack stack = new GenericStack<>(); - for (String s : ops) { - if (s.equals("+")) { - int num1 = stack.pop(); - int num2 = stack.pop(); - int num = num1 + num2; - stack.push(num2); - stack.push(num1); - stack.push(num); - } else if (s.equals("D")) { - stack.push(stack.peek() * 2); - } else if (s.equals("C")) { - stack.pop(); - } else { - stack.push(Integer.valueOf(s)); + static class Solution { + + public int calPoints(String[] operations) { + Stack stack = new Stack<>(); + for (String op : operations) { + switch (op) { + case "C": + stack.pop(); + break; + case "D": + stack.push(stack.peek() * 2); + break; + case "+": + int cur = stack.pop(); + int prev = stack.pop(); + int next = prev + cur; + stack.push(prev); + stack.push(cur); + stack.push(next); + break; + default: + stack.push(Integer.valueOf(op)); + break; + } } - } - while (stack.getSize() != 0) { - total += stack.pop(); + int res = 0; + while (!stack.isEmpty()) { + res += stack.pop(); + } + return res; } - return total; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" index 6315118..5fee00b 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" @@ -2,40 +2,51 @@ import org.junit.jupiter.api.Assertions; +import java.util.Stack; + /** + * 844. 比较含退格的字符串 + * * @author Zhang Peng - * @see 844. 比较含退格的字符串 * @since 2020-06-09 */ public class 比较含退格的字符串 { public static void main(String[] args) { - Assertions.assertTrue(backspaceCompare("ab#c", "ad#c")); - Assertions.assertTrue(backspaceCompare("ab##", "c#d#")); - Assertions.assertTrue(backspaceCompare("a##c", "#a#c")); - Assertions.assertFalse(backspaceCompare("a#c", "b")); - } - - public static boolean backspaceCompare(String S, String T) { - return getFinalStr(S).equals(getFinalStr(T)); + Solution s = new Solution(); + Assertions.assertTrue(s.backspaceCompare("ab#c", "ad#c")); + Assertions.assertTrue(s.backspaceCompare("ab##", "c#d#")); + Assertions.assertTrue(s.backspaceCompare("a##c", "#a#c")); + Assertions.assertFalse(s.backspaceCompare("a#c", "b")); } - public static String getFinalStr(String S) { - GenericStack stack = new GenericStack<>(); - for (char c : S.toCharArray()) { - if (c == '#') { - stack.pop(); - } else { - stack.push(c); + static class Solution { + + public boolean backspaceCompare(String s, String t) { + Stack a = new Stack<>(); + Stack b = new Stack<>(); + for (char c : s.toCharArray()) { + if (c == '#') { + if (!a.isEmpty()) { a.pop(); } + } else { + a.push(c); + } + } + for (char c : t.toCharArray()) { + if (c == '#') { + if (!b.isEmpty()) { b.pop(); } + } else { + b.push(c); + } } - } - StringBuilder sb = new StringBuilder(); - while (stack.getSize() > 0) { - sb.append(stack.pop()); + if (a.size() != b.size()) { return false; } + while (!a.isEmpty()) { + if (a.pop() != b.pop()) { return false; } + } + return true; } - return sb.reverse().toString(); } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/SampleBrowser.java "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" similarity index 96% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/SampleBrowser.java rename to "codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" index ac44ef7..c12f789 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/SampleBrowser.java +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" @@ -5,10 +5,10 @@ * * @author chinalwb */ -public class SampleBrowser { +public class 用栈实现浏览器的前进后退 { public static void main(String[] args) { - SampleBrowser browser = new SampleBrowser(); + 用栈实现浏览器的前进后退 browser = new 用栈实现浏览器的前进后退(); browser.open("http://www.baidu.com"); browser.open("http://news.baidu.com/"); browser.open("http://news.baidu.com/ent"); @@ -30,7 +30,7 @@ public static void main(String[] args) { private LinkedListBasedStack backStack; private LinkedListBasedStack forwardStack; - public SampleBrowser() { + public 用栈实现浏览器的前进后退() { this.backStack = new LinkedListBasedStack(); this.forwardStack = new LinkedListBasedStack(); } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" index c340e5b..3051ecf 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" @@ -1,55 +1,81 @@ package io.github.dunwu.algorithm.stack; -import java.util.LinkedList; +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; /** - * @see 232. 用栈实现队列 + * 232. 用栈实现队列 + * + * @author Zhang Peng + * @since 2020-01-18 */ public class 用栈实现队列 { public static void main(String[] args) { - 用栈实现队列 queue = new 用栈实现队列(); - queue.push(1); - queue.push(2); - System.out.println(queue.peek()); // 返回 1 - System.out.println(queue.pop()); // 返回 1 - System.out.println(queue.empty()); // 返回 false - } - private LinkedList stack1; - private LinkedList stack2; + MyQueue q1 = new MyQueue(); + q1.push(1); // queue is: [1] + q1.push(2); // queue is: [1, 2] (leftmost is front of the queue) + Assertions.assertEquals(1, q1.peek()); + Assertions.assertEquals(1, q1.pop()); + Assertions.assertFalse(q1.empty()); + Assertions.assertEquals(2, q1.pop()); + Assertions.assertTrue(q1.empty()); - /** Initialize your data structure here. */ - public 用栈实现队列() { - stack1 = new LinkedList<>(); - stack2 = new LinkedList<>(); - } + MyQueue q2 = new MyQueue(); + q2.push(1); + q2.push(2); + Assertions.assertEquals(1, q2.pop()); + q2.push(3); + q2.push(4); + Assertions.assertEquals(2, q2.pop()); + Assertions.assertEquals(3, q2.peek()); - /** Push element x to the back of queue. */ - public void push(int x) { - stack1.push(x); + MyQueue q3 = new MyQueue(); + int max = 10; + for (int i = 1; i <= max; i++) { + q3.push(i); + } + for (int i = 1; i <= max; i++) { + Assertions.assertEquals(i, q3.peek()); + Assertions.assertEquals(i, q3.pop()); + } } - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - peek(); - return stack2.pop(); - } + static class MyQueue { - /** Get the front element. */ - public int peek() { - if (stack2.size() > 0) { - return stack2.peek(); + private Stack s1; + private Stack s2; + + public MyQueue() { + s1 = new Stack<>(); + s2 = new Stack<>(); } - while (!stack1.isEmpty()) { - stack2.push(stack1.pop()); + + public void push(int x) { + s1.push(x); + } + + public int pop() { + peek(); + Integer top = s2.pop(); + return top == null ? 0 : top; + } + + public int peek() { + if (s2.isEmpty()) { + while (!s1.isEmpty()) { + s2.push(s1.pop()); + } + } + return s2.isEmpty() ? 0 : s2.peek(); + } + + public boolean empty() { + return s1.isEmpty() && s2.isEmpty(); } - return stack2.peek(); - } - /** Returns whether the queue is empty. */ - public boolean empty() { - return stack1.isEmpty() && stack2.isEmpty(); } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" deleted file mode 100644 index c350aec..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.stack; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * 基于队列实现的栈 - * - * @author Zhang Peng - * @see 225. 用队列实现栈 - * @since 2020-01-18 - */ -public class 用队列实现栈 { - - public static void main(String[] args) { - 用队列实现栈 stack = new 用队列实现栈<>(); - stack.push(1); - stack.push(2); - System.out.println(stack.pop()); - System.out.println(stack.pop()); - } - - private Queue q1 = new LinkedList<>(); - - /** - * Initialize your data structure here. - */ - public 用队列实现栈() { } - - /** - * Push element x onto stack. - */ - public void push(T x) { - q1.add(x); - int sz = q1.size(); - while (sz > 1) { - q1.add(q1.remove()); - sz--; - } - } - - /** - * Removes the element on top of the stack and returns that element. - */ - public T pop() { - return q1.poll(); - } - - /** - * Get the top element. - */ - public T top() { - return q1.peek(); - } - - /** - * Returns whether the stack is empty. - */ - public boolean empty() { - return q1.isEmpty(); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" new file mode 100644 index 0000000..aa7357b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 71. 简化路径 + * + * @author Zhang Peng + * @since 2025-08-08 + */ +public class 简化路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("/home", s.simplifyPath("/home/")); + Assertions.assertEquals("/home/foo", s.simplifyPath("/home//foo/")); + Assertions.assertEquals("/home/user/Pictures", s.simplifyPath("/home/user/Documents/../Pictures")); + Assertions.assertEquals("/", s.simplifyPath("/../")); + Assertions.assertEquals("/.../b/d", s.simplifyPath("/.../a/../b/c/../d/./")); + } + + static class Solution { + + public String simplifyPath(String path) { + String[] parts = path.split("/"); + Stack stk = new Stack<>(); + // 借助栈计算最终的文件夹路径 + for (String part : parts) { + if (part.isEmpty() || part.equals(".")) { + continue; + } + if (part.equals("..")) { + if (!stk.isEmpty()) stk.pop(); + continue; + } + stk.push(part); + } + // 栈中存储的文件夹组成路径 + String res = ""; + while (!stk.isEmpty()) { + res = "/" + stk.pop() + res; + } + return res.isEmpty() ? "/" : res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" new file mode 100644 index 0000000..e83ae8f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 150. 逆波兰表达式求值 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 逆波兰表达式求值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(9, s.evalRPN(new String[] { "2", "1", "+", "3", "*" })); + Assertions.assertEquals(6, s.evalRPN(new String[] { "4", "13", "5", "/", "+" })); + Assertions.assertEquals(22, + s.evalRPN(new String[] { "10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+" })); + } + + static class Solution { + + public int evalRPN(String[] tokens) { + Stack stack = new Stack<>(); + for (String token : tokens) { + if ("+".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA + numB); + } else if ("-".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA - numB); + } else if ("*".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA * numB); + } else if ("/".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA / numB); + } else { + stack.push(Integer.parseInt(token)); + } + } + return stack.pop(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" new file mode 100644 index 0000000..48cdd0e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.stack; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.List; +import java.util.Stack; + +/** + * 143. 重排链表 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 重排链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 3, 4); + s.reorderList(input); + List list = ListNode.toList(input); + Assertions.assertArrayEquals(new Integer[] { 1, 4, 2, 3 }, list.toArray()); + + ListNode input2 = ListNode.buildList(1, 2, 3, 4, 5); + s.reorderList(input2); + List list2 = ListNode.toList(input2); + Assertions.assertArrayEquals(new Integer[] { 1, 5, 2, 4, 3 }, list2.toArray()); + } + + static class Solution { + + public void reorderList(ListNode head) { + Stack stack = new Stack<>(); + // 先把所有节点装进栈里,得到倒序结果 + ListNode p = head; + while (p != null) { + stack.push(p); + p = p.next; + } + + p = head; + while (p != null) { + // 链表尾部的节点 + ListNode last = stack.pop(); + ListNode next = p.next; + if (last == next || last.next == next) { + // 结束条件,链表节点数为奇数或偶数时均适用 + last.next = null; + break; + } + p.next = last; + last.next = next; + p = next; + } + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java index ade2285..b0a3d12 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java @@ -1,226 +1,293 @@ package io.github.dunwu.algorithm.str; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Zhang Peng - * @since 2020-05-12 + * @since 2020-01-18 */ public class StringAlgorithm { /** - * @see 01. 判定字符是否唯一 + * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 + *

+ * 示例 1: + *

+ * 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 示例 2: + *

+ * 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 示例 3: + *

+ * 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 + *

+ * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 + * + * @see 无重复字符的最长子串 */ - public static boolean isUnique(String str) { - if (str == null || str.length() <= 1) { return true; } - Set set = new HashSet<>(); - for (char c : str.toCharArray()) { - if (set.contains(c)) { - return false; + public static int lengthOfLongestSubstring(String s) { + if (null == s || s.length() == 0) { + return 0; + } + + int max = 0; + int left = 0; + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + if (map.containsKey(s.charAt(i))) { + left = Math.max(left, map.get(s.charAt(i)) + 1); } - set.add(c); + map.put(s.charAt(i), i); + max = Math.max(max, i - left + 1); } - return true; + return max; } /** - * @see 02. 判定是否互为字符重排 + * 编写一个函数来查找字符串数组中的最长公共前缀。 + *

+ * 如果不存在公共前缀,返回空字符串 ""。 + *

+ * 示例 1: + *

+ * 输入: ["flower","flow","flight"] 输出: "fl" 示例 2: + *

+ * 输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀。 说明: + *

+ * 所有输入只包含小写字母 a-z 。 + * + * @see 最长公共前缀 */ - public static boolean checkPermutation(String s1, String s2) { - if (!(s1 != null && s2 != null)) { - return false; + public static String longestCommonPrefix(String[] array) { + if (array == null || array.length == 0) { + return ""; + } else if (array.length == 1) { + return array[0]; + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array[0].length(); i++) { + char c = array[0].charAt(i); + boolean end = false; + for (int index = 1; index < array.length; index++) { + if (array[index].length() - 1 < i) { + end = true; + break; + } + + if (array[index].charAt(i) != c) { + end = true; + break; + } + } + if (end) { + break; + } else { + sb.append(c); + } + } + return sb.toString(); } + } - if (s1.length() != s2.length()) { + /** + * 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 + *

+ * 换句话说,第一个字符串的排列之一是第二个字符串的子串。 + *

+ * 示例1: 输入: s1 = "ab" s2 = "eidbaooo" 输出: True 解释: s2 包含 s1 的排列之一 ("ba"). + *

+ * 示例2: 输入: s1= "ab" s2 = "eidboaoo" 输出: False + *

+ * 注意:输入的字符串只包含小写字母,两个字符串的长度都在 [1, 10,000] 之间 + * + * @see 字符串的排列 + */ + public static boolean checkInclusion(String s1, String s2) { + if (s1 == null || s1.length() == 0 || s2 == null || s2.length() == 0) { return false; } - Map countMap1 = new HashMap<>(); - Map countMap2 = new HashMap<>(); - for (char c : s1.toCharArray()) { - if (countMap1.containsKey(c)) { - Integer cnt = countMap1.get(c); - cnt++; - } else { - countMap1.put(c, 1); - } - } - for (char c : s2.toCharArray()) { - if (countMap2.containsKey(c)) { - Integer cnt = countMap2.get(c); - cnt++; - } else { - countMap2.put(c, 1); - } - } + int len1 = s1.length(); + int len2 = s2.length(); - Set keySet1 = countMap1.keySet(); - Set keySet2 = countMap2.keySet(); - if (keySet1.size() != keySet2.size()) { - return false; + // 字母命中数统计 + int[] count1 = new int[26]; + int[] count2 = new int[26]; + + for (char c : s1.toCharArray()) { + count1[c - 'a']++; } - for (Character key : keySet1) { - if (!countMap2.containsKey(key)) { - return false; + for (int i = 0; i < len2; i++) { + if (i >= len1) { + count2[s2.charAt(i - len1) - 'a']--; } - if (countMap2.get(key).intValue() != countMap1.get(key).intValue()) { - return false; + count2[s2.charAt(i) - 'a']++; + if (Arrays.equals(count1, count2)) { + return true; } } - return true; + return false; } /** - * @see 03. URL化 + * 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 + *

+ * 示例 1: + *

+ * 输入: num1 = "2", num2 = "3" 输出: "6" 示例 2: + *

+ * 输入: num1 = "123", num2 = "456" 输出: "56088" + *

+ * 说明:num1 和 num2 的长度小于110。 num1 和 num2 只包含数字 0-9。 num1 和 num2 均不以零开头,除非是数字 0 本身。 + *

+ * 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 + * + * @see 字符串相乘 */ - public static String replaceSpaces(String str, int length) { - int realLength = str.length(); - int min = Math.min(length, realLength); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < min; i++) { - char c = str.charAt(i); - if (str.charAt(i) == ' ') { - sb.append("%20"); - } else { - sb.append(c); - } + public static String multiply(String num1, String num2) { + if (num1.equals("0") || num2.equals("0")) { + return "0"; } - return sb.toString(); - } - /** - * @see 04. 回文排列 - */ - public static boolean canPermutePalindrome(String s) { - int length = s.length(); - boolean isEven = (length % 2) == 0; - Map map = new HashMap<>(length); - for (char c : s.toCharArray()) { - if (map.containsKey(c)) { - Integer cnt = map.get(c); - cnt++; - map.put(c, cnt); - } else { - map.put(c, 1); - } - } + String result = "0"; + for (int i = num1.length() - 1; i >= 0; i--) { - int oddCount = 0; - for (char c : map.keySet()) { - int count = map.get(c); + int carry = 0; - if (isEven && (count % 2) != 0) { return false; } - if (!isEven && (count % 2) != 0) { - if (oddCount > 1) { - return false; - } - oddCount++; + StringBuilder tempBuilder = new StringBuilder(); + int value1 = num1.charAt(i) - '0'; + + for (int temp = i; temp < num1.length() - 1; temp++) { + tempBuilder.append("0"); } - } - return true; - } - /** - * @see 05. 一次编辑 - */ - public static boolean oneEditAway(String first, String second) { - if (first == null || second == null) { - return false; - } - int len1 = first.length(); - int len2 = second.length(); - if (Math.abs(len1 - len2) > 1) { - return false; - } - if (len2 > len1) { return oneEditAway(second, first); } + for (int j = num2.length() - 1; j >= 0; j--) { + int value2 = num2.charAt(j) - '0'; + int value = value1 * value2 + carry; + int current = value % 10; + carry = value / 10; + tempBuilder.append(current); + } - for (int i = 0; i < len2; i++) { - if (first.charAt(i) != second.charAt(i)) { - return first.substring(i + 1).equals(second.substring(len1 == len2 ? i + 1 : i)); + if (carry > 0) { + tempBuilder.append(carry); } + + result = add(result, tempBuilder.reverse().toString()); } - return true; + + return result; } - /** - * @see 06. 字符串压缩 - */ - public static String compressString(String str) { - if (str == null) { return null; } + public static String add(String num1, String num2) { + StringBuilder builder = new StringBuilder(); + int carry = 0; - int originLen = str.length(); - if (str.length() <= 1) { - return str; - } + for (int i = num1.length() - 1, j = num2.length() - 1; + i >= 0 || j >= 0; + i--, j--) { - int cnt = 0; - char mark = str.charAt(0); - StringBuilder sb = new StringBuilder(); - for (char c : str.toCharArray()) { - if (mark == c) { - cnt++; - } else { - sb.append(mark).append(cnt); - // 设置新字符 - mark = c; - cnt = 1; + int result = carry; + if (i >= 0) { + result += num1.charAt(i) - '0'; + } + if (j >= 0) { + result += num2.charAt(j) - '0'; } + carry = result / 10; + int current = result % 10; + builder.append(current); } - sb.append(mark).append(cnt); - - String newStr = sb.toString(); - if (newStr.length() >= originLen) { - return str; - } else { - return newStr; + if (carry > 0) { + builder.append(carry); } + return builder.reverse().toString(); } /** - * @see 09. 字符串轮转 + * 给定一个字符串,逐个翻转字符串中的每个单词。 + *

+ * 示例 1: 输入: "the sky is blue" 输出: "blue is sky the" + *

+ * 示例 2: 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + *

+ * 示例 3: 输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *

+ * 说明: 无空格字符构成一个单词。 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *

+ * 进阶: 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 + * + * @see 翻转字符串里的单词 */ - public static boolean isFlipedString(String s1, String s2) { - if (s1 == null || s2 == null) { return false; } - - int len1 = s1.length(), len2 = s2.length(); - if (len1 != len2) { - return false; + public static String reverseWords(String s) { + StringBuilder builder = new StringBuilder(); + List list = new ArrayList<>(); + for (char c : s.toCharArray()) { + if (c != ' ') { + builder.append(c); + } else { + if (!builder.toString().equals("")) { + list.add(builder.toString()); + } + builder = new StringBuilder(); + } } - - if (s1.equals(s2)) { - return true; + if (!builder.toString().equals("")) { + list.add(builder.toString()); } - if (len1 == 1) { - return false; - } + builder = new StringBuilder(); + for (int i = list.size() - 1; i >= 0; i--) { - int begin = s1.indexOf(s2.charAt(0)) - 1; - for (int i = begin; i < len2 - 1; i++) { - String temp = leftMove(s1, i + 1); - if (s2.equals(temp)) { - return true; + builder.append(list.get(i)); + if (i != 0) { + builder.append(" "); } } - return false; + return builder.toString(); } /** - * 字符串整体向左偏移 + * 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。 + *

+ * 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs + * 相对路径 + *

+ * 请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。 + *

+ * 示例 1: 输入:"/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。 + *

+ * 示例 2: 输入:"/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。 + *

+ * 示例 3: 输入:"/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 + *

+ * 示例 4: 输入:"/a/./b/../../c/" 输出:"/c" + *

+ * 示例 5: 输入:"/a/../../b/../c//.//" 输出:"/c" + *

+ * 示例 6: 输入:"/a//b////c/d//././/.." 输出:"/a/b/c" + * + * @see 简化路径 */ - private static String leftMove(String str, int pos) { - if (str == null || str.length() <= 1 || pos <= 0) { - return str; + public static String simplifyPath(String path) { + if (path.equals("/")) { + return path; + } + + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + if (path.startsWith("/../")) { + path = path.replaceFirst("/../", "/"); } - String temp = str.substring(pos); - temp = temp + str.substring(0, pos); - return temp; + path = path.replaceAll("//", "/"); + return path; } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java similarity index 98% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java index 60dc9fc..1dd91de 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java @@ -1,4 +1,4 @@ -package io.github.dunwu.algorithm.string; +package io.github.dunwu.algorithm.str; import java.util.HashMap; import java.util.Map; diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/StringAlgorithm.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/StringAlgorithm.java deleted file mode 100644 index b02ed5b..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/StringAlgorithm.java +++ /dev/null @@ -1,289 +0,0 @@ -package io.github.dunwu.algorithm.string; - -import java.util.*; - -/** - * @author Zhang Peng - * @since 2020-01-18 - */ -public class StringAlgorithm { - - /** - * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 - *

- * 示例 1: - *

- * 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 示例 2: - *

- * 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 示例 3: - *

- * 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 - *

- * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 - * - * @see 无重复字符的最长子串 - */ - public static int lengthOfLongestSubstring(String s) { - if (null == s || s.length() == 0) { - return 0; - } - - int max = 0; - int left = 0; - Map map = new HashMap<>(); - for (int i = 0; i < s.length(); i++) { - if (map.containsKey(s.charAt(i))) { - left = Math.max(left, map.get(s.charAt(i)) + 1); - } - map.put(s.charAt(i), i); - max = Math.max(max, i - left + 1); - } - return max; - } - - /** - * 编写一个函数来查找字符串数组中的最长公共前缀。 - *

- * 如果不存在公共前缀,返回空字符串 ""。 - *

- * 示例 1: - *

- * 输入: ["flower","flow","flight"] 输出: "fl" 示例 2: - *

- * 输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀。 说明: - *

- * 所有输入只包含小写字母 a-z 。 - * - * @see 最长公共前缀 - */ - public static String longestCommonPrefix(String[] array) { - if (array == null || array.length == 0) { - return ""; - } else if (array.length == 1) { - return array[0]; - } else { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < array[0].length(); i++) { - char c = array[0].charAt(i); - boolean end = false; - for (int index = 1; index < array.length; index++) { - if (array[index].length() - 1 < i) { - end = true; - break; - } - - if (array[index].charAt(i) != c) { - end = true; - break; - } - } - if (end) { - break; - } else { - sb.append(c); - } - } - return sb.toString(); - } - } - - /** - * 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 - *

- * 换句话说,第一个字符串的排列之一是第二个字符串的子串。 - *

- * 示例1: 输入: s1 = "ab" s2 = "eidbaooo" 输出: True 解释: s2 包含 s1 的排列之一 ("ba"). - *

- * 示例2: 输入: s1= "ab" s2 = "eidboaoo" 输出: False - *

- * 注意:输入的字符串只包含小写字母,两个字符串的长度都在 [1, 10,000] 之间 - * - * @see 字符串的排列 - */ - public static boolean checkInclusion(String s1, String s2) { - if (s1 == null || s1.length() == 0 || s2 == null || s2.length() == 0) { - return false; - } - - int len1 = s1.length(); - int len2 = s2.length(); - - // 字母命中数统计 - int[] count1 = new int[26]; - int[] count2 = new int[26]; - - for (char c : s1.toCharArray()) { - count1[c - 'a']++; - } - - for (int i = 0; i < len2; i++) { - if (i >= len1) { - count2[s2.charAt(i - len1) - 'a']--; - } - - count2[s2.charAt(i) - 'a']++; - if (Arrays.equals(count1, count2)) { - return true; - } - } - return false; - } - - /** - * 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 - *

- * 示例 1: - *

- * 输入: num1 = "2", num2 = "3" 输出: "6" 示例 2: - *

- * 输入: num1 = "123", num2 = "456" 输出: "56088" - *

- * 说明:num1 和 num2 的长度小于110。 num1 和 num2 只包含数字 0-9。 num1 和 num2 均不以零开头,除非是数字 0 本身。 - *

- * 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 - * - * @see 字符串相乘 - */ - public static String multiply(String num1, String num2) { - if (num1.equals("0") || num2.equals("0")) { - return "0"; - } - - String result = "0"; - for (int i = num1.length() - 1; i >= 0; i--) { - - int carry = 0; - - StringBuilder tempBuilder = new StringBuilder(); - int value1 = num1.charAt(i) - '0'; - - for (int temp = i; temp < num1.length() - 1; temp++) { - tempBuilder.append("0"); - } - - for (int j = num2.length() - 1; j >= 0; j--) { - int value2 = num2.charAt(j) - '0'; - int value = value1 * value2 + carry; - int current = value % 10; - carry = value / 10; - tempBuilder.append(current); - } - - if (carry > 0) { - tempBuilder.append(carry); - } - - result = add(result, tempBuilder.reverse().toString()); - } - - return result; - } - - public static String add(String num1, String num2) { - StringBuilder builder = new StringBuilder(); - int carry = 0; - - for (int i = num1.length() - 1, j = num2.length() - 1; - i >= 0 || j >= 0; - i--, j--) { - - int result = carry; - if (i >= 0) { - result += num1.charAt(i) - '0'; - } - if (j >= 0) { - result += num2.charAt(j) - '0'; - } - carry = result / 10; - int current = result % 10; - builder.append(current); - } - if (carry > 0) { - builder.append(carry); - } - return builder.reverse().toString(); - } - - /** - * 给定一个字符串,逐个翻转字符串中的每个单词。 - *

- * 示例 1: 输入: "the sky is blue" 输出: "blue is sky the" - *

- * 示例 2: 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 - *

- * 示例 3: 输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 - *

- * 说明: 无空格字符构成一个单词。 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 - *

- * 进阶: 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 - * - * @see 翻转字符串里的单词 - */ - public static String reverseWords(String s) { - StringBuilder builder = new StringBuilder(); - List list = new ArrayList<>(); - for (char c : s.toCharArray()) { - if (c != ' ') { - builder.append(c); - } else { - if (!builder.toString().equals("")) { - list.add(builder.toString()); - } - builder = new StringBuilder(); - } - } - if (!builder.toString().equals("")) { - list.add(builder.toString()); - } - - builder = new StringBuilder(); - for (int i = list.size() - 1; i >= 0; i--) { - - builder.append(list.get(i)); - if (i != 0) { - builder.append(" "); - } - } - return builder.toString(); - } - - /** - * 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。 - *

- * 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs - * 相对路径 - *

- * 请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。 - *

- * 示例 1: 输入:"/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。 - *

- * 示例 2: 输入:"/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。 - *

- * 示例 3: 输入:"/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 - *

- * 示例 4: 输入:"/a/./b/../../c/" 输出:"/c" - *

- * 示例 5: 输入:"/a/../../b/../c//.//" 输出:"/c" - *

- * 示例 6: 输入:"/a//b////c/d//././/.." 输出:"/a/b/c" - * - * @see 简化路径 - */ - public static String simplifyPath(String path) { - if (path.equals("/")) { - return path; - } - - if (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - - if (path.startsWith("/../")) { - path = path.replaceFirst("/../", "/"); - } - - path = path.replaceAll("//", "/"); - return path; - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java index d953673..cac20fd 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java @@ -1,6 +1,11 @@ package io.github.dunwu.algorithm.tree; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; /** * 二叉树 @@ -23,7 +28,7 @@ public BTree(TreeNode root) { this.root = root; } - public static > BTree buildTree(T... array) { + public static > BTree build(T... array) { BTree tree = new BTree<>(); List> list = new ArrayList<>(); @@ -79,32 +84,7 @@ public static > BTree buildTree(T... array) { * @return true / false */ public static > boolean isEquals(final BTree tree1, final BTree tree2) { - return isEquals(tree1.root, tree2.root); - } - - /** - * 判断两颗二叉树是否完全一致 - * - * @param root1 二叉树根节点,类型:{@link BTree#root} - * @param root2 二叉树根节点,类型:{@link BTree#root} - * @param 元素类型 - * @return true / false - * @see 相同的树 - */ - private static > boolean isEquals(TreeNode root1, TreeNode root2) { - if (root1 == null && root2 == null) { - return true; - } - - if (root1 == null || root2 == null) { - return false; - } - - if (!root1.value.equals(root2.value)) { - return false; - } - - return isEquals(root1.left, root2.left) && isEquals(root1.right, root2.right); + return TreeNode.isEquals(tree1.root, tree2.root); } /** @@ -116,37 +96,16 @@ private static > boolean isEquals(TreeNode root1, Tre * @see 叶子相似的树 */ public static > boolean isLeafSimilar(final BTree tree1, final BTree tree2) { - List leafs1 = new LinkedList<>(); - List leafs2 = new LinkedList<>(); - getLeafNodes(tree1, leafs1); - getLeafNodes(tree2, leafs2); + List leafs1 = TreeNode.getLeafNodes(tree1.root); + List leafs2 = TreeNode.getLeafNodes(tree2.root); return Arrays.equals(leafs1.toArray(), leafs2.toArray()); } /** * 获取叶子节点 - * - * @param tree {@link BTree} - * @param leafs [出参]叶子节点列表{@link List} - * @param 元素类型 - */ - public static > void getLeafNodes(BTree tree, List leafs) { - getLeafNodes(tree.root, leafs); - } - - /** - * 获取叶子节点 - * - * @param root {@link TreeNode} - * @param leafs [出参]叶子节点列表{@link List} - * @param 元素类型 */ - private static > void getLeafNodes(TreeNode root, List leafs) { - if (root == null) { return; } - - if (root.left == null && root.right == null) { leafs.add(root.value); } - getLeafNodes(root.left, leafs); - getLeafNodes(root.right, leafs); + public List getLeafNodes() { + return TreeNode.getLeafNodes(this.root); } /** @@ -155,24 +114,7 @@ private static > void getLeafNodes(TreeNode root, Lis * @return 二叉树的最大深度 */ public int maxDepth() { - return maxDepth(this.root); - } - - /** - * 采用递归方法获取二叉树的最大深度 - * - * @param root 二叉树根节点,类型:{@link BTree#root} - * @return 二叉树的最大深度 - * @see 二叉树的最大深度 - */ - private int maxDepth(TreeNode root) { - if (root == null) return 0; - - int left = maxDepth(root.left); - - int right = maxDepth(root.right); - - return Math.max(left, right) + 1; + return TreeNode.maxDepth(this.root); } /** @@ -181,86 +123,246 @@ private int maxDepth(TreeNode root) { * @return 二叉树的最小深度 */ public int minDepth() { - return minDepth(this.root); + return TreeNode.minDepth(this.root); } + // ------------------------------------------------------------- 遍历元素 + /** - * 采用递归方法获取二叉树的最小深度 + * 将二叉树按层次遍历顺序转换为列表,即广度优先搜索(BFS) * - * @param root 二叉树根节点,类型:{@link BTree#root} - * @return 二叉树的最小深度 - * @see 二叉树的最小深度 + * @return {@link List>} */ - private int minDepth(TreeNode root) { - if (root == null) { return 0; } + public List> levelOrderLists() { + return TreeNode.levelOrderLists(this.root); + } + + public static class TreeNode> { - int left = minDepth(root.left); + T val; - int right = minDepth(root.right); + TreeNode left; + + TreeNode right; - if (left == 0 || right == 0) { - return left + right + 1; + public TreeNode(T val) { + this.val = val; } - return Math.min(left, right) + 1; - } + public TreeNode(T val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } - // ------------------------------------------------------------- 遍历元素 + @Override + public String toString() { + return String.valueOf(val); + } - /** - * 将二叉树按层次遍历顺序转换为列表,即广度优先搜索(BFS) - * - * @return {@link List>} - * @see 二叉树的层次遍历 II - */ - public List> levelOrderLists() { - List> lists = new ArrayList<>(); - if (root == null) { return lists; } - Queue> queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - List temp = new ArrayList<>(); - for (int i = 0; i < size; i++) { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TreeNode)) return false; + TreeNode treeNode = (TreeNode) o; + return Objects.equals(val, treeNode.val) && + Objects.equals(left, treeNode.left) && + Objects.equals(right, treeNode.right); + } + + @Override + public int hashCode() { + return Objects.hash(val, left, right); + } + + public static > TreeNode build(T... values) { + + if (values == null || values.length == 0 || values[0] == null) { + return null; + } + + Queue> queue = new LinkedList<>(); + TreeNode root = new TreeNode<>(values[0]); + queue.offer(root); + + int i = 1; + while (!queue.isEmpty()) { + TreeNode current = queue.poll(); + + // 处理左子节点 + if (i < values.length && values[i] != null) { + current.left = new TreeNode(values[i]); + queue.offer(current.left); + } + i++; + + // 处理右子节点 + if (i < values.length && values[i] != null) { + current.right = new TreeNode(values[i]); + queue.offer(current.right); + } + i++; + } + + return root; + } + + public static > TreeNode find(TreeNode root, T val) { + if (root == null || Objects.equals(root.val, val)) { return root; } + TreeNode left = find(root.left, val); + if (left != null) return left; + return find(root.right, val); + } + + public static > List> toList(TreeNode root) { + List> list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue> queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { TreeNode node = queue.poll(); - temp.add(node.value); - if (node.left != null) { queue.offer(node.left); } - if (node.right != null) { queue.offer(node.right); } + list.add(node); + if (node == null) continue; + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; } - lists.add(temp); + return list.subList(0, last + 1); } - return lists; - } - public List levelOrderList() { - List list = new ArrayList<>(); - if (root == null) { return list; } - Queue> queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - for (int i = 0; i < size; i++) { + public static > List toValueList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue> queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { TreeNode node = queue.poll(); - list.add(node.value); - if (node.left != null) { queue.offer(node.left); } - if (node.right != null) { queue.offer(node.right); } + if (node == null) { + list.add(null); + continue; + } else { + list.add(node.val); + } + + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; } + return list.subList(0, last + 1); + } + + /** + * 判断两颗二叉树是否完全一致 + * + * @param root1 二叉树根节点,类型:{@link BTree#root} + * @param root2 二叉树根节点,类型:{@link BTree#root} + * @param 元素类型 + * @return true / false + * @see 相同的树 + */ + private static > boolean isEquals(TreeNode root1, TreeNode root2) { + if (root1 == null && root2 == null) { return true; } + if (root1 == null || root2 == null) { return false; } + if (!root1.val.equals(root2.val)) { return false; } + return isEquals(root1.left, root2.left) && isEquals(root1.right, root2.right); } - return list; - } - static class TreeNode> { + /** + * 获取叶子节点 + * + * @param root {@link TreeNode} + * @param 元素类型 + */ + public static > List getLeafNodes(TreeNode root) { + List res = new ArrayList<>(); + getLeafNodes(root, res); + return res; + } - T value; + /** + * 获取叶子节点 + * + * @param root {@link TreeNode} + * @param leafs [出参]叶子节点列表{@link List} + * @param 元素类型 + */ + private static > void getLeafNodes(TreeNode root, List leafs) { + if (root == null) { return; } + if (root.left == null && root.right == null) { leafs.add(root.val); } + getLeafNodes(root.left, leafs); + getLeafNodes(root.right, leafs); + } - TreeNode left; + /** + * 采用递归方法获取二叉树的最大深度 + * + * @param root 二叉树根节点,类型:{@link BTree#root} + * @return 二叉树的最大深度 + * @see 二叉树的最大深度 + */ + public static > int maxDepth(TreeNode root) { + if (root == null) { return 0; } + int left = maxDepth(root.left); + int right = maxDepth(root.right); + return Math.max(left, right) + 1; + } - TreeNode right; + /** + * 采用递归方法获取二叉树的最小深度 + * + * @param root 二叉树根节点,类型:{@link BTree#root} + * @return 二叉树的最小深度 + * @see 二叉树的最小深度 + */ + public static > int minDepth(TreeNode root) { + if (root == null) { return 0; } + int left = minDepth(root.left); + int right = minDepth(root.right); + if (left == 0 || right == 0) { + return left + right + 1; + } + return Math.min(left, right) + 1; + } - public TreeNode(T value, TreeNode left, TreeNode right) { - this.value = value; - this.left = left; - this.right = right; + /** + * 将二叉树按层次遍历顺序转换为列表,即广度优先搜索(BFS) + * + * @return {@link List>} + * @see 二叉树的层次遍历 II + */ + public static > List> levelOrderLists(TreeNode root) { + List> lists = new ArrayList<>(); + if (root == null) { return lists; } + Queue> queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + List list = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + lists.add(list); + } + return lists; } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java new file mode 100644 index 0000000..8da17c6 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.List; + +/** + * 基本示例 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class BaseCase { + + public static class Node extends NTree { + + public Node(int val) { + super(val); + } + + public Node(int val, List children) { + super(val, children); + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BinaryTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BinaryTree.java deleted file mode 100644 index 84fbd3d..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BinaryTree.java +++ /dev/null @@ -1,937 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import io.github.dunwu.algorithm.common.ITree; - -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Deque; - -/** - * B树是一种树数据结构,可以对数据进行排序,并允许以对数时间进行搜索,顺序访问,插入和删除。 - *

- * B树是二叉搜索树的一般化,因为节点可以有两个以上的子节点。 - *

- * 与自平衡二进制搜索树不同,B树针对读取和写入大块数据的系统进行了优化。 - *

- * 它通常用于数据库和文件系统。 - *

- * - * @author Justin Wetherell - * @see B-Tree (Wikipedia) - */ -@SuppressWarnings("ALL") -public class BinaryTree> implements ITree { - - private int minKeySize = 1; - - private int minChildrenSize = minKeySize + 1; // 2 - - private int maxKeySize = 2 * minKeySize; // 2 - - private int maxChildrenSize = maxKeySize + 1; // 3 - - private Node root = null; - - private int size = 0; - - /** - * Constructor for B-Tree which defaults to a 2-3 B-Tree. - */ - public BinaryTree() { - } - - /** - * Constructor for B-Tree of ordered parameter. Order here means minimum number of keys in a non-root node. - * - * @param order of the B-Tree. - */ - public BinaryTree(int order) { - this.minKeySize = order; - this.minChildrenSize = minKeySize + 1; - this.maxKeySize = 2 * minKeySize; - this.maxChildrenSize = maxKeySize + 1; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(T value) { - if (root == null) { - root = new Node(null, maxKeySize, maxChildrenSize); - root.addKey(value); - } else { - Node node = root; - while (node != null) { - if (node.numberOfChildren() == 0) { - node.addKey(value); - if (node.numberOfKeys() <= maxKeySize) { - // A-OK - break; - } - // Need to split up - split(node); - break; - } - // Navigate - - // Lesser or equal - T lesser = node.getKey(0); - if (value.compareTo(lesser) <= 0) { - node = node.getChild(0); - continue; - } - - // Greater - int numberOfKeys = node.numberOfKeys(); - int last = numberOfKeys - 1; - T greater = node.getKey(last); - if (value.compareTo(greater) > 0) { - node = node.getChild(numberOfKeys); - continue; - } - - // Search internal nodes - for (int i = 1; i < node.numberOfKeys(); i++) { - T prev = node.getKey(i - 1); - T next = node.getKey(i); - if (value.compareTo(prev) > 0 && value.compareTo(next) <= 0) { - node = node.getChild(i); - break; - } - } - } - } - - size++; - - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public T remove(T value) { - T removed = null; - Node node = this.getNode(value); - removed = remove(value, node); - return removed; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - root = null; - size = 0; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(T value) { - Node node = getNode(value); - return (node != null); - } - - /** - * Get the node with value. - * - * @param value to find in the tree. - * @return Node with value. - */ - private Node getNode(T value) { - Node node = root; - while (node != null) { - T lesser = node.getKey(0); - if (value.compareTo(lesser) < 0) { - if (node.numberOfChildren() > 0) { - node = node.getChild(0); - } else { - node = null; - } - continue; - } - - int numberOfKeys = node.numberOfKeys(); - int last = numberOfKeys - 1; - T greater = node.getKey(last); - if (value.compareTo(greater) > 0) { - if (node.numberOfChildren() > numberOfKeys) { - node = node.getChild(numberOfKeys); - } else { - node = null; - } - continue; - } - - for (int i = 0; i < numberOfKeys; i++) { - T currentValue = node.getKey(i); - if (currentValue.compareTo(value) == 0) { - return node; - } - - int next = i + 1; - if (next <= last) { - T nextValue = node.getKey(next); - if (currentValue.compareTo(value) < 0 && nextValue.compareTo(value) > 0) { - if (next < node.numberOfChildren()) { - node = node.getChild(next); - break; - } - return null; - } - } - } - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return size; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean validate() { - if (root == null) { - return true; - } - return validateNode(root); - } - - /** - * {@inheritDoc} - */ - @Override - public java.util.Collection toCollection() { - return (new JavaCompatibleBinaryTree(this)); - } - - /** - * The node's key size is greater than maxKeySize, split down the middle. - * - * @param nodeToSplit to split. - */ - private void split(Node nodeToSplit) { - Node node = nodeToSplit; - int numberOfKeys = node.numberOfKeys(); - int medianIndex = numberOfKeys / 2; - T medianValue = node.getKey(medianIndex); - - Node left = new Node(null, maxKeySize, maxChildrenSize); - for (int i = 0; i < medianIndex; i++) { - left.addKey(node.getKey(i)); - } - if (node.numberOfChildren() > 0) { - for (int j = 0; j <= medianIndex; j++) { - Node c = node.getChild(j); - left.addChild(c); - } - } - - Node right = new Node(null, maxKeySize, maxChildrenSize); - for (int i = medianIndex + 1; i < numberOfKeys; i++) { - right.addKey(node.getKey(i)); - } - if (node.numberOfChildren() > 0) { - for (int j = medianIndex + 1; j < node.numberOfChildren(); j++) { - Node c = node.getChild(j); - right.addChild(c); - } - } - - if (node.parent == null) { - // new root, height of tree is increased - Node newRoot = new Node(null, maxKeySize, maxChildrenSize); - newRoot.addKey(medianValue); - node.parent = newRoot; - root = newRoot; - node = root; - node.addChild(left); - node.addChild(right); - } else { - // Move the median value up to the parent - Node parent = node.parent; - parent.addKey(medianValue); - parent.removeChild(node); - parent.addChild(left); - parent.addChild(right); - - if (parent.numberOfKeys() > maxKeySize) { - split(parent); - } - } - } - - /** - * Remove the value from the Node and check invariants - * - * @param value T to remove from the tree - * @param node Node to remove value from - * @return True if value was removed from the tree. - */ - private T remove(T value, Node node) { - if (node == null) { - return null; - } - - T removed = null; - int index = node.indexOf(value); - removed = node.removeKey(value); - if (node.numberOfChildren() == 0) { - // leaf node - if (node.parent != null && node.numberOfKeys() < minKeySize) { - this.combined(node); - } else if (node.parent == null && node.numberOfKeys() == 0) { - // Removing root node with no keys or children - root = null; - } - } else { - // internal node - Node lesser = node.getChild(index); - Node greatest = this.getGreatestNode(lesser); - T replaceValue = this.removeGreatestValue(greatest); - node.addKey(replaceValue); - if (greatest.parent != null && greatest.numberOfKeys() < minKeySize) { - this.combined(greatest); - } - if (greatest.numberOfChildren() > maxChildrenSize) { - this.split(greatest); - } - } - - size--; - - return removed; - } - - /** - * Remove greatest valued key from node. - * - * @param node to remove greatest value from. - * @return value removed; - */ - private T removeGreatestValue(Node node) { - T value = null; - if (node.numberOfKeys() > 0) { - value = node.removeKey(node.numberOfKeys() - 1); - } - return value; - } - - /** - * Get the greatest valued child from node. - * - * @param nodeToGet child with the greatest value. - * @return Node child with greatest value. - */ - private Node getGreatestNode(Node nodeToGet) { - Node node = nodeToGet; - while (node.numberOfChildren() > 0) { - node = node.getChild(node.numberOfChildren() - 1); - } - return node; - } - - /** - * Combined children keys with parent when size is less than minKeySize. - * - * @param node with children to combined. - * @return True if combined successfully. - */ - private boolean combined(Node node) { - Node parent = node.parent; - int index = parent.indexOf(node); - int indexOfLeftNeighbor = index - 1; - int indexOfRightNeighbor = index + 1; - - Node rightNeighbor = null; - int rightNeighborSize = -minChildrenSize; - if (indexOfRightNeighbor < parent.numberOfChildren()) { - rightNeighbor = parent.getChild(indexOfRightNeighbor); - rightNeighborSize = rightNeighbor.numberOfKeys(); - } - - // Try to borrow neighbor - if (rightNeighbor != null && rightNeighborSize > minKeySize) { - // Try to borrow from right neighbor - T removeValue = rightNeighbor.getKey(0); - int prev = getIndexOfPreviousValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - T neighborValue = rightNeighbor.removeKey(0); - node.addKey(parentValue); - parent.addKey(neighborValue); - if (rightNeighbor.numberOfChildren() > 0) { - node.addChild(rightNeighbor.removeChild(0)); - } - } else { - Node leftNeighbor = null; - int leftNeighborSize = -minChildrenSize; - if (indexOfLeftNeighbor >= 0) { - leftNeighbor = parent.getChild(indexOfLeftNeighbor); - leftNeighborSize = leftNeighbor.numberOfKeys(); - } - - if (leftNeighbor != null && leftNeighborSize > minKeySize) { - // Try to borrow from left neighbor - T removeValue = leftNeighbor.getKey(leftNeighbor.numberOfKeys() - 1); - int prev = getIndexOfNextValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - T neighborValue = leftNeighbor.removeKey(leftNeighbor.numberOfKeys() - 1); - node.addKey(parentValue); - parent.addKey(neighborValue); - if (leftNeighbor.numberOfChildren() > 0) { - node.addChild(leftNeighbor.removeChild(leftNeighbor.numberOfChildren() - 1)); - } - } else if (rightNeighbor != null && parent.numberOfKeys() > 0) { - // Can't borrow from neighbors, try to combined with right neighbor - T removeValue = rightNeighbor.getKey(0); - int prev = getIndexOfPreviousValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - parent.removeChild(rightNeighbor); - node.addKey(parentValue); - for (int i = 0; i < rightNeighbor.keysSize; i++) { - T v = rightNeighbor.getKey(i); - node.addKey(v); - } - for (int i = 0; i < rightNeighbor.childrenSize; i++) { - Node c = rightNeighbor.getChild(i); - node.addChild(c); - } - - if (parent.parent != null && parent.numberOfKeys() < minKeySize) { - // removing key made parent too small, combined up tree - this.combined(parent); - } else if (parent.numberOfKeys() == 0) { - // parent no longer has keys, make this node the new root - // which decreases the height of the tree - node.parent = null; - root = node; - } - } else if (leftNeighbor != null && parent.numberOfKeys() > 0) { - // Can't borrow from neighbors, try to combined with left neighbor - T removeValue = leftNeighbor.getKey(leftNeighbor.numberOfKeys() - 1); - int prev = getIndexOfNextValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - parent.removeChild(leftNeighbor); - node.addKey(parentValue); - for (int i = 0; i < leftNeighbor.keysSize; i++) { - T v = leftNeighbor.getKey(i); - node.addKey(v); - } - for (int i = 0; i < leftNeighbor.childrenSize; i++) { - Node c = leftNeighbor.getChild(i); - node.addChild(c); - } - - if (parent.parent != null && parent.numberOfKeys() < minKeySize) { - // removing key made parent too small, combined up tree - this.combined(parent); - } else if (parent.numberOfKeys() == 0) { - // parent no longer has keys, make this node the new root - // which decreases the height of the tree - node.parent = null; - root = node; - } - } - } - - return true; - } - - /** - * Get the index of previous key in node. - * - * @param node to find the previous key in. - * @param value to find a previous value for. - * @return index of previous key or -1 if not found. - */ - private int getIndexOfPreviousValue(Node node, T value) { - for (int i = 1; i < node.numberOfKeys(); i++) { - T t = node.getKey(i); - if (t.compareTo(value) >= 0) { - return i - 1; - } - } - return node.numberOfKeys() - 1; - } - - /** - * Get the index of next key in node. - * - * @param node to find the next key in. - * @param value to find a next value for. - * @return index of next key or -1 if not found. - */ - private int getIndexOfNextValue(Node node, T value) { - for (int i = 0; i < node.numberOfKeys(); i++) { - T t = node.getKey(i); - if (t.compareTo(value) >= 0) { - return i; - } - } - return node.numberOfKeys() - 1; - } - - /** - * Validate the node according to the B-Tree invariants. - * - * @param node to validate. - * @return True if valid. - */ - private boolean validateNode(Node node) { - int keySize = node.numberOfKeys(); - if (keySize > 1) { - // Make sure the keys are sorted - for (int i = 1; i < keySize; i++) { - T p = node.getKey(i - 1); - T n = node.getKey(i); - if (p.compareTo(n) > 0) { - return false; - } - } - } - int childrenSize = node.numberOfChildren(); - if (node.parent == null) { - // root - if (keySize > maxKeySize) { - // check max key size. root does not have a min key size - return false; - } else if (childrenSize == 0) { - // if root, no children, and keys are valid - return true; - } else if (childrenSize < minChildrenSize) { - // root should have zero or at least two children - return false; - } else if (childrenSize > maxChildrenSize) { - return false; - } - } else { - // non-root - if (keySize < minKeySize) { - return false; - } else if (keySize > maxKeySize) { - return false; - } else if (childrenSize == 0) { - return true; - } else if (keySize != (childrenSize - 1)) { - // If there are chilren, there should be one more child then - // keys - return false; - } else if (childrenSize < minChildrenSize) { - return false; - } else if (childrenSize > maxChildrenSize) { - return false; - } - } - - Node first = node.getChild(0); - // The first child's last key should be less than the node's first key - if (first.getKey(first.numberOfKeys() - 1).compareTo(node.getKey(0)) > 0) { - return false; - } - - Node last = node.getChild(node.numberOfChildren() - 1); - // The last child's first key should be greater than the node's last key - if (last.getKey(0).compareTo(node.getKey(node.numberOfKeys() - 1)) < 0) { - return false; - } - - // Check that each node's first and last key holds it's invariance - for (int i = 1; i < node.numberOfKeys(); i++) { - T p = node.getKey(i - 1); - T n = node.getKey(i); - Node c = node.getChild(i); - if (p.compareTo(c.getKey(0)) > 0) { - return false; - } - if (n.compareTo(c.getKey(c.numberOfKeys() - 1)) < 0) { - return false; - } - } - - for (int i = 0; i < node.childrenSize; i++) { - Node c = node.getChild(i); - boolean valid = this.validateNode(c); - if (!valid) { - return false; - } - } - - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return TreePrinter.getString(this); - } - - private static class Node> { - - protected Node parent = null; - - private T[] keys = null; - - private int keysSize = 0; - - private Node[] children = null; - - private int childrenSize = 0; - - private Comparator> comparator = new Comparator>() { - @Override - public int compare(Node arg0, Node arg1) { - return arg0.getKey(0).compareTo(arg1.getKey(0)); - } - }; - - private Node(Node parent, int maxKeySize, int maxChildrenSize) { - this.parent = parent; - this.keys = (T[]) new Comparable[maxKeySize + 1]; - this.keysSize = 0; - this.children = new Node[maxChildrenSize + 1]; - this.childrenSize = 0; - } - - private int indexOf(T value) { - for (int i = 0; i < keysSize; i++) { - if (keys[i].equals(value)) { - return i; - } - } - return -1; - } - - private void addKey(T value) { - keys[keysSize++] = value; - Arrays.sort(keys, 0, keysSize); - } - - private T removeKey(T value) { - T removed = null; - boolean found = false; - if (keysSize == 0) { - return null; - } - for (int i = 0; i < keysSize; i++) { - if (keys[i].equals(value)) { - found = true; - removed = keys[i]; - } else if (found) { - // shift the rest of the keys down - keys[i - 1] = keys[i]; - } - } - if (found) { - keysSize--; - keys[keysSize] = null; - } - return removed; - } - - private T removeKey(int index) { - if (index >= keysSize) { - return null; - } - T value = keys[index]; - for (int i = index + 1; i < keysSize; i++) { - // shift the rest of the keys down - keys[i - 1] = keys[i]; - } - keysSize--; - keys[keysSize] = null; - return value; - } - - private Node getChild(int index) { - if (index >= childrenSize) { - return null; - } - return children[index]; - } - - private int indexOf(Node child) { - for (int i = 0; i < childrenSize; i++) { - if (children[i].equals(child)) { - return i; - } - } - return -1; - } - - private boolean addChild(Node child) { - child.parent = this; - children[childrenSize++] = child; - Arrays.sort(children, 0, childrenSize, comparator); - return true; - } - - private boolean removeChild(Node child) { - boolean found = false; - if (childrenSize == 0) { - return found; - } - for (int i = 0; i < childrenSize; i++) { - if (children[i].equals(child)) { - found = true; - } else if (found) { - // shift the rest of the keys down - children[i - 1] = children[i]; - } - } - if (found) { - childrenSize--; - children[childrenSize] = null; - } - return found; - } - - private Node removeChild(int index) { - if (index >= childrenSize) { - return null; - } - Node value = children[index]; - children[index] = null; - for (int i = index + 1; i < childrenSize; i++) { - // shift the rest of the keys down - children[i - 1] = children[i]; - } - childrenSize--; - children[childrenSize] = null; - return value; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - - builder.append("keys=["); - for (int i = 0; i < numberOfKeys(); i++) { - T value = getKey(i); - builder.append(value); - if (i < numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("]\n"); - - if (parent != null) { - builder.append("parent=["); - for (int i = 0; i < parent.numberOfKeys(); i++) { - T value = parent.getKey(i); - builder.append(value); - if (i < parent.numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("]\n"); - } - - if (children != null) { - builder.append("keySize=").append(numberOfKeys()).append(" children=").append(numberOfChildren()) - .append("\n"); - } - - return builder.toString(); - } - - private int numberOfKeys() { - return keysSize; - } - - private T getKey(int index) { - return keys[index]; - } - - private int numberOfChildren() { - return childrenSize; - } - - } - - private static class TreePrinter { - - public static > String getString(BinaryTree tree) { - if (tree.root == null) { - return "Tree has no nodes."; - } - return getString(tree.root, "", true); - } - - private static > String getString(Node node, String prefix, boolean isTail) { - StringBuilder builder = new StringBuilder(); - - builder.append(prefix).append((isTail ? "└── " : "├── ")); - for (int i = 0; i < node.numberOfKeys(); i++) { - T value = node.getKey(i); - builder.append(value); - if (i < node.numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("\n"); - - if (node.children != null) { - for (int i = 0; i < node.numberOfChildren() - 1; i++) { - Node obj = node.getChild(i); - builder.append(getString(obj, prefix + (isTail ? " " : "│ "), false)); - } - if (node.numberOfChildren() >= 1) { - Node obj = node.getChild(node.numberOfChildren() - 1); - builder.append(getString(obj, prefix + (isTail ? " " : "│ "), true)); - } - } - - return builder.toString(); - } - - } - - public static class JavaCompatibleBinaryTree> extends java.util.AbstractCollection { - - private BinaryTree tree = null; - - public JavaCompatibleBinaryTree(BinaryTree tree) { - this.tree = tree; - } - - /** - * {@inheritDoc} - */ - @Override - public java.util.Iterator iterator() { - return (new BinaryTreeIterator(this.tree)); - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return tree.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(Object value) { - return tree.contains((T) value); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(T value) { - return tree.add(value); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean remove(Object value) { - return (tree.remove((T) value) != null); - } - - private static class BinaryTreeIterator> implements java.util.Iterator { - - private BinaryTree tree = null; - - private Node lastNode = null; - - private C lastValue = null; - - private int index = 0; - - private Deque> toVisit = new ArrayDeque>(); - - protected BinaryTreeIterator(BinaryTree tree) { - this.tree = tree; - if (tree.root != null && tree.root.keysSize > 0) { - toVisit.add(tree.root); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasNext() { - boolean toVisitSizeNotZero = toVisit.size() > 0; - boolean lastNodeNotZero = lastNode != null && index < lastNode.keysSize; - if (lastNodeNotZero || toVisitSizeNotZero) { - return true; - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public C next() { - if (lastNode != null && (index < lastNode.keysSize)) { - lastValue = lastNode.getKey(index++); - return lastValue; - } - while (toVisit.size() > 0) { - // Go thru the current nodes - Node n = toVisit.pop(); - - // Add non-null children - for (int i = 0; i < n.childrenSize; i++) { - toVisit.add(n.getChild(i)); - } - - // Update last node (used in remove method) - index = 0; - lastNode = n; - lastValue = lastNode.getKey(index++); - return lastValue; - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public void remove() { - if (lastNode != null && lastValue != null) { - // On remove, reset the iterator (very inefficient, I know) - tree.remove(lastValue, lastNode); - - lastNode = null; - lastValue = null; - index = 0; - toVisit.clear(); - if (tree.root != null && tree.root.keysSize > 0) { - toVisit.add(tree.root); - } - } - } - - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/IntBTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/IntBTree.java deleted file mode 100644 index 00bac67..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/IntBTree.java +++ /dev/null @@ -1,175 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import java.util.LinkedList; -import java.util.Queue; -import java.util.Stack; - -/** - * @author Zhang Peng - * @since 2020-01-20 - */ -public class IntBTree { - - /** - * 前序遍历递归方法 - * - * @param root {@link TreeNode} - */ - public static void preOrder(TreeNode root) { - TreeNode node = root; - if (node != null) { - System.out.print(node.val + " "); - preOrder(node.left); - preOrder(node.right); - } - } - - /** - * 前序遍历非递归方法 - * - * @param root {@link TreeNode} - */ - public static void preOrder2(TreeNode root) { - if (root == null) return; - Stack stack = new Stack<>(); - while (!stack.isEmpty() || root != null) { - while (root != null) { - System.out.print(root.val + " "); - stack.push(root); - root = root.left; - } - if (!stack.isEmpty()) { - TreeNode t = stack.pop(); - root = t.right; - } - } - } - - /** - * 中序遍历递归方法 - * - * @param root {@link TreeNode} - */ - public static void inOrder(TreeNode root) { - if (root != null) { - preOrder(root.left); - System.out.print(root.val + " "); - preOrder(root.right); - } - } - - /** - * 中序遍历非递归方法 - * - * @param root {@link TreeNode} - */ - public static void inOrder2(TreeNode root) { - if (root == null) { - return; - } - - Stack stack = new Stack<>(); - while (!stack.isEmpty() || root != null) { - while (root != null) { - stack.push(root); - root = root.left; - } - if (!stack.isEmpty()) { - TreeNode t = stack.pop(); - System.out.print(t.val + " "); - root = t.right; - } - } - } - - public static void postOrder(TreeNode root) { - if (root != null) { - postOrder(root.left); - postOrder(root.right); - System.out.print(root.val + " "); - } - } - - /** - * 中序遍历非递归方法 - * - * @param root {@link TreeNode} - */ - public static void postOrder2(TreeNode root) { - if (root == null) { - return; - } - - Stack stack = new Stack<>(); - while (!stack.isEmpty() || root != null) { - while (root != null) { - stack.push(root); - root = root.left; - } - if (!stack.isEmpty()) { - TreeNode t = stack.pop(); - System.out.print(t.val + " "); - root = t.left; - } - } - } - - public static void levelTraverse(TreeNode root) { - if (root == null) { - return; - } - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); - System.out.print(node.val + " "); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); - } - } - - public static void depthOrderTraverse(TreeNode root) { - if (root == null) { - return; - } - LinkedList stack = new LinkedList<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - System.out.print(node.val + " "); - if (node.left != null) stack.push(node.left); - if (node.right != null) stack.push(node.right); - } - } - - public static TreeNode sortedArrayToBST(int[] nums) { - if (nums == null || nums.length == 0) return null; - return _sortedArrayToBST(nums, 0, nums.length - 1); - } - - public static TreeNode _sortedArrayToBST(int[] nums, int left, int right) { - if (left > right) return null; - - // always choose left middle node as a root - int p = (left + right) / 2; - - // inorder traversal: left -> node -> right - TreeNode root = new TreeNode(nums[p]); - root.left = _sortedArrayToBST(nums, left, p - 1); - root.right = _sortedArrayToBST(nums, p + 1, right); - return root; - } - - public static class TreeNode { - - public int val; - - public TreeNode left; - - public TreeNode right; - - public TreeNode(int val) { this.val = val; } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java new file mode 100644 index 0000000..664e896 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.LinkedList; +import java.util.List; + +/** + * N 叉树 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class NTree> { + + public int val; + public List children; + + public NTree() { + val = -1; + children = new LinkedList<>(); + } + + public NTree(int val) { + this.val = val; + this.children = new LinkedList<>(); + } + + public NTree(int val, List children) { + this.val = val; + this.children = children; + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java new file mode 100644 index 0000000..8c295ec --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java @@ -0,0 +1,27 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.LinkedList; +import java.util.List; + +// 多叉树节点 +public class Node { + + public int val; + public List children; + + public Node() { + val = -1; + children = new LinkedList<>(); + } + + public Node(int val) { + this.val = val; + this.children = new LinkedList<>(); + } + + public Node(int val, List children) { + this.val = val; + this.children = children; + } + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" deleted file mode 100644 index e2ce861..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import java.util.List; - -// 559. N叉树的最大深度 -// -// https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/ -// -// 给定一个 N 叉树,找到其最大深度。 -// -// 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 -// -// 例如,给定一个 3叉树 : -// -// 我们应返回其最大深度,3。 -// -// 说明: -// -// 树的深度不会超过 1000。 -// 树的节点总不会超过 5000。 -public class N叉树的最大深度 { - - public static int maxDepth(Node root) { - if (root == null) return 0; - if (root.children == null || root.children.size() == 0) return 1; - int max = 0; - for (Node node : root.children) { - int temp = maxDepth(node); - if (temp > max) { - max = temp; - } - } - return max + 1; - } - - static class Node { - - public int val; - - public List children; - - public Node() {} - - public Node(int val) { - this.val = val; - } - - public Node(int val, List children) { - this.val = val; - this.children = children; - } - - } - -} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java new file mode 100644 index 0000000..9f19b02 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java @@ -0,0 +1,15 @@ +package io.github.dunwu.algorithm.tree; + +// 多叉树的层序遍历 +// 每个节点自行维护 State 类,记录深度等信息 +public class State { + + public Node node; + public int depth; + + public State(Node node, int depth) { + this.node = node; + this.depth = depth; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java index a552ff1..632d59e 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java @@ -1,8 +1,15 @@ package io.github.dunwu.algorithm.tree; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; +import java.util.Queue; /** + * 二叉树节点 + * * @author Zhang Peng * @since 2020-01-28 */ @@ -42,4 +49,139 @@ public int hashCode() { return Objects.hash(val, left, right); } + public static String serialize(TreeNode root) { + return serialize(root, "NULL", ","); + } + + public static String serialize(TreeNode root, String nullFlag, String sepFlag) { + StringBuilder sb = new StringBuilder(); + doSerialize(root, sb, nullFlag, sepFlag); + return sb.toString(); + } + + static void doSerialize(TreeNode root, StringBuilder sb, String nullFlag, String sepFlag) { + if (root == null) { + sb.append(nullFlag).append(sepFlag); + return; + } + sb.append(root.val).append(sepFlag); + doSerialize(root.left, sb, nullFlag, sepFlag); + doSerialize(root.right, sb, nullFlag, sepFlag); + } + + public static TreeNode deserialize(String data) { + return deserialize(data, "NULL", ","); + } + + public static TreeNode deserialize(String data, String nullFlag, String sepFlag) { + LinkedList nodes = new LinkedList<>(Arrays.asList(data.split(sepFlag))); + return doDeserialize(nodes, nullFlag); + } + + static TreeNode doDeserialize(LinkedList nodes, String nullFlag) { + if (nodes.isEmpty()) return null; + + // =============== 前序遍历处理 =============== + String val = nodes.removeFirst(); + if (nullFlag.equals(val)) { return null; } + TreeNode root = new TreeNode(Integer.parseInt(val)); + // ========================================== + + root.left = doDeserialize(nodes, nullFlag); + root.right = doDeserialize(nodes, nullFlag); + return root; + } + + public static TreeNode buildTree(Integer... values) { + + if (values == null || values.length == 0 || values[0] == null) { + return null; + } + + Queue queue = new LinkedList<>(); + TreeNode root = new TreeNode(values[0]); + queue.offer(root); + + int i = 1; + while (!queue.isEmpty() && i < values.length) { + TreeNode current = queue.poll(); + + // 处理左子节点 + if (i < values.length && values[i] != null) { + current.left = new TreeNode(values[i]); + queue.offer(current.left); + } + i++; + + // 处理右子节点 + if (i < values.length && values[i] != null) { + current.right = new TreeNode(values[i]); + queue.offer(current.right); + } + i++; + } + + return root; + } + + public static TreeNode find(TreeNode root, int val) { + if (root == null || root.val == val) { return root; } + TreeNode left = find(root.left, val); + if (left != null) return left; + return find(root.right, val); + } + + public static List toList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + list.add(node); + if (node == null) continue; + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + + public static List toValueList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node == null) { + list.add(null); + continue; + } else { + list.add(node.val); + } + + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeUtils.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeUtils.java deleted file mode 100644 index 2395dbf..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeUtils.java +++ /dev/null @@ -1,213 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import java.util.*; - -/** - * @author Zhang Peng - * @since 2020-01-28 - */ -public class TreeUtils { - - public static TreeNode buildTree(Integer[] array) { - List list = new ArrayList<>(); - - for (Integer value : array) { - // 创建结点,每一个结点的左结点和右结点为null - TreeNode node; - if (value == null) { - node = null; - } else { - node = new TreeNode(value, null, null); - } - // list中存着每一个结点 - list.add(node); - } - - // 构建二叉树 - if (list.size() > 0) { - // i表示的是根节点的索引,从0开始 - for (int i = 0; i < array.length / 2 - 1; i++) { - if (list.get(2 * i + 1) != null) { - // 左结点 - list.get(i).left = list.get(2 * i + 1); - } - if (list.get(2 * i + 2) != null) { - // 右结点 - list.get(i).right = list.get(2 * i + 2); - } - } - // 判断最后一个根结点:因为最后一个根结点可能没有右结点,所以单独拿出来处理 - int lastIndex = array.length / 2 - 1; - - // 左结点 - list.get(lastIndex).left = list.get(lastIndex * 2 + 1); - // 右结点,如果数组的长度为奇数才有右结点 - if (array.length % 2 == 1) { - list.get(lastIndex).right = list.get(lastIndex * 2 + 2); - } - - return list.get(0); - } else { - return null; - } - } - - public static TreeNode asTree(Integer... array) { - return buildTree(array); - } - - public static TreeNode find(TreeNode root, int val) { - if (root == null || root.val == val) { return root;} - TreeNode left = find(root.left, val); - if (left != null) return left; - return find(root.right, val); - } - - public static void depthOrderTraverse(TreeNode root) { - if (root == null) { - return; - } - LinkedList stack = new LinkedList<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - System.out.print(node.val + " "); - if (node.left != null) stack.push(node.left); - if (node.right != null) stack.push(node.right); - } - } - - public static List toBfsList(TreeNode root) { - List list = new ArrayList<>(); - if (root == null) { - return list; - } - - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); - list.add(node); - if (node == null) continue; - queue.add(node.left); - queue.add(node.right); - } - - // 删除队列尾部的所有 null - int last = list.size() - 1; - while (last > 0 && list.get(last) == null) { - last--; - } - return list.subList(0, last + 1); - } - - public static List toBfsValueList(TreeNode root) { - List list = new ArrayList<>(); - if (root == null) { - return list; - } - - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); - if (node == null) { - list.add(null); - continue; - } else { - list.add(node.val); - } - - queue.add(node.left); - queue.add(node.right); - } - - // 删除队列尾部的所有 null - int last = list.size() - 1; - while (last > 0 && list.get(last) == null) { - last--; - } - return list.subList(0, last + 1); - } - - public static String rserialize(TreeNode root, String str) { - if (root == null) { - str += "null,"; - } else { - str += str.valueOf(root.val) + ","; - str = rserialize(root.left, str); - str = rserialize(root.right, str); - } - return str; - } - - public static String serialize(TreeNode root) { - String text = rserialize(root, ""); - while (text.endsWith("null,")) { - int index = text.lastIndexOf("null,"); - text = text.substring(0, index); - } - if (text.endsWith(",")) { - text = text.substring(0, text.length() - 1); - } - return text; - } - - public static TreeNode rdeserialize(List list) { - List nodes = new ArrayList<>(); - - for (String value : list) { - // 创建结点,每一个结点的左结点和右结点为null - TreeNode node; - if (value == null || value.equalsIgnoreCase("null")) { - node = null; - } else { - node = new TreeNode(Integer.parseInt(value), null, null); - } - // list中存着每一个结点 - nodes.add(node); - } - - // 构建二叉树 - if (nodes.size() > 0) { - // i表示的是根节点的索引,从0开始 - for (int i = 0; i < list.size() / 2 - 1; i++) { - if (nodes.get(2 * i + 1) != null) { - // 左结点 - nodes.get(i).left = nodes.get(2 * i + 1); - } - if (nodes.get(2 * i + 2) != null) { - // 右结点 - nodes.get(i).right = nodes.get(2 * i + 2); - } - } - // 判断最后一个根结点:因为最后一个根结点可能没有右结点,所以单独拿出来处理 - int lastIndex = list.size() / 2 - 1; - - // 左结点 - nodes.get(lastIndex).left = nodes.get(lastIndex * 2 + 1); - // 右结点,如果数组的长度为奇数才有右结点 - if (list.size() % 2 == 1) { - nodes.get(lastIndex).right = nodes.get(lastIndex * 2 + 2); - } - - return nodes.get(0); - } else { - return null; - } - } - - public static TreeNode deserialize(String data) { - data = data.substring(1, data.length() - 1); - String[] nums = data.split(","); - List list = new LinkedList<>(Arrays.asList(nums)); - return rdeserialize(list); - } - - public static void main(String[] args) { - Integer[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - TreeNode head = TreeUtils.asTree(array); - toBfsList(head); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" new file mode 100644 index 0000000..c2d9a86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import org.junit.jupiter.api.Assertions; + +/** + * 96. 不同的二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 不同的二叉搜索树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.numTrees(3)); + Assertions.assertEquals(1, s.numTrees(1)); + } + + static class Solution { + + // 备忘录 + int[][] memo; + + // 主函数 + public int numTrees(int n) { + // 备忘录的值初始化 + memo = new int[n + 1][n + 1]; + // 计算闭区间 [1, n] 组成的 BST 个数 + return count(1, n); + } + + // 计算闭区间 [lo, hi] 组成的 BST 个数 + int count(int low, int high) { + // base case + if (low > high) { return 1; } + if (memo[low][high] != 0) { return memo[low][high]; } + + int res = 0; + for (int i = low; i <= high; i++) { + // i 的值作为根节点 root + int left = count(low, i - 1); + int right = count(i + 1, high); + // 左右子树的组合数乘积是 BST 的总数 + res += left * right; + } + memo[low][high] = res; + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" new file mode 100644 index 0000000..19d183b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" @@ -0,0 +1,85 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 95. 不同的二叉搜索树 II + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 不同的二叉搜索树2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + List output1 = s.generateTrees(3); + LinkedList> expectList1 = new LinkedList<>(); + expectList1.add(new LinkedList<>(Arrays.asList(1, null, 2, null, 3))); + expectList1.add(new LinkedList<>(Arrays.asList(1, null, 3, 2))); + expectList1.add(new LinkedList<>(Arrays.asList(2, 1, 3))); + expectList1.add(new LinkedList<>(Arrays.asList(3, 1, null, null, 2))); + expectList1.add(new LinkedList<>(Arrays.asList(3, 2, null, 1))); + Assertions.assertEquals(expectList1.size(), output1.size()); + output1.forEach(tree -> { + List expect = expectList1.poll(); + Assertions.assertArrayEquals(expect.toArray(), TreeNode.toValueList(tree).toArray()); + }); + + List output2 = s.generateTrees(1); + LinkedList> expectList2 = new LinkedList<>(); + expectList2.add(new LinkedList<>(Collections.singletonList(1))); + Assertions.assertEquals(expectList2.size(), output2.size()); + output2.forEach(tree -> { + List expect = expectList2.poll(); + Assertions.assertArrayEquals(expect.toArray(), TreeNode.toValueList(tree).toArray()); + }); + } + + static class Solution { + + // 主函数 + public List generateTrees(int n) { + if (n == 0) return new LinkedList<>(); + // 构造闭区间 [1, n] 组成的 BST + return build(1, n); + } + + // 构造闭区间 [low, high] 组成的 BST + List build(int low, int high) { + List res = new LinkedList<>(); + // base case + if (low > high) { + res.add(null); + return res; + } + + // 1、穷举 root 节点的所有可能。 + for (int i = low; i <= high; i++) { + // 2、递归构造出左右子树的所有合法 BST。 + List leftTree = build(low, i - 1); + List rightTree = build(i + 1, high); + // 3、给 root 节点穷举所有左右子树的组合。 + for (TreeNode left : leftTree) { + for (TreeNode right : rightTree) { + // i 作为根节点 root 的值 + TreeNode root = new TreeNode(i); + root.left = left; + root.right = right; + res.add(root); + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" new file mode 100644 index 0000000..42aa113 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 96. 不同的二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索子树的最大键值和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(20, + s.maxSumBST(TreeNode.buildTree(1, 4, 3, 2, 4, 2, 5, null, null, null, null, null, null, 4, 6))); + Assertions.assertEquals(2, s.maxSumBST(TreeNode.buildTree(4, 3, null, 1, 2))); + Assertions.assertEquals(0, s.maxSumBST(TreeNode.buildTree(-4, -2, -5))); + Assertions.assertEquals(6, s.maxSumBST(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(7, s.maxSumBST(TreeNode.buildTree(5, 4, 8, 3, null, 6, 3))); + } + + static class Solution { + + // 记录 BST 最大节点之和 + private int maxSum = 0; + + public int maxSumBST(TreeNode root) { + maxSum = 0; + findMaxMinSum(root); + return maxSum; + } + + // 计算以 root 为根的二叉树的最大值、最小值、节点和 + int[] findMaxMinSum(TreeNode root) { + // base case + if (root == null) { + return new int[] { + 1, Integer.MAX_VALUE, Integer.MIN_VALUE, 0 + }; + } + + // 递归计算左右子树 + int[] left = findMaxMinSum(root.left); + int[] right = findMaxMinSum(root.right); + + // ******* 后序位置 ******* + // 通过 left 和 right 推导返回值 + // 并且正确更新 maxSum 变量 + int[] res = new int[4]; + // 这个 if 在判断以 root 为根的二叉树是不是 BST + if (left[0] == 1 && right[0] == 1 && + root.val > left[2] && root.val < right[1]) { + // 以 root 为根的二叉树是 BST + res[0] = 1; + // 计算以 root 为根的这棵 BST 的最小值 + res[1] = Math.min(left[1], root.val); + // 计算以 root 为根的这棵 BST 的最大值 + res[2] = Math.max(right[2], root.val); + // 计算以 root 为根的这棵 BST 所有节点之和 + res[3] = left[3] + right[3] + root.val; + // 更新全局变量 + maxSum = Math.max(maxSum, res[3]); + } else { + // 以 root 为根的二叉树不是 BST + res[0] = 0; + // 其他的值都没必要计算了,因为用不到 + } + + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" index 528305b..9a6925c 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" @@ -1,36 +1,38 @@ package io.github.dunwu.algorithm.tree.bstree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; - -import java.util.List; +import org.junit.jupiter.api.Assertions; /** + * 701. 二叉搜索树中的插入操作 + * * @author Zhang Peng - * @see 701. 二叉搜索树中的插入操作 * @since 2020-07-06 */ public class 二叉搜索树中的插入操作 { public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(4, 2, 7, 1, 3); - insertIntoBST(tree, 5); - List treeNodes = TreeUtils.toBfsList(tree); - System.out.println(treeNodes); + TreeNode input1 = TreeNode.buildTree(4, 2, 7, 1, 3); + TreeNode output1 = insertIntoBST(input1, 5); + Assertions.assertArrayEquals(new Integer[] { 4, 2, 7, 1, 3, 5 }, TreeNode.toValueList(output1).toArray()); + + TreeNode input2 = TreeNode.buildTree(40, 20, 60, 10, 30, 50, 70); + TreeNode output2 = insertIntoBST(input2, 25); + Assertions.assertArrayEquals(new Integer[] { 40, 20, 60, 10, 30, 50, 70, null, null, 25 }, + TreeNode.toValueList(output2).toArray()); + + TreeNode input3 = TreeNode.buildTree(4, 2, 7, 1, 3, null, null, null, null, null, null); + TreeNode output3 = insertIntoBST(input3, 5); + Assertions.assertArrayEquals(new Integer[] { 4, 2, 7, 1, 3, 5 }, + TreeNode.toValueList(output3).toArray()); } public static TreeNode insertIntoBST(TreeNode root, int val) { - if (root == null) return new TreeNode(val); - - TreeNode node = root; - if (val > node.val) { - if (node.right == null) { - node.right = new TreeNode(val); - } else { insertIntoBST(node.right, val); } + if (root == null) { return new TreeNode(val); } + if (root.val < val) { + root.right = (root.right == null) ? new TreeNode(val) : insertIntoBST(root.right, val); } else { - if (node.left == null) { - node.left = new TreeNode(val); - } else { insertIntoBST(node.left, val); } + root.left = (root.left == null) ? new TreeNode(val) : insertIntoBST(root.left, val); } return root; } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" new file mode 100644 index 0000000..9650df6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索树中的搜索 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 2, 7, 1, 3); + TreeNode output1 = s.searchBST(input1, 2); + Assertions.assertArrayEquals(new Integer[] { 2, 1, 3 }, TreeNode.toValueList(output1).toArray()); + + TreeNode output2 = s.searchBST(input1, 5); + Assertions.assertNull(output2); + } + + static class Solution { + + public TreeNode searchBST(TreeNode root, int val) { + if (root == null) { return null; } + if (root.val == val) { + return root; + } else if (root.val < val) { + return searchBST(root.right, val); + } else { + return searchBST(root.left, val); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" new file mode 100644 index 0000000..5ce506e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 98. 验证二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索树中第K小的元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.kthSmallest(TreeNode.buildTree(3, 1, 4, null, 2), 1)); + Assertions.assertEquals(3, s.kthSmallest(TreeNode.buildTree(5, 3, 6, 2, 4, null, null, 1), 3)); + } + + static class Solution { + + private int rank = 1; + private int res = 0; + + public int kthSmallest(TreeNode root, int k) { + rank = 1; + res = 0; + dfs(root, k); + return res; + } + + void dfs(TreeNode root, int k) { + if (root == null) { return; } + dfs(root.left, k); + if (rank++ == k) { + res = root.val; + return; + } + dfs(root.right, k); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" index 95f3b32..5b8039e 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" @@ -1,82 +1,53 @@ package io.github.dunwu.algorithm.tree.bstree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import io.github.dunwu.algorithm.tree.btree.二叉树的最近公共祖先; import org.junit.jupiter.api.Assertions; /** - * 235. 二叉搜索树的最近公共祖先 算法实现 + * 235. 二叉搜索树的最近公共祖先 * - * @see 二叉树的最近公共祖先 可以使用二叉树的最近公共祖先,但没有利用二叉搜索树特性,性能略差 - * @see 235. 二叉搜索树的最近公共祖先 + * @author Zhang Peng + * @date 2025-10-22 */ public class 二叉搜索树的最近公共祖先 { public static void main(String[] args) { - TreeNode root = TreeUtils.asTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); - TreeNode p = TreeUtils.find(root, 2); - TreeNode q = TreeUtils.find(root, 8); - // TreeNode treeNode = lowestCommonAncestor(root, p, q); - TreeNode treeNode = lowestCommonAncestor2(root, p, q); - Assertions.assertNotNull(treeNode); - Assertions.assertEquals(6, treeNode.val); - System.out.println("公共祖先节点 = " + treeNode.val); - TreeNode root2 = TreeUtils.asTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); - TreeNode p2 = TreeUtils.find(root2, 2); - TreeNode q2 = TreeUtils.find(root2, 4); - // TreeNode treeNode2 = lowestCommonAncestor(root2, p2, q2); - TreeNode treeNode2 = lowestCommonAncestor2(root2, p2, q2); - Assertions.assertNotNull(treeNode2); - Assertions.assertEquals(2, treeNode2.val); - System.out.println("公共祖先节点 = " + treeNode2.val); - } + Solution s = new Solution(); - /** - * 递归方式求解 - *

- * 时间复杂度:O(N) 线性级 - *

- * 空间复杂度:O(2) 常数级 - */ - public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - // 如果当前节点为空,直接返回 - // 或当前节点就是 p 或 q 其中一个,显然就是要找的最近公共祖先,直接返回 - if (root == null || root == p || root == q) return root; + TreeNode root = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); - if (root.val > p.val && root.val > q.val) { // 如果当前节点值同时大于 p、q 的值,说明 p、q 肯定都在左子树 - return lowestCommonAncestor(root.left, p, q); - } else if (root.val < p.val && root.val < q.val) { // 如果当前节点值同时小于 p、q 的值,说明 p、q 肯定都在右子树 - return lowestCommonAncestor(root.right, p, q); - } else { - return root; - } + TreeNode node1 = s.lowestCommonAncestor(root, TreeNode.find(root, 2), TreeNode.find(root, 8)); + Assertions.assertNotNull(node1); + Assertions.assertEquals(6, node1.val); + + TreeNode node2 = s.lowestCommonAncestor(root, TreeNode.find(root, 2), TreeNode.find(root, 4)); + Assertions.assertNotNull(node2); + Assertions.assertEquals(2, node2.val); } - /** - * 非递归方式求解 - *

- * 时间复杂度:O(N) 线性级 - *

- * 空间复杂度:O(2) 常数级 - */ - public static TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) { - // 如果当前节点为空,直接返回 - // 或当前节点就是 p 或 q 其中一个,显然就是要找的最近公共祖先,直接返回 - if (root == null || root == p || root == q) return root; + static class Solution { - TreeNode curr = root; - while (curr != null) { - if (curr.val > p.val && curr.val > q.val) { // 如果当前节点值同时大于 p、q 的值,说明 p、q 肯定都在左子树 - curr = curr.left; - } else if (curr.val < p.val && curr.val < q.val) { // 如果当前节点值同时小于 p、q 的值,说明 p、q 肯定都在右子树 - curr = curr.right; + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) return null; + if (p.val > q.val) { + // 保证 p.val <= q.val,便于后续情况讨论 + return lowestCommonAncestor(root, q, p); + } + if (root.val >= p.val && root.val <= q.val) { + // p <= root <= q + // 即 p 和 q 分别在 root 的左右子树,那么 root 就是 LCA + return root; + } + if (root.val > q.val) { + // p 和 q 都在 root 的左子树,那么 LCA 在左子树 + return lowestCommonAncestor(root.left, p, q); } else { - return curr; + // p 和 q 都在 root 的右子树,那么 LCA 在右子树 + return lowestCommonAncestor(root.right, p, q); } } - return curr; + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" index b543c50..884b648 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" @@ -1,90 +1,51 @@ package io.github.dunwu.algorithm.tree.bstree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import org.junit.jupiter.api.Assertions; /** - * 二叉搜索树结点最小距离 算法实现 - * - *

- * 给定一个二叉搜索树的根结点 root, 返回树中任意两节点的差的最小值。
- *
- * 示例:
- *
- * 输入: root = [4,2,6,1,3,null,null]
- * 输出: 1
- * 解释:
- * 注意,root是树结点对象(TreeNode object),而不是数组。
- *
- * 给定的树 [4,2,6,1,3,null,null] 可表示为下图:
- *
- *           4
- *         /   \
- *       2      6
- *      / \
- *     1   3
- *
- * 最小的差值是 1, 它是节点1和节点2的差值, 也是节点3和节点2的差值。
- * 
+ * 783. 二叉搜索树节点最小距离 * - * @see 二叉搜索树结点最小距离 + * @author Zhang Peng + * @date 2020-06-18 */ public class 二叉搜索树节点最小距离 { public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(4, 2, 6, 1, 3, null, null); - System.out.println("result = " + minDiffInBST2(tree)); + Solution s = new Solution(); + Assertions.assertEquals(1, s.minDiffInBST(TreeNode.buildTree(4, 2, 6, 1, 3))); + Assertions.assertEquals(1, s.minDiffInBST(TreeNode.buildTree(1, 0, 48, null, null, 12, 49))); } - // ------------------------------------------------------------------------------------------------- + static class Solution { - // 方法一:排序【通过】 - // 思路和算法:将树中所有节点的值写入数组,之后将数组排序。依次计算相邻数之间的差值,找出其中最小的值。 + private int pre; + private int res; - public static int minDiffInBST(TreeNode root) { - List list = new ArrayList(); - dfs(root, list); - Collections.sort(list); - - int min = Integer.MAX_VALUE; - for (int i = 0; i < list.size() - 1; ++i) { - min = Math.min(min, list.get(i + 1) - list.get(i)); + public int minDiffInBST(TreeNode root) { + pre = -1; + res = Integer.MAX_VALUE; + dfs(root); + return res; } - return min; - } - - public static void dfs(TreeNode node, List list) { - if (node == null) { return; } - list.add(node.val); - dfs(node.left, list); - dfs(node.right, list); - } + public void dfs(TreeNode root) { + // base case + if (root == null) { return; } - // ------------------------------------------------------------------------------------------------- + // 【前序】 + dfs(root.left); - // 方法二:中序遍历【通过】 - // 思路和算法:在二叉搜索树中,中序遍历会将树中节点按数值大小顺序输出。只需要遍历计算相邻数的差值,取其中最小的就可以了。 + // 【中序】中序保证递增有序 + if (pre != -1) { + res = Math.min(res, Math.abs(root.val - pre)); + } + pre = root.val; - public static Integer prev = null; - public static Integer min = Integer.MAX_VALUE; - - public static int minDiffInBST2(TreeNode root) { - if (root == null) return 0; - dfs2(root); - return min; - } + // 【后序】 + dfs(root.right); + } - public static void dfs2(TreeNode node) { - if (node == null) { return; } - dfs2(node.left); - if (prev != null) min = Math.min(min, node.val - prev); - prev = node.val; - dfs2(node.right); } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" new file mode 100644 index 0000000..e76ff50 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 从二叉搜索树到更大和树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8); + TreeNode output1 = s.bstToGst(input1); + TreeNode expect1 = TreeNode.buildTree(30, 36, 21, 36, 35, 26, 15, null, null, null, 33, null, null, null, 8); + Assertions.assertEquals(expect1, output1); + + Assertions.assertEquals(TreeNode.buildTree(1, null, 1), s.bstToGst(TreeNode.buildTree(0, null, 1))); + } + + static class Solution { + + private int sum = 0; + + public TreeNode bstToGst(TreeNode root) { + sum = 0; + dfs(root); + return root; + } + + public void dfs(TreeNode root) { + if (root == null) { return; } + dfs(root.right); + sum += root.val; + root.val = sum; + // System.out.printf("%s\n", root.val); + dfs(root.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" new file mode 100644 index 0000000..e088862 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 701. 二叉搜索树中的插入操作 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 删除二叉搜索树中的节点 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 3); + Assertions.assertEquals(TreeNode.buildTree(5, 4, 6, 2, null, null, 7), output1); + + TreeNode output2 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 0); + Assertions.assertEquals(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), output2); + + TreeNode output3 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 5); + Assertions.assertEquals(TreeNode.buildTree(6, 3, 7, 2, 4), output3); + + Assertions.assertEquals(TreeNode.buildTree(1), s.deleteNode(TreeNode.buildTree(2, 1), 2)); + + Assertions.assertNull(s.deleteNode(TreeNode.buildTree(), 0)); + Assertions.assertNull(s.deleteNode(TreeNode.buildTree(0), 0)); + } + + static class Solution { + + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) { return null; } + if (root.val == key) { + if (root.left == null) { return root.right; } + if (root.right == null) { return root.left; } + // 获得右子树最小的节点 + TreeNode minRightNode = getMin(root.right); + // 删除右子树最小的节点 + root.right = deleteNode(root.right, minRightNode.val); + // 用右子树最小的节点替换 root 节点 + minRightNode.left = root.left; + minRightNode.right = root.right; + root = minRightNode; + } else if (root.val > key) { + // 去左子树找 + root.left = deleteNode(root.left, key); + } else if (root.val < key) { + // 去右子树找 + root.right = deleteNode(root.right, key); + } + return root; + } + + public TreeNode getMin(TreeNode root) { + if (root == null) { return null; } + if (root.left != null) { return getMin(root.left); } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" index e40f46e..2ba0983 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -1,32 +1,46 @@ package io.github.dunwu.algorithm.tree.bstree; import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; /** + * 108. 将有序数组转换为二叉搜索树 + * * @author Zhang Peng * @since 2020-07-07 */ public class 将有序数组转换为二叉搜索树 { public static void main(String[] args) { - System.out.println("result = " + sortedArrayToBST(new int[] { -10, -3, 0, 5, 9 })); + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(0, -3, 9, -10, null, 5), + s.sortedArrayToBST(new int[] { -10, -3, 0, 5, 9 })); + Assertions.assertEquals(TreeNode.buildTree(3, 1), s.sortedArrayToBST(new int[] { 1, 3 })); } - public static TreeNode sortedArrayToBST(int[] nums) { - if (nums == null || nums.length == 0) return null; - return backtrack(nums, 0, nums.length - 1); - } + static class Solution { + + public TreeNode sortedArrayToBST(int[] nums) { + return sortedArrayToBST(nums, 0, nums.length - 1); + } - public static TreeNode backtrack(int[] nums, int left, int right) { - if (left > right) return null; - // always choose left middle node as a root - int p = (left + right) / 2; + // 将闭区间 [left, right] 中的元素转化成 BST,返回根节点 + TreeNode sortedArrayToBST(int[] nums, int left, int right) { + if (left > right) { + // 区间为空 + return null; + } + // 构造根节点 + // BST 节点左小右大,中间的元素就是根节点 + int mid = (left + right) / 2; + TreeNode root = new TreeNode(nums[mid]); + // 递归构建左子树 + root.left = sortedArrayToBST(nums, left, mid - 1); + // 递归构造右子树 + root.right = sortedArrayToBST(nums, mid + 1, right); + return root; + } - // inorder traversal: left -> node -> right - TreeNode root = new TreeNode(nums[p]); - root.left = backtrack(nums, left, p - 1); - root.right = backtrack(nums, p + 1, right); - return root; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" new file mode 100644 index 0000000..2b0c161 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 把二叉搜索树转换为累加树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8); + TreeNode output1 = s.convertBST(input1); + Assertions.assertArrayEquals( + new Integer[] { 30, 36, 21, 36, 35, 26, 15, null, null, null, 33, null, null, null, 8 }, + TreeNode.toValueList(output1).toArray()); + + TreeNode input2 = TreeNode.buildTree(0, null, 1); + TreeNode output2 = s.convertBST(input2); + Assertions.assertArrayEquals(new Integer[] { 1, null, 1 }, TreeNode.toValueList(output2).toArray()); + + TreeNode input3 = TreeNode.buildTree(1, 0, 2); + TreeNode output3 = s.convertBST(input3); + Assertions.assertArrayEquals(new Integer[] { 3, 3, 2 }, TreeNode.toValueList(output3).toArray()); + + TreeNode input4 = TreeNode.buildTree(3, 2, 4, 1); + TreeNode output4 = s.convertBST(input4); + Assertions.assertArrayEquals(new Integer[] { 7, 9, 4, 10 }, TreeNode.toValueList(output4).toArray()); + } + + static class Solution { + + int sum = 0; + + public TreeNode convertBST(TreeNode root) { + sum = 0; // 重置 + traverse(root); + return root; + } + + public void traverse(TreeNode root) { + if (root == null) return; + traverse(root.right); + sum += root.val; + root.val = sum; + // System.out.printf("val: %d\n", root.val); + traverse(root.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" index 1974f3a..1177d66 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -1,69 +1,43 @@ package io.github.dunwu.algorithm.tree.bstree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; -import java.util.*; - /** - * 98. 验证二叉搜索树 算法实现 + * 98. 验证二叉搜索树 * * @author Zhang Peng - * @see 98. 验证二叉搜索树 * @since 2020-07-02 */ public class 验证二叉搜索树 { public static void main(String[] args) { - TreeNode root = TreeUtils.asTree(2, 1, 3); - TreeNode root2 = TreeUtils.asTree(5, 1, 4, null, null, 3, 6); - TreeNode root3 = TreeUtils.asTree(1, 1); - - Assertions.assertTrue(isValidBST(root)); - Assertions.assertFalse(isValidBST(root2)); - Assertions.assertFalse(isValidBST(root3)); - - Assertions.assertTrue(isValidBST2(root)); - Assertions.assertFalse(isValidBST2(root2)); - Assertions.assertFalse(isValidBST2(root3)); + Solution s = new Solution(); + Assertions.assertTrue(s.isValidBST(TreeNode.buildTree(2, 1, 3))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(5, 1, 4, null, null, 3, 6))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(2, 2, 2))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(5, 4, 6, null, null, 3, 7))); + Assertions.assertTrue(s.isValidBST(TreeNode.buildTree(3, 1, 5, 0, 2, 4, 6))); } - public static boolean isValidBST(TreeNode root) { - return help(root, null, null); - } + static class Solution { - public static boolean help(TreeNode root, Integer min, Integer max) { - if (root == null) return true; - if (min != null && root.val <= min) return false; - if (max != null && root.val >= max) return false; - return help(root.left, min, root.val) && help(root.right, root.val, max); - } - - /** - * 中序遍历二叉搜索树获取到的一定是有序数组。符合这个条件的就是有效二叉搜索树 - *

- * 遍历二叉搜索树的时间复杂度:O(N) 但是对 List 进行排序,也要耗费时间,所以综合来看,应该是 O(N) + O(log N) - *

- * 空间复杂度:用两个链表进行比较,占用了 O(2N),比较耗费空间,性能并不好 - */ - public static boolean isValidBST2(TreeNode root) { - if (root == null) { return true; } - List list = new LinkedList<>(); - inOrder2(root, list); + public boolean isValidBST(TreeNode root) { + return isValidBST(root, null, null); + } - // 这里使用 TreeSet,基于两个目的:(1)去重 (2)根据值排序 - Set set2 = new TreeSet<>(); - set2.addAll(list); - return Arrays.equals(list.toArray(), set2.toArray()); - } + // 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val + boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) { + // base case + if (root == null) return true; + // 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST + if (min != null && root.val <= min.val) return false; + if (max != null && root.val >= max.val) return false; + // 限定左子树的最大值是 root.val,右子树的最小值是 root.val + return isValidBST(root.left, min, root) + && isValidBST(root.right, root, max); + } - // 单纯的中序遍历 - public static void inOrder2(TreeNode root, List list) { - if (root == null) return; - inOrder2(root.left, list); - list.add(root.val); - inOrder2(root.right, list); } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" new file mode 100644 index 0000000..c9c6a89 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 863. 二叉树中所有距离为 K 的结点 + * + * @author Zhang Peng + * @date 2025-12-01 + */ +public class 二叉树中所有距离为K的结点 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode input = TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7); + TreeNode target = TreeNode.find(input, 5); + Assertions.assertArrayEquals(new Integer[] { 7, 4, 1 }, s.distanceK(input, target, 2).toArray()); + } + + static class Solution { + + // 记录父节点:node.val -> parentNode + // 题目说了树中所有节点值都是唯一的,所以可以用 node.val 代表 TreeNode + HashMap parent = new HashMap<>(); + + public List distanceK(TreeNode root, TreeNode target, int k) { + // 遍历所有节点,记录每个节点的父节点 + traverse(root, null); + + // 开始从 target 节点施放 BFS 算法,找到距离为 k 的节点 + LinkedList q = new LinkedList<>(); + HashSet visited = new HashSet<>(); + q.offer(target); + visited.add(target.val); + // 记录离 target 的距离 + int dist = 0; + List res = new LinkedList<>(); + + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + TreeNode cur = q.poll(); + if (dist == k) { + // 找到距离起点 target 距离为 k 的节点 + res.add(cur.val); + } + // 向父节点、左右子节点扩散 + TreeNode parentNode = parent.get(cur.val); + if (parentNode != null && !visited.contains(parentNode.val)) { + visited.add(parentNode.val); + q.offer(parentNode); + } + if (cur.left != null && !visited.contains(cur.left.val)) { + visited.add(cur.left.val); + q.offer(cur.left); + } + if (cur.right != null && !visited.contains(cur.right.val)) { + visited.add(cur.right.val); + q.offer(cur.right); + } + } + // 向外扩展一圈 + dist++; + } + + return res; + } + + private void traverse(TreeNode root, TreeNode parentNode) { + if (root == null) { + return; + } + parent.put(root.val, parentNode); + // 二叉树递归框架 + traverse(root.left, root); + traverse(root.right, root); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" new file mode 100644 index 0000000..d27f609 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 958. 二叉树的完全性检验 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的完全性检验 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 4, 5, 6))); + Assertions.assertTrue(s.isCompleteTree( + TreeNode.buildTree(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))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 4, 5, null, 7))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 5, null, 7, 8))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, null, 7))); + } + + static class Solution { + + static class NodeInfo { + + public int id; + public TreeNode node; + + public NodeInfo(int id, TreeNode node) { + this.id = id; + this.node = node; + } + + } + + public boolean isCompleteTree(TreeNode root) { + + if (root == null) { return false; } + + int expect = 1; + LinkedList queue = new LinkedList<>(); + queue.offer(new NodeInfo(1, root)); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + NodeInfo info = queue.poll(); + if (expect != info.id) { return false; } + if (info.node.left == null && info.node.right != null) { + return false; + } + if (info.node.left != null) { + queue.offer(new NodeInfo(info.id * 2, info.node.left)); + } + if (info.node.right != null) { + queue.offer(new NodeInfo(info.id * 2 + 1, info.node.right)); + } + expect++; + } + } + + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" new file mode 100644 index 0000000..71b0a95 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 637. 二叉树的层平均值 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的层平均值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Double[] { 3.00000, 14.50000, 11.00000 }, + s.averageOfLevels(TreeNode.buildTree(3, 9, 20, null, null, 15, 7)).toArray()); + Assertions.assertArrayEquals(new Double[] { 3.00000, 14.50000, 11.00000 }, + s.averageOfLevels(TreeNode.buildTree(3, 9, 20, 15, 7)).toArray()); + } + + static class Solution { + + public List averageOfLevels(TreeNode root) { + if (root == null) { return new LinkedList<>(); } + + LinkedList res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + double sum = 0.0; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + res.add(sum / size); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" new file mode 100644 index 0000000..dd133f3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 二叉树的层次遍历 + * + * @author Zhang Peng + * @since 2020-06-18 + */ +public class 二叉树的层次遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(3)); + expectList.add(Arrays.asList(9, 20)); + expectList.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expectList.toArray(), s.levelOrder(root).toArray()); + + Solution s2 = new Solution(); + TreeNode root2 = TreeNode.buildTree(1); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expectList2.toArray(), s2.levelOrder(root2).toArray()); + + Solution s3 = new Solution(); + TreeNode root3 = TreeNode.buildTree(); + Assertions.assertArrayEquals(new LinkedList<>().toArray(), s3.levelOrder(root3).toArray()); + } + + static class Solution { + + public List> levelOrder(TreeNode root) { + + LinkedList> res = new LinkedList<>(); + if (root == null) { return res; } + + Queue queue = new LinkedList<>(); + queue.offer(root); + // while 循环控制从上向下一层层遍历 + while (!queue.isEmpty()) { + int size = queue.size(); + // 记录这一层的节点值 + List level = new LinkedList<>(); + // for 循环控制每一层从左向右遍历 + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + level.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + res.add(level); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" new file mode 100644 index 0000000..dae1cfc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" @@ -0,0 +1,73 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 二叉树的层次遍历 II + * + * @author Zhang Peng + * @since 2020-06-18 + */ +public class 二叉树的层次遍历2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(15, 7)); + expectList.add(Arrays.asList(9, 20)); + expectList.add(Arrays.asList(3)); + Assertions.assertArrayEquals(expectList.toArray(), s.levelOrderBottom(root).toArray()); + + Solution s2 = new Solution(); + TreeNode root2 = TreeNode.buildTree(1); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expectList2.toArray(), s2.levelOrderBottom(root2).toArray()); + + Solution s3 = new Solution(); + TreeNode root3 = TreeNode.buildTree(); + Assertions.assertArrayEquals(new LinkedList<>().toArray(), s3.levelOrderBottom(root3).toArray()); + } + + static class Solution { + + public List> levelOrderBottom(TreeNode root) { + + if (root == null) { + return new ArrayList(); + } + + LinkedList queue = new LinkedList<>(); + List> result = new LinkedList<>(); + queue.add(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List currentLevel = new LinkedList<>(); + result.add(currentLevel); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + currentLevel.add(node.val); + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + } + Collections.reverse(result); + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" new file mode 100644 index 0000000..df94b8e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 104. 二叉树的最大深度 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的最大宽度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.widthOfBinaryTree(TreeNode.buildTree(1, 3, 2, 5, 3, null, 9))); + Assertions.assertEquals(7, s.widthOfBinaryTree(TreeNode.buildTree(1, 3, 2, 5, null, null, 9, 6, null, 7))); + } + + static class Solution { + + public static class NodeInfo { + + public int id; + public TreeNode node; + + public NodeInfo(int id, TreeNode node) { + this.id = id; + this.node = node; + } + + @Override + public String toString() { + return "NodeInfo{" + + "id=" + id + + ", node=" + node.val + + '}'; + } + + } + + public int widthOfBinaryTree(TreeNode root) { + + if (root == null) return 0; + + int level = 0; + int maxWidth = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(new NodeInfo(1, root)); + while (!queue.isEmpty()) { + level++; + int size = queue.size(); + NodeInfo left = null, right = null; + for (int i = 0; i < size; i++) { + NodeInfo info = queue.poll(); + if (info == null) { continue; } + if (left == null) { left = info; } + right = info; + if (info.node.left != null) { + queue.offer(new NodeInfo(2 * info.id, info.node.left)); + } + if (info.node.right != null) { + queue.offer(new NodeInfo(2 * info.id + 1, info.node.right)); + } + } + // System.out.printf("level: %d, left: %s, right: %s\n", level, left.toString(), right.toString()); + int width = right.id - left.id + 1; + maxWidth = Math.max(maxWidth, width); + } + return maxWidth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..0d458c6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 103. 二叉树的锯齿形层序遍历 + * + * @author Zhang Peng + * @date 2025-10-21 + */ +public class 二叉树的锯齿形层序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(3)); + expectList.add(Arrays.asList(20, 9)); + expectList.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expectList.toArray(), s.zigzagLevelOrder(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(1, 2, 3, 4, null, null, 5); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + expectList2.add(Arrays.asList(3, 2)); + expectList2.add(Arrays.asList(4, 5)); + Assertions.assertArrayEquals(expectList2.toArray(), s.zigzagLevelOrder(root2).toArray()); + } + + static class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + List> result = new LinkedList<>(); + if (root == null) return result; + + boolean reverse = false; + LinkedList queue = new LinkedList<>(); + queue.addLast(root); + while (!queue.isEmpty()) { + int size = queue.size(); + LinkedList layer = new LinkedList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.removeFirst(); + if (reverse) { + layer.addFirst(node.val); + } else { + layer.addLast(node.val); + } + if (node.left != null) queue.addLast(node.left); + if (node.right != null) queue.addLast(node.right); + } + reverse = !reverse; + result.add(layer); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..c9bdb3c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 515. 在每个树行中找最大值 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 在每个树行中找最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 9 }, + s.largestValues(TreeNode.buildTree(1, 3, 2, 5, 3, null, 9)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s.largestValues(TreeNode.buildTree(1, 2, 3)).toArray()); + } + + static class Solution { + + public List largestValues(TreeNode root) { + + if (root == null) { return new LinkedList<>(); } + + LinkedList res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + int max = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + max = Math.max(max, node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + res.add(max); + } + + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" new file mode 100644 index 0000000..061e188 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import cn.hutool.json.JSONUtil; +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; + +/** + * 116. 填充每个节点的下一个右侧节点指针 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 填充每个节点的下一个右侧节点指针 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode treeNode = TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7); + Node root = JSONUtil.toBean(JSONUtil.toJsonStr(treeNode), Node.class); + s.connect(root); + System.out.println(root); + } + + static class Solution { + + public Node connect(Node root) { + if (root == null) return root; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + Node prev = queue.poll(); + if (prev.left != null) queue.offer(prev.left); + if (prev.right != null) queue.offer(prev.right); + for (int i = 1; i < size; i++) { + Node next = queue.poll(); + prev.next = next; + prev = next; + if (next.left != null) queue.offer(next.left); + if (next.right != null) queue.offer(next.right); + } + } + return root; + } + + } + + static class Node extends TreeNode { + + public Node next; + public Node left; + public Node right; + + public Node(int val) { + super(val); + } + + public Node(int val, TreeNode left, TreeNode right) { + super(val, left, right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" new file mode 100644 index 0000000..187eae0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import cn.hutool.json.JSONUtil; +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; + +/** + * 117. 填充每个节点的下一个右侧节点指针 II + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 填充每个节点的下一个右侧节点指针2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode treeNode = TreeNode.buildTree(1, 2, 3, 4, 5, null, 7); + Node root = JSONUtil.toBean(JSONUtil.toJsonStr(treeNode), Node.class); + s.connect(root); + System.out.println(root); + } + + static class Solution { + + public Node connect(Node root) { + if (root == null) return null; + traverse(root); + return root; + } + + public void traverse(Node root) { + if (root == null) return; + LinkedList q = new LinkedList<>(); + q.offer(root); + + while (!q.isEmpty()) { + int size = q.size(); + Node prev = null; + for (int i = 0; i < size; i++) { + Node cur = q.poll(); + if (prev != null) { prev.next = cur; } + if (cur.left != null) q.offer(cur.left); + if (cur.right != null) q.offer(cur.right); + prev = cur; + } + } + } + + } + + static class Node extends TreeNode { + + public Node next; + public Node left; + public Node right; + + public Node(int val) { + super(val); + } + + public Node(int val, TreeNode left, TreeNode right) { + super(val, left, right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" new file mode 100644 index 0000000..8d58a76 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1609. 奇偶树 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 奇偶树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isEvenOddTree(TreeNode.buildTree(1, 10, 4, 3, null, 7, 9, 12, 8, 6, null, null, 2))); + Assertions.assertFalse(s.isEvenOddTree(TreeNode.buildTree(5, 4, 2, 3, 3, 7))); + Assertions.assertFalse(s.isEvenOddTree(TreeNode.buildTree(5, 9, 1, 3, 5, 7))); + Assertions.assertTrue(s.isEvenOddTree(TreeNode.buildTree(1))); + Assertions.assertTrue( + s.isEvenOddTree(TreeNode.buildTree(11, 8, 6, 1, 3, 9, 11, 30, 20, 18, 16, 12, 10, 4, 2, 17))); + } + + static class Solution { + + public boolean isEvenOddTree(TreeNode root) { + if (root == null) return false; + int level = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + boolean odd = level % 2 != 0; + Integer lastVal = null; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (odd) { // 奇数层 + // 奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减 + if (node.val % 2 != 0) { return false; } + if (lastVal != null && node.val >= lastVal) { return false; } + } else { // 偶数层 + // 偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增 + if (node.val % 2 == 0) { return false; } + if (lastVal != null && node.val <= lastVal) { return false; } + } + lastVal = node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + level++; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" new file mode 100644 index 0000000..740f4ea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 919. 完全二叉树插入器 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 完全二叉树插入器 { + + public static void main(String[] args) { + CBTInserter c = new CBTInserter(TreeNode.buildTree(1, 2)); + Assertions.assertEquals(1, c.insert(3)); + Assertions.assertEquals(2, c.insert(4)); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4), c.get_root()); + } + + static class CBTInserter { + + private TreeNode root; + private final LinkedList unfull; + + public CBTInserter(TreeNode root) { + this.root = root; + this.unfull = new LinkedList<>(); + + LinkedList q = new LinkedList<>(); + q.offer(this.root); + while (!q.isEmpty()) { + for (int i = 0; i < q.size(); i++) { + TreeNode node = q.poll(); + if (node.left != null) { + q.offer(node.left); + } + if (node.right != null) { + q.offer(node.right); + } + if (node.left == null || node.right == null) { + unfull.offer(node); + } + } + } + } + + public int insert(int val) { + if (root == null) { + root = new TreeNode(val); + return 0; + } + + TreeNode parent = unfull.peek(); + TreeNode node = new TreeNode(val); + if (parent.left == null) { + parent.left = node; + } else if (parent.right == null) { + parent.right = node; + unfull.poll(); + } + unfull.offer(node); + return parent.val; + } + + public TreeNode get_root() { + return this.root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" new file mode 100644 index 0000000..0992a9a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1302. 层数最深叶子节点的和 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 层数最深叶子节点的和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, + s.deepestLeavesSum(TreeNode.buildTree(1, 2, 3, 4, 5, null, 6, 7, null, null, null, null, 8))); + Assertions.assertEquals(19, + s.deepestLeavesSum(TreeNode.buildTree(6, 7, 8, 2, 7, 1, 3, 9, null, 1, 4, null, null, null, 5))); + } + + static class Solution { + + public int deepestLeavesSum(TreeNode root) { + + if (root == null) { return 0; } + + int sum = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + sum = 0; + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + } + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" new file mode 100644 index 0000000..37abde6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * LCR 149. 彩灯装饰记录 I + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + Assertions.assertArrayEquals(new int[] { 8, 17, 21, 18, 6 }, s.decorateRecord(root)); + } + + static class Solution { + + public int[] decorateRecord(TreeNode root) { + ArrayList list = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + return list.stream().mapToInt(Integer::intValue).toArray(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" new file mode 100644 index 0000000..26ba27f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * LCR 150. 彩灯装饰记录 II + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + List> expectList = new ArrayList<>(); + expectList.add(Arrays.asList(8)); + expectList.add(Arrays.asList(17, 21)); + expectList.add(Arrays.asList(18, 6)); + List> output = s.decorateRecord(root); + for (int i = 0; i < expectList.size(); i++) { + Assertions.assertArrayEquals(expectList.get(i).toArray(), output.get(i).toArray()); + } + } + + static class Solution { + + public List> decorateRecord(TreeNode root) { + List> res = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + List list = new ArrayList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (list.size() != 0) { + res.add(list); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" new file mode 100644 index 0000000..7985e9f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * LCR 151. 彩灯装饰记录 III + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录3 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + List> expectList = new ArrayList<>(); + expectList.add(Arrays.asList(8)); + expectList.add(Arrays.asList(21, 17)); + expectList.add(Arrays.asList(18, 6)); + List> output = s.decorateRecord(root); + for (int i = 0; i < expectList.size(); i++) { + Assertions.assertArrayEquals(expectList.get(i).toArray(), output.get(i).toArray()); + } + } + + static class Solution { + + public List> decorateRecord(TreeNode root) { + List> res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + boolean reverse = false; + while (!queue.isEmpty()) { + LinkedList list = new LinkedList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + if (reverse) { + list.addFirst(node.val); + } else { + list.addLast(node.val); + } + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (list.size() != 0) { + res.add(list); + } + reverse = !reverse; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" new file mode 100644 index 0000000..d0bd78b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1161. 最大层内元素和 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 最大层内元素和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, + s.maxLevelSum(TreeNode.buildTree(1, 7, 0, 7, -8, null, null))); + Assertions.assertEquals(2, + s.maxLevelSum(TreeNode.buildTree(989, null, 10250, 98693, -89388, null, null, null, -32127))); + Assertions.assertEquals(3, + s.maxLevelSum(TreeNode.buildTree(-100, -200, -300, -20, -5, -10, null))); + } + + static class Solution { + + public int maxLevelSum(TreeNode root) { + if (root == null) { return 0; } + int depth = 1; + int max = Integer.MIN_VALUE; + int maxDepth = 1; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int sum = 0; + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (sum > max) { + max = sum; + maxDepth = depth; + } + depth++; + } + return maxDepth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" new file mode 100644 index 0000000..ca687c2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 814. 二叉树剪枝 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 二叉树剪枝 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(1, null, 0, null, 1), + s.pruneTree(TreeNode.buildTree(1, null, 0, 0, 1))); + Assertions.assertEquals(TreeNode.buildTree(1, null, 1, null, 1), + s.pruneTree(TreeNode.buildTree(1, 0, 1, 0, 0, 0, 1))); + Assertions.assertEquals(TreeNode.buildTree(1, 1, 0, 1, 1, null, 1), + s.pruneTree(TreeNode.buildTree(1, 1, 0, 1, 1, 0, 1, 0))); + } + + static class Solution { + + public TreeNode pruneTree(TreeNode root) { + if (root == null) { return null; } + + root.left = pruneTree(root.left); + root.right = pruneTree(root.right); + + if (root.left == null && root.right == null && root.val == 0) { + return null; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..0572005 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 94. 二叉树的中序遍历 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树的中序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 2 }, + s.inorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.inorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s.inorderTraversal(TreeNode.buildTree(1)).toArray()); + } + + private static class Solution { + + List values; + + public List inorderTraversal(TreeNode root) { + values = new ArrayList<>(); + traverse(root); + return values; + } + + public void traverse(TreeNode root) { + if (root == null) return; + // 【前序】 + // System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + // System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + // System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + values.add(root.val); + traverse(root.right); + // 【后序】 + // System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..ba15417 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 144. 二叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的前序遍历 { + + public static void main(String[] args) { + + Solution s1 = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, + s1.preorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s1.preorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s1.preorderTraversal(TreeNode.buildTree(1)).toArray(new Integer[0])); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, + s2.preorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s2.preorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s2.preorderTraversal(TreeNode.buildTree(1)).toArray(new Integer[0])); + } + + /** + * 【分解】思路解法 + */ + private static class Solution { + + public List preorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) { + return res; + } + // 前序遍历的结果,root.val 在第一个 + res.add(root.val); + // 利用函数定义,后面接着左子树的前序遍历结果 + res.addAll(preorderTraversal(root.left)); + // 利用函数定义,最后接着右子树的前序遍历结果 + res.addAll(preorderTraversal(root.right)); + return res; + } + + } + + /** + * 【遍历】思路解法 + */ + private static class Solution2 { + + List res; + + public List preorderTraversal(TreeNode root) { + res = new ArrayList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + res.add(root.val); + traverse(root.left); + traverse(root.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..cdbcba2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 145. 二叉树的后序遍历 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树的后序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 3, 2, 1 }, + s.postorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.postorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s.postorderTraversal(TreeNode.buildTree(1)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 4, 6, 7, 5, 2, 9, 8, 3, 1 }, + s.postorderTraversal(TreeNode.buildTree(1, 2, 3, 4, 5, null, 8, null, null, 6, 7, 9)).toArray()); + } + + private static class Solution { + + List values = null; + + public List postorderTraversal(TreeNode root) { + values = new ArrayList<>(); + traverse(root); + return values; + } + + public void traverse(TreeNode root) { + if (root == null) return; + // 【前序】 + System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + values.add(root.val); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" new file mode 100644 index 0000000..2d5500b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 110. 平衡二叉树 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 二叉树的坡度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.findTilt(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(15, s.findTilt(TreeNode.buildTree(4, 2, 9, 3, 5, null, 7))); + Assertions.assertEquals(9, s.findTilt(TreeNode.buildTree(21, 7, 14, 1, 1, 2, 2, 3, 3))); + } + + static class Solution { + + int res = 0; + + public int findTilt(TreeNode root) { + res = 0; + sum(root); + return res; + } + + public int sum(TreeNode root) { + if (root == null) { return 0; } + int left = sum(root.left); + int right = sum(root.right); + res += Math.abs(left - right); + return left + right + root.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" new file mode 100644 index 0000000..550c275 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 508. 出现次数最多的子树元素和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 出现次数最多的子树元素和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, -3, 4 }, s.findFrequentTreeSum(TreeNode.buildTree(5, 2, -3))); + Assertions.assertArrayEquals(new int[] { 2 }, s.findFrequentTreeSum(TreeNode.buildTree(5, 2, -5))); + } + + static class Solution { + + private Map map; + + public int[] findFrequentTreeSum(TreeNode root) { + map = new HashMap<>(); + sum(root); + + int max = Integer.MIN_VALUE; + List list = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + Integer k = entry.getKey(); + Integer v = entry.getValue(); + if (v > max) { + max = v; + list.clear(); + list.add(k); + } else if (v == max) { + list.add(k); + } + } + + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } + + public int sum(TreeNode root) { + if (root == null) { return 0; } + int left = sum(root.left); + int right = sum(root.right); + int sum = left + right + root.val; + map.put(sum, map.getOrDefault(sum, 0) + 1); + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" new file mode 100644 index 0000000..f273bae --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 814. 二叉树剪枝 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 删除给定值的叶子节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(1, null, 3, null, 4), + s.removeLeafNodes(TreeNode.buildTree(1, 2, 3, 2, null, 2, 4), 2)); + Assertions.assertEquals(TreeNode.buildTree(1, 3, null, null, 2), + s.removeLeafNodes(TreeNode.buildTree(1, 3, 3, 3, 2), 3)); + Assertions.assertEquals(TreeNode.buildTree(1), + s.removeLeafNodes(TreeNode.buildTree(1, 2, null, 2, null, 2), 2)); + } + + static class Solution { + + public TreeNode removeLeafNodes(TreeNode root, int target) { + if (root == null) { return null; } + root.left = removeLeafNodes(root.left, target); + root.right = removeLeafNodes(root.right, target); + if (root.left == null && root.right == null && root.val == target) { + return null; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" new file mode 100644 index 0000000..e1463ce --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * LCR 149. 彩灯装饰记录 I + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 叶子相似的树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.leafSimilar(TreeNode.buildTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4), + TreeNode.buildTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8))); + } + + static class Solution { + + public boolean leafSimilar(TreeNode root1, TreeNode root2) { + List root1Leafs = new ArrayList<>(); + List root2Leafs = new ArrayList<>(); + getLeafs(root1, root1Leafs); + getLeafs(root2, root2Leafs); + if (root1Leafs.size() != root2Leafs.size()) { + return false; + } + for (int i = 0; i < root1Leafs.size(); i++) { + if (!Objects.equals(root1Leafs.get(i), root2Leafs.get(i))) { + return false; + } + } + return true; + } + + public void getLeafs(TreeNode root, List leafs) { + if (root == null) { + return; + } + if (root.left == null && root.right == null) { + leafs.add(root.val); + } + getLeafs(root.left, leafs); + getLeafs(root.right, leafs); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..b67c5f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 110. 平衡二叉树 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 平衡二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isBalanced(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertFalse(s.isBalanced(TreeNode.buildTree(1, 2, 2, 3, 3, null, null, 4, 4))); + Assertions.assertTrue(s.isBalanced(TreeNode.buildTree())); + } + + static class Solution { + + boolean isOk = true; + + public boolean isBalanced(TreeNode root) { + isOk = true; + maxDepth(root); + return isOk; + } + + public int maxDepth(TreeNode root) { + if (root == null) { return 0; } + if (root.left == null && root.right == null) { return 1; } + int leftDepth = maxDepth(root.left); + int rightDepth = maxDepth(root.right); + if (Math.abs(leftDepth - rightDepth) > 1) { + isOk = false; + } + return Math.max(leftDepth, rightDepth) + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..820c810 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 124. 二叉树中的最大路径和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树中的最大路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.maxPathSum(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(42, s.maxPathSum(TreeNode.buildTree(-10, 9, 20, null, null, 15, 7))); + } + + static class Solution { + + int res = Integer.MIN_VALUE; + + public int maxPathSum(TreeNode root) { + if (root == null) { return 0; } + oneSideMax(root); + return res; + } + + int oneSideMax(TreeNode root) { + if (root == null) { return 0; } + int left = Math.max(oneSideMax(root.left), 0); + int right = Math.max(oneSideMax(root.right), 0); + int pathMaxSum = root.val + left + right; + res = Math.max(res, pathMaxSum); + return Math.max(left, right) + root.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..b0b70bf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 106. + * 从中序与后序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 从中序与后序遍历序列构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.buildTree(new int[] { 9, 3, 15, 20, 7 }, new int[] { 9, 15, 7, 20, 3 }); + Assertions.assertEquals(TreeNode.buildTree(3, 9, 20, null, null, 15, 7), output1); + + TreeNode output2 = s.buildTree(new int[] { -1 }, new int[] { -1 }); + Assertions.assertEquals(TreeNode.buildTree(-1), output2); + } + + static class Solution { + + Map map = null; + + public TreeNode buildTree(int[] inorder, int[] postorder) { + if (inorder == null || postorder == null) { return null; } + map = new HashMap<>(inorder.length); + for (int i = 0; i < inorder.length; i++) { + map.put(inorder[i], i); + } + return build(inorder, 0, inorder.length - 1, + postorder, 0, postorder.length - 1); + } + + public TreeNode build(int[] inorder, int inLow, int inHigh, + int[] postorder, int postLow, int postHigh) { + if (postLow > postHigh) { return null; } + int inMid = map.get(postorder[postHigh]); + int leftLen = inMid - inLow; + TreeNode root = new TreeNode(postorder[postHigh]); + root.left = build(inorder, inLow, inMid - 1, + postorder, postLow, postLow + leftLen - 1); + root.right = build(inorder, inMid + 1, inHigh, + postorder, postLow + leftLen, postHigh - 1); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..af00d0e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 105. + * 从前序与中序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 从前序与中序遍历序列构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.buildTree(new int[] { 3, 9, 20, 15, 7 }, new int[] { 9, 3, 15, 20, 7 }); + Assertions.assertEquals(TreeNode.buildTree(3, 9, 20, null, null, 15, 7), output1); + + TreeNode output2 = s.buildTree(new int[] { -1 }, new int[] { -1 }); + Assertions.assertEquals(TreeNode.buildTree(-1), output2); + } + + static class Solution { + + Map map = null; + + public TreeNode buildTree(int[] preorder, int[] inorder) { + if (inorder == null || preorder == null) { return null; } + map = new HashMap<>(inorder.length); + for (int i = 0; i < inorder.length; i++) { + map.put(inorder[i], i); + } + return build(preorder, 0, preorder.length - 1, + inorder, 0, inorder.length - 1); + } + + public TreeNode build(int[] preorder, int preLow, int preHigh, + int[] inorder, int inLow, int inHigh) { + if (preLow > preHigh) { return null; } + int inMid = map.get(preorder[preLow]); + int leftLen = inMid - inLow; + TreeNode root = new TreeNode(preorder[preLow]); + root.left = build(preorder, preLow + 1, preLow + leftLen, + inorder, inLow, inMid - 1); + root.right = build(preorder, preLow + leftLen + 1, preHigh, + inorder, inMid + 1, inHigh); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" new file mode 100644 index 0000000..c3a3959 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 1110. 删点成林 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 删点成林 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List output = s.delNodes(TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7), new int[] { 3, 5 }); + List expect = new ArrayList<>(); + expect.add(TreeNode.buildTree(1, 2, null, 4)); + expect.add(TreeNode.buildTree(6)); + expect.add(TreeNode.buildTree(7)); + for (int i = 0; i < output.size(); i++) { + Assertions.assertEquals(expect.get(i), output.get(i)); + } + + List output2 = s.delNodes(TreeNode.buildTree(1, 2, 4, null, 3), new int[] { 3 }); + List expect2 = new ArrayList<>(); + expect2.add(TreeNode.buildTree(1, 2, 4)); + for (int i = 0; i < output2.size(); i++) { + Assertions.assertEquals(expect2.get(i), output2.get(i)); + } + } + + static class Solution { + + private List res; + + public List delNodes(TreeNode root, int[] to_delete) { + res = new ArrayList<>(); + Set set = new HashSet<>(); + for (int val : to_delete) { + set.add(val); + } + delNodes(root, set, false); + return res; + } + + public TreeNode delNodes(TreeNode root, Set set, boolean hasParent) { + if (root == null) { return null; } + boolean deleted = set.contains(root.val); + if (!deleted && !hasParent) { + res.add(root); + } + + root.left = delNodes(root.left, set, !deleted); + root.right = delNodes(root.right, set, !deleted); + return deleted ? null : root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..0daa7f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 101. 对称二叉树 + * + * @author Zhang Peng + * @date 2020-01-28 + */ +public class 对称二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSymmetric(TreeNode.buildTree(1, 2, 2, 3, 4, 4, 3))); + Assertions.assertFalse(s.isSymmetric(TreeNode.buildTree(1, 2, 2, null, 3, null, 3))); + } + + static class Solution { + + public boolean isSymmetric(TreeNode root) { + if (root == null) { return true; } + return isSymmetric(root.left, root.right); + } + + boolean isSymmetric(TreeNode left, TreeNode right) { + if (left == null && right == null) { return true; } + if (left == null || right == null) { return false; } + // 两个根节点需要相同 + if (left.val != right.val) { return false; } + // 左右子树也需要镜像对称 + return isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..e943be3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; +import java.util.List; + +/** + * 894. 所有可能的真二叉树 + * + * @author Zhang Peng + * @date 2025-10-21 + */ +public class 所有可能的真二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + List trees = s.allPossibleFBT(7); + trees.forEach(tree -> { + System.out.println(TreeNode.serialize(tree)); + }); + } + + static class Solution { + + // 备忘录,记录 n 个节点能够组合成的所有可能二叉树 + List[] memo; + + public List allPossibleFBT(int n) { + if (n % 2 == 0) { + // 题目描述的满二叉树不可能是偶数个节点 + return new LinkedList<>(); + } + memo = new LinkedList[n + 1]; + return build(n); + } + + // 定义:输入一个 n,生成节点树为 n 的所有可能的满二叉树 + public List build(int n) { + List res = new LinkedList<>(); + // base case + if (n == 1) { + res.add(new TreeNode(0)); + return res; + } + if (memo[n] != null) { + // 避免冗余计算 + return memo[n]; + } + + // 递归生成所有符合条件的左右子树 + for (int i = 1; i < n; i += 2) { + int j = n - i - 1; + // 利用函数定义,生成左右子树 + List leftSubTrees = build(i); + List rightSubTrees = build(j); + // 左右子树的不同排列也能构成不同的二叉树 + for (TreeNode left : leftSubTrees) { + for (TreeNode right : rightSubTrees) { + // 生成根节点 + TreeNode root = new TreeNode(0); + // 组装出一种可能的二叉树形状 + root.left = left; + root.right = right; + // 加入结果列表 + res.add(root); + } + } + } + // 存入备忘录 + memo[n] = res; + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" new file mode 100644 index 0000000..4cac6df --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 998. 最大二叉树 II + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 最大二叉树2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + Assertions.assertEquals(TreeNode.buildTree(5, 4, null, 1, 3, null, null, 2), + s.insertIntoMaxTree(TreeNode.buildTree(4, 1, 3, null, null, 2), 5)); + + Assertions.assertEquals(TreeNode.buildTree(5, 2, 4, null, 1, null, 3), + s.insertIntoMaxTree(TreeNode.buildTree(5, 2, 4, null, 1), 3)); + + Assertions.assertEquals(TreeNode.buildTree(5, 2, 4, null, 1, 3), + s.insertIntoMaxTree(TreeNode.buildTree(5, 2, 3, null, 1), 4)); + } + + static class Solution { + + public TreeNode insertIntoMaxTree(TreeNode root, int val) { + if (root == null) { return new TreeNode(val); } + if (root.val < val) { + // 如果 val 是整棵树最大的,那么原来的这棵树应该是 val 节点的左子树, + // 因为 val 节点是接在原始数组 a 的最后一个元素 + TreeNode node = new TreeNode(val); + node.left = root; + return node; + } else { + // 如果 val 不是最大的,那么就应该在右子树上, + // 因为 val 节点是接在原始数组 a 的最后一个元素 + root.right = insertIntoMaxTree(root.right, val); + return root; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..5bb6c8f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 106. + * 从中序与后序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 根据前序和后序遍历构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + TreeNode output1 = s.constructFromPrePost(new int[] { 1, 2, 4, 5, 3, 6, 7 }, + new int[] { 4, 5, 2, 6, 7, 3, 1 }); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7), output1); + + TreeNode output2 = s.constructFromPrePost(new int[] { 1 }, new int[] { 1 }); + Assertions.assertEquals(TreeNode.buildTree(1), output2); + } + + static class Solution { + + Map map = new HashMap<>(); + + public TreeNode constructFromPrePost(int[] preorder, int[] postorder) { + if (preorder.length == 0 || postorder.length == 0) { return null; } + for (int i = 0; i < postorder.length; i++) { + map.put(postorder[i], i); + } + return build(preorder, 0, preorder.length - 1, + postorder, 0, postorder.length - 1); + } + + public TreeNode build(int[] preorder, int preLow, int preHigh, + int[] postorder, int postLow, int postHigh) { + if (preLow > preHigh) { return null; } + if (preLow == preHigh) { return new TreeNode(preorder[preLow]); } + int leftRootVal = preorder[preLow + 1]; + int leftPostHigh = map.get(leftRootVal); + int leftLen = leftPostHigh - postLow + 1; + TreeNode root = new TreeNode(preorder[preLow]); + root.left = build(preorder, preLow + 1, preLow + leftLen, + postorder, postLow, leftPostHigh); + root.right = build(preorder, preLow + leftLen + 1, preHigh, + postorder, leftPostHigh + 1, postHigh - 1); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" new file mode 100644 index 0000000..04205a8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 100. 相同的树 + * + * @author Zhang Peng + * @date 2020-01-28 + */ +public class 相同的树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSameTree(TreeNode.buildTree(1, 2, 3), TreeNode.buildTree(1, 2, 3))); + Assertions.assertFalse(s.isSameTree(TreeNode.buildTree(1, 2), TreeNode.buildTree(1, 2, 3))); + Assertions.assertFalse(s.isSameTree(TreeNode.buildTree(1, 2, 1), TreeNode.buildTree(1, 1, 2))); + } + + static class Solution { + + public boolean isSameTree(TreeNode p, TreeNode q) { + if (p == null && q == null) { return true; } + if (p == null || q == null) { return false; } + if (p.val != q.val) { return false; } + return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..f3d1c1f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 951. 翻转等价二叉树 + * + * @author Zhang Peng + * @date 2025-10-29 + */ +public class 翻转等价二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode p = TreeNode.buildTree(1, 2, 3, 4, 5, 6, null, null, null, 7, 8); + TreeNode q = TreeNode.buildTree(1, 3, 2, null, 6, 4, 5, null, null, null, null, 8, 7); + Assertions.assertTrue(s.flipEquiv(p, q)); + Assertions.assertTrue(s.flipEquiv(TreeNode.buildTree(), TreeNode.buildTree())); + Assertions.assertFalse(s.flipEquiv(TreeNode.buildTree(), TreeNode.buildTree(1))); + } + + static class Solution { + + // 定义:输入两棵二叉树,判断这两棵二叉树是否是翻转等价的 + public boolean flipEquiv(TreeNode root1, TreeNode root2) { + // 判断 root1 和 root2 两个节点是否能够匹配 + if (root1 == null && root2 == null) { return true; } + if (root1 == null || root2 == null) { return false; } + if (root1.val != root2.val) { return false; } + // 根据函数定义,判断子树是否能够匹配 + // 不翻转、翻转两种情况满足一种即可算是匹配 + return (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right)) // 不翻转子树 + || (flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left)); // 翻转子树 + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" new file mode 100644 index 0000000..6165bf4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 331. 验证二叉树的前序序列化 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 验证二叉树的前序序列化 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isValidSerialization("9,3,4,#,#,1,#,#,2,#,6,#,#")); + Assertions.assertFalse(s.isValidSerialization("1,#")); + Assertions.assertFalse(s.isValidSerialization("9,#,#,1")); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.isValidSerialization("9,3,4,#,#,1,#,#,2,#,6,#,#")); + Assertions.assertFalse(s2.isValidSerialization("1,#")); + Assertions.assertFalse(s2.isValidSerialization("9,#,#,1")); + } + + static class Solution { + + /** + * 参考题解:https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/solutions/651132/pai-an-jiao-jue-de-liang-chong-jie-fa-zh-66nt + */ + public boolean isValidSerialization(String preorder) { + LinkedList stack = new LinkedList<>(); + for (String s : preorder.split(",")) { + stack.push(s); + while (stack.size() >= 3 + && stack.get(0).equals("#") + && stack.get(1).equals("#") + && !stack.get(2).equals("#")) { + stack.pop(); + stack.pop(); + stack.pop(); + stack.push("#"); + } + } + return stack.size() == 1 && stack.pop().equals("#"); + } + + } + + static class Solution2 { + + /** + * 参考题解:https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/solutions/651132/pai-an-jiao-jue-de-liang-chong-jie-fa-zh-66nt + */ + public boolean isValidSerialization(String preorder) { + int diff = 1; + for (String s : preorder.split(",")) { + diff -= 1; + if (diff < 0) { + return false; + } + if (!s.equals("#")) { + diff += 2; + } + } + return diff == 0; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" new file mode 100644 index 0000000..908fd91 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 1457. 二叉树中的伪回文路径 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 二叉树中的伪回文路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, + s.pseudoPalindromicPaths(TreeNode.buildTree(2, 3, 1, 3, 1, null, 1))); + Assertions.assertEquals(1, + s.pseudoPalindromicPaths(TreeNode.buildTree(2, 1, 1, 1, 3, null, null, null, null, null, 1))); + Assertions.assertEquals(1, + s.pseudoPalindromicPaths(TreeNode.buildTree(9))); + } + + static class Solution { + + int res = 0; + // 计数数组,题目说了 1 <= root.val <= 9 + int[] count; + + public int pseudoPalindromicPaths(TreeNode root) { + res = 0; + count = new int[10]; + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + + // 选择 + if (root.left == null && root.right == null) { + count[root.val]++; + int odd = 0; + for (int cnt : count) { + if (cnt % 2 != 0) { + odd++; + } + } + if (odd <= 1) { + res++; + } + count[root.val]--; + return; + } + + // 选择 + count[root.val]++; + + traverse(root.left); + traverse(root.right); + + // 取消选择 + count[root.val]--; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" new file mode 100644 index 0000000..58ab169 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" @@ -0,0 +1,96 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * 求根节点到叶节点数字之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 二叉树的右视图 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4 }, + s.rightSideView(TreeNode.buildTree(1, 2, 3, null, 5, null, 4)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4, 5 }, + s.rightSideView(TreeNode.buildTree(1, 2, 3, 4, null, null, null, 5)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s.rightSideView(TreeNode.buildTree(1, null, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.rightSideView(TreeNode.buildTree()).toArray()); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4 }, + s2.rightSideView(TreeNode.buildTree(1, 2, 3, null, 5, null, 4)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4, 5 }, + s2.rightSideView(TreeNode.buildTree(1, 2, 3, 4, null, null, null, 5)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s2.rightSideView(TreeNode.buildTree(1, null, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s2.rightSideView(TreeNode.buildTree()).toArray()); + + } + + // 【层序遍历】思路 + static class Solution { + + LinkedList res = null; + + public List rightSideView(TreeNode root) { + if (root == null) { return new LinkedList<>(); } + + res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + // 每层将最后一个元素加入结果集 + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + if (i == size - 1) { + res.add(node.val); + } + } + } + return res; + } + + } + + // 【遍历递归】思路 + static class Solution2 { + + int depth = 0; + LinkedHashMap map = null; + + public List rightSideView(TreeNode root) { + map = new LinkedHashMap<>(); + traverse(root); + return new LinkedList<>(map.values()); + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + depth++; + if (!map.containsKey(depth)) { + map.put(depth, root.val); + } + traverse(root.right); + traverse(root.left); + depth--; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" new file mode 100644 index 0000000..8da62a1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 二叉树的所有路径 + * + * @author Zhang Peng + * @since 2025-08-15 + */ +public class 二叉树的所有路径 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertArrayEquals(Arrays.asList("1->2->5", "1->3").toArray(), + s.binaryTreePaths(TreeNode.buildTree(1, 2, 3, 5)).toArray()); + Assertions.assertArrayEquals(Collections.singletonList("1").toArray(), + s.binaryTreePaths(TreeNode.buildTree(1)).toArray()); + } + + static class Solution { + + LinkedList res = null; + LinkedList paths = null; + + public List binaryTreePaths(TreeNode root) { + res = new LinkedList<>(); + paths = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + + // 选择 + paths.addLast(String.valueOf(root.val)); + if (root.left == null && root.right == null) { + res.addLast(String.join("->", paths)); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + + // 【前序】 + // System.out.format("root: %s -> root.left: %s\n", root.val, root.left.val); + traverse(root.left); + // 【中序】 + // System.out.format("root.left: %s -> root: %s\n", root.left.val, root.val); + // System.out.format("root: %s -> root.right: %s\n", root.val, root.right.val); + traverse(root.right); + // 【后序】 + // System.out.format("root.right: %s -> root: %s\n", root.right.val, root.val); + + // 取消选择 + paths.removeLast(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..cabcee9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 988. 从叶结点开始的最小字符串 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 从叶结点开始的最小字符串 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertEquals("dba", + s.smallestFromLeaf(TreeNode.buildTree(0, 1, 2, 3, 4, 3, 4))); + Assertions.assertEquals("adz", + s.smallestFromLeaf(TreeNode.buildTree(25, 1, 3, 1, 3, 0, 2))); + Assertions.assertEquals("abc", + s.smallestFromLeaf(TreeNode.buildTree(2, 2, 1, null, 1, 0, null, 0))); + } + + static class Solution { + + String res = null; + LinkedList nodes = null; + + public String smallestFromLeaf(TreeNode root) { + res = null; + nodes = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + // 校验 + if (root == null) { return; } + + // 选择 + nodes.addLast(root.val); + if (root.left == null && root.right == null) { + String str = toString(nodes); + // System.out.printf("\tstr: %s\n", str); + if (res == null || str.compareTo(res) < 0) { + res = str; + } + } + + traverse(root.left); + traverse(root.right); + + // 取消选择 + nodes.removeLast(); + } + + public String toString(LinkedList nodes) { + StringBuilder sb = new StringBuilder(); + for (int node : nodes) { + char c = (char) (node + 'a'); + sb.insert(0, c); + } + return sb.toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..6b45eec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1022. 从根到叶的二进制数之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 从根到叶的二进制数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(22, s.sumRootToLeaf(TreeNode.buildTree(1, 0, 1, 0, 1, 0, 1))); + Assertions.assertEquals(0, s.sumRootToLeaf(TreeNode.buildTree(0))); + } + + static class Solution { + + int sum = 0; + LinkedList nodes; + + public int sumRootToLeaf(TreeNode root) { + sum = 0; + nodes = new LinkedList<>(); + traverse(root); + return sum; + } + + public void traverse(TreeNode node) { + // 校验 + if (node == null) return; + + // 选择 + nodes.addLast(node.val); + if (node.left == null && node.right == null) { + Integer num = toNum(nodes); + System.out.printf("\tnum: %d\n", num); + sum += num; + } + + traverse(node.left); + traverse(node.right); + + // 取消选择 + nodes.removeLast(); + } + + Integer toNum(LinkedList nodes) { + int num = 0; + for (int node : nodes) { + num = num * 2 + node; + } + return num; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" new file mode 100644 index 0000000..b04a5ff --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 623. 在二叉树中增加一行 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 在二叉树中增加一行 { + + public static void main(String[] args) { + TreeNode root = TreeNode.buildTree(4, 2, 6, 3, 1, 5); + TreeNode newRoot = new Solution().addOneRow(root, 1, 2); + List list = TreeNode.toValueList(newRoot); + Assertions.assertArrayEquals(new Integer[] { 4, 1, 1, 2, null, null, 6, 3, 1, 5 }, list.toArray()); + + TreeNode root2 = TreeNode.buildTree(4, 2, 6, 3, 1, 5); + TreeNode newRoot2 = new Solution().addOneRow(root2, 1, 1); + List list2 = TreeNode.toValueList(newRoot2); + Assertions.assertArrayEquals(new Integer[] { 1, 4, null, 2, 6, 3, 1, 5 }, list2.toArray()); + } + + static class Solution { + + public TreeNode addOneRow(TreeNode root, int val, int depth) { + if (root == null) { return root; } + if (depth == 1) { + TreeNode newRoot = new TreeNode(val); + newRoot.left = root; + return newRoot; + } + LinkedList q = new LinkedList<>(); + int level = 1; + q.offer(root); + while (!q.isEmpty()) { + + int size = q.size(); + if (level == depth - 1) { + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + TreeNode left = node.left; + node.left = new TreeNode(val); + node.left.left = left; + + TreeNode right = node.right; + node.right = new TreeNode(val); + node.right.right = right; + } + break; + } + + // 层序遍历 + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + if (node.left != null) { + q.offer(node.left); + } + if (node.right != null) { + q.offer(node.right); + } + } + + level++; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" new file mode 100644 index 0000000..7d24178 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 404. 左叶子之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 左叶子之和 { + + public static void main(String[] args) { + Assertions.assertEquals(24, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + + Assertions.assertEquals(4, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(1, 2, 3, 4, 5))); + + Assertions.assertEquals(0, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(1))); + } + + static class Solution { + + int sum = 0; + + public int sumOfLeftLeaves(TreeNode root) { + traverse(root); + return sum; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + if (root.left != null && + root.left.left == null && root.left.right == null) { + sum += root.left.val; + } + traverse(root.left); + traverse(root.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" new file mode 100644 index 0000000..737d9b7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 求根节点到叶节点数字之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 求根节点到叶节点数字之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(25, s.sumNumbers(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(1026, s.sumNumbers(TreeNode.buildTree(4, 9, 0, 5, 1))); + } + + static class Solution { + + int res = 0; + LinkedList paths = null; + + public int sumNumbers(TreeNode root) { + res = 0; + paths = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + // 【校验】 + if (root == null) { return; } + + // 选择 + paths.addLast(root.val); + if (root.left == null && root.right == null) { + int num = 0; + for (Integer path : paths) { + num = num * 10 + path; + } + res += num; + } + + // 【前序】 + traverse(root.left); + // 【中序】 + traverse(root.right); + // 【后序】 + + // 取消选择 + paths.removeLast(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" deleted file mode 100644 index 748279f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @see 124. 二叉树中的最大路径和 - * @since 2020-07-06 - */ -public class 二叉树中的最大路径和 { - - public static void main(String[] args) { - 二叉树中的最大路径和 demo = new 二叉树中的最大路径和(); - TreeNode tree = TreeUtils.asTree(1, 2, 3); - Assertions.assertEquals(6, demo.maxPathSum(tree)); - TreeNode tree2 = TreeUtils.asTree(-10, 9, 20, null, null, 15, 7); - Assertions.assertEquals(42, demo.maxPathSum(tree2)); - TreeNode tree3 = TreeUtils.asTree(2, -1); - Assertions.assertEquals(2, demo.maxPathSum(tree3)); - TreeNode tree4 = TreeUtils.asTree(-2, -1); - Assertions.assertEquals(-1, demo.maxPathSum(tree4)); - } - - int maxSum; - - public int maxPathSum(TreeNode root) { - maxSum = Integer.MIN_VALUE; - maxGain(root); - return maxSum; - } - - public int maxGain(TreeNode node) { - if (node == null) { - return 0; - } - - // 递归计算左右子节点的最大贡献值 - // 只有在最大贡献值大于 0 时,才会选取对应子节点 - int left = Math.max(maxGain(node.left), 0); - int right = Math.max(maxGain(node.right), 0); - - // 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值 - int current = node.val + left + right; - - // 更新答案 - maxSum = Math.max(maxSum, current); - - // 返回节点的最大贡献值 - return node.val + Math.max(left, right); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" new file mode 100644 index 0000000..7eb9b65 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 114. 二叉树展开为链表 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树展开为链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(1, 2, 5, 3, 4, null, 6); + s.flatten(root); + Assertions.assertArrayEquals(new Integer[] { 1, null, 2, null, 3, null, 4, null, 5, null, 6 }, + TreeNode.toValueList(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(0); + s.flatten(root2); + Assertions.assertArrayEquals(new Integer[] { 0 }, TreeNode.toValueList(root2).toArray()); + + TreeNode root3 = TreeNode.buildTree(); + s.flatten(root3); + Assertions.assertArrayEquals(new Integer[] {}, TreeNode.toValueList(root3).toArray()); + } + + static class Solution { + + public void flatten(TreeNode root) { + // base case + if (root == null) { return; } + + // 利用定义,把左右子树拉平 + flatten(root.left); + flatten(root.right); + + // *** 后序遍历位置 *** + // 1、左右子树已经被拉平成一条链表 + TreeNode left = root.left; + TreeNode right = root.right; + + // 2、将左子树作为右子树 + root.left = null; + root.right = left; + + // 3、将原先的右子树接到当前右子树的末端 + TreeNode p = root; + while (p.right != null) { + p = p.right; + } + p.right = right; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" deleted file mode 100644 index fc5581f..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 二叉树的中序遍历 { - - public static List inorderTraversal(TreeNode root) { - List list = new ArrayList<>(); - if (root == null) return list; - backtrack(root, list); - return list; - } - - public static void backtrack(TreeNode root, List list) { - if (root == null) return; - if (root.left != null) backtrack(root.left, list); - list.add(root.val); - if (root.right != null) backtrack(root.right, list); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" deleted file mode 100644 index d368ddf..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 二叉树的前序遍历 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.buildTree(new Integer[] { 1, null, 2, 3 }); - List list = preorderTraversal(tree); - Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, list.toArray(new Integer[0])); - } - - public static List preorderTraversal(TreeNode root) { - List list = new ArrayList<>(); - if (root == null) return list; - backtrack(root, list); - return list; - } - - public static void backtrack(TreeNode root, List list) { - if (root == null) return; - list.add(root.val); - if (root.left != null) backtrack(root.left, list); - if (root.right != null) backtrack(root.right, list); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" deleted file mode 100644 index 742464c..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 二叉树的后序遍历 { - - public List postorderTraversal(TreeNode root) { - List list = new ArrayList<>(); - if (root == null) return list; - backtrack(root, list); - return list; - } - - public static void backtrack(TreeNode root, List list) { - if (root == null) return; - if (root.left != null) backtrack(root.left, list); - if (root.right != null) backtrack(root.right, list); - list.add(root.val); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" deleted file mode 100644 index c690a6a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.*; - -/** - * @author Zhang Peng - * @see 二叉树的层次遍历 - * @since 2020-06-18 - */ -public class 二叉树的层次遍历 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(3, 9, 20, null, null, 15, 7); - List> resultList = levelOrder(tree); - List> expectList = new LinkedList<>(); - expectList.add(Arrays.asList(3)); - expectList.add(Arrays.asList(9, 20)); - expectList.add(Arrays.asList(15, 7)); - Assertions.assertArrayEquals(expectList.toArray(), resultList.toArray()); - } - - /** - * 基于 BFS 实现二叉树层次遍历。关键在于使用一个队列存储 - */ - public static List> levelOrder(TreeNode root) { - List> result = new ArrayList<>(); - if (root == null) return result; - - Queue queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - List list = new ArrayList<>(); - for (int i = 0; i < size; i++) { - TreeNode node = queue.poll(); - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); - list.add(node.val); - } - result.add(list); - } - - return result; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" deleted file mode 100644 index 1bc152a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * 二叉树的层次遍历 II 算法实现 - * - *

- * 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
- *
- * 例如:
- * 给定二叉树 [3,9,20,null,null,15,7],
- *
- *     3
- *    / \
- *   9  20
- *     /  \
- *    15   7
- * 返回其自底向上的层次遍历为:
- *
- * [
- *   [15,7],
- *   [9,20],
- *   [3]
- * ]
- * 
- * - * @see 二叉树的层次遍历 II - */ -public class 二叉树的层次遍历2 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(3, 9, 20, null, null, 15, 7); - List> resultList = levelOrderBottom(tree); - List> expectList = new LinkedList<>(); - expectList.add(Arrays.asList(15, 7)); - expectList.add(Arrays.asList(9, 20)); - expectList.add(Arrays.asList(3)); - Assertions.assertArrayEquals(expectList.toArray(), resultList.toArray()); - } - - public static List> levelOrderBottom(TreeNode root) { - List> result = new LinkedList<>(); - LinkedList queue = new LinkedList<>(); - if (root == null) return result; - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - List list = new LinkedList<>(); - for (int i = 0; i < size; i++) { - TreeNode node = queue.poll(); - if (node != null) { - list.add(node.val); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); - } - } - result.add(list); - } - Collections.reverse(result); - return result; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" index f194694..4eac5e3 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" @@ -1,67 +1,88 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; -import java.util.Arrays; import java.util.LinkedList; -import java.util.List; /** + * 297. 二叉树的序列化与反序列化 + * * @author Zhang Peng * @since 2020-07-06 */ public class 二叉树的序列化与反序列化 { public static void main(String[] args) { - TreeNode tree = deserialize("[1,2,3,null,null,4,5]"); - System.out.println("args = " + serialize(tree)); - } - public static String rserialize(TreeNode root, String str) { - if (root == null) { - str += "null,"; - } else { - str += str.valueOf(root.val) + ","; - str = rserialize(root.left, str); - str = rserialize(root.right, str); - } - return str; + String input1 = "1,2,3,null,null,4,5,null,null,null,null,"; + String input2 = "null,"; + String input3 = "1,null,null,"; + String input4 = "1,2,null,null,null,"; + + Solution s = new Solution(); + Assertions.assertEquals(input1, s.serialize(s.deserialize(input1))); + Assertions.assertEquals(input2, s.serialize(s.deserialize(input2))); + Assertions.assertEquals(input3, s.serialize(s.deserialize(input3))); + Assertions.assertEquals(input4, s.serialize(s.deserialize(input4))); } - public static String serialize(TreeNode root) { - String text = rserialize(root, ""); - while (text.endsWith("null,")) { - int index = text.lastIndexOf("null,"); - text = text.substring(0, index); - } - if (text.endsWith(",")) { - text = text.substring(0, text.length() - 1); + static class Solution { + + public static final String SEP = ","; + public static final String NULL = "null"; + + // 主函数,将二叉树序列化为字符串 + public String serialize(TreeNode root) { + StringBuilder sb = new StringBuilder(); + serialize(root, sb); + return sb.toString(); } - return text; - } - public static TreeNode rdeserialize(List list) { - if (list == null || list.size() == 0) { - return null; + // 辅助函数,将二叉树存入 StringBuilder + void serialize(TreeNode root, StringBuilder sb) { + if (root == null) { + sb.append(NULL).append(SEP); + return; + } + + // ****** 前序位置 ******** + sb.append(root.val).append(SEP); + + // *********************** + + serialize(root.left, sb); + serialize(root.right, sb); } - if (list.get(0).equalsIgnoreCase("null")) { - list.remove(0); - return null; + + // 主函数,将字符串反序列化为二叉树结构 + public TreeNode deserialize(String data) { + // 将字符串转化成列表 + LinkedList nodes = new LinkedList<>(); + for (String s : data.split(SEP)) { + nodes.addLast(s); + } + return deserialize(nodes); } - TreeNode root = new TreeNode(Integer.valueOf(list.get(0))); - list.remove(0); - root.left = rdeserialize(list); - root.right = rdeserialize(list); + // 辅助函数,通过 nodes 列表构造二叉树 + TreeNode deserialize(LinkedList nodes) { + if (nodes.isEmpty()) return null; - return root; - } + // ****** 前序位置 ******** + // 列表最左侧就是根节点 + String first = nodes.removeFirst(); + if (first.equals(NULL)) return null; + TreeNode root = new TreeNode(Integer.parseInt(first)); + + // ********************* + + root.left = deserialize(nodes); + root.right = deserialize(nodes); + + return root; + } - public static TreeNode deserialize(String data) { - data = data.substring(1, data.length() - 1); - String[] nums = data.split(","); - List list = new LinkedList(Arrays.asList(nums)); - return rdeserialize(list); } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" deleted file mode 100644 index 483939b..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -/** - * 二叉树的所有路径 算法实现 - * - *
- * 给定一个二叉树,返回所有从根节点到叶子节点的路径。
- *
- * 说明: 叶子节点是指没有子节点的节点。
- *
- * 示例:
- *
- * 输入:
- *
- *    1
- *  /   \
- * 2     3
- *  \
- *   5
- *
- * 输出: ["1->2->5", "1->3"]
- *
- * 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
- * 
- * - * @see 二叉树的所有路径 - */ -public class 二叉树的所有路径 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(1, 2, 3, 5); - System.out.println("result = " + binaryTreePaths(tree)); - Assertions.assertArrayEquals(Arrays.asList("1->2->5", "1->3").toArray(), - binaryTreePaths(tree).toArray(new String[0])); - } - - public static List binaryTreePaths(TreeNode root) { - List paths = new LinkedList<>(); - recordPath(root, "", paths); - return paths; - } - - private static void recordPath(TreeNode node, String path, List paths) { - if (node == null) return; - path += node.val; - if (node.left == null && node.right == null) { - paths.add(path); - } else { - path += "->"; - recordPath(node.left, path, paths); - recordPath(node.right, path, paths); - } - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" index fea75ee..c01f06a 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" @@ -1,55 +1,82 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; -import java.util.LinkedList; -import java.util.Queue; - /** - * 104. 二叉树的最大深度 算法实现 + * 104. 二叉树的最大深度 * - * @see 104. 二叉树的最大深度 + * @author Zhang Peng + * @date 2025-08-11 */ public class 二叉树的最大深度 { public static void main(String[] args) { - TreeNode tree = TreeUtils.deserialize("[3,9,20,null,null,15,7]"); - System.out.println("result = " + maxDepthInDFS(tree)); - Assertions.assertEquals(3, maxDepthInDFS(tree)); - Assertions.assertEquals(3, maxDepthInBFS(tree)); + + Solution s = new Solution(); + Assertions.assertEquals(3, s.maxDepth(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertEquals(2, s.maxDepth(TreeNode.buildTree(1, null, 2))); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.maxDepth(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertEquals(2, s2.maxDepth(TreeNode.buildTree(1, null, 2))); } - // 基于 DFS 实现 - // 时间复杂度 O(N) - public static int maxDepthInDFS(TreeNode root) { - if (root == null) return 0; - return 1 + Math.max(maxDepthInDFS(root.left), maxDepthInDFS(root.right)); + /** + * 【分解】思路解法 + */ + static class Solution { + + public int maxDepth(TreeNode root) { + if (root == null) { return 0; } + + // 计算左右子树的最大深度 + int left = maxDepth(root.left); + int right = maxDepth(root.right); + + // 根据左右子树的最大深度推出原二叉树的最大深度 + // 整棵树的最大深度等于左右子树的最大深度取最大值, + // 然后再加上根节点自己 + return Math.max(left, right) + 1; + } + } - // 基于 BFS 实现 - // 逐层扫描,只要每层有节点,层级数+1 - // 时间复杂度 O(N) - public static int maxDepthInBFS(TreeNode root) { - - if (root == null) return 0; - - int level = 0; - Queue queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - level++; - int size = queue.size(); - for (int i = 0; i < size; i++) { - TreeNode node = queue.poll(); - if (node == null) continue; - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); + /** + * 【遍历】思路解法 + */ + static class Solution2 { + + // 记录最大深度 + int res = 0; + // 记录遍历到的节点的深度 + int depth = 0; + + public int maxDepth(TreeNode root) { + // 重置全局变量 + res = 0; + depth = 0; + traverse(root); + return res; + } + + // 遍历二叉树 + void traverse(TreeNode root) { + if (root == null) { return; } + + // 【前序遍历位置】(进入节点)增加深度 + depth++; + // 遍历到叶子节点时记录最大深度 + if (root.left == null && root.right == null) { + res = Math.max(res, depth); } + traverse(root.left); + // 【中序遍历位置】 + traverse(root.right); + // 【后序遍历位置】(离开节点)减少深度 + depth--; } - return level; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" index a2333fb..38bfbb8 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" @@ -1,80 +1,36 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; -import java.util.LinkedList; -import java.util.Queue; - /** - * 二叉树的最小深度 算法实现 - * - *
- * 给定一个二叉树,找出其最小深度。
- *
- * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
- *
- * 说明: 叶子节点是指没有子节点的节点。
- *
- * 示例:
+ * 二叉树的最小深度
  *
- * 给定二叉树 [3,9,20,null,null,15,7],
- *
- *     3
- *    / \
- *   9  20
- *     /  \
- *    15   7
- * 返回它的最小深度  2.
- * 
- * - * @see 二叉树的最小深度 + * @author Zhang Peng + * @date 2025-08-11 */ public class 二叉树的最小深度 { public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(3, 9, 20, null, null, 15, 7); - System.out.println("result = " + minDepthInDFS(tree)); - Assertions.assertEquals(2, minDepthInDFS(tree)); - Assertions.assertEquals(2, minDepthInBFS(tree)); - } + Solution s = new Solution(); - // 基于 DFS 实现 - // 时间复杂度 O(N) - public static int minDepthInDFS(TreeNode root) { - if (root == null) return 0; - if (root.left == null) return 1 + minDepthInDFS(root.right); - if (root.right == null) return 1 + minDepthInDFS(root.left); - return 1 + Math.min(minDepthInDFS(root.left), minDepthInDFS(root.right)); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + Assertions.assertEquals(2, s.minDepth(root)); + + TreeNode root2 = TreeNode.buildTree(2, null, 3, null, 4, null, 5, null, 6); + Assertions.assertEquals(5, s.minDepth(root2)); } - // 基于 BFS 实现 - // 时间复杂度 O(N) - public static int minDepthInBFS(TreeNode root) { - if (root == null) return 0; - int level = 0; - int min = -1; - Queue queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - level++; - int size = queue.size(); - for (int i = 0; i < size; i++) { - TreeNode node = queue.poll(); - if (node.left == null && node.right == null) { - if (min == -1) { - min = level; - } else { - min = Math.min(min, level); - } - } - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); - } + static class Solution { + + public int minDepth(TreeNode root) { + if (root == null) { return 0; } + int left = minDepth(root.left); + int right = minDepth(root.right); + if (root.left == null || root.right == null) { return left + right + 1; } + return Math.min(left, right) + 1; } - return min; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" index da64616..976393b 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" @@ -1,57 +1,82 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; /** - * 236. 二叉树的最近公共祖先 算法实现 + * 236. 二叉树的最近公共祖先 + * 解题思路 * - * @see 236. 二叉树的最近公共祖先 - * @see 解题思路 + * @author Zhang Peng + * @date 2020-01-28 */ public class 二叉树的最近公共祖先 { public static void main(String[] args) { - TreeNode root = TreeUtils.asTree(3, 5, 1, 6, 2, 0, 8, null, null, 7, 4); - TreeNode p = TreeUtils.find(root, 5); - TreeNode q = TreeUtils.find(root, 1); - TreeNode treeNode = lowestCommonAncestor(root, p, q); - Assertions.assertNotNull(treeNode); - Assertions.assertEquals(3, treeNode.val); - System.out.println("公共祖先节点 = " + treeNode.val); - - TreeNode p2 = TreeUtils.find(root, 5); - TreeNode q2 = TreeUtils.find(root, 4); - TreeNode treeNode2 = lowestCommonAncestor(root, p2, q2); - Assertions.assertNotNull(treeNode2); - Assertions.assertEquals(5, treeNode2.val); - System.out.println("公共祖先节点 = " + treeNode2.val); + + Solution s = new Solution(); + TreeNode input = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); + TreeNode output1 = s.lowestCommonAncestor(input, TreeNode.find(input, 2), TreeNode.find(input, 8)); + Assertions.assertNotNull(output1); + Assertions.assertEquals(6, output1.val); + TreeNode output2 = s.lowestCommonAncestor(input, TreeNode.find(input, 2), TreeNode.find(input, 4)); + Assertions.assertNotNull(output2); + Assertions.assertEquals(2, output2.val); + + Solution2 s2 = new Solution2(); + TreeNode input2 = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); + TreeNode output3 = s2.lowestCommonAncestor(input2, TreeNode.find(input2, 2), TreeNode.find(input2, 8)); + Assertions.assertNotNull(output3); + Assertions.assertEquals(6, output3.val); + TreeNode output4 = s2.lowestCommonAncestor(input2, TreeNode.find(input2, 2), TreeNode.find(input2, 4)); + Assertions.assertNotNull(output4); + Assertions.assertEquals(2, output4.val); + } + + static class Solution { + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + + if (root == null) return null; + if (root == p || root == q) return root; + + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + + if (left != null && right != null) { return root; } + return left != null ? left : right; + } + } - /** - * 递归方式求解 - *

- * 时间复杂度:O(N) 线性级 - *

- * 空间复杂度:O(2) 常数级 - */ - public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - // 如果当前节点为空,直接返回 - // 或当前节点就是 p 或 q 其中一个,显然就是要找的最近公共祖先,直接返回 - if (root == null || root == p || root == q) return root; - - TreeNode left = lowestCommonAncestor(root.left, p, q); - TreeNode right = lowestCommonAncestor(root.right, p, q); - - if (left == null) { // p、q 都不在左子树,查找右子树 - return right; - } else if (right == null) { // p、q 都不在右子树,查找左子树 - return left; - } else { - // p、q 既不在左子树,又不在右子树,直接返回当前节点 - return root; + static class Solution2 { + + private TreeNode res = null; + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + res = null; + return find(root, p.val, q.val); } + + TreeNode find(TreeNode root, int p, int q) { + if (root == null) { return null; } + // 如果已经找到 LCA 节点,直接返回 + if (res != null) { return res; } + + if (root.val == p || root.val == q) return root; + + TreeNode left = find(root.left, p, q); + TreeNode right = find(root.right, p, q); + + if (left != null && right != null) { + // 当前节点是 LCA 节点,记录下来 + res = root; + return res; + } + return left != null ? left : right; + } + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" new file mode 100644 index 0000000..c55a9d6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 543. 二叉树的直径 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的直径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.diameterOfBinaryTree(TreeNode.buildTree(1, 2, 3, 4, 5))); + Assertions.assertEquals(1, s.diameterOfBinaryTree(TreeNode.buildTree(1, 2))); + Assertions.assertEquals(0, s.diameterOfBinaryTree(TreeNode.buildTree(1))); + Assertions.assertEquals(2, s.diameterOfBinaryTree(TreeNode.buildTree(2, 3, null, 1))); + } + + static class Solution { + + private int max = 0; + + public int diameterOfBinaryTree(TreeNode root) { + max = 0; + depth(root); + return max; + } + + public int depth(TreeNode root) { + if (root == null) { return 0; } + int left = depth(root.left); + int right = depth(root.right); + int depth = Math.max(left, right) + 1; + max = Math.max(max, left + right); + return depth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" index 7bd0b78..1b91f8f 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" @@ -1,74 +1,73 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; /** - * 103. 二叉树的锯齿形层次遍历 算法实现 - *

- * 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 - *

- * 例如:给定二叉树 [3,9,20,null,null,15,7], - *

- *     3
- *    / \
- *   9  20
- *     /  \
- *    15   7
- * 
- * 返回锯齿形层次遍历如下: - *
- * [
- *   [3],
- *   [20,9],
- *   [15,7]
- * ]
- * 
+ * 103. 二叉树的锯齿形层次遍历 * - * @see 103. 二叉树的锯齿形层次遍历 + * @author Zhang Peng + * @date 2025-08-18 */ public class 二叉树的锯齿形层次遍历 { public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(3, 9, 20, null, null, 15, 7); - List> resultList = zigzagLevelOrder(tree); - System.out.println(resultList); - List> expectList = new LinkedList<>(); - expectList.add(Arrays.asList(3)); - expectList.add(Arrays.asList(20, 9)); - expectList.add(Arrays.asList(15, 7)); - Assertions.assertArrayEquals(expectList.toArray(), resultList.toArray()); + + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expect = new LinkedList<>(); + expect.add(Arrays.asList(3)); + expect.add(Arrays.asList(20, 9)); + expect.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expect.toArray(), s.zigzagLevelOrder(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(1); + List> expect2 = new LinkedList<>(); + expect2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expect2.toArray(), s.zigzagLevelOrder(root2).toArray()); + + TreeNode root3 = TreeNode.buildTree(); + List> expect3 = new LinkedList<>(); + Assertions.assertArrayEquals(expect3.toArray(), s.zigzagLevelOrder(root3).toArray()); } - public static List> zigzagLevelOrder(TreeNode root) { - List> result = new LinkedList<>(); - LinkedList queue = new LinkedList<>(); - if (root == null) return result; - queue.offer(root); - boolean reverse = false; - while (!queue.isEmpty()) { - int size = queue.size(); - List list = new ArrayList<>(); - for (int i = 0; i < size; i++) { - TreeNode node = queue.poll(); - if (node != null) { - list.add(node.val); - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); + static class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + + if (root == null) { return new LinkedList<>(); } + + LinkedList q = new LinkedList<>(); + LinkedList> res = new LinkedList<>(); + q.offer(root); + + boolean reverse = false; + while (!q.isEmpty()) { + int size = q.size(); + List cur = new LinkedList<>(); + + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + cur.add(node.val); + if (node.left != null) { q.offer(node.left); } + if (node.right != null) { q.offer(node.right); } } + if (reverse) { + Collections.reverse(cur); + } + res.add(cur); + reverse = !reverse; } - if (reverse) { - Collections.reverse(list); - result.add(list); - } else { - result.add(list); - } - reverse = !reverse; + + return res; } - return result; + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" deleted file mode 100644 index 78bf624..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Zhang Peng - * @since 2020-07-07 - */ -public class 从中序与后序遍历序列构造二叉树 { - - public static void main(String[] args) { - int[] postorder = { 9, 15, 7, 20, 3 }; - int[] inorder = { 9, 3, 15, 20, 7 }; - 从中序与后序遍历序列构造二叉树 demo = new 从中序与后序遍历序列构造二叉树(); - TreeNode root = demo.buildTree(inorder, postorder); - List list = TreeUtils.toBfsValueList(root); - System.out.println(list); - Assertions.assertArrayEquals(Arrays.asList(3, 9, 20, null, null, 15, 7).toArray(), list.toArray()); - } - - private Map map; - - public TreeNode backtrack(int[] postorder, int[] inorder, int postLeft, int postRight, int inLeft, int inRight) { - if (postLeft > postRight) return null; - - // 后序遍历中的最后一个节点就是根节点 - // 在中序遍历中定位根节点 - int inRoot = map.get(postorder[postRight]); - - // 先把根节点建立出来 - TreeNode root = new TreeNode(postorder[postRight]); - - // 得到右子树中的节点数目 - int rightTreeSize = inRight - inRoot; - - // System.out.printf("左子树:postLeft = %s, postRight = %s, inLeft = %s, inRight = %s\n", - // postorder[postLeft], postorder[postRight - rightTreeSize - 1], inorder[inLeft], inorder[inRoot - 1]); - // System.out.printf("右子树:postLeft = %s, postRight = %s, inLeft = %s, inRight = %s\n", - // postorder[postRight - rightTreeSize], postorder[postRight - 1], inorder[inRoot + 1], inorder[inRight]); - - // 递归地构造左子树,并连接到根节点 - // 先序遍历中「从 左边界+1 开始的 leftTreeSize」个元素就对应了 - // 中序遍历中「从 左边界 开始到 根节点定位-1」的元素 - root.left = backtrack(postorder, inorder, postLeft, postRight - rightTreeSize - 1, inLeft, inRoot - 1); - - // 递归地构造右子树,并连接到根节点 - // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了 - // 中序遍历中「从 根节点定位+1 到 右边界」的元素 - root.right = backtrack(postorder, inorder, postRight - rightTreeSize, postRight - 1, inRoot + 1, inRight); - - return root; - } - - public TreeNode buildTree(int[] inorder, int[] postorder) { - if (postorder == null || inorder == null) { return null;} - int n = inorder.length; - map = new HashMap<>(n); - for (int i = 0; i < n; i++) { - map.put(inorder[i], i); - } - return backtrack(postorder, inorder, 0, n - 1, 0, n - 1); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" index 74a4935..729b3c0 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" @@ -1,7 +1,6 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import java.util.Stack; @@ -13,7 +12,7 @@ public class 从先序遍历还原二叉树 { public static void main(String[] args) { TreeNode result = recoverFromPreorder("1-2--3--4-5--6--7"); - System.out.println(TreeUtils.toBfsList(result)); + System.out.println(TreeNode.toList(result)); } public static TreeNode recoverFromPreorder(String S) { diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" deleted file mode 100644 index ec1944b..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Zhang Peng - * @since 2020-07-07 - */ -public class 从前序与中序遍历序列构造二叉树 { - - public static void main(String[] args) { - int[] preorder = { 3, 9, 20, 15, 7 }; - int[] inorder = { 9, 3, 15, 20, 7 }; - 从前序与中序遍历序列构造二叉树 demo = new 从前序与中序遍历序列构造二叉树(); - TreeNode root = demo.buildTree(preorder, inorder); - List list = TreeUtils.toBfsValueList(root); - System.out.println(list); - Assertions.assertArrayEquals(Arrays.asList(3, 9, 20, null, null, 15, 7).toArray(), list.toArray()); - } - - // 中序遍历结构,key 是值,value 是索引 - private Map map; - - public TreeNode backtrack(int[] preorder, int preLeft, int preRight, int inLeft, int inRight) { - if (preLeft > preRight) { - return null; - } - - // 前序遍历中的第一个节点就是根节点 - // 在中序遍历中定位根节点 - int inRoot = map.get(preorder[preLeft]); - - // 先把根节点建立出来 - TreeNode root = new TreeNode(preorder[preLeft]); - - // 得到左子树中的节点数目 - int leftTreeSize = inRoot - inLeft; - - // 递归地构造左子树,并连接到根节点 - // 先序遍历中「从 左边界+1 开始的 leftTreeSize」个元素就对应了 - // 中序遍历中「从 左边界 开始到 根节点定位-1」的元素 - root.left = backtrack(preorder, preLeft + 1, preLeft + leftTreeSize, inLeft, inRoot - 1); - - // 递归地构造右子树,并连接到根节点 - // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了 - // 中序遍历中「从 根节点定位+1 到 右边界」的元素 - root.right = backtrack(preorder, preLeft + leftTreeSize + 1, preRight, inRoot + 1, inRight); - return root; - } - - public TreeNode buildTree(int[] preorder, int[] inorder) { - if (preorder == null || inorder == null) { return null;} - int n = preorder.length; - // 构造哈希映射,帮助我们快速定位根节点 - map = new HashMap<>(n); - for (int i = 0; i < n; i++) { - map.put(inorder[i], i); - } - return backtrack(preorder, 0, n - 1, 0, n - 1); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" index d6e0219..03dc2b9 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" @@ -1,7 +1,6 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; import java.util.Arrays; @@ -32,8 +31,8 @@ public class 叶子相似的树 { public static void main(String[] args) { - TreeNode tree1 = TreeUtils.asTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); - TreeNode tree2 = TreeUtils.asTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); + TreeNode tree1 = TreeNode.buildTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); + TreeNode tree2 = TreeNode.buildTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); Assertions.assertTrue(leafSimilar(tree1, tree2)); } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" deleted file mode 100644 index 65d625a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import java.util.LinkedList; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 填充每个节点的下一个右侧节点指针 { - - public Node connect(Node root) { - if (root == null) return null; - bfs(root); - return root; - } - - /** - * 基于 BFS 实现二叉树层次遍历。关键在于使用一个队列存储 - */ - public void bfs(Node root) { - LinkedList queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - for (int i = 1; i < size; i++) { - queue.get(i - 1).next = queue.get(i); - } - - for (int i = 0; i < size; i++) { - Node node = queue.poll(); - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); - } - } - } - - private static class Node { - - public int val; - public Node left; - public Node right; - public Node next; - - public Node(int val) { this.val = val; } - - public Node(int val, Node left, Node right) { - this.val = val; - this.left = left; - this.right = right; - } - - @Override - public String toString() { - return "Node{" + - "val=" + val + - '}'; - } - - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210II.java" deleted file mode 100644 index 04f2e67..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210II.java" +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import java.util.LinkedList; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 填充每个节点的下一个右侧节点指针II { - - public Node connect(Node root) { - if (root == null) return null; - bfs(root); - return root; - } - - /** - * 基于 BFS 实现二叉树层次遍历。关键在于使用一个队列存储 - */ - public void bfs(Node root) { - LinkedList queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - int size = queue.size(); - for (int i = 1; i < size; i++) { - queue.get(i - 1).next = queue.get(i); - } - - for (int i = 0; i < size; i++) { - Node node = queue.poll(); - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); - } - } - } - - private static class Node { - - public int val; - public Node left; - public Node right; - public Node next; - - public Node(int val) { this.val = val; } - - public Node(int val, Node left, Node right) { - this.val = val; - this.left = left; - this.right = right; - } - - @Override - public String toString() { - return "Node{" + - "val=" + val + - '}'; - } - - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" new file mode 100644 index 0000000..e6aeaec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" @@ -0,0 +1,30 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 222. 完全二叉树的节点个数 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 完全二叉树的节点个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.countNodes(TreeNode.buildTree(1, 2, 3, 4, 5, 6))); + Assertions.assertEquals(0, s.countNodes(TreeNode.buildTree())); + Assertions.assertEquals(1, s.countNodes(TreeNode.buildTree(1))); + } + + static class Solution { + + public int countNodes(TreeNode root) { + if (root == null) { return 0; } + return countNodes(root.left) + countNodes(root.right) + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" deleted file mode 100644 index a87d430..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; - -/** - * 101. 对称二叉树 算法实现 - *

- * 给定一个二叉树,检查它是否是镜像对称的。 - *

- * 例如,二叉树 [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
- * 
- * 说明:如果你可以运用递归和迭代两种方法解决这个问题,会很加分。 - * - * @see 101. 对称二叉树 - */ -public class 对称二叉树 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(1, 2, 2, 3, 4, 4, 3); - System.out.println("result = " + isSymmetric(tree)); - - tree = TreeUtils.asTree(1, 2, 2, null, 3, null, 3); - System.out.println("result = " + isSymmetric(tree)); - } - - public static boolean isSymmetric(TreeNode root) { - return isMirror(root, root); - } - - private static boolean isMirror(TreeNode tree1, TreeNode tree2) { - if (tree1 == null && tree2 == null) return true; - if (tree1 == null || tree2 == null) return false; - if (tree1.val != tree2.val) return false; - return isMirror(tree1.left, tree2.right) && isMirror(tree1.right, tree2.left); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" deleted file mode 100644 index 1a16951..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; -import org.junit.jupiter.api.Assertions; - -/** - * @author Zhang Peng - * @since 2020-07-06 - */ -public class 平衡二叉树 { - - public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(3, 9, 20, null, null, 15, 7); - TreeNode tree2 = TreeUtils.asTree(1, 2, 2, 3, 3, null, null, 4, 4); - TreeNode tree3 = TreeUtils.asTree(null); - 平衡二叉树 demo = new 平衡二叉树(); - Assertions.assertTrue(demo.isBalanced(tree)); - Assertions.assertFalse(demo.isBalanced(tree2)); - Assertions.assertTrue(demo.isBalanced(tree3)); - } - - private boolean flag = true; - - public boolean isBalanced(TreeNode root) { - if (root == null) return true; - backtrack(root); - return flag; - } - - public int backtrack(TreeNode root) { - if (root == null) return 0; - if (root.left == null && root.right == null) return 1; - int left = backtrack(root.left); - int right = backtrack(root.right); - int temp = left - right; - if (temp > 1 || temp < -1) { - flag = false; - } - return Math.max(left, right) + 1; - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..9ac1276 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 654. 最大二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 最大二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode output = s.constructMaximumBinaryTree(new int[] { 3, 2, 1, 6, 0, 5 }); + Assertions.assertEquals(TreeNode.buildTree(6, 3, 5, null, 2, 0, null, null, 1), output); + TreeNode output2 = s.constructMaximumBinaryTree(new int[] { 3, 2, 1 }); + Assertions.assertEquals(TreeNode.buildTree(3, null, 2, null, 1), output2); + } + + static class Solution { + + public TreeNode constructMaximumBinaryTree(int[] nums) { + return dfs(nums, 0, nums.length - 1); + } + + public TreeNode dfs(int[] nums, int start, int end) { + if (start > end) { return null; } + int max = -1; + int maxIndex = start; + for (int i = start; i <= end; i++) { + if (nums[i] > max) { + maxIndex = i; + max = nums[i]; + } + } + TreeNode root = new TreeNode(nums[maxIndex]); + root.left = dfs(nums, start, maxIndex - 1); + root.right = dfs(nums, maxIndex + 1, end); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\233\270\345\220\214\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\233\270\345\220\214\347\232\204\346\240\221.java" deleted file mode 100644 index 405373a..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\233\270\345\220\214\347\232\204\346\240\221.java" +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.dunwu.algorithm.tree.btree; - -import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; - -/** - * 100. 相同的树 算法实现 - *

- * 给定两个二叉树,编写一个函数来检验它们是否相同。 - *

- * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 - *

- * 示例 1: - *

- * 输入:       1         1
- *           / \       / \
- *          2   3     2   3
- *
- *         [1,2,3],   [1,2,3]
- *
- * 输出: true
- * 
- * 示例 2: - *
- * 输入:      1          1
- *           /           \
- *          2             2
- *
- *         [1,2],     [1,null,2]
- *
- * 输出: false
- * 
- * 示例 3: - *
- * 输入:       1         1
- *           / \       / \
- *          2   1     1   2
- *
- *         [1,2,1],   [1,1,2]
- *
- * 输出: false
- * 
- * - * @see 100. 相同的树 - */ -public class 相同的树 { - - public static void main(String[] args) { - TreeNode tree1 = TreeUtils.asTree(1, 2, 3); - TreeNode tree2 = TreeUtils.asTree(1, 2, 3); - System.out.println("result = " + isSameTree(tree1, tree2)); - - tree1 = TreeUtils.asTree(1, 2); - tree2 = TreeUtils.asTree(1, 2, 3); - System.out.println("result = " + isSameTree(tree1, tree2)); - - tree1 = TreeUtils.asTree(1, 2, 1); - tree2 = TreeUtils.asTree(1, 1, 2); - System.out.println("result = " + isSameTree(tree1, tree2)); - } - - public static boolean isSameTree(TreeNode p, TreeNode q) { - if (p == null && q == null) return true; - - if (p == null || q == null) return false; - - if (p.val != q.val) return false; - - return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); - } - -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" index 8328d06..30885c1 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" @@ -1,52 +1,79 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; +import org.junit.jupiter.api.Assertions; /** - * 翻转二叉树 算法实现 + * 226. 翻转二叉树 * - *
- * 翻转一棵二叉树。
- *
- * 示例:
- *
- * 输入:
- *
- *      4
- *    /   \
- *   2     7
- *  / \   / \
- * 1   3 6   9
- * 输出:
- *
- *      4
- *    /   \
- *   7     2
- *  / \   / \
- * 9   6 3   1
- * 备注:
- * 这个问题是受到 Max Howell 的 原问题 启发的 :
- * 
- * - * @see 翻转二叉树 + * @author Zhang Peng + * @date 2025-08-11 */ public class 翻转二叉树 { public static void main(String[] args) { - TreeNode tree = TreeUtils.asTree(4, 2, 7, 1, 3, 6, 9); - System.out.println("result = " + invertTree(tree)); + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(4, 2, 7, 1, 3, 6, 9), + s.invertTree(TreeNode.buildTree(4, 7, 2, 9, 6, 3, 1))); + Assertions.assertEquals(TreeNode.buildTree(2, 3, 1), + s.invertTree(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(TreeNode.buildTree(), + s.invertTree(TreeNode.buildTree())); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(TreeNode.buildTree(4, 2, 7, 1, 3, 6, 9), + s2.invertTree(TreeNode.buildTree(4, 7, 2, 9, 6, 3, 1))); + Assertions.assertEquals(TreeNode.buildTree(2, 3, 1), + s2.invertTree(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(TreeNode.buildTree(), + s2.invertTree(TreeNode.buildTree())); } - public static TreeNode invertTree(TreeNode root) { - if (root == null) { return null; } + /** + * 【分解】思路解法 + */ + static class Solution { + + public TreeNode invertTree(TreeNode root) { + if (root == null) { return root; } + TreeNode left = invertTree(root.left); + TreeNode right = invertTree(root.right); + root.right = left; + root.left = right; + return root; + } + + } + + /** + * 【遍历】思路解法 + */ + static class Solution2 { + + public TreeNode invertTree(TreeNode root) { + traverse(root); + return root; + } + + // 遍历二叉树 + void traverse(TreeNode root) { + if (root == null) { return; } + + // 【前序】 + // System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + // System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + // System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + // System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); - TreeNode right = invertTree(root.right); - TreeNode left = invertTree(root.left); + TreeNode temp = root.left; + root.left = root.right; + root.right = temp; + } - root.left = right; - root.right = left; - return root; } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" index 28cc9b1..28ae963 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" @@ -1,47 +1,33 @@ package io.github.dunwu.algorithm.tree.btree; import io.github.dunwu.algorithm.tree.TreeNode; -import io.github.dunwu.algorithm.tree.TreeUtils; import org.junit.jupiter.api.Assertions; /** - * 路径总和 算法实现 + * 112. 路径总和 * - *
- * 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
- *
- * 说明: 叶子节点是指没有子节点的节点。
- *
- * 示例: 
- * 给定如下二叉树,以及目标和 sum = 22,
- *
- *               5
- *              / \
- *             4   8
- *            /   / \
- *           11  13  4
- *          /  \      \
- *         7    2      1
- * 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
- * 
- * - * @see 112. 路径总和 + * @author Zhang Peng + * @date 2020-01-29 */ public class 路径总和 { public static void main(String[] args) { - TreeNode - tree = TreeUtils.asTree(5, 4, 8, 11, null, 13, 4, 7, 2, null, null, null, null, null, 1); - Assertions.assertTrue(hasPathSum(tree, 22)); - TreeNode tree2 = TreeUtils.asTree(1, 2); - Assertions.assertFalse(hasPathSum(tree2, 1)); + Solution s = new Solution(); + TreeNode tree = TreeNode.buildTree(5, 4, 8, 11, null, 13, 4, 7, 2, null, null, null, null, null, 1); + Assertions.assertTrue(s.hasPathSum(tree, 22)); + TreeNode tree2 = TreeNode.buildTree(1, 2); + Assertions.assertFalse(s.hasPathSum(tree2, 1)); } - public static boolean hasPathSum(TreeNode root, int sum) { - if (root == null) { return false; } - sum -= root.val; - if (root.left == null && root.right == null) { return sum == 0; } - return hasPathSum(root.left, sum) || hasPathSum(root.right, sum); + static class Solution { + + public boolean hasPathSum(TreeNode root, int targetSum) { + if (root == null) { return false; } + if (root.left == null && root.right == null && root.val == targetSum) { return true; } + return hasPathSum(root.left, targetSum - root.val) + || hasPathSum(root.right, targetSum - root.val); + } + } } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..3f2138f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 589. N 叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的前序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 5, 6, 2, 4 }, s.preorder(root).toArray()); + } + + static class Solution { + + public List preorder(Node root) { + List res = new ArrayList<>(); + dfs(root, res); + return res; + } + + public void dfs(Node root, List res) { + if (root == null) { return; } + res.add(root.val); + for (int i = 0; i < root.children.size(); i++) { + dfs(root.children.get(i), res); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..17db4ac --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 589. N 叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的后序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + Assertions.assertArrayEquals(new Integer[] { 5, 6, 3, 2, 4, 1 }, s.postorder(root).toArray()); + } + + static class Solution { + + public List postorder(Node root) { + List res = new ArrayList<>(); + dfs(root, res); + return res; + } + + public void dfs(Node root, List res) { + if (root == null) { return; } + for (int i = 0; i < root.children.size(); i++) { + dfs(root.children.get(i), res); + } + res.add(root.val); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..ca4152f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 429. N 叉树的层序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的层序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + List> res = s.levelOrder(root); + System.out.printf("res: %s\n", res); + } + + static class Solution { + + public List> levelOrder(Node root) { + List> res = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + LinkedList list = new LinkedList<>(); + for (int i = 0; i < size; i++) { + Node node = queue.poll(); + if (node == null) { + continue; + } + list.add(node.val); + if (!node.children.isEmpty()) { + for (Node child : node.children) { + queue.offer(child); + } + } + } + if (!list.isEmpty()) { + res.add(list); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" new file mode 100644 index 0000000..b4ab98a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +/** + * 559. N叉树的最大深度 + * + * @author Zhang Peng + * @date 2020-03-23 + */ +public class N叉树的最大深度 { + + public static void main(String[] args) { + Solution s = new Solution(); + io.github.dunwu.algorithm.tree.Node node3 = new io.github.dunwu.algorithm.tree.Node(3); + node3.children.add(new io.github.dunwu.algorithm.tree.Node(5)); + node3.children.add(new io.github.dunwu.algorithm.tree.Node(6)); + io.github.dunwu.algorithm.tree.Node root = new io.github.dunwu.algorithm.tree.Node(1); + root.children.add(node3); + root.children.add(new io.github.dunwu.algorithm.tree.Node(2)); + root.children.add(new io.github.dunwu.algorithm.tree.Node(4)); + Assertions.assertEquals(3, s.maxDepth(root)); + } + + static class Solution { + + private int max = 0; + + public int maxDepth(Node root) { + max = 0; + dfs(root); + return max; + } + + public int dfs(Node root) { + if (root == null) { return 0; } + + int depth = 0; + for (int i = 0; i < root.children.size(); i++) { + depth = Math.max(depth, dfs(root.children.get(i))); + } + max = Math.max(max, depth + 1); + return depth + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" new file mode 100644 index 0000000..7fbb430 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" @@ -0,0 +1,30 @@ +package io.github.dunwu.algorithm.tree.template; + +import io.github.dunwu.algorithm.tree.TreeNode; + +/** + * 二叉树递归遍历框架 + * + * @author Zhang Peng + * @date 2025-10-23 + */ +public class 二叉树遍历框架 { + + /** + * 二叉树的遍历框架 + */ + public void traverse(TreeNode root) { + // 【校验】 + if (root == null) { return; } + // 【前序】 + System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" new file mode 100644 index 0000000..a8154e0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" @@ -0,0 +1,97 @@ +package io.github.dunwu.algorithm.tree.template; + +import io.github.dunwu.algorithm.tree.Node; +import io.github.dunwu.algorithm.tree.State; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 多叉树遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 多叉树遍历框架 { + + // 多叉树的遍历框架 + void dfs(Node root) { + // base case + if (root == null) { + return; + } + // 前序位置 + System.out.println("visit " + root.val); + for (Node child : root.children) { + dfs(child); + } + // 后序位置 + } + + void bfs(Node root) { + // base case + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + q.offer(root); + while (!q.isEmpty()) { + Node node = q.poll(); + // 访问 cur 节点 + System.out.println(node.val); + + // 把 cur 的所有子节点加入队列 + for (Node child : node.children) { + q.offer(child); + } + } + } + + // 记录遍历步数的写法 + void bfs2(Node root) { + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + q.offer(root); + // 记录当前遍历到的层数(根节点视为第 1 层) + int depth = 1; + + while (!q.isEmpty()) { + int size = q.size(); + for (int i = 0; i < size; i++) { + Node node = q.poll(); + // 访问 cur 节点,同时知道它所在的层数 + System.out.println("depth = " + depth + ", val = " + node.val); + + for (Node child : node.children) { + q.offer(child); + } + } + depth++; + } + } + + // 每个节点自行维护 State 类,记录深度等信息 + void bfs3(Node root) { + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + // 记录当前遍历到的层数(根节点视为第 1 层) + q.offer(new State(root, 1)); + + while (!q.isEmpty()) { + State state = q.poll(); + Node node = state.node; + int depth = state.depth; + // 访问 cur 节点,同时知道它所在的层数 + System.out.println("depth = " + depth + ", val = " + node.val); + + for (Node child : node.children) { + q.offer(new State(child, depth + 1)); + } + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" index 2f9d3d1..3ad1f6e 100644 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" @@ -2,7 +2,11 @@ import org.junit.jupiter.api.Assertions; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * @author Zhang Peng diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java index f6ff852..117d1b1 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java @@ -1,60 +1,98 @@ package io.github.dunwu.algorithm.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Random; /** + * 数组工具类 + * * @author Zhang Peng */ +@Slf4j public class ArrayUtil { - private static final Logger logger = LoggerFactory.getLogger(ArrayUtil.class); + public static int[] toIntArray(List list) { + if (list == null || list.isEmpty()) { return new int[0]; } + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } - public static void debugLogArray(T[] list, int begin, int end, String tip) { - String content = tip + getArrayString(list, begin, end); - if (logger.isDebugEnabled()) { - logger.debug(content); + public static List> toIntMatrixList(int[][] arr) { + if (arr == null || arr.length == 0) { return new ArrayList<>(); } + List> listlist = new ArrayList<>(); + for (int i = 0; i < arr.length; i++) { + List list = new ArrayList<>(); + listlist.add(list); + for (int j = 0; j < arr[i].length; j++) { + list.add(arr[i][j]); + } } + return listlist; } - public static String getArrayString(T[] list) { - return getArrayString(list, 0, list.length); + public static int[][] toIntMatrixArray(List> listlist) { + if (listlist == null || listlist.size() == 0) { return new int[0][0]; } + List arrList = new ArrayList<>(); + for (List list : listlist) { + arrList.add(toIntArray(list)); + } + return arrList.toArray(new int[listlist.size()][]); } - public static String getArrayString(T[] list, int begin, int end) { - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - for (int i = 0; i < begin; i++) { - sb.append("\t"); + public static String[] toStringArray(List list) { + if (list == null || list.isEmpty()) { return new String[0]; } + String[] res = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); } - int count = 0; - for (int i = begin; i <= end; i++) { - sb.append(list[i] + "\t"); - if (++count == 10) { - sb.append("\n"); - count = 0; - } + return res; + } + + public static List> toStringMatrixList(String[][] arr) { + if (arr == null || arr.length == 0) { return new ArrayList<>(); } + List> listlist = new ArrayList<>(); + for (String[] strings : arr) { + List list = new ArrayList<>(); + listlist.add(list); + Collections.addAll(list, strings); } + return listlist; + } - return sb.toString(); + public static String[][] toStringMatrixArray(List> listlist) { + if (listlist == null || listlist.size() == 0) { return new String[0][0]; } + List arrList = new ArrayList<>(); + for (List list : listlist) { + arrList.add(toStringArray(list)); + } + return arrList.toArray(new String[listlist.size()][]); + } + + public static void printArray(T[] arr, int begin, int end, String tip) { + System.out.printf("%s -> %s\n", tip, getArrayString(arr, begin, end)); + } + + public static String getArrayString(T[] arr) { + return getArrayString(arr, 0, arr.length); } - public static String getArrayString(int[] list, int begin, int end) { + public static String getArrayString(T[] arr, int begin, int end) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < begin; i++) { - sb.append("\t"); - } int count = 0; - for (int i = begin; i < end; i++) { - sb.append(list[i] + "\t"); - if (++count == 10) { + for (int i = begin; i <= end; i++) { + if (count != 0 && count % 10 == 0) { sb.append("\n"); - count = 0; } + sb.append("\t" + arr[i]); + count++; } - sb.append(list[end]); return sb.toString(); } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/\346\213\254\345\217\267\347\224\237\346\210\220.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/\346\213\254\345\217\267\347\224\237\346\210\220.java" deleted file mode 100644 index 0b79ae4..0000000 --- "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/\346\213\254\345\217\267\347\224\237\346\210\220.java" +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.algorithm; - -import org.junit.jupiter.api.Assertions; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author Zhang Peng - * @since 2020-07-03 - */ -public class 括号生成 { - - public static void main(String[] args) { - List list1 = Collections.singletonList("()"); - List list2 = new ArrayList<>(); - list2.add("(())"); - list2.add("()()"); - Assertions.assertArrayEquals(list1.toArray(), generateParenthesis(1).toArray()); - Assertions.assertArrayEquals(list2.toArray(), generateParenthesis(2).toArray()); - } - - public static List generateParenthesis(int n) { - List list = new ArrayList<>(); - generateOneByOne(list, 0, 0, n, ""); - return list; - } - - private static void generateOneByOne(List list, int left, int right, int n, String str) { - // 因为括号必然成对出现,所以左括号数和右括号都等于 N,即符合条件 - if (left == n && right == n) { - list.add(str); - return; - } - // 左括号数小于 N,就累加,将其 ( 加入字符串 - if (left < n) generateOneByOne(list, left + 1, right, n, str + "("); - // 右括号数小于 N 并且小于左括号数(右括号数多于左括号数,则语义不合法),就累加,将其 ) 加入字符串 - if (right < n && right < left) generateOneByOne(list, left, right + 1, n, str + ")"); - } - -} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java new file mode 100644 index 0000000..89c3766 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.list; + +import io.github.dunwu.algorithm.linkedlist.demo.DoublyLinkedList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author Zhang Peng + * @since 2020-01-26 + */ +public class DoubleLinkListTests { + + @Test + public void addTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(2); + list.addLast(3); + list.addFirst(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + + @Test + public void removeFirstTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.remove(new Integer(1)); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(1)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 2, 3 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(3)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.remove(new Integer(4)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + } + + @Test + public void removeAllTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.addLast(1); + list.removeAll(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] {}, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.removeAll(4); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java index 3df27b9..15020fd 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java @@ -1,5 +1,6 @@ package io.github.dunwu.algorithm.list; +import io.github.dunwu.algorithm.linkedlist.demo.SinglyLinkedList; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -13,10 +14,10 @@ public class SingleLinkListTests { @Test public void addTest() { - 单链表示例 list = new 单链表示例<>(); - list.addTail(2); - list.addTail(3); - list.addHead(1); + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(2); + list.addLast(3); + list.addFirst(1); List result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); @@ -24,36 +25,36 @@ public void addTest() { @Test public void removeFirstTest() { - 单链表示例 list = new 单链表示例<>(); - list.addTail(1); - list.addTail(1); - list.removeFirst(1); + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.remove(new Integer(1)); List result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] { 1 }, result.toArray()); list.clear(); - list.addTail(1); - list.addTail(2); - list.addTail(3); - list.removeFirst(1); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(1)); result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] { 2, 3 }, result.toArray()); list.clear(); - list.addTail(1); - list.addTail(2); - list.addTail(3); - list.removeFirst(3); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(3)); result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); list.clear(); - list.addTail(1); - list.addTail(2); - list.removeFirst(4); + list.addLast(1); + list.addLast(2); + list.remove(new Integer(4)); result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); @@ -61,19 +62,19 @@ public void removeFirstTest() { @Test public void removeAllTest() { - 单链表示例 list = new 单链表示例<>(); - list.addTail(1); - list.addTail(1); - list.addTail(1); + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.addLast(1); list.removeAll(1); List result = list.toList(); System.out.println(result); Assertions.assertArrayEquals(new Integer[] {}, result.toArray()); list.clear(); - list.addTail(1); - list.addTail(2); - list.addTail(3); + list.addLast(1); + list.addLast(2); + list.addLast(3); list.removeAll(4); result = list.toList(); System.out.println(result); diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/map/LRUCacheTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/map/LRUCacheTest.java deleted file mode 100644 index b1b3272..0000000 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/map/LRUCacheTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.dunwu.algorithm.map; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -/** - * @author Zhang Peng - * @since 2020-01-18 - */ -public class LRUCacheTest { - - @Test - public void test() { - LRUCache cache = new LRUCache(3); - Assertions.assertEquals(-1, cache.get(2)); - cache.put(2, 6); - Assertions.assertEquals(-1, cache.get(1)); - cache.put(1, 5); - cache.put(1, 2); - Assertions.assertEquals(2, cache.get(1)); - Assertions.assertEquals(6, cache.get(2)); - } - -} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java index d1a04f4..2783f81 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java @@ -1,6 +1,13 @@ package io.github.dunwu.algorithm.sort; -import io.github.dunwu.algorithm.sort.strategy.*; +import io.github.dunwu.algorithm.sort.strategy.BubbleSort; +import io.github.dunwu.algorithm.sort.strategy.BubbleSort2; +import io.github.dunwu.algorithm.sort.strategy.HeapSort; +import io.github.dunwu.algorithm.sort.strategy.InsertSort; +import io.github.dunwu.algorithm.sort.strategy.MergeSort; +import io.github.dunwu.algorithm.sort.strategy.QuickSort; +import io.github.dunwu.algorithm.sort.strategy.SelectionSort; +import io.github.dunwu.algorithm.sort.strategy.ShellSort; import io.github.dunwu.algorithm.util.ArrayUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -21,29 +28,23 @@ public class SortStrategyTest { /** * 随机样本一 */ - private static Integer[] origin01; - - private static Integer[] target01; - - private static Integer[] expected01; + private static Integer[] s1; + private static Integer[] t1; + private static Integer[] e1; /** * 随机样本二 */ - private static Integer[] origin02; - - private static Integer[] target02; - - private static Integer[] expected02; + private static Integer[] s2; + private static Integer[] t2; + private static Integer[] e2; /** * 随机样本三 */ - private static Integer[] origin03; - - private static Integer[] target03; - - private static Integer[] expected03; + private static Integer[] s3; + private static Integer[] t3; + private static Integer[] e3; /** * 生成随机数组样本,并调用 JDK api 生成期望的有序数组 @@ -51,19 +52,31 @@ public class SortStrategyTest { @BeforeAll public static void beforeClass() { // 在 [0, 100] 间生成长度为 10 的存在重复的随机数组 - origin01 = ArrayUtil.randomRepeatIntegerArray(0, 10, 9); - expected01 = Arrays.copyOf(origin01, origin01.length); - Arrays.sort(expected01); + s1 = ArrayUtil.randomRepeatIntegerArray(0, 10, 5); + e1 = Arrays.copyOf(s1, s1.length); + Arrays.sort(e1); // 在 [0, 100] 间生成长度为 17 的不重复的随机数组 - origin02 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 17); - expected02 = Arrays.copyOf(origin02, origin02.length); - Arrays.sort(expected02); + s2 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 10); + e2 = Arrays.copyOf(s2, s2.length); + Arrays.sort(e2); // 在 [0, 100] 间生成长度为 100 的不重复的随机数组 - origin03 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 100); - expected03 = Arrays.copyOf(origin03, origin03.length); - Arrays.sort(expected03); + s3 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 30); + e3 = Arrays.copyOf(s3, s3.length); + Arrays.sort(e3); + } + + /** + * 注入 SortStrategy,执行对三个样本的排序测试 + */ + private void executeSort(SortStrategy strategy) { + strategy.sort(t1); + Assertions.assertArrayEquals(e1, t1); + strategy.sort(t2); + Assertions.assertArrayEquals(e2, t2); + strategy.sort(t3); + Assertions.assertArrayEquals(e3, t3); } /** @@ -71,9 +84,9 @@ public static void beforeClass() { */ @BeforeEach public void before() { - target01 = Arrays.copyOf(origin01, origin01.length); - target02 = Arrays.copyOf(origin02, origin02.length); - target03 = Arrays.copyOf(origin03, origin03.length); + t1 = Arrays.copyOf(s1, s1.length); + t2 = Arrays.copyOf(s2, s2.length); + t3 = Arrays.copyOf(s3, s3.length); } @Test @@ -82,18 +95,6 @@ public void testBubbleSort() { executeSort(strategy); } - /** - * 注入 SortStrategy,执行对三个样本的排序测试 - */ - private void executeSort(SortStrategy strategy) { - strategy.sort(target01); - Assertions.assertArrayEquals(expected01, target01); - strategy.sort(target02); - Assertions.assertArrayEquals(expected02, target02); - strategy.sort(target03); - Assertions.assertArrayEquals(expected03, target03); - } - @Test public void testBubbleSort2() { SortStrategy strategy = new SortStrategy(new BubbleSort2()); diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/StringAlgorithmTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/StringAlgorithmTest.java deleted file mode 100644 index b68a837..0000000 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/StringAlgorithmTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dunwu.algorithm.str; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -/** - * @author Zhang Peng - * @since 2020-05-12 - */ -public class StringAlgorithmTest { - - @Test - public void isUniqueTest() { - Assertions.assertTrue(StringAlgorithm.isUnique("")); - Assertions.assertTrue(StringAlgorithm.isUnique("abc")); - Assertions.assertFalse(StringAlgorithm.isUnique("leetcode")); - } - - @Test - public void checkPermutationTest() { - Assertions.assertTrue(StringAlgorithm.checkPermutation("abc", "bca")); - Assertions.assertFalse(StringAlgorithm.checkPermutation("abc", "bad")); - Assertions.assertFalse(StringAlgorithm.checkPermutation( - "krqdpwdvgfuogtobtyylexrebrwzynzlpkotoqmokfpqeibbhzdjcwpgprzpqersmmdxdmwssfbfwmmvrxkjyjteirloxpbiopso", - "pyymgxtdqzqxxkmirptmbewjobpslwkbmmzfbwzmltowevsofkydrejdpcoripjlewoqzgusvypotrdkepbqspxdmoyrfnyrbrof")); - Assertions.assertFalse(StringAlgorithm.checkPermutation( - "jzvthzihsvghjhbrpfhdwixmyaxjrdzfvnhpmyrbqjpdffykqgahgzpjwvouurr", - "hhqhxjyrghjjsmduaxppwrqkikqnfdrzjowapehtbyrgrfyprrfrebzduxvvhhu")); - } - - @Test - public void replaceSpacesTest() { - Assertions.assertEquals("Mr%20John%20Smith", StringAlgorithm.replaceSpaces("Mr John Smith ", 13)); - } - - @Test - public void canPermutePalindromeTest() { - Assertions.assertTrue(StringAlgorithm.canPermutePalindrome("tactcoa")); - } - - @Test - public void oneEditAwayTest() { - Assertions.assertTrue(StringAlgorithm.oneEditAway("pale", "ple")); - Assertions.assertFalse(StringAlgorithm.oneEditAway("pales", "pal")); - } - - @Test - public void compressStringTest() { - Assertions.assertEquals("a2b1c5a3", StringAlgorithm.compressString("aabcccccaaa")); - Assertions.assertEquals("abbccd", StringAlgorithm.compressString("abbccd")); - } - - @Test - public void isFlipedStringTest() { - Assertions.assertTrue(StringAlgorithm.isFlipedString("waterbottle", "erbottlewat")); - Assertions.assertTrue(StringAlgorithm.isFlipedString("ab", "ba")); - Assertions.assertFalse(StringAlgorithm.isFlipedString("aa", "aba")); - Assertions.assertTrue(StringAlgorithm.isFlipedString( - "LmMoLrUxaeSgUhqFsicojxzsbbBobkzkigNyzreunviUECVpPaWKUTMfgskTiirzDkLQFQcTmvdeKpeAvypDMTJTGZRcGOlbJDVFlXNmORIJjhhGyMdGnGpefjLinqwESmCexewgloibkZxeTydRnyFRyDPMyPumFjhjuGnNmCKhOnWPYfUBmlppeBcqhoggzPqXcpYRYIuFbCuxpbaScUGYZWZVQxnOChBPIbozFagLalTGGjjAJJnPiMPcMJMxlYfBJBqtejxqXqgHgcoLGqqIaORJrEQMbqlbzddjOWJOyFkksYvdxUBPbzYVrxMKOYPigNwtXWGBqtXxUOCuIpGigRErIkkYbdDKuXmHoIXXWIiisBLwWqdKporlfbcHZicevgpAUOkeiFIeaSoaeXlXGfHFzImzCYunHleAEkzfmAirrMeUGczkBfzHBnrrBoiXVXELXMjOEXkQYJWkzRfTMeAyEKJstkUAuwkywhpDreIfrwQJLWLdAmaSlCLKdGcCjbaPPyHcGNbskyZgRahgieOqmztbakPRBOUHLRSfquGjUtlfbRJfMlOiFKFQcYDJaMgOGlnEQmHtaYDRpcGamZlZqLrAIDdMRPvelfBykZWNCCspjmLPczMQuTSxviiLtOMvdmLXDsffTAYMeVQReYRGoNVViteGksLdYyWLOxTpwqknfysBLASGgPoyGSRsAdXIXmKuWYIUjZeeRKfarTRTFlQDwvzFPFpcEMCxRMWyuySyMPCFFmAnCONFxTsIzQIMhApfVifcxlUTXdCKEKdDeJCfPnmRXTsNoCllqjRMtXBISwfeUMzeLwQwgQbbXvMzGctiBIQciIljKIkzMmnedtLCxaVnfBXEimIlrBqmsvWDEIoWiUFSMxeVgzqkqFJLdywojNkLwWVkIrzneqSPIsIPvwNaXbUfpxegkVhhIKOAdCtmmesmyYGhfJtqadOsGQIBuTOZxHINAMwKuBWikjwEdFDTksuICVTCDHEqvrUWOpgNONVkNkERATbHBonoAbjPFkthfgOTffCzgQrcaEYEkKyFNbBNmwljQFeKIyEPSiHZhlFUaEdYGDcYJgIYpwrevmFKEoqNtRhKSFrQNRJNkNOqjgKCMGYCCbDMMgerlvjanGrovokGfUUWDINbGCsPNINPBFygvDoynpxDZHQVbsXLFNjKmuFlYOhstBGdTsNSgsZVRXUQKrLqSTMCMHobsTZGVYOfGxkVPGbtaiAJostQbRctXJgysaWQuSfvYxwidQuLbkxIaJXIzbWvNDphIbeYBRBGlChLmxVpTezcjYmBkBslMqkEaPYrLROcZrxXiEDiBuLRcqlyNZAfVTlQawPVcqxadDcWKGXuDJUBxsRqoXXqqkzchsqgrguPpJDXrcrbXLIfmucbgWaIwqxwZNyuKJrqcEisRpVeuEdznTJsbQimFLfUQriOYYqaychChJogAZjvLjuDPONzyNzGnnaFyhCVLsxTmRMVVOpfhQqgPArwwOSOIdeiroBBhWUFWvTsdRZYWzTrXpRIWGFgKMaODDnOtLMZmzwxOJfoBlTmngxpBhsDYzUqpMtpknioFZeSBYqiSAVkZqifBQyqYuFoOPUIqdTWtQsotGLhVWKhesGgTVJPXmXqUeFpIpFeVVyOUmJxjiQfppSUKLdwNSoWVPIdzHXbIQvzyhiTUGomjTcVWgxSXyEznahWANjwhalYNnchohNAkAXWkWIVdiKPaAPnkWprRIHmqWfnLnOgUfNhThPmwJymURQHejsnqgdWKRUJZqEwfVnSDMyuHWKTPoNNnTrQhytdYWBsiKfAqWUfgjGQyxpQsRZxuUyaXerBDsJANuEhpNNejoaXrkVDqGrkQMbTogtdHsOHrhmIZajoMZjNwYGlBXrkTOphhvNWArIeyEMYrEkGofZIKQkaMyTLrpuWpjClEsbCEVjmwCEPmdDfFELazsIDopLmsrmXgEROgitmYUDqWqrpNtnhtEcxKsccAYKlhFGtzqwLSxgfXCQykyUEpIqpoVwPizirScwwQSbnhfBDYzVriWYpKZhxLwrHBtrcXsiJgTvRRNIkwJfIRfqZramufpeCQxMTZAhGrQLelrJQhScdqPyKUdNVZTCMdwZzydnInkQgyOiMxkAGqJfKakOOwsbNvIffJxFuGbtIyonefNHCCPAonrPaZihkEeMGZyTembSLsBpLeERBFFwnPhrTXTVvoNRTOwDwIKoBrMAGwPvhHOWlVkZcvSIjjUTuArbxnjkCnqmyQpIxqMqLlXxTKMztlQFxUDWhbpYCexaSyVvtGfwbCMcZgovtHslmazhQJTNQpmomjPYzrRiPGpodFHTiFSjQijeXEeBUEVaggRjTdAyMViqcwKDkUxXtSXuXOKRkYHTgZKyRqBJAcmmhXVyiBvceeOyfGauHXnnPAWOrNylLbPBbuxRfVTwOXJxQslgmldRKAICHRgOxvuaAPOtgDYBWFXABExfUyvuuxpMBWHFSyWCLzWcKQfntdciWKBfLTxYxWtVVYoNiJbFOawEEJChUCEoWLkXQCjEnXmOYOBTnXNxgCBcyKUuftmPyQgByuuDSOUMSbQFjuYOrQmLRVqYODLxhJyuhJnoM", - "xzsbbBobkzkigNyzreunviUECVpPaWKUTMfgskTiirzDkLQFQcTmvdeKpeAvypDMTJTGZRcGOlbJDVFlXNmORIJjhhGyMdGnGpefjLinqwESmCexewgloibkZxeTydRnyFRyDPMyPumFjhjuGnNmCKhOnWPYfUBmlppeBcqhoggzPqXcpYRYIuFbCuxpbaScUGYZWZVQxnOChBPIbozFagLalTGGjjAJJnPiMPcMJMxlYfBJBqtejxqXqgHgcoLGqqIaORJrEQMbqlbzddjOWJOyFkksYvdxUBPbzYVrxMKOYPigNwtXWGBqtXxUOCuIpGigRErIkkYbdDKuXmHoIXXWIiisBLwWqdKporlfbcHZicevgpAUOkeiFIeaSoaeXlXGfHFzImzCYunHleAEkzfmAirrMeUGczkBfzHBnrrBoiXVXELXMjOEXkQYJWkzRfTMeAyEKJstkUAuwkywhpDreIfrwQJLWLdAmaSlCLKdGcCjbaPPyHcGNbskyZgRahgieOqmztbakPRBOUHLRSfquGjUtlfbRJfMlOiFKFQcYDJaMgOGlnEQmHtaYDRpcGamZlZqLrAIDdMRPvelfBykZWNCCspjmLPczMQuTSxviiLtOMvdmLXDsffTAYMeVQReYRGoNVViteGksLdYyWLOxTpwqknfysBLASGgPoyGSRsAdXIXmKuWYIUjZeeRKfarTRTFlQDwvzFPFpcEMCxRMWyuySyMPCFFmAnCONFxTsIzQIMhApfVifcxlUTXdCKEKdDeJCfPnmRXTsNoCllqjRMtXBISwfeUMzeLwQwgQbbXvMzGctiBIQciIljKIkzMmnedtLCxaVnfBXEimIlrBqmsvWDEIoWiUFSMxeVgzqkqFJLdywojNkLwWVkIrzneqSPIsIPvwNaXbUfpxegkVhhIKOAdCtmmesmyYGhfJtqadOsGQIBuTOZxHINAMwKuBWikjwEdFDTksuICVTCDHEqvrUWOpgNONVkNkERATbHBonoAbjPFkthfgOTffCzgQrcaEYEkKyFNbBNmwljQFeKIyEPSiHZhlFUaEdYGDcYJgIYpwrevmFKEoqNtRhKSFrQNRJNkNOqjgKCMGYCCbDMMgerlvjanGrovokGfUUWDINbGCsPNINPBFygvDoynpxDZHQVbsXLFNjKmuFlYOhstBGdTsNSgsZVRXUQKrLqSTMCMHobsTZGVYOfGxkVPGbtaiAJostQbRctXJgysaWQuSfvYxwidQuLbkxIaJXIzbWvNDphIbeYBRBGlChLmxVpTezcjYmBkBslMqkEaPYrLROcZrxXiEDiBuLRcqlyNZAfVTlQawPVcqxadDcWKGXuDJUBxsRqoXXqqkzchsqgrguPpJDXrcrbXLIfmucbgWaIwqxwZNyuKJrqcEisRpVeuEdznTJsbQimFLfUQriOYYqaychChJogAZjvLjuDPONzyNzGnnaFyhCVLsxTmRMVVOpfhQqgPArwwOSOIdeiroBBhWUFWvTsdRZYWzTrXpRIWGFgKMaODDnOtLMZmzwxOJfoBlTmngxpBhsDYzUqpMtpknioFZeSBYqiSAVkZqifBQyqYuFoOPUIqdTWtQsotGLhVWKhesGgTVJPXmXqUeFpIpFeVVyOUmJxjiQfppSUKLdwNSoWVPIdzHXbIQvzyhiTUGomjTcVWgxSXyEznahWANjwhalYNnchohNAkAXWkWIVdiKPaAPnkWprRIHmqWfnLnOgUfNhThPmwJymURQHejsnqgdWKRUJZqEwfVnSDMyuHWKTPoNNnTrQhytdYWBsiKfAqWUfgjGQyxpQsRZxuUyaXerBDsJANuEhpNNejoaXrkVDqGrkQMbTogtdHsOHrhmIZajoMZjNwYGlBXrkTOphhvNWArIeyEMYrEkGofZIKQkaMyTLrpuWpjClEsbCEVjmwCEPmdDfFELazsIDopLmsrmXgEROgitmYUDqWqrpNtnhtEcxKsccAYKlhFGtzqwLSxgfXCQykyUEpIqpoVwPizirScwwQSbnhfBDYzVriWYpKZhxLwrHBtrcXsiJgTvRRNIkwJfIRfqZramufpeCQxMTZAhGrQLelrJQhScdqPyKUdNVZTCMdwZzydnInkQgyOiMxkAGqJfKakOOwsbNvIffJxFuGbtIyonefNHCCPAonrPaZihkEeMGZyTembSLsBpLeERBFFwnPhrTXTVvoNRTOwDwIKoBrMAGwPvhHOWlVkZcvSIjjUTuArbxnjkCnqmyQpIxqMqLlXxTKMztlQFxUDWhbpYCexaSyVvtGfwbCMcZgovtHslmazhQJTNQpmomjPYzrRiPGpodFHTiFSjQijeXEeBUEVaggRjTdAyMViqcwKDkUxXtSXuXOKRkYHTgZKyRqBJAcmmhXVyiBvceeOyfGauHXnnPAWOrNylLbPBbuxRfVTwOXJxQslgmldRKAICHRgOxvuaAPOtgDYBWFXABExfUyvuuxpMBWHFSyWCLzWcKQfntdciWKBfLTxYxWtVVYoNiJbFOawEEJChUCEoWLkXQCjEnXmOYOBTnXNxgCBcyKUuftmPyQgByuuDSOUMSbQFjuYOrQmLRVqYODLxhJyuhJnoMLmMoLrUxaeSgUhqFsicoj")); - } - -} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java index dcb56d4..7aa1f30 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java @@ -1,5 +1,6 @@ package io.github.dunwu.algorithm.string; +import io.github.dunwu.algorithm.str.StringAlgorithm; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeDemoTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeDemoTests.java deleted file mode 100644 index 72c3450..0000000 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeDemoTests.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import org.junit.jupiter.api.Test; - -/** - * @author Zhang Peng - * @since 2020-01-18 - */ -public class BTreeDemoTests { - - public IntBTree.TreeNode initBTree() { - IntBTree.TreeNode root = new IntBTree.TreeNode(3); - root.left = new IntBTree.TreeNode(9); - root.right = new IntBTree.TreeNode(20); - root.left.left = null; - root.left.right = null; - root.right.left = new IntBTree.TreeNode(15); - root.right.right = new IntBTree.TreeNode(17); - return root; - } - - @Test - public void preOrderTest() { - IntBTree.TreeNode root = new IntBTree.TreeNode(3); - root.left = new IntBTree.TreeNode(9); - root.right = new IntBTree.TreeNode(20); - root.left.left = null; - root.left.right = null; - root.right.left = new IntBTree.TreeNode(15); - root.right.right = new IntBTree.TreeNode(17); - IntBTree.preOrder(root); - System.out.println(); - IntBTree.preOrder2(root); - System.out.println(); - } - - @Test - public void inOrderTest() { - IntBTree.TreeNode root = new IntBTree.TreeNode(3); - root.left = new IntBTree.TreeNode(9); - root.right = new IntBTree.TreeNode(20); - root.left.left = null; - root.left.right = null; - root.right.left = new IntBTree.TreeNode(15); - root.right.right = new IntBTree.TreeNode(17); - IntBTree.inOrder(root); - System.out.println(); - IntBTree.inOrder2(root); - System.out.println(); - } - - @Test - public void postOrderTest() { - IntBTree.TreeNode root = initBTree(); - IntBTree.postOrder(root); - System.out.println(); - IntBTree.postOrder2(root); - System.out.println(); - } - - @Test - public void levelTraverseTest() { - IntBTree.TreeNode root = initBTree(); - IntBTree.levelTraverse(root); - System.out.println(); - } - - @Test - public void depthOrderTraverseTest() { - IntBTree.TreeNode root = initBTree(); - IntBTree.depthOrderTraverse(root); - System.out.println(); - } - -} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java index 9605eed..f53c916 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java @@ -18,36 +18,36 @@ public class BTreeTests { @Test @DisplayName("二叉树的最大深度") public void maxDepthTest() { - BTree tree = BTree.buildTree(1, 2, 3, 4, 5); + BTree tree = BTree.build(1, 2, 3, 4, 5); Assertions.assertEquals(3, tree.maxDepth()); } @Test @DisplayName("二叉树的最小深度") public void minDepthTest() { - BTree tree = BTree.buildTree(3, 9, 20, null, null, 15, 7); + BTree tree = BTree.build(3, 9, 20, null, null, 15, 7); Assertions.assertEquals(2, tree.minDepth()); - tree = BTree.buildTree(1, 2); + tree = BTree.build(1, 2); Assertions.assertEquals(2, tree.minDepth()); } @Test @DisplayName("判断两颗二叉树是否完全一致") public void isEqualsTest() { - BTree tree1 = BTree.buildTree(1, 2, 3); - BTree tree2 = BTree.buildTree(1, 2, 3); + BTree tree1 = BTree.build(1, 2, 3); + BTree tree2 = BTree.build(1, 2, 3); Assertions.assertTrue(BTree.isEquals(tree1, tree2)); - tree1 = BTree.buildTree(1, 2, 1); - tree2 = BTree.buildTree(1, 1, 2); + tree1 = BTree.build(1, 2, 1); + tree2 = BTree.build(1, 1, 2); Assertions.assertFalse(BTree.isEquals(tree1, tree2)); } @Test @DisplayName("广度优先搜索(BFS)") public void levelOrderBottomTest() { - BTree tree = BTree.buildTree(3, 9, 20, null, null, 15, 7); + BTree tree = BTree.build(3, 9, 20, null, null, 15, 7); List> lists = new ArrayList<>(); lists.add(Collections.singletonList(3)); lists.add(Arrays.asList(9, 20)); @@ -58,8 +58,8 @@ public void levelOrderBottomTest() { @Test @DisplayName("判断两颗二叉树的叶子节点是否相似") public void isLeafSimilarTest() { - BTree tree1 = BTree.buildTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); - BTree tree2 = BTree.buildTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); + BTree tree1 = BTree.build(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); + BTree tree2 = BTree.build(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); Assertions.assertTrue(BTree.isLeafSimilar(tree1, tree2)); } diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BinaryTreeTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BinaryTreeTests.java deleted file mode 100644 index 140f3f2..0000000 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BinaryTreeTests.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.dunwu.algorithm.tree; - -import io.github.dunwu.algorithm.common.JavaCollectionTest; -import io.github.dunwu.algorithm.common.TreeTest; -import io.github.dunwu.algorithm.common.Utils; -import org.junit.jupiter.api.Test; - -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class BinaryTreeTests { - - @Test - public void testBTree() { - Utils.TestData data = Utils.generateTestData(1000); - - String bstName = "B-Tree"; - BinaryTree bst = new BinaryTree(2); - Collection bstCollection = bst.toCollection(); - - assertTrue(TreeTest.testTree(bst, Integer.class, bstName, data.unsorted, data.invalid)); - assertTrue(JavaCollectionTest.testCollection(bstCollection, Integer.class, bstName, data.unsorted, data.sorted, - data.invalid)); - } - -} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 0000000..f18b41b --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,172 @@ +const htmlModules = require('./config/htmlModules.js') + +module.exports = { + port: '4000', + dest: 'docs/.temp', + base: '/algorithm-tutorial/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件) + title: 'ALGORITHM-TUTORIAL', + description: '☕ algorithm-tutorial 是一个数据结构与算法教程。', + theme: 'vdoing', // 使用依赖包主题 + // theme: require.resolve('../../vdoing'), // 使用本地主题 + head: [ + // 注入到页面 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?] + ['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹 + ['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }], + ['meta', { name: 'theme-color', content: '#11a8cd' }], // 移动浏览器主题颜色 + ], + markdown: { + // lineNumbers: true, + extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3'] + externalLinks: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }, + // 主题配置 + themeConfig: { + nav: [], + sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题) + logo: 'https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png', // 导航栏logo + repo: 'dunwu/algorithm-tutorial', // 导航栏右侧生成Github链接 + searchMaxSuggestions: 10, // 搜索结果显示最大数 + lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间) + + docsDir: 'docs', // 编辑的文件夹 + editLinks: true, // 编辑链接 + editLinkText: '📝 帮助改善此页面!', + + // 以下配置是Vdoing主题改动的和新增的配置 + sidebar: { mode: 'structuring', collapsable: false }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页 + + // sidebarOpen: false, // 初始状态是否打开侧边栏,默认true + updateBar: { + // 最近更新栏 + showToArticle: true, // 显示到文章页底部,默认true + // moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives' + }, + // titleBadge: false, // 文章标题前的图标是否显示,默认true + // titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标 + // '图标地址1', + // '图标地址2' + // ], + // bodyBgImg: [ + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175828.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175845.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175846.jpeg' + // ], // body背景大图,默认无。 单张图片 String || 多张图片 Array, 多张图片时每隔15秒换一张。 + + // categoryText: '随笔', // 碎片化文章(_posts文件夹的文章)预设生成的分类值,默认'随笔' + + // contentBgStyle: 1, + + category: true, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。 + tag: true, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。 + archive: true, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。 + + author: { + // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String} + name: 'dunwu', // 必需 + href: 'https://github.com/dunwu', // 可选的 + }, + social: { + // 社交图标,显示于博主信息栏和页脚栏 + // iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加 + icons: [ + { + iconClass: 'icon-youjian', + title: '发邮件', + link: 'mailto:forbreak@163.com', + }, + { + iconClass: 'icon-github', + title: 'GitHub', + link: 'https://github.com/dunwu', + }, + ], + }, + footer: { + // 页脚信息 + createYear: 2019, // 博客创建年份 + copyrightInfo: '钝悟(dunwu) | CC-BY-SA-4.0', // 博客版权信息,支持a标签 + }, + htmlModules, + }, + + // 插件 + plugins: [ + [ + require('./plugins/love-me'), + { + // 鼠标点击爱心特效 + color: '#11a8cd', // 爱心颜色,默认随机色 + excludeClassName: 'theme-vdoing-content', // 要排除元素的class, 默认空'' + }, + ], + + ['fulltext-search'], // 全文搜索 + + // ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用) + // thirdparty: [ // 可选,默认 [] + // { + // title: '在GitHub中搜索', + // frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分 + // behindUrl: '' // 搜索链接的后面部分,可选,默认 '' + // }, + // { + // title: '在npm中搜索', + // frontUrl: 'https://www.npmjs.com/search?q=', + // }, + // { + // title: '在Bing中搜索', + // frontUrl: 'https://cn.bing.com/search?q=' + // } + // ] + // }], + + [ + 'one-click-copy', + { + // 代码块复制按钮 + copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array + copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.' + duration: 1000, // prompt message display time. + showInMobile: false, // whether to display on the mobile side, default: false. + }, + ], + [ + 'demo-block', + { + // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block + settings: { + // jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖 + // cssLib: ['http://xxx'], // 在线示例中的css依赖 + // vue: 'https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖 + jsfiddle: false, // 是否显示 jsfiddle 链接 + codepen: true, // 是否显示 codepen 链接 + horizontal: false, // 是否展示为横向样式 + }, + }, + ], + [ + 'vuepress-plugin-zooming', // 放大图片 + { + selector: '.theme-vdoing-content img:not(.no-zoom)', + options: { + bgColor: 'rgba(0,0,0,0.6)', + }, + }, + ], + [ + '@vuepress/last-updated', // "上次更新"时间格式 + { + transformer: (timestamp, lang) => { + const dayjs = require('dayjs') // https://day.js.org/ + return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss') + }, + }, + ], + ], + + // 监听文件变化并重新构建 + extraWatchFiles: ['.vuepress/config.js', '.vuepress/config/htmlModules.js'], +} diff --git a/docs/.vuepress/config/baiduCode.js b/docs/.vuepress/config/baiduCode.js new file mode 100644 index 0000000..9dc5fc1 --- /dev/null +++ b/docs/.vuepress/config/baiduCode.js @@ -0,0 +1 @@ +module.exports = ''; diff --git a/docs/.vuepress/config/htmlModules.js b/docs/.vuepress/config/htmlModules.js new file mode 100644 index 0000000..6ba3782 --- /dev/null +++ b/docs/.vuepress/config/htmlModules.js @@ -0,0 +1,52 @@ +/** 插入自定义html模块 (可用于插入广告模块等) + * { + * homeSidebarB: htmlString, 首页侧边栏底部 + * + * sidebarT: htmlString, 全局左侧边栏顶部 + * sidebarB: htmlString, 全局左侧边栏底部 + * + * pageT: htmlString, 全局页面顶部 + * pageB: htmlString, 全局页面底部 + * pageTshowMode: string, 页面顶部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * pageBshowMode: string, 页面底部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * + * windowLB: htmlString, 全局左下角② + * windowRB: htmlString, 全局右下角② + * } + * + * ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。 + * ②注:windowLB 和 windowRB:1.展示区块最大宽高200px*400px。2.请给自定义元素定一个不超过200px*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。 + */ + +module.exports = { + // 万维广告 + pageB: ` +
+ + `, + windowRB: ` +
+ + `, +} + +// module.exports = { +// homeSidebarB: `
自定义模块测试
`, +// sidebarT: `
自定义模块测试
`, +// sidebarB: `
自定义模块测试
`, +// pageT: `
自定义模块测试
`, +// pageB: `
自定义模块测试
`, +// windowLB: `
自定义模块测试
`, +// windowRB: `
自定义模块测试
`, +// } diff --git a/docs/.vuepress/config/sidebar.js b/docs/.vuepress/config/sidebar.js new file mode 100644 index 0000000..9c97278 --- /dev/null +++ b/docs/.vuepress/config/sidebar.js @@ -0,0 +1,105 @@ +// !!!注:此文件没有使用到,仅用于测试和侧边栏数据格式的参考。 + +// 侧边栏 +module.exports = { + '/01.前端/': [ + { + title: 'JavaScript', + collapsable: false, //是否可折叠,可选的,默认true + children: [ + ['01.JavaScript/01.JavaScript中的名词概念','JavaScript中的名词概念'], + ['01.JavaScript/02.数据类型转换','数据类型转换'], + ['01.JavaScript/03.ES5面向对象','ES5面向对象'], + ['01.JavaScript/04.ES6面向对象','ES6面向对象'], + ['01.JavaScript/05.new命令原理','new命令原理'], + ['01.JavaScript/06.多种数组去重性能对比','多种数组去重性能对比'], + ] + }, + ], + '/02.页面/': [ + { + title: 'html-css', + collapsable: false, + children: [ + ['01.html-css/00.flex布局语法','flex布局语法'], + ['01.html-css/01.flex布局案例-基础','flex布局案例-基础'], + ['01.html-css/02.flex布局案例-骰子','flex布局案例-骰子'], + ['01.html-css/03.flex布局案例-网格布局','flex布局案例-网格布局'], + ['01.html-css/04.flex布局案例-圣杯布局','flex布局案例-圣杯布局'], + ['01.html-css/05.flex布局案例-输入框布局','flex布局案例-输入框布局'], + ['01.html-css/06.CSS3之transform过渡','CSS3之transform过渡'], + ['01.html-css/07.CSS3之animation动画','CSS3之animation动画'], + ] + }, + ], + '/03.技术杂谈/': [ + { + title: '技术杂谈', + collapsable: false, //是否可折叠,可选的,默认true + sidebarDepth: 2, // 深度,可选的, 默认值是 1 + children: [ + ['01.Git使用手册','Git使用手册'], // 同 {path: '01.Git使用手册', title: 'Git使用文档'} + ['02.GitHub高级搜索技巧','GitHub高级搜索技巧'], + ['03.Markdown使用教程','Markdown使用教程'], + ['04.npm常用命令','npm常用命令'], + ['05.yaml语言教程','yaml语言教程'], + ['06.解决百度无法收录搭建在GitHub上的个人博客的问题','解决百度无法收录搭建在GitHub上的个人博客的问题'], + ['07.使用Gitalk实现静态博客无后台评论系统','使用Gitalk实现静态博客无后台评论系统'], + ] + } + ], + '/04.其他/': [ + { + title: '学习', + collapsable: false, + children: [ + ['01.学习/01.学习网站','学习网站'], + ['01.学习/02.学习效率低,忘性很大怎么办?','学习效率低,忘性很大怎么办?'], + ] + }, + { + title: '学习笔记', + collapsable: false, + children: [ + ['02.学习笔记/01.小程序笔记','小程序笔记'], + ] + }, + { + title: '面试', + collapsable: false, //是否可折叠,可选的,默认true + children: [ + ['03.面试/01.面试问题集锦','面试问题集锦'], + ] + }, + ['01.在线工具','在线工具'], + ['02.友情链接','友情链接'], + ], + // '/': [ // 在最后定义,在没有单独设置侧边栏时统一使用这个侧边栏 + // '', + // 'git', + // 'github', + // 'markdown', + // 'study', + // 'interview' + // // '/', + // // { + // // title: 'foo', // 标题,必要的 + // // path: '/foo/', // 标题的路径,可选的, 应该是一个绝对路径 + // // collapsable: false, // 是否可折叠,可选的,默认true + // // sidebarDepth: 1, // 深度,可选的, 默认值是 1 + // // children: [ + // // ['foo/', '子页1'], + // // 'foo/1', + // // 'foo/2', + // // ] + // // }, + // // { + // // title: 'bar', + // // children: [ + // // ['bar/', '子页2'], + // // 'bar/3', + // // 'bar/4', + // // ] + // // } + // ], +} diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js new file mode 100644 index 0000000..6dd9f98 --- /dev/null +++ b/docs/.vuepress/enhanceApp.js @@ -0,0 +1,64 @@ +// import vue from 'vue/dist/vue.esm.browser' +export default ({ + Vue, // VuePress 正在使用的 Vue 构造函数 + options, // 附加到根实例的一些选项 + router, // 当前应用的路由实例 + siteData // 站点元数据 +}) => { + try { + document && integrateGitalk(router) + } catch (e) { + console.error(e.message) + } +} + +// 集成 Gitalk 评论插件 +function integrateGitalk(router) { + const linkGitalk = document.createElement('link') + linkGitalk.href = 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css' + linkGitalk.rel = 'stylesheet' + document.body.appendChild(linkGitalk) + const scriptGitalk = document.createElement('script') + scriptGitalk.src = 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js' + document.body.appendChild(scriptGitalk) + + router.afterEach((to) => { + if (scriptGitalk.onload) { + loadGitalk(to) + } else { + scriptGitalk.onload = () => { + loadGitalk(to) + } + } + }) + + function loadGitalk(to) { + let commentsContainer = document.getElementById('gitalk-container') + if (!commentsContainer) { + commentsContainer = document.createElement('div') + commentsContainer.id = 'gitalk-container' + commentsContainer.classList.add('content') + } + const $page = document.querySelector('.page') + if ($page) { + $page.appendChild(commentsContainer) + if (typeof Gitalk !== 'undefined' && Gitalk instanceof Function) { + renderGitalk(to.fullPath) + } + } + } + function renderGitalk(fullPath) { + console.info(fullPath) + const gitalk = new Gitalk({ + clientID: '8772d9c11ed3dc0b8922', + clientSecret: '7c6d2d583ff9437f5405bf9479e08db63d3a75fb', // come from github development + repo: 'blog', + owner: 'dunwu', + admin: ['dunwu'], + id: 'comment', + distractionFreeMode: false, + language: 'zh-CN', + }) + gitalk.render('gitalk-container') + } +} diff --git a/docs/.vuepress/plugins/love-me/index.js b/docs/.vuepress/plugins/love-me/index.js new file mode 100644 index 0000000..67f5ea9 --- /dev/null +++ b/docs/.vuepress/plugins/love-me/index.js @@ -0,0 +1,12 @@ +const path = require('path') +const LoveMyPlugin = (options = {}) => ({ + define() { + const COLOR = + options.color || + 'rgb(' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ')' + const EXCLUDECLASS = options.excludeClassName || '' + return { COLOR, EXCLUDECLASS } + }, + enhanceAppFiles: [path.resolve(__dirname, 'love-me.js')], +}) +module.exports = LoveMyPlugin diff --git a/docs/.vuepress/plugins/love-me/love-me.js b/docs/.vuepress/plugins/love-me/love-me.js new file mode 100644 index 0000000..f93855e --- /dev/null +++ b/docs/.vuepress/plugins/love-me/love-me.js @@ -0,0 +1,62 @@ +export default () => { + if (typeof window !== "undefined") { + (function(e, t, a) { + function r() { + for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999"); + requestAnimationFrame(r) + } + function n() { + var t = "function" == typeof e.onclick && e.onclick; + + e.onclick = function(e) { + // 过滤指定元素 + let mark = true; + EXCLUDECLASS && e.path && e.path.forEach((item) =>{ + if(item.nodeType === 1) { + typeof item.className === 'string' && item.className.indexOf(EXCLUDECLASS) > -1 ? mark = false : '' + } + }) + + if(mark) { + t && t(), + o(e) + } + } + } + function o(e) { + var a = t.createElement("div"); + a.className = "heart", + s.push({ + el: a, + x: e.clientX - 5, + y: e.clientY - 5, + scale: 1, + alpha: 1, + color: COLOR + }), + t.body.appendChild(a) + } + function i(e) { + var a = t.createElement("style"); + a.type = "text/css"; + try { + a.appendChild(t.createTextNode(e)) + } catch(t) { + a.styleSheet.cssText = e + } + t.getElementsByTagName("head")[0].appendChild(a) + } + // function c() { + // return "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")" + // } + var s = []; + e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || + function(e) { + setTimeout(e, 1e3 / 60) + }, + i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"), + n(), + r() + })(window, document) + } +} \ No newline at end of file diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000..51e9bfa Binary files /dev/null and b/docs/.vuepress/public/favicon.ico differ diff --git a/docs/.vuepress/public/img/bg.gif b/docs/.vuepress/public/img/bg.gif new file mode 100644 index 0000000..d4bf3c4 Binary files /dev/null and b/docs/.vuepress/public/img/bg.gif differ diff --git a/docs/.vuepress/public/img/bg.jpeg b/docs/.vuepress/public/img/bg.jpeg new file mode 100644 index 0000000..85e53e7 Binary files /dev/null and b/docs/.vuepress/public/img/bg.jpeg differ diff --git a/docs/.vuepress/public/img/bg.jpg b/docs/.vuepress/public/img/bg.jpg new file mode 100644 index 0000000..f093e79 Binary files /dev/null and b/docs/.vuepress/public/img/bg.jpg differ diff --git a/docs/.vuepress/public/img/dunwu-logo.png b/docs/.vuepress/public/img/dunwu-logo.png new file mode 100644 index 0000000..61570e2 Binary files /dev/null and b/docs/.vuepress/public/img/dunwu-logo.png differ diff --git a/docs/.vuepress/public/img/favicon.ico b/docs/.vuepress/public/img/favicon.ico new file mode 100644 index 0000000..51e9bfa Binary files /dev/null and b/docs/.vuepress/public/img/favicon.ico differ diff --git a/docs/.vuepress/public/img/git.png b/docs/.vuepress/public/img/git.png new file mode 100644 index 0000000..82ba43f Binary files /dev/null and b/docs/.vuepress/public/img/git.png differ diff --git a/docs/.vuepress/public/img/logo.png b/docs/.vuepress/public/img/logo.png new file mode 100644 index 0000000..8e1d567 Binary files /dev/null and b/docs/.vuepress/public/img/logo.png differ diff --git a/docs/.vuepress/public/img/more.png b/docs/.vuepress/public/img/more.png new file mode 100644 index 0000000..830613b Binary files /dev/null and b/docs/.vuepress/public/img/more.png differ diff --git a/docs/.vuepress/public/img/other.png b/docs/.vuepress/public/img/other.png new file mode 100644 index 0000000..87f8098 Binary files /dev/null and b/docs/.vuepress/public/img/other.png differ diff --git a/docs/.vuepress/public/img/panda-waving.png b/docs/.vuepress/public/img/panda-waving.png new file mode 100644 index 0000000..20246c6 Binary files /dev/null and b/docs/.vuepress/public/img/panda-waving.png differ diff --git a/docs/.vuepress/public/img/python.png b/docs/.vuepress/public/img/python.png new file mode 100644 index 0000000..c3ddebe Binary files /dev/null and b/docs/.vuepress/public/img/python.png differ diff --git a/docs/.vuepress/public/img/ui.png b/docs/.vuepress/public/img/ui.png new file mode 100644 index 0000000..617c56d Binary files /dev/null and b/docs/.vuepress/public/img/ui.png differ diff --git a/docs/.vuepress/public/img/web.png b/docs/.vuepress/public/img/web.png new file mode 100644 index 0000000..0a6e27c Binary files /dev/null and b/docs/.vuepress/public/img/web.png differ diff --git a/docs/.vuepress/public/markmap/01.html b/docs/.vuepress/public/markmap/01.html new file mode 100644 index 0000000..c55f2d0 --- /dev/null +++ b/docs/.vuepress/public/markmap/01.html @@ -0,0 +1,25 @@ + + + + + + +Markmap + + + + + + + + diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl new file mode 100644 index 0000000..3113dd6 --- /dev/null +++ b/docs/.vuepress/styles/index.styl @@ -0,0 +1,93 @@ +.home-wrapper .banner .banner-conent .hero h1{ + font-size 2.8rem!important +} +// 文档中适配 +table + width auto +.page >*:not(.footer),.card-box + box-shadow: none!important + +.page + @media (min-width $contentWidth + 80) + padding-top $navbarHeight!important +.home-wrapper .banner .banner-conent + padding 0 2.9rem + box-sizing border-box +.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a + h2 + margin-top 2rem + font-size 1.2rem!important + p + padding 0 1rem + +// 评论区颜色重置 +.gt-container + .gt-ico-tip + &::after + content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji' + color: #999 + .gt-meta + border-color var(--borderColor)!important + .gt-comments-null + color var(--textColor) + opacity .5 + .gt-header-textarea + color var(--textColor) + background rgba(180,180,180,0.1)!important + .gt-btn + border-color $accentColor!important + background-color $accentColor!important + .gt-btn-preview + background-color rgba(255,255,255,0)!important + color $accentColor!important + a + color $accentColor!important + .gt-svg svg + fill $accentColor!important + .gt-comment-content,.gt-comment-admin .gt-comment-content + background-color rgba(150,150,150,0.1)!important + &:hover + box-shadow 0 0 25px rgba(150,150,150,.5)!important + .gt-comment-body + color var(--textColor)!important + + +// qq徽章 +.qq + position: relative; +.qq::after + content: "可撩"; + background: $accentColor; + color:#fff; + padding: 0 5px; + border-radius: 10px; + font-size:12px; + position: absolute; + top: -4px; + right: -35px; + transform:scale(0.85); + +// demo模块图标颜色 +body .vuepress-plugin-demo-block__wrapper + &,.vuepress-plugin-demo-block__display + border-color rgba(160,160,160,.3) + .vuepress-plugin-demo-block__footer:hover + .vuepress-plugin-demo-block__expand::before + border-top-color: $accentColor !important; + border-bottom-color: $accentColor !important; + svg + fill: $accentColor !important; + + +// 全文搜索框 +.suggestions + overflow: auto + max-height: calc(100vh - 6rem) + @media (max-width: 719px) { + width: 90vw; + min-width: 90vw!important; + margin-right: -20px; + } + .highlight + color: $accentColor + font-weight: bold diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl new file mode 100644 index 0000000..d98e697 --- /dev/null +++ b/docs/.vuepress/styles/palette.styl @@ -0,0 +1,62 @@ + +// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。 + +//***vdoing主题-变量***// + +// // 颜色 + +// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色 +// $accentColor = #11A8CD +// $arrowBgColor = #ccc +// $badgeTipColor = #42b983 +// $badgeWarningColor = darken(#ffe564, 35%) +// $badgeErrorColor = #DA5961 + +// // 布局 +// $navbarHeight = 3.6rem +// $sidebarWidth = 18rem +// $contentWidth = 860px +// $homePageWidth = 1100px +// $rightMenuWidth = 230px // 右侧菜单 + +// // 代码块 +// $lineNumbersWrapperWidth = 2.5rem + +// 浅色模式 +.theme-mode-light + --bodyBg: rgba(255,255,255,1) + --mainBg: rgba(255,255,255,1) + --sidebarBg: rgba(255,255,255,.8) + --blurBg: rgba(255,255,255,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #f6f6f6 + --codeColor: #525252 + codeThemeLight() + +// 深色模式 +.theme-mode-dark + --bodyBg: rgba(30,30,34,1) + --mainBg: rgba(30,30,34,1) + --sidebarBg: rgba(30,30,34,.8) + --blurBg: rgba(30,30,34,.8) + --textColor: rgb(140,140,150) + --textLightenColor: #0085AD + --borderColor: #2C2C3A + --codeBg: #252526 + --codeColor: #fff + codeThemeDark() + +// 阅读模式 +.theme-mode-read + --bodyBg: rgba(245,245,213,1) + --mainBg: rgba(245,245,213,1) + --sidebarBg: rgba(245,245,213,.8) + --blurBg: rgba(245,245,213,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #282c34 + --codeColor: #fff + codeThemeDark() diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" new file mode 100644 index 0000000..7e0fa0b --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" @@ -0,0 +1,33 @@ +--- +title: 数据结构和算法指南 +categories: + - 数据结构和算法 + - 综合 +tags: + - 数据结构 + - 算法 +abbrlink: e74901af +date: 2015-03-10 18:29:37 +permalink: /pages/8b1bd0/ +--- +# 数据结构和算法指南 + +## 1. 为什么学习数据结构和算法 + +- **为了找到一份好工作**:大厂面试喜欢考算法 +- **更深入了解流行技术的设计思想**:数据结构和算法是计算机基础学科,很多框架、中间、底层系统设的设计,都借鉴了其思想。因此,掌握数据结构和算法,有利于更深入了解这些技术的设计思想。 +- 提升个人的编程水平 +- 不满足于做业务狗,拓展性能思考的视角 + +## 2. 如何学习数据结构和算法 + +数据结构就是指一组数据的存储结构。算法就是操作数据的一组方法。 + +数据结构和算法是相辅相成的。**数据结构是为算法服务的,算法要作用在特定的数据结构之上。** + +先要学会复杂度分析,才能识别数据结构和算法的利弊。 + +- 循序渐进 +- 边学边练,适度刷题 +- 学习并思考:学而不思则罔,思而不学则殆 +- 知识需要沉淀,不要想试图一下子掌握所有 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" new file mode 100644 index 0000000..8ff51fc --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" @@ -0,0 +1,171 @@ +--- +title: 复杂度分析 +categories: + - 数据结构和算法 + - 综合 +tags: + - 数据结构 + - 算法 +abbrlink: a1a87ec3 +date: 2022-03-20 23:25:17 +permalink: /pages/2a4131/ +--- + +# 复杂度分析 + +## 为什么需要复杂度分析 + +衡量算法的优劣,有两种评估方式:事前估计和后期测试。 + +后期测试有性能测试、基准测试(Benchmark)等手段。 + +但是,后期测试有以下限制: + +- **测试结果非常依赖测试环境**。如:不同机型、不同编译器版本、不同硬件配置等等,都会影响测试结果。 +- **测试结果受数据规模的影响很大**。 + +所以,需要一种方法,可以不受环境或数据规模的影响,粗略地估计算法的执行效率。这种方法就是复杂度分析。 + +## 时间复杂度分析 + +### 大 O 表示法 + +假设问题的规模为 n,则程序的时间复杂度表示为 `T(n)`。**代码的执行时间 T(n) 与每行代码的执行次数 n 成正比**。 + +当 n 增大时,T(n) 也随之增大,想要准确估计其变化比较困难。所以,可以采用大 O 时间复杂度来粗略估计其复杂度,其表达式为:**`T(n) = O(f(n))`**。 + +**大 O 表示法**实际上并不具体表示代码真正的执行时间,而是表示**代码执行时间随数据规模增长的变化趋势**,所以,也叫作**渐进时间复杂度**(asymptotic time complexity),简称**时间复杂度**。 + +### 时间复杂度分析的要点 + +- **只关注循环执行次数最多的一段代码** +- **加法法则:总复杂度等于量级最大的那段代码的复杂度** +- **乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积** + +### 最好、最坏和平均情况 + +- **最好情况时间复杂度**(best case time complexity):**在最理想的情况下,执行代码的时间复杂度**。例如:在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,此时最好情况时间复杂度为 1。 +- **最坏情况时间复杂度**(worst case time complexity):**在最糟糕的情况下,执行代码的时间复杂度**。例如:在最理想的情况下,要查找的变量 x 正好是数组的最后个元素,此时最好情况时间复杂度为 n。 +- **平均情况时间复杂度**(average case time complexity):平均时间复杂度的全称应该叫**加权平均时间复杂度**或者**期望时间复杂度**。 + +### 时间复杂度分析示例 + +【示例】从 1 累加到 100 的时间复杂度是多少? + +```java +int sum = 0; +int N = 100; +for (int i = 1; i <= N; i++) { + sum = sum + i; +} +``` + +时间复杂度计算:显然,这段代码执行了 100 次加法,其时间复杂度和 N 的大小完全一致 + +``` +T(n) = O(n) +``` + +【示例】嵌套循环的时间复杂度是多少? + +```java +int M = 10; +int N = 20; +for (int i = 1; i < M; i++) { + for (int j = 1; j < N; j++) { + System.out.println("i = " + i + ", j = " + j); + } +} +``` + +时间复杂度计算: + +``` +T(n) = (M-1)(N-1) = O(M*N) ≈ O(N^2) +``` + +【示例】递归函数的时间复杂度是多少?思考一下斐波那契数列 `f(n) = f(n-1) + f(n-2)` 的时间复杂度是多少? + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320110642.png) + +``` +T(n) = O(2^N) +``` + +## 空间复杂度分析 + +时间复杂度的全称是**渐进时间复杂度**,**表示算法的执行时间与数据规模之间的增长关系**。 + +类比一下,空间复杂度全称就是**渐进空间复杂度**(asymptotic space complexity),**表示算法的存储空间与数据规模之间的增长关系**。 + +## 复杂度量级 + +复杂度有以下量级: + +- **`O(1)`**:常数复杂度 +- **`O(log n)`**:对数复杂度 +- **`O(n)`**:线性复杂度 +- **`O(nlog n)`**:线性对数阶复杂度 +- **`O(n^2)`**:平方复杂度 +- **`O(n^3)`**:立方复杂度 +- **`O(n^k)`**:K 次方复杂度 +- **`O(2^n)`**:指数复杂度 +- **`O(n!)`**:阶乘复杂度 + +在数据量比较小的时候,复杂度量级差异并不明显;但是,随着数据规模大小的变化,差异会逐渐突出。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320160627.png) + +`O(1)` 复杂度示例: + +```java +int num = 100; +System.out.println("num = " + num); +``` + +`O(log n)` 对数复杂度示例: + +```java +int max = 100; +for (int i = 1; i < max; i = i * 2) { + System.out.println("i = " + i); +} +``` + +`O(n)` 复杂度示例: + +```java +int max = 100; +for (int i = 1; i < max; i++) { + System.out.println("i = " + i); +} +``` + +`O(n^2)` 复杂度示例: + +```java +int M = 10; +int N = 20; +for (int i = 1; i < M; i++) { + for (int j = 1; j < N; j++) { + System.out.println("i = " + i + ", j = " + j); + } +} +``` + +`O(k^n)` 复杂度示例: + +```java +int max = 10; +for (int i = 1; i <= Math.pow(2, max); i++) { + System.out.println("i = " + i); +} +``` + +## 常见数据结构的复杂度 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" new file mode 100644 index 0000000..343b216 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" @@ -0,0 +1,440 @@ +--- +title: 数组和链表 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 数组 + - 链表 +abbrlink: 50ba53aa +date: 2015-04-10 18:46:13 +permalink: /pages/5a9bff/ +--- + +# 数组和链表 + +> 数组和链表分别代表了连续空间和不连续空间的存储方式,它们是线性表(Linear List)的典型代表。其他所有的数据结构,比如栈、队列、二叉树、B+ 树等,实际上都是这两者的结合和变化。 + +## 数组 + +数组用 **连续** 的内存空间来存储数据。 + +### 数组的访问 + +数组元素的访问是以行或列索引的单一下标表示。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) + +在上面的例子中,数组 a 中有 5 个元素。`也就是说`,a 的长度是 6 。我们可以使用 a[0] 来表示数组中的第一个元素。因此,a[0] = A 。类似地,a[1] = B,a[2] = C,依此类推。 + +### 数组的插入 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115848.png) + +### 数组的删除 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115859.png) + +### 数组的特性 + +数组设计之初是在形式上依赖内存分配而成的,所以必须在使用前预先分配好空间大小。这使得数组有以下特性: + +1. **用连续的内存空间来存储数据**。 +2. **数组支持随机访问,根据下标随机访问的时间复杂度为 `O(1)`**。 +3. **数组的插入、删除操作,平均时间复杂度为 `O(n)`**。 +4. **空间大小固定**,一旦建立,不能再改变。扩容只能采用复制数组的方式。 +5. 在旧式编程语言中(如有中阶语言之称的 C),程序不会对数组的操作做下界判断,也就有潜在的越界操作的风险。 + +### 多维数组 + +数组是有下标和值组成集合。 + +如果数组的下标有多个维度,即为多维数组。比如:二维数组可以视为『数组元素为一维数组』的一维数组;三维数组可以视为『数组元素为二维数组』的一维数组;依次类推。 + +下图是由 M 个行向量,N 个列向量组成的二维数组. + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320152607.png) + +## 链表 + +> **链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 + +区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,链表在插入数据的时候可以达到 `O(1)` 的复杂度,但是查找一个节点或者访问特定编号的节点则需要 `O(n)` 的时间。 + +链表具有以下特性: + +- 链表允许插入和移除任意位置上的节点,其时间复杂度为 `O(1)` +- 链表没有数组的随机访问特性,**链表只支持顺序访问**,其时间复杂度为 `O(n)`。 +- 数组的空间大小是固定的,而**链表的空间大小可以动态增长**。相比于数组,链表支持扩容,显然更为灵活,但是由于多了指针域,空间开销也更大。 +- 链表相比于数组,多了头指针、尾指针(非必要),合理使用可以大大提高访问效率。 + +链表有多种类型: + +- 单链表 +- 双链表 +- 循环链表 + +### 单链表 + +单链表中的每个结点不仅包含数据值,还包含一个指针,指向其后继节点。通过这种方式,单链表将所有结点按顺序组织起来。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) + +与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按 `索引` 来 `访问元素` 平均要花费 `O(N)` 时间,其中 N 是链表的长度。 + +#### 单链表插入 + +如果我们想在给定的结点 `prev` 之后添加新值,我们应该: + +(1)使用给定值初始化新结点 `cur`; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174908.png) + +(2)将 `cur` 的 `next` 字段链接到 `prev` 的下一个结点 `next` ; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174919.png) + +(3)将 `prev` 中的 `next` 字段链接到 `cur` 。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174932.png) + +与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 `O(1)` 时间复杂度中将新结点插入到链表中,这非常高效。 + +#### 单链表删除 + +如果我们想从单链表中删除现有结点 `cur`,可以分两步完成: + +(1)找到 `cur` 的上一个结点 `prev` 及其下一个结点 `next` ; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174953.png) + +(2)接下来链接 `prev` 到 `cur` 的下一个节点 `next` 。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320175006.png) + +在我们的第一步中,我们需要找出 `prev` 和 `next`。使用 `cur` 的参考字段很容易找出 `next`,但是,我们必须从头结点遍历链表,以找出 `prev`,它的平均时间是 `O(N)`,其中 `N` 是链表的长度。因此,删除结点的时间复杂度将是 `O(N)`。 + +空间复杂度为 `O(1)`,因为我们只需要常量空间来存储指针。 + +### 双链表 + +双链表中的每个结点不仅包含数据值,还包含两个指针,分别指向指向其前驱节点和后继节点。 + +单链表的访问是单向的,而双链表的访问是双向的。显然,双链表比单链表操作更灵活,但是空间开销也更大。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181150.png) + +双链表以类似的方式工作,但`还有一个引用字段`,称为`“prev”`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 + +#### 双链表插入 + +如果我们想在给定的结点 `prev` 之后添加新值,我们应该: + +(1)使用给定值初始化新结点 `cur`; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181208.png) + +(2)链接 `cur` 与 `prev` 和 `next`,其中 `next` 是 `prev` 原始的下一个节点; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181303.png) + +(3)用 `cur` 重新链接 `prev` 和 `next`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181504.png) + +与单链表类似,添加操作的时间和空间复杂度都是 `O(1)`。 + +#### 双链表删除 + +如果我们想从双链表中删除一个现有的结点 `cur`,我们可以简单地将它的前一个结点 `prev` 与下一个结点 `next` 链接起来。 + +与单链表不同,使用 `prev` 字段可以很容易地在常量时间内获得前一个结点。 + +因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是 `O(1)`。 + +### 循环链表 + +#### 循环单链表 + +**循环单链表是一种特殊的单链表**。它和单链表唯一的区别就在最后结点。 + +- 单链表的最后一个结点的后继指针 `next` 指向空地址。 +- 循环链表的最后一个结点的后继指针 `next` 指向第一个节点(如果有头节点,就指向头节点)。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190534.png) + +#### 循环双链表 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190423.png) + +## 数组 vs. 链表 + +- **存储方式** + - 数组用 **连续** 的内存空间来存储数据。 + - 链表用 **不连续** 的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链。 +- **访问方式** + - 数组**支持随机访问**。根据下标随机访问的时间复杂度为 `O(1)` + - 链表**不支持随机访问**,只能顺序访问,时间复杂度为 `O(n)`。 +- **空间大小** + - 数组空间**大小固定**,扩容只能采用复制数组的方式。 + - 链表空间**大小不固定**,扩容灵活。 +- **效率比较** + - 数组的 **查找** 效率高于链表。 + - 链表的 **添加**、**删除** 效率高于数组。 + +## 数组和链表的基本操作示例 + +关于数组和链表的基本操作,网上和各种书籍、教程中已经有大量的示例,感兴趣可以自行搜索。本文只是简单展示一下数组和链表的基本操作。 + +### 一维数组的基本操作 + +```java +public class Main { + public static void main(String[] args) { + // 1. Initialize + int[] a0 = new int[5]; + int[] a1 = {1, 2, 3}; + // 2. Get Length + System.out.println("The size of a1 is: " + a1.length); + // 3. Access Element + System.out.println("The first element is: " + a1[0]); + // 4. Iterate all Elements + System.out.print("[Version 1] The contents of a1 are:"); + for (int i = 0; i < a1.length; ++i) { + System.out.print(" " + a1[i]); + } + System.out.println(); + System.out.print("[Version 2] The contents of a1 are:"); + for (int item: a1) { + System.out.print(" " + item); + } + System.out.println(); + // 5. Modify Element + a1[0] = 4; + // 6. Sort + Arrays.sort(a1); + } +} +``` + +### 二维数组的基本操作 + +```java +public class TwoDimensionArray { + private static void printArray(int[][] a) { + for (int i = 0; i < a.length; ++i) { + System.out.println(a[i]); + } + for (int i = 0; i < a.length; ++i) { + for (int j = 0; a[i] != null && j < a[i].length; ++j) { + System.out.print(a[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + System.out.println("Example I:"); + int[][] a = new int[2][5]; + printArray(a); + System.out.println("Example II:"); + int[][] b = new int[2][]; + printArray(b); + System.out.println("Example III:"); + b[0] = new int[3]; + b[1] = new int[5]; + printArray(b); + } +} +``` + +### 单链表的基本操作 + +单链表节点的数据结构 + +```java +public class ListNode { + E value; + ListNode next; // 指向后继节点 +} + +public class SingleLinkList { + private ListNode head; // 头节点 +} +``` + +(1)从头部添加节点(即头插法) + +```java +void addHead(E value) { + ListNode newNode = new ListNode<>(value, null); + newNode.next = this.head.next; + this.head.next = newNode; +} +``` + +(2)从尾部添加节点(即尾插法) + +```java +void addTail(E value) { + // init new node + ListNode newNode = new ListNode<>(value, null); + + // find the last node + ListNode node = this.head; + while (node.next != null) { + node = node.next; + } + + // add new node to tail + node.next = newNode; +} +``` + +(3)删除节点 + +找到要删除元素的前驱节点,将前驱节点的 next 指针指向下一个节点。 + +```java +public void remove(E value) { + ListNode prev = this.head; + while (prev.next != null) { + ListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + break; + } + prev = prev.next; + } +} +``` + +(4)查找节点 + +从头开始查找,一旦发现有数值与查找值相等的节点,直接返回此节点。如果遍历结束,表明未找到节点,返回 null。 + +```java +public ListNode find(E value) { + ListNode node = this.head.next; + while (node != null) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; +} +``` + +### 双链表的基本操作 + +双链表节点的数据结构: + +```java +static class DListNode { + E value; + DListNode prev; // 指向前驱节点 + DListNode next; // 指向后继节点 +} + +public class DoubleLinkList { + /** 头节点 */ + private DListNode head; + /** 尾节点 */ + private DListNode tail; +} +``` + +(1)从头部添加节点 + +```java +public void addHead(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.head.next.prev = newNode; + newNode.next = this.head.next; + + this.head.next = newNode; + newNode.prev = this.head; +} +``` + +(2)从尾部添加节点 + +```java +public void addTail(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.tail.prev.next = newNode; + newNode.prev = this.tail.prev; + + this.tail.prev = newNode; + newNode.next = this.tail; +} +``` + +(3)删除节点 + +```java +public void remove(E value) { + DListNode prev = this.head; + while (prev.next != this.tail) { + DListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + curr.next.prev = prev; + curr.next = null; + curr.prev = null; + break; + } + prev = prev.next; + } +} +``` + +(4)查找节点 + +```java +public DListNode find(E value) { + DListNode node = this.head.next; + while (node != this.tail) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; +} +``` + +## 练习 + +- 数组 + - [x] [724. 寻找数组的中心下标](https://leetcode-cn.com/problems/find-pivot-index/) + - [x] [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/) + - [x] [56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/) +- 链表 + - [ ] [设计链表](https://leetcode-cn.com/leetbook/read/linked-list/jy291/) + - [ ] [环形链表](https://leetcode-cn.com/leetbook/read/linked-list/jbex5/) + - [ ] [环形链表 II](https://leetcode-cn.com/leetbook/read/linked-list/jjhf6/) + - [ ] [相交链表](https://leetcode-cn.com/leetbook/read/linked-list/jjbj2/) + - [ ] [删除链表的倒数第 N 个节点](https://leetcode-cn.com/leetbook/read/linked-list/jf1cc/) + - [ ] [反转链表](https://leetcode-cn.com/leetbook/read/linked-list/f58sg/) + - [ ] [移除链表元素](https://leetcode-cn.com/leetbook/read/linked-list/f9izv/) + - [ ] [奇偶链表](https://leetcode-cn.com/leetbook/read/linked-list/fe0kj/) + - [ ] [回文链表](https://leetcode-cn.com/leetbook/read/linked-list/fov6t/) + - [ ] [合并两个有序链表](https://leetcode-cn.com/leetbook/read/linked-list/fnzd1/) + - [ ] [两数相加](https://leetcode-cn.com/leetbook/read/linked-list/fv6w7/) + - [ ] [扁平化多级双向链表](https://leetcode-cn.com/leetbook/read/linked-list/fw8v5/) + - [ ] [复制带随机指针的链表](https://leetcode-cn.com/leetbook/read/linked-list/fdi26/) + - [ ] [旋转链表](https://leetcode-cn.com/leetbook/read/linked-list/f00a2/) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- [数据结构(C 语言版)](https://item.jd.com/12407475.html) +- [数据结构(C++语言版)](https://book.douban.com/subject/25859528/) +- [Leetcode:数组和字符串](https://leetcode-cn.com/leetbook/detail/array-and-string/) +- [Leetcode:链表](https://leetcode-cn.com/tag/linked-list/) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" new file mode 100644 index 0000000..2df6ac1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" @@ -0,0 +1,109 @@ +--- +title: 栈和队列 +categories: + - 数据结构和算法 +tags: + - 数据结构 + - 线性表 + - 栈 + - 队列 +abbrlink: 8d66b5f2 +date: 2014-01-25 16:46:13 +permalink: /pages/1f15c3/ +--- + +# 栈和队列 + +> **队列**和**栈**都是**操作受限**的**线性表**:前者先进先出,后者先进后出。 + +## 栈 + +### 栈是什么 + +在 **LIFO(后进先出)** 数据结构中,将首先处理添加到队列中的最新元素。 + +**栈是一个 LIFO(后进先出) 数据结构**。**栈是一种“操作受限”的线性表**,只允许在一端插入和删除数据。通常,插入操作在栈中被称作入栈 push 。与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,退栈 pop ,将始终删除队列中相对于它的最后一个元素。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200148.png) + +**当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构**。 + +从栈的定义可以看出,栈只支持两个基本操作:**入栈 `push()`** 和 **出栈 `pop()`** ,也就是在栈顶插入一个数据和从栈顶删除一个数据。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 `O(1)`。 + +栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作**顺序栈**,用链表实现的栈,我们叫作**链式栈**。 + +### 为什么需要栈 + +相比数组和链表,栈只是对操作进行了限制,似乎并没有任何优势。为什么不直接使用数组或者链表?为什么还要用这个“操作受限”的“栈”呢? + +特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。 + +### 栈的应用场景 + +(1)**函数调用栈** + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091000.jpg) + +(2)**表达式求值** + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091100.jpg) + +(3)**表达式匹配** + +可以借助栈来检查表达式中的括号是否匹配 + +## 队列 + +在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。 + +队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。 + +### 什么是队列 + +**队列:先进先出的线性表**。 + +**队列是一种“操作受限”的线性表**,只允许在一端插入数据,在另一端删除数据。 + +队列的最基本操作:**入队 `enqueue()`**,放一个数据到队列尾部;**出队 `dequeue()`**,从队列头部取一个元素。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200213.png) + +队列可以用数组来实现,也可以用链表来实现。用数组实现的队列叫作**顺序队列**,用链表实现的队列叫作**链式队列**。 + +队满的判断条件是 `tail == n`,队空的判断条件是 `head == tail`。 + +### 循环队列 + +循环队列是一种较为特殊的队列。 + +循环队列的要点是确定好 **队空和队满的判定条件**。 + +在用数组实现的非循环队列中,队满的判断条件是 `(tail+1) % n == head`,队空的判断条件是 `head == tail`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322214822.png) + +### 为什么需要队列 + +为什么需要队列和为什么需要栈,是同样的道理,参考 为什么需要栈 + +### 队列的应用场景 + +(1)**阻塞队列** + +**阻塞队列**其实就是在队列基础上增加了阻塞操作。简单来说,就是: + +- 在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回; +- 如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310092908.jpg) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310093026.jpg) + +(2)**并发队列** + +线程安全的队列我们叫作**并发队列**。最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- [Leetcode:栈和队列](https://leetcode-cn.com/leetbook/detail/queue-stack/) diff --git a/docs/linear-list-search.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" similarity index 91% rename from docs/linear-list-search.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" index 0eeb547..1cfc918 100644 --- a/docs/linear-list-search.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" @@ -1,6 +1,20 @@ +--- +title: 线性表的查找 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 查找 +abbrlink: 443d1da2 +date: 2015-03-10 18:29:13 +permalink: /pages/4b1ed0/ +--- + # 线性表的查找 -## 概念 +## 查找简介 ### 什么是查找? @@ -18,7 +32,7 @@ ### 查找算法性能比较的标准 -**——平均查找长度ASL(Average Search Length)** +**——平均查找长度 ASL(Average Search Length)** 由于查找算法的主要运算是关键字的比较过程,所以通常把查找过程中对关键字需要执行的**平均比较长度**(也称为**平均比较次数**)作为衡量一个查找算法效率优劣的比较标准。 @@ -42,9 +56,9 @@ **基本思想** -从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值k相**比较**,若相等则表示查找成功; +从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值 k 相**比较**,若相等则表示查找成功; -若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 +若扫描结束仍没有找到关键字等于 k 的结点,表示查找失败。 **核心代码** @@ -56,7 +70,7 @@ public int orderSearch(int[] list, int length, int key) { return i; } } - + // 如果扫描完,说明没有元素的值匹配key,返回-1,表示查找失败 return -1; } @@ -143,15 +157,15 @@ public int binarySearch(int[] list, int length, int key) { 所谓**“分块有序”的线性表**,是指: -假设要排序的表为R[0...N-1],**将表均匀分成b块**,前b-1块中记录个数为s=N/b,最后一块记录数小于等于s; +假设要排序的表为 R[0...N-1],**将表均匀分成 b 块**,前 b-1 块中记录个数为 s=N/b,最后一块记录数小于等于 s; 每一块中的关键字不一定有序,但**前一块中的最大关键字必须小于后一块中的最小关键字**。 -***注:这是使用分块查找的前提条件。*** +**_注:这是使用分块查找的前提条件。_** -如上将表均匀分成b块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表IDX[0...b-1]。 +如上将表均匀分成 b 块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表 IDX[0...b-1]。 -由于表R是分块有序的,所以**索引表是一个递增有序表**。 +由于表 R 是分块有序的,所以**索引表是一个递增有序表**。 下图就是一个分块查找表的存储结构示意图 @@ -278,7 +292,7 @@ class BlockSearch { **运行结果** ``` -线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 +线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 构造索引表如下: key = 14, link = 0 key = 34, link = 5 @@ -292,7 +306,7 @@ key = 85, link = 15 因为分块查找实际上是两次查找过程之和。若以二分查找来确定块,显然它的查找效率介于顺序查找和二分查找之间。 -## 三种线性查找的PK +## 三种线性查找的 PK (1) 以平均查找长度而言,二分查找 > 分块查找 > 顺序查找。 @@ -302,6 +316,4 @@ key = 85, link = 15 (4) 分块查找综合了顺序查找和二分查找的优点,既可以较为快速,也能使用动态变化的要求。 -## 资源 - -《数据结构习题与解析》(B级第3版) +## 参考资料 diff --git a/docs/sort.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" similarity index 93% rename from docs/sort.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" index 6b8e51c..2bee638 100644 --- a/docs/sort.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" @@ -1,53 +1,23 @@ -# 细说排序算法 +--- +title: 线性表的排序 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 排序 +abbrlink: d3b2b8db +date: 2015-03-03 17:37:24 +permalink: /pages/21c5f2/ +--- + +# 线性表的排序 > 📦 本文已归档到:「[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)」 > > 🔁 本文中的示例代码已归档到:「[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)」 - - -- [冒泡排序](#冒泡排序) - - [要点](#要点) - - [算法思想](#算法思想) - - [算法分析](#算法分析) - - [示例代码](#示例代码) -- [快速排序](#快速排序) - - [要点](#要点-1) - - [算法思想](#算法思想-1) - - [算法分析](#算法分析-1) - - [示例代码](#示例代码-1) -- [插入排序](#插入排序) - - [要点](#要点-2) - - [算法思想](#算法思想-2) - - [算法分析](#算法分析-2) - - [示例代码](#示例代码-2) -- [希尔排序](#希尔排序) - - [要点](#要点-3) - - [算法思想](#算法思想-3) - - [算法分析](#算法分析-3) - - [示例代码](#示例代码-3) -- [简单选择排序](#简单选择排序) - - [要点](#要点-4) - - [算法思想](#算法思想-4) - - [算法分析](#算法分析-4) - - [示例代码](#示例代码-4) -- [堆排序](#堆排序) - - [要点](#要点-5) - - [算法思想](#算法思想-5) - - [算法分析](#算法分析-5) - - [示例代码](#示例代码-5) -- [归并排序](#归并排序) - - [要点](#要点-6) - - [算法思想](#算法思想-6) - - [算法分析](#算法分析-6) - - [示例代码](#示例代码-6) -- [基数排序](#基数排序) - - [要点](#要点-7) - - [算法分析](#算法分析-7) - - [示例代码](#示例代码-7) - - - ## 冒泡排序 ### 要点 @@ -66,7 +36,7 @@ 假设有一个大小为 N 的无序序列。冒泡排序就是要每趟排序过程中通过两两比较,找到第 i 个小(大)的元素,将其往上排。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/bubble-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/bubble-sort.png) 以上图为例,演示一下冒泡排序的实际流程: @@ -209,7 +179,7 @@ public void bubbleSort_2(int[] list) { 详细的图解往往比大堆的文字更有说明力,所以直接上图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/quick-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/quick-sort.png) 上图中,演示了快速排序的处理过程: @@ -314,7 +284,7 @@ private void quickSort(int[] list, int left, int right) { 在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/insert-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/insert-sort.png) - 先拿一张 5 在手里, - 再摸到一张 4,比 5 小,插到 5 前面, @@ -414,7 +384,7 @@ public void insertSort(int[] list) { 我们来通过演示图,更深入的理解一下这个过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/shell-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/shell-sort.png) 在上面这幅图中: @@ -520,7 +490,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 **核心代码** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/selection-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/selection-sort.png) ### 算法分析 @@ -574,7 +544,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 其中 i=1,2,…,n/2 向下取整; -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort.png) 如上图所示,序列 R{3, 8,15, 31, 25} 是一个典型的小根堆。 @@ -608,13 +578,13 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 设有一个无序序列 { 1, 3,4, 5, 2, 6, 9, 7, 8, 0 }。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort-02.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-02.png) 构造了初始堆后,我们来看一下完整的堆排序处理: 还是针对前面提到的无序序列 { 1,3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort-03.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-03.png) 相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。 @@ -783,7 +753,7 @@ public void Merge(int[] array2, int low, int mid, int high) { 掌握了合并的方法,接下来,让我们来了解**如何分解**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/merge-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/merge-sort.png) 在某趟归并中,设各子表的长度为 **gap**,则归并前 R[0...n-1] 中共有 **n/gap** 个有序的子表:`R[0...gap-1]`, `R[gap...2*gap-1]`, ... , `R[(n/gap)*gap ... n-1]`。 @@ -877,7 +847,7 @@ public int[] sort(int[] list) { 我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是 0,将这个数存入编号为 0 的桶中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/radix-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/radix-sort.png) 分类后,我们在从各个桶中,将这些数按照从编号 0 到编号 9 的顺序依次将所有数取出来。 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 0000000..8660f49 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,183 @@ +--- +title: 树和二叉树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 完全二叉树 +abbrlink: dd5c0739 +date: 2014-06-15 15:39:23 +permalink: /pages/92e4c1/ +--- + +# 树和二叉树 + +## 树 + +### 什么是树 + +在计算机科学中,**树**(英语:tree)是一种[抽象数据类型](https://zh.wikipedia.org/wiki/抽象資料型別)(ADT)或是实现这种抽象数据类型的[数据结构](https://zh.wikipedia.org/wiki/資料結構),用来模拟具[有树状结构](https://zh.wikipedia.org/wiki/樹狀結構)性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的[集合](https://zh.wikipedia.org/wiki/集合)。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 + +它具有以下的特点: + +- 每个节点都只有有限个子节点或无子节点。 +- 树有且仅有一个根节点。 +- 根节点没有父节点;非根节点有且仅有一个父节点。 +- 每个非根节点可以分为多个不相交的子树。 +- 树里面没有环路。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403163620.png) + +### 树的术语 + +- **节点的度**:一个节点含有的子树的个数称为该节点的度; +- **树的度**:一棵树中,最大的节点度称为树的度; +- **叶子节点**或**终端节点**:度为零的节点; +- **非终端节点**或**分支节点**:度不为零的节点; +- **父节点**:若一个节点含有子节点,则这个节点称为其子节点的父节点; +- **子节点**:一个节点含有的子树的根节点称为该节点的子节点; +- **兄弟节点**:具有相同父节点的节点互称为兄弟节点; +- **堂兄弟节点**:父节点在同一层的节点互为堂兄弟; +- **节点的祖先**:从根到该节点所经分支上的所有节点; +- **子孙**:以某节点为根的子树中任一节点都称为该节点的子孙。 +- **森林**:由 m(m>=0)棵互不相交的树的集合称为森林; + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403164732.png) + +- **节点的高度**:节点到叶子节点的最长路径。高度是从下往上度量。 +- **节点的深度**:根节点到该节点的最长路径。深度是从上往下度量。 +- **节点的层次**:节点的深度 + 1。 +- **树的高度**:根节点的高度。 + +### 树的性质 + +- 树中的节点数等于所有节点的度数加 1。 +- 度为 m 的树中第 `i` 层上至多有 $$m^{i-1}$$ 个节点($$i ≥ 1$$)。 +- 高度为 h 的 m 次树至多有 $$(m^h-1)/(m-1)$$ 个节点。 +- 具有 n 个节点的 m 次树的最小高度为 $$\log_m{(n(m-1)+1)}$$ 。 + +### 树的种类 + +**无序树**:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为[自由树](https://zh.wikipedia.org/wiki/自由树); + +**有序树**:树中任意节点的子节点之间有顺序关系,这种树称为有序树; + +- 二叉树:每个节点最多含有两个子树的树称为二叉树; + - **完全二叉树**:对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; +- [满二叉树](https://zh.wikipedia.org/wiki/满二叉树):所有叶节点都在最底层的完全二叉树; +- [平衡二叉树](https://zh.wikipedia.org/wiki/平衡二叉树)([AVL 树](https://zh.wikipedia.org/wiki/AVL树)):当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树; +- [排序二叉树](https://zh.wikipedia.org/wiki/排序二元樹)([二叉查找树](https://zh.wikipedia.org/wiki/二叉查找树)(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树; +- [霍夫曼树](https://zh.wikipedia.org/wiki/霍夫曼树):[带权路径](https://zh.wikipedia.org/w/index.php?title=带权路径&action=edit&redlink=1)最短的二叉树称为哈夫曼树或最优二叉树; +- [B 树](https://zh.wikipedia.org/wiki/B树):一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。 + +## 二叉树 + +二叉树中的每个节点最多有两个子节点,分别是**左子节点**和**右子节点**。 + +### 二叉树的性质 + +1. 二叉树第 i 层上的结点数目最多为 **2i-1** (i≥1)。 +2. 深度为 k 的二叉树至多有 **2k-1** 个结点(k≥1)。 +3. 包含 n 个结点的二叉树的高度至少为 **log2(n+1)**。 +4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。 + +### 满二叉树 + +除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫作**满二叉树**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183927.png) + +### 完全二叉树 + +叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫作**完全二叉树**。 + +特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183640.png) + +存储一棵二叉树,有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。 + +**二叉链式存储法** + +每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403212249.png) + +**顺序存储法** + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403214627.png) + +如果节点 X 存储在数组中下标为 i 的位置,下标为 2 _ i 的位置存储的就是左子节点,下标为 2 _ i + 1 的位置存储的就是右子节点。反过来,下标为 i/2 的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为 1 的位置),这样就可以通过下标计算,把整棵树都串起来。 + +如果是非完全二叉树,其实会浪费比较多的数组存储空间。所以,如果某棵二叉树是一棵完全二叉树,那用数组存储无疑是最节省内存的一种方式。因为数组的存储方式并不需要像链式存储法那样,要存储额外的左右子节点的指针。这也是 为什么完全二叉树要求最后一层的子节点都靠左的原因。 + +### 二叉树的遍历 + +二叉树的遍历有三种方式: + +- **前序遍历**:对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。 +- **中序遍历**:对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。 +- **后序遍历**是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220404201713.png) + +## 二叉查找树 + +二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。 + +**二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。** + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172359.png) + +### 二叉查找树的查找 + +首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172537.png) + +### 二叉查找树的插入 + +如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172549.png) + +### 二叉查找树的删除 + +第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。 + +第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200219.png) + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200234.png) + +第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200456.png) + +### 二叉查找树的时间复杂度 + +不管操作是插入、删除还是查找,**时间复杂度其实都跟树的高度成正比,也就是 O(log n)**。 + +二叉查找树的形态各式各样。比如这个图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405234630.png) + +### 为什么需要二叉查找树 + +第一,哈希表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。 + +第二,哈希表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。 + +第三,笼统地来说,尽管哈希表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。 + +第四,哈希表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。 + +最后,为了避免过多的散列冲突,哈希表装载因子不能太大,特别是基于开放寻址法解决冲突的哈希表,不然会浪费一定的存储空间。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" new file mode 100644 index 0000000..c66e6e1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" @@ -0,0 +1,60 @@ +--- +title: 堆 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 堆 +abbrlink: fab451a5 +date: 2015-03-09 16:01:27 +permalink: /pages/ce297c/ +--- + +# 堆 + +## 什么是堆? + +堆(Heap)是一个可以被看成近似完全二叉树的数组。 + +- **堆是一个完全二叉树**。完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。 +- **堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值**。 + +堆可以分为大顶堆和小顶堆。 + +- 对于每个节点的值都大于等于子树中每个节点值的堆,叫作“**大顶堆**”。 + +- 对于每个节点的值都小于等于子树中每个节点值的堆,叫作“**小顶堆**”。 + +## 如何实现堆 + +完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311112542.jpg) + +堆常见的操作: + +- HEAPIFY 建堆:把一个乱序的数组变成堆结构的数组,时间复杂度为 $$O(n)$$。 +- HEAPPUSH:把一个数值放进已经是堆结构的数组中,并保持堆结构,时间复杂度为 $$O(log N)$$ +- HEAPPOP:从最大堆中取出最大值或从最小堆中取出最小值,并将剩余的数组保持堆结构,时间复杂度为 $$O(log N)$$。 +- HEAPSORT:借由 HEAPFY 建堆和 HEAPPOP 堆数组进行排序,时间复杂度为$$ O(N log N)$$,空间复杂度为 $$O(1)$$。 + +## 堆的应用场景 + +### 求 TOP N + +堆结构的一个常见应用是建立优先队列(Priority Queue)。 + +求 Top K 的问题抽象成两类。一类是针对静态数据集合;另一类是针对动态数据集合 + +### 优先级队列 + +在优先级队列中,数据的出队顺序不是先进先出,而是按照优先级来,优先级最高的,最先出队。 + +堆和优先级队列非常相似:往优先级队列中插入一个元素,就相当于往堆中插入一个元素;从优先级队列中取出优先级最高的元素,就相当于取出堆顶元素。 + +> 参考:Java 的 `PriorityQueue` 类 + +### 求中位数 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" new file mode 100644 index 0000000..908780d --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" @@ -0,0 +1,63 @@ +--- +title: B+树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - B+ 树 +abbrlink: 17426722 +date: 2022-03-13 22:37:27 +permalink: /pages/3fd76e/ +--- + +# B+树 + +## 什么是 B+树 + +B+树是在二叉查找树的基础上进行了改造:树中的节点并不存储数据本身,而是只是作为索引。每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092926.jpg) + +改造之后,如果我们要求某个区间的数据。我们只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,我们再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值的所有数据。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092929.jpg) + +但是,我们要为几千万、上亿的数据构建索引,如果将索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是,占用的内存会非常多。 + +比如,我们给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节点假设占用 16 个字节,那就需要大约 1GB 的内存空间。给一张表建立索引,我们需要 1GB 的内存空间。如果我们要给 10 张表建立索引,那对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢? + +我们可以借助时间换空间的思路,把索引存储在硬盘中,而非内存中。我们都知道,硬盘是一个非常慢速的存储设备。通常内存的访问速度是纳秒级别的,而磁盘访问的速度是毫秒级别的。读取同样大小的数据,从磁盘中读取花费的时间,是从内存中读取所花费时间的上万倍,甚至几十万倍。 + +这种将索引存储在硬盘中的方案,尽管减少了内存消耗,但是在数据查找的过程中,需要读取磁盘中的索引,因此数据查询效率就相应降低很多。 + +二叉查找树,经过改造之后,支持区间查找的功能就实现了。不过,为了节省内存,如果把树存储在硬盘中,那么每个节点的读取(或者访问),都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。 + +我们前面讲到,比起内存读写操作,磁盘 IO 操作非常耗时,所以我们优化的重点就是尽量减少磁盘 IO 操作,也就是,尽量降低树的高度。那如何降低树的高度呢? + +我们来看下,如果我们把索引构建成 m 叉树,高度是不是比二叉树要小呢?如图所示,给 16 个数据构建二叉树索引,树的高度是 4,查找一个数据,就需要 4 个磁盘 IO 操作(如果根节点存储在内存中,其他结点存储在磁盘中),如果对 16 个数据构建五叉树索引,那高度只有 2,查找一个数据,对应只需要 2 次磁盘操作。如果 m 叉树中的 m 是 100,那对一亿个数据构建索引,树的高度也只是 3,最多只要 3 次磁盘 IO 就能获取到数据。磁盘 IO 变少了,查找数据的效率也就提高了。 + +## 为什么需要 B+树 + +关系型数据库中常用 B+ 树作为索引,这是为什么呢? + +思考以下经典应用场景 + +- 根据某个值查找数据,比如 `select * from user where id=1234`。 +- 根据区间值来查找某些数据,比如 `select * from user where id > 1234 and id < 2345`。 + +为了提高查询效率,需要使用索引。而对于索引的性能要求,主要考察**执行效率和存储空间**。如果让你选择一种数据结构去存储索引,你会如何考虑? + +以一些常见数据结构为例: + +- **哈希表**:哈希表的查询性能很好,时间复杂度是 `O(1)`。但是,哈希表不能支持按照区间快速查找数据。所以,哈希表不能满足我们的需求。 +- **平衡二叉查找树**:尽管平衡二叉查找树查询的性能也很高,时间复杂度是 `O(logn)`。而且,对树进行中序遍历,我们还可以得到一个从小到大有序的数据序列,但这仍然不足以支持按照区间快速查找数据。 +- **跳表**:跳表是在链表之上加上多层索引构成的。它支持快速地插入、查找、删除数据,对应的时间复杂度是 `O(logn)`。并且,跳表也支持按照区间快速地查找数据。我们只需要定位到区间起点值对应在链表中的结点,然后从这个结点开始,顺序遍历链表,直到区间终点对应的结点为止,这期间遍历得到的数据就是满足区间值的数据。 + +实际上,数据库索引所用到的数据结构跟跳表非常相似,叫作 B+ 树。不过,它是通过二叉查找树演化过来的,而非跳表。B+树的应用场景 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" new file mode 100644 index 0000000..c5d67cd --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" @@ -0,0 +1,79 @@ +--- +title: LSM树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - LSM 树 +abbrlink: 5bf5ed66 +date: 2022-03-16 09:27:21 +permalink: /pages/4a217d/ +--- + +# LSM 树 + +## 什么是 LSM 树 + +LSM 树具有以下 3 个特点: + +1. 将索引分为内存和磁盘两部分,并在内存达到阈值时启动树合并(Merge Trees); +2. 用批量写入代替随机写入,并且用预写日志 WAL 技术(Write AheadLog,预写日志技术)保证内存数据,在系统崩溃后可以被恢复; +3. 数据采取类似日志追加写的方式写入(Log Structured)磁盘,以顺序写的方式提高写 + 入效率。 + +LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅提升。所以,许多 NoSQL 系统都使用 LSM 树作为检索引擎,而且还对 LSM 树进行了优化以提升检索性能。 + +LSM 树就是根据这个思路设计了这样一个机制:当数据写入时,延迟写磁盘,将数据先存放在内存中的树里,进行常规的存储和查询。当内存中的树持续变大达到阈值时,再批量地以块为单位写入磁盘的树中。因此,LSM 树至少需要由两棵树组成,一棵是存储在内存中较小的 C0 树,另一棵是存储在磁盘中较大的 C1 树。 + +### 如何将内存数据与磁盘数据合并 + +可以参考两个有序链表归并排序的过程,将 C0 树和 C1 树的所有叶子节点中存储的数据,看作是两个有序链表,那滚动合并问题就变成了我们熟悉的两个有序链表的归并问题。不过由于涉及磁盘操作,那为了提高写入效率和检索效率,我们还需要针对磁盘的特性,在一些归并细节上进行优化。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316105440.png) + +由于磁盘具有顺序读写效率高的特性,因此,为了提高 C1 树中节点的读写性能,除了根节点以外的节点都要尽可能地存放到连续的块中,让它们能作为一个整体单位来读写。这种包含多个节点的块就叫作多页块(Multi-Pages Block)。 + +第一步,以多页块为单位,将 C1 树的当前叶子节点从前往后读入内存。读入内存的多页块,叫作清空块(Emptying Block),意思是处理完以后会被清空。 + +第二步,将 C0 树的叶子节点和清空块中的数据进行归并排序,把归并的结果写入内存的一个新块中,叫作填充块(Filling Block)。 + +第三步,如果填充块写满了,我们就要将填充块作为新的叶节点集合顺序写入磁盘。这个时候,如果 C0 树的叶子节点和清空块都没有遍历完,我们就继续遍历归并,将数据写入新的填充块。如果清空块遍历完了,我们就去 C1 树中顺序读取新的多页块,加载到清空块中。 + +第四步,重复第三步,直到遍历完 C0 树和 C1 树的所有叶子节点,并将所有的归并结果写入到磁盘。这个时候,我们就可以同时删除 C0 树和 C1 树中被处理过的叶子节点。这样就完成了滚动归并的过程。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316110736.png) + +### LSM 树是如何检索 + +因为同时存在 C0 和 C1 树,所以要查询一个 key 时,我们会先到 C0 树中查询。如果查询到了则直接返回;如过没有查询到,则查询 C1 树。 + +需要注意一种特殊情况:删除操作。假设某数据在 C0 树中被删除了,但是在 C1 树中仍存在。这此时查询时,可以在 C1 树中查到这个 key,这其实是过期数据了,如何应对这种情况呢?对于被删除的数据,可以将这些数据的 key 插入到 C0 树中,并标记一个删除标志。如果查到了一个带着删除标志的 key,就直接返回查询失败。 + +## 为什么需要 LSM 树 + +在关系型数据库中,通常使用 B+ 树作为索引。B+ 树的数据都存储在叶子节点中,而叶子节点一般都存储在磁盘中。因此,每次插入的新数据都需要随机写入磁盘,而随机写入的性能非常慢。如果是一个日志系统,每秒钟要写入上千条甚至上万条数据,这样的磁盘操作代价会使得系统性能急剧下降,甚至无法使用。 + +操作系统对磁盘的读写是以块为单位的,我们能否以块为单位写入,而不是每次插入一个数据都要随机写入磁盘呢?这样是不是就可以大幅度减少写入操作了呢?解决方案就是:**LSM 树**(Log Structured Merge Trees)。 + +## WAL 技术 + +LSM 树至少需要由两棵树组成,一棵是存储在内存中较小的 C0 树,另一棵是存储在磁盘中较大的 C1 树。 + +如果机器断电或系统崩溃了,那内存中还未写入磁盘的数据岂不就永远丢失了?这种情况我们该如何解决呢? + +为了保证内存中的数据在系统崩溃后能恢复,可以使用 WAL 技术(Write Ahead Log,预写日志技术)将数据第一时间高效写入磁盘进行备份。 + +WAL 技术保存和恢复数据的具体步骤如下: + +1. 内存中的程序在处理数据时,会先将对数据的修改作为一条记录,顺序写入磁盘的 log 文件作为备份。由于磁盘文件的顺序追加写入效率很高,因此许多应用场景都可以接受这种备份处理。 +2. 在数据写入 log 文件后,备份就成功了。接下来,该数据就可以长期驻留在内存中了。 +3. 系统会周期性地检查内存中的数据是否都被处理完了(比如,被删除或者写入磁盘),并且生成对应的检查点(Check Point)记录在磁盘中。然后,我们就可以随时删除被处理完的数据了。这样一来,log 文件就不会无限增长了。 +4. 系统崩溃重启,我们只需要从磁盘中读取检查点,就能知道最后一次成功处理的数据在 log 文件中的位置。接下来,我们就可以把这个位置之后未被处理的数据,从 log 文件中读出,然后重新加载到内存中。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316104837.png) + +## 参考资料 + +- [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" new file mode 100644 index 0000000..d5916a6 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" @@ -0,0 +1,98 @@ +--- +title: 字典树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 字典树 +abbrlink: eea60a6a +date: 2022-03-13 22:37:27 +permalink: /pages/0a4984/ +--- + +# 字典树 + +## 什么是字典树 + +Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 + +- 根节点(Root)不包含字符,除根节点外的每一个节点都仅包含一个字符; +- 从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串; +- 任意节点的所有子节点所包含的字符都不相同; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181057.jpg) + +### 字典树的构造 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181243.jpg) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181425.jpg) + +构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。 + +**字典树非常耗费内存**。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +### 字典树的查找 + +1. 每次从根结点开始搜索; +2. 获取关键词的第一个字符,根据该字符选择对应的子节点,转到该子节点继续检索; +3. 在相应的子节点上,获取关键词的第二个字符,进一步选择对应的子节点进行检索; +4. 以此类推,进行迭代过程; +5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,查找完成。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181305.jpg) + +每次查询时,如果要查询的字符串长度是 k,那我们只需要比对大约 k 个节点,就能完成查询操作。跟原本那组字符串的长度和个数没有任何关系。所以说,构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。 + +## 字典树的应用场景 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +第一,字符串中包含的字符集不能太大。我们前面讲到,如果字符集太大,那存储空间可能就会浪费很多。即便可以优化,但也要付出牺牲查询、插入效率的代价。 + +第二,要求字符串的前缀重合比较多,不然空间消耗会变大很多。 + +第三,如果要用 Trie 树解决问题,那我们就要自己从零开始实现一个 Trie 树,还要保证没有 bug,这个在工程上是将简单问题复杂化,除非必须,一般不建议这样做。 + +第四,我们知道,通过指针串起来的数据块是不连续的,而 Trie 树中用到了指针,所以,对缓存并不友好,性能上会打个折扣。 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +(1)自动补全 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305095300.png) + +(2)拼写检查 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305101637.png) + +(3)IP 路由 (最长前缀匹配) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305102959.gif) + +图 3. 使用 Trie 树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径。 + +(4)T9 (九宫格) 打字预测 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103047.jpg) + +(5)单词游戏 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103052.png) + +Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/ diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" new file mode 100644 index 0000000..dde06c0 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" @@ -0,0 +1,171 @@ +--- +title: 红黑树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 红黑树 +abbrlink: f89cb603 +date: 2018-06-01 21:10:23 +permalink: /pages/0a4414/ +--- + +# 红黑树 + +## 平衡二叉树 + +平衡二叉树的严格定义是这样的:二叉树中任意一个节点的左右子树的高度相差不能大于 1。 + +完全二叉树、满二叉树其实都是平衡二叉树,但是非完全二叉树也有可能是平衡二叉树。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202113.jpg) + +**平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些**。 + +## 什么是红黑树 + +红黑树的英文是“Red-Black Tree”,简称 R-B Tree。它是一种不严格的平衡二叉查找树。 + +红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求: + +- 根节点是黑色的; +- 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据; +- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; +- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202612.jpg) + +### 为什么说红黑树是“近似平衡”的? + +平衡二叉查找树的初衷,是为了解决二叉查找树因为动态更新导致的性能退化问题。 + +所以,**“平衡”的意思可以等价为性能不退化。“近似平衡”就等价为性能不会退化的太严重**。 + +**如果我们将红色节点从红黑树中去掉,那单纯包含黑色节点的红黑树的高度是多少呢**? + +红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202902.jpg) + +前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。 + +**现在把红色节点加回去,高度会变成多少呢**? + +在红黑树中,红色节点不能相邻,也就是说,有一个红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2log2n,也就是说,红黑树的高度近似 2log2n。 + +所以,红黑树的高度只比高度平衡的 AVL 树的高度(log2n)仅仅大了一倍,在性能上,下降得并不多。这样推导出来的结果不够精确,实际上红黑树的性能更好。 + +## 为什么需要红黑树 + +AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但是,有利就有弊,AVL 树为了维持这种高度的平衡,就要付出更多的代价。每次插入、删除都要做调整,就比较复杂、耗时。所以,对于有频繁的插入、删除操作的数据集合,使用 AVL 树的代价就有点高了。 + +红黑树只是做到了近似平衡,并不是严格的平衡,所以在维护平衡的成本上,要比 AVL 树要低。 + +所以,红黑树的插入、删除、查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支撑这种工业级的应用,我们更倾向于这种性能稳定的平衡二叉查找树。 + +## 红黑树平衡调整 + +### 插入操作的平衡调整 + +**红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节点上**。 + +- 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。 +- 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。 + +除此之外,其他情况都会违背红黑树的定义,于是我们就需要进行调整,调整的过程包含两种基础的操作:**左右旋转**和**改变颜色**。 + +红黑树的平衡调整过程是一个迭代的过程。我们把正在处理的节点叫作**关注节点**。关注节点会随着不停地迭代处理,而不断发生变化。最开始的关注节点就是新插入的节点。 + +新节点插入之后,如果红黑树的平衡被打破,那一般会有下面三种情况。我们只需要根据每种情况的特点,不停地调整,就可以让红黑树继续符合定义,也就是继续保持平衡。 + +**CASE 1:如果关注节点是 a,它的叔叔节点 d 是红色**,我们就依次执行下面的操作: + +- 将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色; +- 将关注节点 a 的祖父节点 c 的颜色设置成红色; +- 关注节点变成 a 的祖父节点 c; +- 跳到 CASE 2 或者 CASE 3。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203600.jpg) + +**CASE 2:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点**,我们就依次执行下面的操作: + +- 关注节点变成节点 a 的父节点 b; +- 围绕新的关注节点 b 左旋; +- 跳到 CASE 3。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203623.jpg) + +**CASE 3:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点**,我们就依次执行下面的操作: + +- 围绕关注节点 a 的祖父节点 c 右旋; +- 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。 +- 调整结束。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203645.jpg) + +### 删除操作的平衡调整 + +#### 针对删除节点初步调整 + +**CASE 1:如果要删除的节点是 a,它只有一个子节点 b**,那我们就依次进行下面的操作: + +- 删除节点 a,并且把节点 b 替换到节点 a 的位置,这一部分操作跟普通的二叉查找树的删除操作一样; +- 节点 a 只能是黑色,节点 b 也只能是红色,其他情况均不符合红黑树的定义。这种情况下,我们把节点 b 改为黑色; +- 调整结束,不需要进行二次调整。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310204215.jpg) + +**CASE 2:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c**。我们就依次进行下面的操作: + +- 如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点 a 的位置。这一部分操作跟普通的二叉查找树的删除操作无异; +- 然后把节点 c 的颜色设置为跟节点 a 相同的颜色; +- 如果节点 c 是黑色,为了不违反红黑树的最后一条定义,我们给节点 c 的右子节点 d 多加一个黑色,这个时候节点 d 就成了“红 - 黑”或者“黑 - 黑”; +- 这个时候,关注节点变成了节点 d,第二步的调整操作就会针对关注节点来做。 + +**CASE 3:如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的后继节点不是右子节点**,我们就依次进行下面的操作: + +- 找到后继节点 d,并将它删除,删除后继节点 d 的过程参照 CASE 1; +- 将节点 a 替换成后继节点 d; +- 把节点 d 的颜色设置为跟节点 a 相同的颜色; +- 如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点 d 的右子节点 c 多加一个黑色,这个时候节点 c 就成了“红 - 黑”或者“黑 - 黑”; +- 这个时候,关注节点变成了节点 c,第二步的调整操作就会针对关注节点来做。 + +#### 针对关注节点进行二次调整 + +**CASE 1:如果关注节点是 a,它的兄弟节点 c 是红色的**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的父节点 b 左旋; +- 关注节点 a 的父节点 b 和祖父节点 c 交换颜色; +- 关注节点不变; +- 继续从四种情况中选择适合的规则来调整。 + +**CASE 2:如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c 的左右子节点 d、e 都是黑色的**,我们就依次进行下面的操作: + +- 将关注节点 a 的兄弟节点 c 的颜色变成红色; +- 从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色或者黑色; +- 给关注节点 a 的父节点 b 添加一个黑色,这个时候节点 b 就变成了“红 - 黑”或者“黑 - 黑”; +- 关注节点从 a 变成其父节点 b; +- 继续从四种情况中选择符合的规则来调整。 + +**CASE 3:如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点 d 是红色,c 的右子节点 e 是黑色**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的兄弟节点 c 右旋; +- 节点 c 和节点 d 交换颜色; +- 关注节点不变; +- 跳转到 CASE 4,继续调整。 + +**CASE 4:如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色的**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的父节点 b 左旋; +- 将关注节点 a 的兄弟节点 c 的颜色,跟关注节点 a 的父节点 b 设置成相同的颜色; +- 将关注节点 a 的父节点 b 的颜色设置为黑色; +- 从关注节点 a 中去掉一个黑色,节点 a 就变成了单纯的红色或者黑色; +- 将关注节点 a 的叔叔节点 e 设置为黑色; +- 调整结束。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" new file mode 100644 index 0000000..a5b9ff1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" @@ -0,0 +1,157 @@ +--- +title: 哈希表 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 哈希表 +abbrlink: 850f2080 +date: 2015-03-16 14:19:59 +permalink: /pages/b501c7/ +--- + +# 哈希表 + +> **哈希表** 是一种使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。 +> +> 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 +> +> - **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +> - **哈希映射** 是映射 数据结构的实现之一,用于存储(key, value)键值对。 + +## 什么是哈希表 + +哈希表的英文叫“Hash Table”,我们平时也叫它“散列表”或者“Hash 表”。 + +**哈希表** 是一种使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。 + +有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 + +- **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +- **哈希映射** 是映射 数据结构的实现之一,用于存储(key, value)键值对。 + +**哈希表用的是数组支持按照下标随机访问数据的特性,所以哈希表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有哈希表**。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) + +哈希表通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置。按照键值查询元素时,用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。 + +有两种不同类型的哈希表:哈希集合和哈希映射。 + +- `哈希集合`是`集合`数据结构的实现之一,用于存储`非重复值`。 +- `哈希映射`是`映射` 数据结构的实现之一,用于存储`(key, value)`键值对。 + +在`标准模板库`的帮助下,哈希表是`易于使用的`。大多数常见语言(如 Java,C ++ 和 Python)都支持哈希集合和哈希映射。 + +## 散列函数 + +散列函数,顾名思义,它是一个函数。我们可以把它定义成 **hash(key)**,其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。 + +哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说, + +1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; +2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 + +散列函数将取决于 `键值的范围` 和 `桶的数量` 。 + +**散列函数设计的基本要求**: + +1. 散列函数计算得到的散列值是一个非负整数; +2. 如果 key1 = key2,那 hash(key1) == hash(key2); +3. 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。 + +### 散列冲突 + +即便像业界著名的[MD5](https://zh.wikipedia.org/wiki/MD5)、[SHA](https://zh.wikipedia.org/wiki/SHA家族)、[CRC](https://zh.wikipedia.org/wiki/循環冗餘校驗)等哈希算法,也无法完全避免这种**散列冲突**。 + +该如何解决散列冲突问题呢?我们常用的散列冲突解决方法有两类,开放寻址法(open addressing)和链表法(chaining)。 + +### 装载因子 + +当哈希表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证哈希表的操作效率,一般情况下,我们会尽可能保证哈希表中有一定比例的空闲槽位。我们用**装载因子**(load factor)来表示空位的多少。 + +装载因子的计算公式是: + +``` +哈希表的装载因子 = 填入表中的元素个数 / 哈希表的长度 +``` + +**装载因子越大,说明空闲位置越少,冲突越多**,哈希表的性能会下降。不仅插入数据的过程要多次寻址或者拉很长的链,查找的过程也会因此变得很慢。 + +当装载因子过大时,就需要对哈希表扩容。新申请一个更大的哈希表,将数据搬移到这个新哈希表中。针对数组的扩容,数据搬移操作比较简单。但是,针对哈希表的扩容,数据搬移操作要复杂很多。因为哈希表的大小变了,数据的存储位置也变了,所以我们需要通过散列函数重新计算每个数据的存储位置。 + +插入一个数据,最好情况下,不需要扩容,最好时间复杂度是 O(1)。最坏情况下,哈希表装载因子过高,启动扩容,我们需要重新申请内存空间,重新计算哈希位置,并且搬移数据,所以时间复杂度是 O(n)。用摊还分析法,均摊情况下,时间复杂度接近最好情况,就是 O(1)。 + +装载因子阈值需要选择得当。如果太大,会导致冲突过多;如果太小,会导致内存浪费严重。 + +### 开放寻址法 + +开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。 + +**当数据量比较小、装载因子小的时候,适合采用开放寻址法。这也是 Java 中的 `ThreadLocalMap` 使用开放寻址法解决散列冲突的原因**。 + +**线性探测**(Linear Probing):当我们往哈希表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200359.png) + +对于使用线性探测法解决冲突的哈希表,删除操作稍微有些特别。我们不能单纯地把要删除的元素设置为空。这是为什么呢?在查找的时候,一旦我们通过线性探测方法,找到一个空闲位置,我们就可以认定哈希表中不存在这个数据。但是,如果这个空闲位置是我们后来删除的,就会导致原来的查找算法失效。本来存在的数据,会被认定为不存在。这个问题如何解决呢? + +我们可以将删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为 deleted 的空间,并不是停下来,而是继续往下探测。 + +线性探测法其实存在很大问题。当哈希表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。极端情况下,我们可能需要探测整个哈希表,所以最坏情况下的时间复杂度为 O(n)。同理,在删除和查找时,也有可能会线性探测整张哈希表,才能找到要查找或者删除的数据。 + +### 链表法 + +在哈希表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。 + +链表法比起开放寻址法,对大装载因子的容忍度更高。开放寻址法只能适用装载因子小于 1 的情况。接近 1 时,就可能会有大量的散列冲突,导致大量的探测、再散列等,性能会下降很多。但是对于链表法来说,只要散列函数的值随机均匀,即便装载因子变成 10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多。 + +**基于链表的散列冲突处理方法比较适合存储大对象、大数据量的哈希表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200419.png) + +当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。那查找或删除操作的时间复杂度是多少呢? + +实际上,这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。对于散列比较均匀的散列函数来说,理论上讲,k=n/m,其中 n 表示散列中数据的个数,m 表示哈希表中“槽”的个数。 + +### 开放寻址法 vs. 链表法 + +**开放寻址法适用于数据量比较小、装载因子小的场景**。 + +**链表法适用于存储大对象、大数据量的哈希表**。**比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表**。 + +## 哈希表的应用场景 + +哈希算法的应用非常非常多,最常见的七个,分别是: + +- **安全加密**:如:MD5、SHA +- **唯一标识**:UUID +- 数据校验:数字签名 +- **散列函数**: +- **负载均衡**:会话粘滞(session sticky)负载均衡算法。**可以通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将取得的哈希值与服务器列表的大小进行取模运算,最终得到的值就是应该被路由到的服务器编号。** 这样,我们就可以把同一个 IP 过来的所有请求,都路由到同一个后端服务器上。 +- 数据分片 +- 分布式存储:一致性哈希算法、虚拟哈希槽 + +### 典型应用场景 + +Java 的 HashMap 工具类,其 + +- HashMap 默认的初始大小是 16 +- 最大装载因子默认是 0.75,当 HashMap 中元素个数超过 0.75\*capacity(capacity 表示哈希表的容量)的时候,就会启动扩容,每次扩容都会扩容为原来的两倍大小。 +- HashMap 底层采用链表法来解决冲突。即使负载因子和散列函数设计得再合理,也免不了会出现链表过长的情况,一旦出现链表过长,则会严重影响 HashMap 的性能。在 JDK1.8 版本中,对 HashMap 做了进一步优化:引入了红黑树。当链表长度太长(默认超过 8)时,链表就转换为红黑树。我们可以利用红黑树快速增删改查的特点,提高 HashMap 的性能。当红黑树结点个数少于 8 个的时候,又会将红黑树转化为链表。因为在数据量较小的情况下,红黑树要维护平衡,比起链表来,性能上的优势并不明显。 + +## 练习 + +Leetcode 练习题: + +- [705. 设计哈希集合](https://leetcode-cn.com/problems/design-hashset/) +- [706. 设计哈希映射](https://leetcode-cn.com/problems/design-hashmap/) + +## 思考 + +1. 假设我们有 10 万条 URL 访问日志,如何按照访问次数给 URL 排序? +2. 有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串? + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" new file mode 100644 index 0000000..f6af72a --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" @@ -0,0 +1,104 @@ +--- +title: 跳表 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 跳表 +abbrlink: 2e152a56 +date: 2020-10-23 09:21:13 +permalink: /pages/62671a/ +--- + +# 跳表 + +## 什么是跳表 + +对于一个有序数组,可以使用高效的二分查找法,其时间复杂度为 `O(log n)`。 + +但是,即使是有序的链表,也只能使用低效的顺序查找,其时间复杂度为 `O(n)`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323113532.png) + +如何提高链表的查找效率呢? + +我们可以对链表加一层索引。具体来说,可以每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作**索引**或**索引层**。索引节点中通过一个 down 指针,指向下一级结点。通过这样的改造,就可以支持类似二分查找的算法。我们把改造之后的数据结构叫作**跳表**(Skip list)。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155309.png) + +随着数据的不断增长,一级索引层也变得越来越长。此时,我们可以为一级索引再增加一层索引层:二级索引层。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155346.png) + +随着数据的膨胀,当二级索引层也变得很长时,我们可以继续为其添加新的索引层。**这种链表加多级索引的结构,就是跳表**。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323114408.png) + +### 跳表的时间复杂度 + +在一个具有多级索引的跳表中,第一级索引的结点个数大约就是 `n/2`,第二级索引的结点个数大约就是 `n/4`,第三级索引的结点个数大约就是 `n/8`,依次类推,也就是说,第 `k` 级索引的结点个数是第 `k-1` 级索引的结点个数的 `1/2`,那第 k 级索引结点的个数就是 `n/(2k)`。所以**跳表查询数据的时间复杂度就是 `O(logn)`**。 + +### 跳表的空间复杂度 + +比起单纯的单链表,跳表需要存储多级索引,肯定要消耗更多的存储空间。 + +假设原始链表大小为 n,那第一级索引大约有 n/2 个结点,第二级索引大约有 n/4 个结点,以此类推,每上升一级就减少一半,直到剩下 2 个结点。如果我们把每层索引的结点数写出来,就是一个等比数列。 + +``` +索引节点数 = n/2 + n/4 + n/8 … + 8 + 4 + 2 = n-2 +``` + +所以,跳表的空间复杂度是 `O(n)`。 + +跳表的存储空间其实还有压缩空间。比如,我们增加索引节点的范围,由『每两个节点抽一个上级索引节点』改为『每五个节点抽一个上级索引节点』,可以显著节省存储空间。 + +实际上,在软件开发中,我们不必太在意索引占用的额外空间。在讲数据结构和算法时,我们习惯性地把要处理的数据看成整数,但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。 + +## 跳表的操作 + +跳表是一种各方面性能都比较优秀的**动态数据结构**,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至可以替代[红黑树](https://zh.wikipedia.org/wiki/红黑树)(Red-black tree)。 + +### 高效的动态插入和删除 + +跳表不仅支持查找操作,还支持动态的插入、删除操作,而且插入、删除操作的时间复杂度也是 `O(logn)`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155933.png) + +- **插入操作**:对于纯粹的单链表,需要遍历每个结点,来找到插入的位置。但是,对于跳表来说,我们讲过查找某个结点的的时间复杂度是 `O(log n)`,所以这里查找某个数据应该插入的位置,方法也是类似的,时间复杂度也是 `O(log n)`。 +- **删除操作**:如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果我们用的是双向链表,就不需要考虑这个问题了。 + +### 跳表索引动态更新 + +当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323161942.png) + +如红黑树、AVL 树这样的平衡二叉树,是通过左右旋的方式保持左右子树的大小平衡,而跳表是通过随机函数来维护前面提到的“平衡性”。 + +当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?可以通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。 + +## 为什么需要跳表 + +跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 `O(logn)`。 + +跳表的空间复杂度是 `O(n)`。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。虽然跳表的代码实现并不简单,但是作为一种动态数据结构,比起红黑树来说,实现要简单多了。所以很多时候,我们为了代码的简单、易读,比起红黑树,我们更倾向用跳表。 + +## 跳表的应用场景 + +经典实现:Redis 的 Sorted Set、JDK 的 `ConcurrentSkipListMap` 和 `ConcurrentSkipListSet` 都是基于跳表实现。 + +为什么 Redis 要用跳表来实现有序集合,而不是红黑树? + +Redis 中的有序集合支持的核心操作主要有下面这几个: + +- 插入一个数据; +- 删除一个数据; +- 查找一个数据; +- 按照区间查找数据(比如查找值在 [100, 356] 之间的数据); +- 迭代输出有序序列。 + +其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git a/docs/graph.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" similarity index 84% rename from docs/graph.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" index 36bfaa5..44dc4d3 100644 --- a/docs/graph.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" @@ -1,10 +1,22 @@ +--- +title: 图 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 图 +abbrlink: ee040603 +date: 2015-03-24 15:31:13 +permalink: /pages/21529b/ +--- + # 图 在计算机科学中,一个图就是一些*顶点*的集合,这些顶点通过一系列*边*结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/graph/graph.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/data-structure/graph/graph.png) -## 术语 +## 什么是图 - **阶(Order)** - 图 G 中点集 V 的大小称作图 G 的阶。 - **子图(Sub-Graph)** - 当图 G'=(V',E')其中 V‘包含于 V,E’包含于 E,则 G'称作图 G=(V,E)的子图。每个图都是本身的子图。 @@ -20,6 +32,10 @@ - **轨迹(Track)** - 如果路径 P(u,v)中的顶点各不相同,则该路径称为 u 到 v 的一条轨迹。闭的轨迹称作圈(Cycle)。 - **桥(Bridge)** - 若去掉一条边,便会使得整个图不连通,该边称为[桥](https://baike.baidu.com/item/%E6%A1%A5)。 +如果图的边没有方向性,则被成为无向图。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220314093554.jpg) + ## 图的基本操作 - 创建一个图结构 - CreateGraph(G) @@ -33,3 +49,7 @@ - 插入一条边 - InsertEdge(G,v,w) - 删除一条边 - DeleteEdge(G,v,w) - 遍历图 - Traverse(G,v) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git a/docs/@pages/archivesPage.md b/docs/@pages/archivesPage.md new file mode 100644 index 0000000..4e2d4ed --- /dev/null +++ b/docs/@pages/archivesPage.md @@ -0,0 +1,6 @@ +--- +archivesPage: true +title: 归档 +permalink: /archives/ +article: false +--- diff --git a/docs/@pages/categoriesPage.md b/docs/@pages/categoriesPage.md new file mode 100644 index 0000000..15f359b --- /dev/null +++ b/docs/@pages/categoriesPage.md @@ -0,0 +1,6 @@ +--- +categoriesPage: true +title: 分类 +permalink: /categories/ +article: false +--- diff --git a/docs/@pages/tagsPage.md b/docs/@pages/tagsPage.md new file mode 100644 index 0000000..943f890 --- /dev/null +++ b/docs/@pages/tagsPage.md @@ -0,0 +1,6 @@ +--- +tagsPage: true +title: 标签 +permalink: /tags/ +article: false +--- diff --git a/docs/README.md b/docs/README.md index ff5d9d5..2e6316d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,32 +1,185 @@ -# Algorithm Ttutorial +--- +home: true +heroImage: img/bg.gif +heroText: ALGORITHM-TUTORIAL +tagline: 💾 algorithm-tutorial 是一个数据结构与算法教程。 +bannerBg: none +postList: none +footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu +--- -> 算法、数据结构学习要点: -> -> 三分学,七分练 +

+ + + star + + + + fork + + + + build + + + + code style + + +

+ +

ALGORITHM-TUTORIAL

+ +> 💾 algorithm-tutorial 是一个数据结构与算法教程。 > -> 坚持 + 坚持 + 坚持 +> 掌握数据结构与算法,你看待问题的深度,解决问题的角度就会完全不一样。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/algorithm-tutorial/) | [Gitee](https://gitee.com/turnon/algorithm-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/algorithm-tutorial/) | [Gitee Pages](http://turnon.gitee.io/algorithm-tutorial/) -## 📝 知识点 +## 📖 内容 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) + +- 综合 +- [数据结构和算法指南](01.数据结构和算法/00.综合/01.数据结构和算法指南.md) +- [复杂度分析](01.数据结构和算法/00.综合/02.复杂度分析.md) - 关键词:**`时间复杂度`**、**`空间复杂度`**、**`大 O 表示法`**、**`复杂度量级`** +- 线性表 + - [数组和链表](01.数据结构和算法/01.线性表/01.数组和链表.md) - 关键词:**`线性表`**、**`一维数组`**、**`多维数组`**、**`随机访问`**、**`单链表`**、**`双链表`**、**`循环链表`** + - [栈和队列](01.数据结构和算法/01.线性表/02.栈和队列.md) - 关键词:**`先进后出`**、**`后进先出`**、**`循环队列`** + - [线性表的查找](01.数据结构和算法/01.线性表/11.线性表的查找.md) + - [线性表的排序](01.数据结构和算法/01.线性表/12.线性表的排序.md) +- 树 + - [树和二叉树](01.数据结构和算法/02.树/01.树和二叉树.md) + - [堆](01.数据结构和算法/02.树/02.堆.md) + - [B+树](01.数据结构和算法/02.树/03.B+树.md) + - [LSM 树](01.数据结构和算法/02.树/04.LSM树.md) + - [字典树](01.数据结构和算法/02.树/05.字典树.md) + - [红黑树](01.数据结构和算法/02.树/06.红黑树.md) +- [哈希表](01.数据结构和算法/03.哈希表.md) - 关键词:**`哈希函数`**、**`装载因子`**、**`哈希冲突`**、**`开放寻址法`**、**`拉链法`** +- [跳表](01.数据结构和算法/04.跳表.md) - 关键词:**`多级索引`** +- [图](01.数据结构和算法/05.图.md) + +## 💻 刷题 + +### 数组 + +- [三数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/三数之和.java) +- [两数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/两数之和.java) +- [二维数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/二维数组.java) +- [删除排序数组中的重复项](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/删除排序数组中的重复项.java) +- [加一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/加一.java) +- [在排序数组中查找元素的第一个和最后一个位置](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找元素的第一个和最后一个位置.java) +- [在排序数组中查找数字 I](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找数字I.java) +- [存在重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/存在重复元素.java) +- [对角线遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/对角线遍历.java) +- [寻找数组的中心索引](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/寻找数组的中心索引.java) +- [将数组分成和相等的三个部分](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/将数组分成和相等的三个部分.java) +- [数组二分查找](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组二分查找.java) +- [数组拆分 1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组拆分1.java) +- [旋转数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转数组.java) +- [旋转矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转矩阵.java) +- [最大连续 1 的个数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/最大连续1的个数.java) +- [杨辉三角](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角.java) +- [杨辉三角 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角2.java) +- [模拟 ArrayList1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList1.java) +- [模拟 ArrayList2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList2.java) +- [移动零](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移动零.java) +- [移除元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移除元素.java) +- [至少是其他数字两倍的最大数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/至少是其他数字两倍的最大数.java) +- [螺旋矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/螺旋矩阵.java) +- [长度最小的子数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/长度最小的子数组.java) +- [零矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/零矩阵.java) + +### 链表 + +- [两数相加](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/两数相加.java) +- [二进制链表转整数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/二进制链表转整数.java) +- [删除排序链表中的重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/删除排序链表中的重复元素.java) +- [单链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/单链表示例.java) +- [双链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/双链表示例.java) +- [反转链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/反转链表.java) +- [合并 K 个排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表.java) +- [合并 K 个排序链表解法 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表解法2.java) +- [合并两个有序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并两个有序链表.java) +- [回文链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/回文链表.java) +- [排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/排序链表.java) +- [环形链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/环形链表.java) +- [相交链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/相交链表.java) +- [移除重复节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除重复节点.java) +- [移除链表元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除链表元素.java) +- [返回倒数第 k 个节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/返回倒数第k个节点.java) +- [链表的中间结点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/链表的中间结点.java) + +### 栈 + +- [三合一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/三合一.java) +- [基本计算器](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/基本计算器.java) +- [最小栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈.java) +- [最小栈 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈2.java) +- [有效的括号](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/有效的括号.java) +- [栈排序](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/栈排序.java) +- [棒球比赛](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/棒球比赛.java) +- [比较含退格的字符串](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/比较含退格的字符串.java) +- [用栈实现队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用栈实现队列.java) +- [用队列实现栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用队列实现栈.java) + +### 队列 + +- [动态扩容数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/动态扩容数组实现的队列.java) +- [数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/数组实现的队列.java) +- [最近的请求次数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/最近的请求次数.java) +- [设计循环队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/设计循环队列.java) +- [链表实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/链表实现的队列.java) + +### 字符串 + +- [二进制求和](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java) +- [实现 strStr()](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java) +- [最长公共前缀](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java) +- [反转字符串](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java) +- [反转字符串中的单词](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java) +- [反转字符串中的单词 III](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java) + +### 树 + +- [N 叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N叉树的最大深度.java) + +#### 二叉树 + +- [二叉树中的最大路径和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树中的最大路径和.java) +- [二叉树的中序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的中序遍历.java) +- [二叉树的前序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的前序遍历.java) +- [二叉树的后序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的后序遍历.java) +- [二叉树的层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历.java) +- [二叉树的层次遍历 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历2.java) +- [二叉树的序列化与反序列化](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的序列化与反序列化.java) +- [二叉树的所有路径](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的所有路径.java) +- [二叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最大深度.java) +- [二叉树的最小深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最小深度.java) +- [二叉树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最近公共祖先.java) +- [二叉树的锯齿形层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的锯齿形层次遍历.java) +- [从先序遍历还原二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/从先序遍历还原二叉树.java) +- [叶子相似的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/叶子相似的树.java) +- [填充每个节点的下一个右侧节点指针](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针.java) +- [填充每个节点的下一个右侧节点指针 II](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针II.java) +- [对称二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/对称二叉树.java) +- [平衡二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/平衡二叉树.java) +- [相同的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/相同的树.java) +- [翻转二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/翻转二叉树.java) +- [路径总和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/路径总和.java) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200605164316.png) +#### 二叉搜索树 -- [数组](array2.md) -- [链表](list.md) -- [栈](stack.md) -- [队列](queue.md) -- [树](tree/README.md) -- [图](graph.md) -- [堆](heap.md) -- [散列表](hash.md) -- [查找算法](algorithm/search) -- [排序算法](sort.md) +- [二叉搜索树中的插入操作](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树中的插入操作.java) +- [二叉搜索树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树的最近公共祖先.java) +- [二叉搜索树节点最小距离](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树节点最小距离.java) +- [将有序数组转换为二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/将有序数组转换为二叉搜索树.java) +- [验证二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/验证二叉搜索树.java) -## 📚 学习资源 +## 📚 资料 -- 书籍 +- **书籍** - 刷题必备 - 《剑指 offer》 - 《编程之美》 @@ -38,17 +191,18 @@ - 《[数据结构与算法分析 : C++描述(第 4 版)](https://www.amazon.cn/gp/product/B01LDG2DSG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01LDG2DSG&linkCode=as2&tag=vastwork-23)》 - 《[数据结构与算法分析 : C 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B002WC7NGS/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B002WC7NGS&linkCode=as2&tag=vastwork-23)》 - 《[数据结构与算法分析 : Java 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B01CNP0CG6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01CNP0CG6&linkCode=as2&tag=vastwork-23)》 - - 《[算法(第 4 版)](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23)》- 这本近千页的书只有 6 章,其中四章分别是排序,查找,图,字符串,足见介绍细致 + - 《[算法(第 4 版)](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23)》 - 算法设计 - 《[算法设计与分析基础(第 3 版)](https://www.amazon.cn/gp/product/B00S4HCQUI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00S4HCQUI&linkCode=as2&tag=vastwork-23)》 - - 《算法引论》 - 告诉你如何创造算法 断货 - 《Algorithm Design Manual》 - 算法设计手册 红皮书 - [《算法导论》](https://www.amazon.cn/gp/product/B00AK7BYJY/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00AK7BYJY&linkCode=as2&tag=vastwork-23) - 是一本对算法介绍比较全面的经典书籍 - 《Algorithms on Strings,Trees and Sequences》 - 《Advanced Data Structures》 - 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600 块 -- 参考链接和学习网站 +- **学习网站** + - https://github.com/TheAlgorithms/Java - https://github.com/nonstriater/Learn-Algorithms - https://github.com/trekhleb/javascript-algorithms + - https://github.com/wangzheng0822/algo - https://github.com/kdn251/interviews/blob/master/README-zh-cn.md#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84 - [July 博客](http://blog.csdn.net/v_july_v) - 《数学建模十大经典算法》 @@ -59,17 +213,18 @@ - [The-Art-Of-Programming-By-July](https://github.com/julycoding/The-Art-Of-Programming-By-July) - [微软面试 100 题](http://blog.csdn.net/column/details/ms100.html) - [程序员编程艺术](http://blog.csdn.net/v_JULY_v/article/details/6460494) -- 基本算法演示 +- **基本算法演示** - - -- 编程网站 - - [leetcode](http://leetcode.com/) - - [openjudge](http://openjudge.cn/) 开放在线程序评测平台,可以创建自己的 OJ 小组 [九度 OJ](http://ac.jobdu.com/index.php) - - 这有个[ACM 训练方案](http://www.java3z.com/cwbwebhome/article/article19/res041.html) -- 其它 +- **编程网站** + - [leetcode](http://leetcode-cn.com/) + - [openjudge](http://openjudge.cn/) +- **教程** - [高级数据结构和算法](https://www.coursera.org/learn/gaoji-shuju-jiegou/) 北大教授张铭老师在 coursera 上的课程。完成这门课之时,你将掌握多维数组、广义表、Trie 树、AVL 树、伸展树等高级数据结构,并结合内排序、外排序、检索、索引有关的算法,高效地解决现实生活中一些比较复杂的应用问题。当然 coursera 上也还有很多其它算法方面的视频课程。 - [算法设计与分析 Design and Analysis of Algorithms](https://class.coursera.org/algorithms-001/lecture) 由北大教授 Wanling Qu 在 coursera 讲授的一门算法课程。首先介绍一些与算法有关的基础知识,然后阐述经典的算法设计思想和分析技术,主要涉及的算法设计技术是:分治策略、动态规划、贪心法、回溯与分支限界等。每个视频都配有相应的讲义(pdf 文件)以便阅读和复习。 + - [算法面试通关 40 讲](https://time.geekbang.org/course/intro/100019701) + - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) -## 🚪 传送门 +## 🚪 传送 | [技术文档归档](https://github.com/dunwu/blog) | [算法和数据结构教程系列](https://github.com/dunwu/algorithm-tutorial) | diff --git a/docs/array.md b/docs/array.md deleted file mode 100644 index 627a9f8..0000000 --- a/docs/array.md +++ /dev/null @@ -1,123 +0,0 @@ -# 数组 - -> **数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。** - -## 简介 - -`数组`是一种基本的数据结构,用于按顺序`存储元素的集合`。但是元素可以随机存取,因为数组中的每个元素都可以通过数组`索引`来识别。 - -数组可以有一个或多个维度。 - -### 一维数组 - -一维(或单维)数组是一种线性数组,其中元素的访问是以行或列索引的单一下标表示。 - -这里有一个例子: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/array2/一维数组.png) - -在上面的例子中,数组 A 中有 6 个元素。也就是说,A 的长度是 6 。我们可以使用 A[0] 来表示数组中的第一个元素。因此,A[0] = 6 。类似地,A[1] = 3,A[2] = 8,依此类推。 - -### 二维数组 - -类似于一维数组,二维数组也是由元素的序列组成。但是这些元素可以排列在矩形网格中而不是直线上。 - -类似于一维数组,`二维数组`也是由元素的序列组成。但是这些元素可以排列在矩形网格中而不是直线上。 - -在一些语言中,多维数组实际上是在`内部`作为一维数组实现的,而在其他一些语言中,`实际上`根本没有`多维数组`。 - -**1. C++ 将二维数组存储为一维数组。** - -下图显示了*大小为 M \* N 的数组 A* 的实际结构: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/array2/C++二维数组.png) - -因此,如果我们将 A 定义为也包含 _M \* N_ 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。 - -**2. 在 Java 中,二维数组实际上是包含着 M 个元素的一维数组,每个元素都是包含有 N 个整数的数组。** - -下图显示了 Java 中二维数组 A 的实际结构: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/array2/JAVA二维数组.png) - -二维数组示例: - -```java -public class TwoDimensionArray { - private static void printArray(int[][] a) { - for (int i = 0; i < a.length; ++i) { - System.out.println(a[i]); - } - for (int i = 0; i < a.length; ++i) { - for (int j = 0; a[i] != null && j < a[i].length; ++j) { - System.out.print(a[i][j] + " "); - } - System.out.println(); - } - } - - public static void main(String[] args) { - System.out.println("Example I:"); - int[][] a = new int[2][5]; - printArray(a); - System.out.println("Example II:"); - int[][] b = new int[2][]; - printArray(b); - System.out.println("Example III:"); - b[0] = new int[3]; - b[1] = new int[5]; - printArray(b); - } -} -``` - -### 多维数组 - -普通数组采用一个整数来作下标。多维数组(高维数组)的概念特别是在数值计算和图形应用方面非常有用。我们在多维数组之中采用一系列有序的整数来标注,如在[ 3,1,5 ] 。这种整数列表之中整数的个数始终相同,且被称为数组的“维度”。关于每个数组维度的边界称为“维”。维度为 k 的数组通常被称为 k 维。 - -多维数组的数组名字,在表达式中自动转换为数组首元素地址值,但这个首元素实际上是去除数组下标第一维之后的数组剩余部分。 - -### 数组的特性 - -数组设计之初是在形式上依赖内存分配而成的,所以必须在使用前预先请求空间。这使得数组有以下特性: - -1. 请求空间以后大小固定,不能再改变(数据溢出问题); -2. 在内存中有空间连续性的表现,中间不会存在其他程序需要调用的数据,为此数组的专用内存空间; -3. 在旧式编程语言中(如有中阶语言之称的 C),程序不会对数组的操作做下界判断,也就有潜在的越界操作的风险(比如会把数据写在运行中程序需要调用的核心部分的内存上)。 - -因为简单数组强烈倚赖计算机硬件之内存,所以不适用于现代的程序设计。欲使用可变大小、硬件无关性的数据类型,Java 等程序设计语言均提供了更高级的数据结构:ArrayList、Vector 等动态数组。 - -## 数组中的操作 - -```java -public class Main { - public static void main(String[] args) { - // 1. Initialize - int[] a0 = new int[5]; - int[] a1 = {1, 2, 3}; - // 2. Get Length - System.out.println("The size of a1 is: " + a1.length); - // 3. Access Element - System.out.println("The first element is: " + a1[0]); - // 4. Iterate all Elements - System.out.print("[Version 1] The contents of a1 are:"); - for (int i = 0; i < a1.length; ++i) { - System.out.print(" " + a1[i]); - } - System.out.println(); - System.out.print("[Version 2] The contents of a1 are:"); - for (int item: a1) { - System.out.print(" " + item); - } - System.out.println(); - // 5. Modify Element - a1[0] = 4; - // 6. Sort - Arrays.sort(a1); - } -} -``` - -## 更多内容 - -- https://zh.wikipedia.org/wiki/数组 diff --git a/docs/book.json b/docs/book.json deleted file mode 100644 index 28ac727..0000000 --- a/docs/book.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "gitbook": "3.2.2", - "title": "db-tutorial", - "language": "zh-hans", - "root": "./", - "structure": { - "summary": "sidebar.md" - }, - "links": { - "sidebar": { - "db-tutorial": "https://github.com/dunwu/db-tutorial" - } - }, - "plugins": [ - "-lunr", - "-search", - "advanced-emoji@^0.2.2", - "anchor-navigation-ex@1.0.10", - "anchors@^0.7.1", - "edit-link@^2.0.2", - "expandable-chapters-small@^0.1.7", - "github@^2.0.0", - "search-plus@^0.0.11", - "simple-page-toc@^0.1.1", - "splitter@^0.0.8", - "tbfed-pagefooter@^0.0.1" - ], - "pluginsConfig": { - "anchor-navigation-ex": { - "showLevel": false, - "associatedWithSummary": true, - "multipleH1": true, - "mode": "float", - "isRewritePageTitle": false, - "float": { - "showLevelIcon": false, - "level1Icon": "fa fa-hand-o-right", - "level2Icon": "fa fa-hand-o-right", - "level3Icon": "fa fa-hand-o-right" - }, - "pageTop": { - "showLevelIcon": false, - "level1Icon": "fa fa-hand-o-right", - "level2Icon": "fa fa-hand-o-right", - "level3Icon": "fa fa-hand-o-right" - } - }, - "edit-link": { - "base": "https://github.com/dunwu/db-tutorial/blob/master/docs", - "label": "编辑此页面" - }, - "github": { - "url": "https://github.com/dunwu" - }, - "simple-page-toc": { - "maxDepth": 4, - "skipFirstH1": true - }, - "sharing": { - "weibo": true, - "all": [ - "weibo" - ] - }, - "tbfed-pagefooter": { - "copyright": "Copyright © Zhang Peng 2017", - "modify_label": "该文件上次修订时间:", - "modify_format": "YYYY-MM-DD HH:mm:ss" - } - } -} diff --git a/docs/coverpage.md b/docs/coverpage.md deleted file mode 100644 index a3447b2..0000000 --- a/docs/coverpage.md +++ /dev/null @@ -1,7 +0,0 @@ -
- -# Algorithm Tutorial - -> 算法和数据结构教程 - -[开始阅读](README.md) diff --git a/docs/hash-search.md b/docs/hash-search.md index 09e78e8..84fddb5 100644 --- a/docs/hash-search.md +++ b/docs/hash-search.md @@ -4,7 +4,7 @@ ### 哈希表和哈希函数 -在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能**唯一对应**。这个映射函数称为**哈希函数**,根据这个原则建立的表称为**哈希表(Hash Table)**,也叫**散列表**。 +在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能**唯一对应**。这个映射函数称为**哈希函数**,根据这个原则建立的表称为**哈希表(Hash Table)**,也叫**哈希表**。 以上描述,如果通过数学形式来描述就是: diff --git a/docs/hash.md b/docs/hash.md deleted file mode 100644 index e4bf9d1..0000000 --- a/docs/hash.md +++ /dev/null @@ -1,81 +0,0 @@ -# 哈希表 - -> 关键词: hash, 哈希表, 哈希函数 - - - -- [简介](#简介) -- [原理](#原理) -- [更多内容](#更多内容) - - - -## 简介 - ---- - -`哈希表`是一种使用`哈希函数`组织数据,以支持快速插入和搜索的数据结构。 - -有两种不同类型的哈希表:哈希集合和哈希映射。 - -- `哈希集合`是`集合`数据结构的实现之一,用于存储`非重复值`。 -- `哈希映射`是`映射` 数据结构的实现之一,用于存储`(key, value)`键值对。 - -在`标准模板库`的帮助下,哈希表是`易于使用的`。大多数常见语言(如 Java,C ++ 和 Python)都支持哈希集合和哈希映射。 - -通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现`出色的性能`。 - -## 原理 - ---- - -哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说, - -1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; -2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 - -### 哈希函数示例 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/hash/哈希函数.png) - -在示例中,我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略: - -1. 插入:我们通过哈希函数解析键,将它们映射到相应的桶中。 - - 例如,1987 分配给桶 2,而 24 分配给桶 4。 -2. 搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。 - - 如果我们搜索 1987,我们将使用相同的哈希函数将 1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。 - - 例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。 - -### 哈希表的关键 - -#### 1. 哈希函数 - -哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。在上一节的示例中,我们使用 `y = x % 5` 作为散列函数,其中 `x` 是键值,`y` 是分配的桶的索引。 - -散列函数将取决于`键值的范围`和`桶的数量。` - -下面是一些哈希函数的示例: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/hash/哈希函数示例.png) - -哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。 - -#### 2. 冲突解决 - -理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(_y = x % 5_)中,1987 和 2 都分配给了桶 2,这是一个`冲突`。 - -冲突解决算法应该解决以下几个问题: - -1. 如何组织在同一个桶中的值? -2. 如果为同一个桶分配了太多的值,该怎么办? -3. 如何在特定的桶中搜索目标值? - -根据我们的哈希函数,这些问题与`桶的容量`和可能映射到`同一个桶`的`键的数目`有关。 - -让我们假设存储最大键数的桶有 `N` 个键。 - -通常,如果 _N_ 是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 _N_ 是可变的或很大,我们可能需要使用`高度平衡的二叉树`来代替。 - -## 更多内容 - -https://leetcode-cn.com/explore/learn/card/hash-table/ diff --git a/docs/heap.md b/docs/heap.md deleted file mode 100644 index ed7a30b..0000000 --- a/docs/heap.md +++ /dev/null @@ -1,20 +0,0 @@ -# 堆 - -## 简介 - -堆(Heap)是一个可以被看成近似完全二叉树的数组。树上的每一个结点对应数组的一个元素。除了最底层外,该树是完全充满的,而且是从左到右填充。 - -堆包括最大堆和最小堆:最大堆的每一个节点(除了根结点)的值不大于其父节点;最小堆的每一个节点(除了根结点)的值不小于其父节点。 - -堆常见的操作: - -- HEAPIFY 建堆:把一个乱序的数组变成堆结构的数组,时间复杂度为 $$O(n)$$。 -- HEAPPUSH:把一个数值放进已经是堆结构的数组中,并保持堆结构,时间复杂度为 $$O(log N)$$ -- HEAPPOP:从最大堆中取出最大值或从最小堆中取出最小值,并将剩余的数组保持堆结构,时间复杂度为 $$O(log N)$$。 -- HEAPSORT:借由 HEAPFY 建堆和 HEAPPOP 堆数组进行排序,时间复杂度为$$ O(N log N)$$,空间复杂度为 $$O(1)$$。 - -堆结构的一个常见应用是建立优先队列(Priority Queue)。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/heap/heap.png) - -## 实战 diff --git a/docs/how-to-learn.md b/docs/how-to-learn.md deleted file mode 100644 index 4c56e7b..0000000 --- a/docs/how-to-learn.md +++ /dev/null @@ -1,52 +0,0 @@ -# 数据结构和算法入门 - -## 如何学习数据结构和算法 - -- 边学边练,适度刷题 -- 多问、多思考、多互动 -- 打怪升级学习法 -- 知识需要沉淀,不要想试图一下子掌握所有 - -## 如何分析、统计算法的执行效率和资源消耗 - -**复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半**。 - -### 为什么需要复杂度分析? - -事后统计法的局限性: - -**1. 测试结果非常依赖测试环境** - -**2. 测试结果受数据规模的影响很大** - -### 时间复杂度分析 - -大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示**代码执行时间随数据规模增长的变化趋势**,所以,也叫作**渐进时间复杂度**(asymptotic time complexity),简称**时间复杂度**。 - -**1. 只关注循环执行次数最多的一段代码** - -**分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了**。 - -**2. 加法法则:总复杂度等于量级最大的那段代码的复杂度** - -**总的时间复杂度就等于量级最大的那段代码的时间复杂度**。 - -**3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积** - -### 几种常见时间复杂度实例分析 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200605165440.png) - -### 空间复杂度分析 - -时间复杂度的全称是**渐进时间复杂度**,**表示算法的执行时间与数据规模之间的增长关系**。类比一下,空间复杂度全称就是**渐进空间复杂度**(asymptotic space complexity),**表示算法的存储空间与数据规模之间的增长关系**。 - -## 浅析最好、最坏、平均、均摊时间复杂度 - -**最好情况时间复杂度**(best case time complexity) - **最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度**。 - -**最坏情况时间复杂度**(worst case time complexity) - **最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度**。 - -**平均情况时间复杂度**(average case time complexity) - -**均摊时间复杂度**(amortized time complexity) diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index ca68df2..0000000 --- a/docs/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - algorithm-tutorial - - - - - - - - - - -
正在加载...
- - - - - - - - - - - - - - - - - - - - - diff --git a/docs/list.md b/docs/list.md deleted file mode 100644 index 83b418e..0000000 --- a/docs/list.md +++ /dev/null @@ -1,201 +0,0 @@ -# 链表 - -链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200126095433.png) - -由于不必须按顺序存储,链表在插入的时候可以达到 $$O(1)$$ 的复杂度,比另一种线性表 —— 顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 $$O(n)$$ 的时间,而顺序表相应的时间复杂度分别是 $$O(log N)$$ 和 $$O(1)$$。 - -使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。 - -在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(links)。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。 - -链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。 - -链表通常可以衍生出循环链表,静态链表,双链表等。对于链表使用,需要注意头结点的使用。 - -## 单链表 - -单链表中的每个结点不仅包含数据值,还包含一个指针,指向其后继节点。通过这种方式,单链表将所有结点按顺序组织起来。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200126095433.png) - -与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按 `索引` 来 `访问元素` 平均要花费 $$O(N)$$ 时间,其中 N 是链表的长度。 - -### 数据结构 - -```java -public class ListNode { - E value; - ListNode next; // 指向后继节点 -} - -public class SingleLinkList { - private ListNode head; // 头节点 -} -``` - -### 基本操作 - -#### 从头部添加节点 - -即头插法 - -```java -void addHead(E value) { - ListNode newNode = new ListNode<>(value, null); - newNode.next = this.head.next; - this.head.next = newNode; -} -``` - -#### 从尾部添加节点 - -即尾插法 - -```java -void addTail(E value) { - // init new node - ListNode newNode = new ListNode<>(value, null); - - // find the last node - ListNode node = this.head; - while (node.next != null) { - node = node.next; - } - - // add new node to tail - node.next = newNode; -} -``` - -#### 删除节点 - -找到要删除元素的前驱节点,将前驱节点的 next 指针指向下一个节点。 - -```java -public void remove(E value) { - ListNode prev = this.head; - while (prev.next != null) { - ListNode curr = prev.next; - if (curr.value.equals(value)) { - prev.next = curr.next; - break; - } - prev = prev.next; - } -} -``` - -#### 查找节点 - -从头开始查找,一旦发现有数值与查找值相等的节点,直接返回此节点。如果遍历结束,表明未找到节点,返回 null。 - -```java -public ListNode find(E value) { - ListNode node = this.head.next; - while (node != null) { - if (node.value.equals(value)) { - return node; - } - node = node.next; - } - return null; -} -``` - -## 双链表 - -双链表中的每个结点不仅包含数据值,还包含两个指针,分别指向指向其前驱节点和后继节点。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200126095616.png) - -双链表以类似的方式工作,但`还有一个引用字段`,称为`“prev”`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 - -### 数据结构 - -```java -static class DListNode { - E value; - DListNode prev; // 指向前驱节点 - DListNode next; // 指向后继节点 -} - -public class DoubleLinkList { - /** 头节点 */ - private DListNode head; - /** 尾节点 */ - private DListNode tail; -} -``` - -### 基本操作 - -#### 从头部添加节点 - -```java -public void addHead(E value) { - DListNode newNode = new DListNode<>(null, value, null); - - this.head.next.prev = newNode; - newNode.next = this.head.next; - - this.head.next = newNode; - newNode.prev = this.head; -} -``` - -#### 从尾部添加节点 - -```java -public void addTail(E value) { - DListNode newNode = new DListNode<>(null, value, null); - - this.tail.prev.next = newNode; - newNode.prev = this.tail.prev; - - this.tail.prev = newNode; - newNode.next = this.tail; -} -``` - -#### 删除节点 - -```java -public void remove(E value) { - DListNode prev = this.head; - while (prev.next != this.tail) { - DListNode curr = prev.next; - if (curr.value.equals(value)) { - prev.next = curr.next; - curr.next.prev = prev; - curr.next = null; - curr.prev = null; - break; - } - prev = prev.next; - } -} -``` - -#### 查找节点 - -```java -public DListNode find(E value) { - DListNode node = this.head.next; - while (node != this.tail) { - if (node.value.equals(value)) { - return node; - } - node = node.next; - } - return null; -} -``` - -## 参考资料 - -- [数据结构(C语言版)](https://item.jd.com/12407475.html) - [严蔚敏](https://book.jd.com/writer/严蔚敏_1.html),[吴伟民](https://book.jd.com/writer/吴伟民_1.html) -- [数据结构:链表](https://www.jianshu.com/p/73d56c3d228c) -- [leetcode 链表题库](https://leetcode-cn.com/tag/linked-list/) - diff --git a/docs/overview.md b/docs/overview.md deleted file mode 100644 index 1038f4a..0000000 --- a/docs/overview.md +++ /dev/null @@ -1,33 +0,0 @@ -# 算法概述 - -## 学习方法 - -- 三分学,七分练 -- 坚持不懈 - -刷题,推荐 leetcode - -## 算法性能分析 - -性能分析有两个维度: - -- 时间复杂度 -- 空间复杂度 - -复杂度级别: - -- $$O(1)$$:常数 - -- $$O(log n)$$:对数 - -- $$O(n)$$:线性 - -- $$O(n^2)$$:平方 - -- $$O(n^3)$$:立方 - -- $$O(2^n)$$:指数 - -- $$O(n!)$$:阶乘 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200702071922.png) diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index bb994f5..0000000 --- a/docs/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "algorithm-tutorial", - "author": "Zhang Peng", - "homepage": "http://dunwu.github.io/algorithm-tutorial", - "repository": { - "type": "git", - "url": "git@github.com:dunwu/algorithm-tutorial.git" - }, - "scripts": { - "start": "docsify serve ./ --port 4000", - "clean": "rimraf _book", - "install": "gitbook install", - "serve": "gitbook serve", - "build": "npm run clean & gitbook build", - "pdf": "gitbook pdf ." - }, - "dependencies": { - "gitbook-plugin-advanced-emoji": "^0.2.2", - "gitbook-plugin-anchor-navigation-ex": "^1.0.10", - "gitbook-plugin-anchors": "^0.7.1", - "gitbook-plugin-edit-link": "^2.0.2", - "gitbook-plugin-expandable-chapters-small": "^0.1.7", - "gitbook-plugin-github": "^2.0.0", - "gitbook-plugin-search-plus": "0.0.11", - "gitbook-plugin-simple-page-toc": "^0.1.2", - "gitbook-plugin-splitter": "0.0.8", - "gitbook-plugin-tbfed-pagefooter": "0.0.1" - }, - "devDependencies": { - "gh-pages": "^2.1.1", - "rimraf": "^3.0.0" - } -} diff --git a/docs/queue.md b/docs/queue.md deleted file mode 100644 index 72122ca..0000000 --- a/docs/queue.md +++ /dev/null @@ -1,12 +0,0 @@ -# 队列 - -队列是元素的集合,其包含了两个基本操作:入队(enqueue) 操作可以用于将元素插入到队列中,而出队(dequeue)操作则是将元素从队列中移除。 - -遵循先入先出原则 (FIFO)。 - -时间复杂度: - -- 索引: `O(n)` -- 搜索: `O(n)` -- 插入: `O(1)` -- 移除: `O(1)` diff --git a/docs/sidebar.md b/docs/sidebar.md deleted file mode 100644 index 160b40b..0000000 --- a/docs/sidebar.md +++ /dev/null @@ -1,14 +0,0 @@ -# algorithm-tutorial - -- [数据结构](data-structure/README.md) - - [数组](array2.md) - - [栈](stack.md) - - [队列](queue.md) - - [链表](list.md) - - [树](tree/README.md) - - [图](graph.md) - - [堆](heap.md) - - [散列表](hash.md) -- [算法](algorithm/README.md) - - [查找算法](algorithm/search) - - [排序算法](sort.md) diff --git a/docs/skiplist.md b/docs/skiplist.md deleted file mode 100644 index 636e7ce..0000000 --- a/docs/skiplist.md +++ /dev/null @@ -1,13 +0,0 @@ -# 跳表 - -> 经典实现:Redis 的 Sorted Set、JDK 的 ConcurrentSkipListMap 和 ConcurrentSkipListSet 都是基于跳表实现。 - -只需要对链表稍加改造,就可以支持类似“二分”的查找算法。我们把改造之后的数据结构叫作**跳表**(Skip list)。 - -跳表是一种各方面性能都比较优秀的**动态数据结构**,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至可以替代[红黑树](https://zh.wikipedia.org/wiki/红黑树)(Red-black tree)。 - -在一个单链表中查询某个数据的时间复杂度是 O(n)。 - -在一个具有多级索引的跳表中,第一级索引的结点个数大约就是 n/2,第二级索引的结点个数大约就是 n/4,第三级索引的结点个数大约就是 n/8,依次类推,也就是说,第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k 级索引结点的个数就是 $$n/(2^k)$$ - -## 参考资料 diff --git a/docs/stack.md b/docs/stack.md deleted file mode 100644 index da27b06..0000000 --- a/docs/stack.md +++ /dev/null @@ -1,41 +0,0 @@ -# 堆栈 - -> 堆栈(英语:stack)又称为栈或堆叠,是计算机科学中一种特殊的串列形式的抽象数据类型,其特殊之处在于只能允许在链表或数组的一端(称为堆栈顶端指针,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。另外堆栈也可以用一维数组或链表的形式来完成。堆栈的另外一个相对的操作方式称为队列。 -> -> 由于堆栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/stack/stack.png) - - - -- [概念](#概念) -- [应用](#应用) -- [更多内容](#更多内容) - - - -## 概念 - -### 特点 - -堆栈的基本特点: - -1. 先入后出,后入先出。 -2. 除头尾节点之外,每个元素有一个前驱,一个后继。 - -### 操作 - -堆栈数据结构使用两种基本操作:推入(压栈,push)和弹出(弹栈,pop): - -- 推入 - 将数据放入堆栈的顶端(数组形式或串列形式),堆栈顶端 top 指针加一。 -- 弹出 - 将顶端数据数据输出(回传),堆栈顶端数据减一。 - -## 应用 - -- 回溯 -- 递归 -- 深度优先搜索 - -## 更多内容 - -- https://zh.wikipedia.org/wiki/堆栈 diff --git a/docs/tree.md b/docs/tree.md deleted file mode 100644 index 65e4baf..0000000 --- a/docs/tree.md +++ /dev/null @@ -1,125 +0,0 @@ -# 数据结构 - 树 - -## 一、树的简介 - -在计算机科学中,**树**(英语:tree)是一种[抽象数据类型](https://zh.wikipedia.org/wiki/抽象資料型別)(ADT)或是实现这种抽象数据类型的[数据结构](https://zh.wikipedia.org/wiki/資料結構),用来模拟具[有树状结构](https://zh.wikipedia.org/wiki/樹狀結構)性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的[集合](https://zh.wikipedia.org/wiki/集合)。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 - -它具有以下的特点: - -- 每个节点都只有有限个子节点或无子节点。 -- 树有且仅有一个根节点。 -- 根节点没有父节点;非根节点有且仅有一个父节点。 -- 每个非根节点可以分为多个不相交的子树。 -- 树里面没有环路。 - -### 树的术语 - -- **节点的度**:一个节点含有的子树的个数称为该节点的度; -- **树的度**:一棵树中,最大的节点度称为树的度; -- **叶子节点**或**终端节点**:度为零的节点; -- **非终端节点**或**分支节点**:度不为零的节点; -- **父节点**:若一个节点含有子节点,则这个节点称为其子节点的父节点; -- **子节点**:一个节点含有的子树的根节点称为该节点的子节点; -- **兄弟节点**:具有相同父节点的节点互称为兄弟节点; -- 节点的**层次**:从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推; -- **深度**:对于任意节点 n,n 的深度为从根到 n 的唯一路径长,根的深度为 0; -- **高度**:对于任意节点 n,n 的高度为从 n 到一片树叶的最长路径长,所有树叶的高度为 0; -- **堂兄弟节点**:父节点在同一层的节点互为堂兄弟; -- **节点的祖先**:从根到该节点所经分支上的所有节点; -- **子孙**:以某节点为根的子树中任一节点都称为该节点的子孙。 -- **森林**:由 m(m>=0)棵互不相交的树的集合称为森林; - -### 树的性质 - -- 树中的节点数等于所有节点的度数加 1。 -- 度为 m 的树中第 i 层上至多有 $$m^{i-1}$$ 个节点($$i ≥ 1$$)。 -- 高度为 h 的 m 次树至多有 $$(m^h-1)/(m-1)$$ 个节点。 -- 具有 n 个节点的 m 次树的最小高度为 $$\log_m{(n(m-1)+1)}$$ 。 - -### 树的种类 - -**无序树**:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为[自由树](https://zh.wikipedia.org/wiki/自由树); - -**有序树**:树中任意节点的子节点之间有顺序关系,这种树称为有序树; - -- 二叉树:每个节点最多含有两个子树的树称为二叉树; - - **完全二叉树**:对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; -- [满二叉树](https://zh.wikipedia.org/wiki/满二叉树):所有叶节点都在最底层的完全二叉树; -- [平衡二叉树](https://zh.wikipedia.org/wiki/平衡二叉树)([AVL 树](https://zh.wikipedia.org/wiki/AVL树)):当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树; -- [排序二叉树](https://zh.wikipedia.org/wiki/排序二元樹)([二叉查找树](https://zh.wikipedia.org/wiki/二叉查找树)(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树; -- [霍夫曼树](https://zh.wikipedia.org/wiki/霍夫曼树):[带权路径](https://zh.wikipedia.org/w/index.php?title=带权路径&action=edit&redlink=1)最短的二叉树称为哈夫曼树或最优二叉树; -- [B 树](https://zh.wikipedia.org/wiki/B树):一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。 - -### 二叉树 - -二叉树是 N 个节点的有限集合,它或者是空树,或者是由一个根节点及两棵不想交的且分别称为左右子树的二叉树所组成。 - -#### 二叉树的性质 - -1. 二叉树第 i 层上的结点数目最多为 **2i-1** (i≥1)。 -2. 深度为 k 的二叉树至多有 **2k-1** 个结点(k≥1)。 -3. 包含 n 个结点的二叉树的高度至少为 **log2(n+1)**。 -4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。 - -#### 满二叉树 - -定义:高度为 h,并且由 **2h–1** 个结点的二叉树,被称为满二叉树。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/tree/满二叉树.png) - -#### 完全二叉树 - -定义:一棵二叉树中,只有最下面两层结点的度可以小于 2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。 - -特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/tree/完全二叉树.png) - -## 二、算法要点 - -## 三、练习 - -### 二叉树经典题 - -#### 深度优先搜索(DFS) - -在这个策略中,我们采用 深度 作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回到达另一个分支。深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为**先序遍历**,**中序遍历**和**后序遍历**。 - -- [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) -- [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal) -- [二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal) - -#### 宽度优先搜索(BFS) - -我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。 - -- [二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal) - -#### 二叉树和递归 - -- [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree) -- [对称二叉树](https://leetcode-cn.com/problems/symmetric-tree) -- [路径总和](https://leetcode-cn.com/problems/path-sum) - -#### 其他 - -- [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) -- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) -- [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) -- [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) -- [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) -- [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) -- [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) -- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) -- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) - -### 二叉搜索树经典题 - -- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) -- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) -- [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) -- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) - -## 参考资料 - -- [https://zh.wikipedia.org/wiki/树\_(数据结构)]() diff --git a/docs/trie.md b/docs/trie.md deleted file mode 100644 index 5b58da4..0000000 --- a/docs/trie.md +++ /dev/null @@ -1,201 +0,0 @@ -# 字典树 - -Trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。 - -字典树设计的核心思想是空间换时间,所以数据结构本身比较消耗空间。但它利用了字符串的**共同前缀(Common Prefix)**作为存储依据,以此来节省存储空间,并加速搜索时间。Trie 的字符串搜索时间复杂度为 **O(m)**,m 为最长的字符串的长度,其查询性能与集合中的字符串的数量无关。其在搜索字符串时表现出的高效,使得特别适用于构建文本搜索和词频统计等应用。 - -## Trie 的性质 - -- 根节点(Root)不包含字符,除根节点外的每一个节点都仅包含一个字符; -- 从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串; -- 任意节点的所有子节点所包含的字符都不相同; - -## Trie 的查找过程 - -1. 每次从根结点开始搜索; -2. 获取关键词的第一个字符,根据该字符选择对应的子节点,转到该子节点继续检索; -3. 在相应的子节点上,获取关键词的第二个字符,进一步选择对应的子节点进行检索; -4. 以此类推,进行迭代过程; -5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,查找完成。 - -## Trie 的应用 - -(1)自动补全 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305095300.png) - -(2)拼写检查 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305101637.png) - -(3)IP 路由 (最长前缀匹配) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305102959.gif) - -图 3. 使用 Trie 树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径。 - -(4)T9 (九宫格) 打字预测 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305103047.jpg) - -(5)单词游戏 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305103052.png) - -图 5. Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏 - -还有其他的数据结构,如平衡树和哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 Trie 树呢?尽管哈希表可以在 O(1)O(1) 时间内寻找键值,却无法高效的完成以下操作: - -- 找到具有同一前缀的全部键值。 -- 按词典序枚举字符串的数据集。 - -Trie 树优于哈希表的另一个理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 $$O(n)$$,其中 n 是插入的键的数量。与哈希表相比,Trie 树在存储多个具有相同前缀的键时可以使用较少的空间。此时 Trie 树只需要 $$O(m)$$ 的时间复杂度,其中 m 为键长。而在平衡树中查找键值需要 $$O(mlogn)$$ 时间复杂度。 - -## Trie 树的结点结构 - -Trie 树是一个有根的树,其结点具有以下字段:。 - -最多 R 个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。 - -- 本文中假定 R 为 26,小写拉丁字母的数量。 -- 布尔字段,以指定节点是对应键的结尾还是只是键前缀。 - -![3463d9e7cb323911aa67cbd94910a34d88c9402a1ab41bbea10852cd0a74f2af-file_1562596867185](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305103530.png) - -```java -class TrieNode { - - // R links to node children - private TrieNode[] links; - - private final int R = 26; - - private boolean isEnd; - - public TrieNode() { - links = new TrieNode[R]; - } - - public boolean containsKey(char ch) { - return links[ch -'a'] != null; - } - public TrieNode get(char ch) { - return links[ch -'a']; - } - public void put(char ch, TrieNode node) { - links[ch -'a'] = node; - } - public void setEnd() { - isEnd = true; - } - public boolean isEnd() { - return isEnd; - } -} -``` -向 Trie 树中插入键 - -我们通过搜索 Trie 树来插入一个键。我们从根开始搜索它对应于第一个键字符的链接。有两种情况: - -- 链接存在。沿着链接移动到树的下一个子层。算法继续搜索下一个键字符。 -- 链接不存在。创建一个新的节点,并将它与父节点的链接相连,该链接与当前的键字符相匹配。 - -重复以上步骤,直到到达键的最后一个字符,然后将当前节点标记为结束节点,算法完成。 - -图 7. 向 Trie 树中插入键 - -Java -class Trie { - private TrieNode root; - - public Trie() { - root = new TrieNode(); - } - - // Inserts a word into the trie. - public void insert(String word) { - TrieNode node = root; - for (int i = 0; i < word.length(); i++) { - char currentChar = word.charAt(i); - if (!node.containsKey(currentChar)) { - node.put(currentChar, new TrieNode()); - } - node = node.get(currentChar); - } - node.setEnd(); - } -} -复杂度分析 - -时间复杂度:O(m)O(m),其中 mm 为键长。在算法的每次迭代中,我们要么检查要么创建一个节点,直到到达键尾。只需要 mm 次操作。 - -空间复杂度:O(m)O(m)。最坏的情况下,新插入的键和 Trie 树中已有的键没有公共前缀。此时需要添加 mm 个结点,使用 O(m)O(m) 空间。 - -在 Trie 树中查找键 -每个键在 trie 中表示为从根到内部节点或叶的路径。我们用第一个键字符从根开始,。检查当前节点中与键字符对应的链接。有两种情况: - -存在链接。我们移动到该链接后面路径中的下一个节点,并继续搜索下一个键字符。 -不存在链接。若已无键字符,且当前结点标记为 isEnd,则返回 true。否则有两种可能,均返回 false : -还有键字符剩余,但无法跟随 Trie 树的键路径,找不到键。 -没有键字符剩余,但当前结点没有标记为 isEnd。也就是说,待查找键只是Trie树中另一个键的前缀。 - - -图 8. 在 Trie 树中查找键 - -Java -class Trie { - ... - - // search a prefix or whole key in trie and - // returns the node where search ends - private TrieNode searchPrefix(String word) { - TrieNode node = root; - for (int i = 0; i < word.length(); i++) { - char curLetter = word.charAt(i); - if (node.containsKey(curLetter)) { - node = node.get(curLetter); - } else { - return null; - } - } - return node; - } - - // Returns if the word is in the trie. - public boolean search(String word) { - TrieNode node = searchPrefix(word); - return node != null && node.isEnd(); - } -} -复杂度分析 - -时间复杂度 : O(m)O(m)。算法的每一步均搜索下一个键字符。最坏的情况下需要 mm 次操作。 -空间复杂度 : O(1)O(1)。 -查找 Trie 树中的键前缀 -该方法与在 Trie 树中搜索键时使用的方法非常相似。我们从根遍历 Trie 树,直到键前缀中没有字符,或者无法用当前的键字符继续 Trie 中的路径。与上面提到的“搜索键”算法唯一的区别是,到达键前缀的末尾时,总是返回 true。我们不需要考虑当前 Trie 节点是否用 “isend” 标记,因为我们搜索的是键的前缀,而不是整个键。 - - - -图 9. 查找 Trie 树中的键前缀 - -Java -class Trie { - ... - - // Returns if there is any word in the trie - // that starts with the given prefix. - public boolean startsWith(String prefix) { - TrieNode node = searchPrefix(prefix); - return node != null; - } -} -复杂度分析 - -时间复杂度 : O(m)O(m)。 -空间复杂度 : O(1)O(1)。 - -## 实战 - -## 参考资料 - -- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/D:\Codes\ZPTutorial\ZPSpring\spring-boot-tutorial\codes\spring-boot-dubbo\README.md diff --git "a/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" "b/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" new file mode 100644 index 0000000..8cec531 --- /dev/null +++ "b/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" @@ -0,0 +1,114 @@ +# 算法思路 + +## 递归 + +### 使用递归的条件 + +递归需要满足的三个条件 + +- **一个问题的解可以分解为几个子问题的解** +- **这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样** +- **存在递归终止条件** + +### 递归代码要警惕堆栈溢出 + +函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 + +那么,如何避免出现堆栈溢出呢? + +我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题 + +## 贪心算法 + +### 贪心算法思路 + +贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,就能得到问题的答案。贪心算法需要充分挖掘题目中条件,没有固定的模式,解决有贪心算法需要一定的直觉和经验。 + +贪心算法**不是对所有问题都能得到整体最优解**。能使用贪心算法解决的问题具有「贪心选择性质」。「贪心选择性质」严格意义上需要数学证明。能使用贪心算法解决的问题必须具备「无后效性」,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。 + +### 贪心算法的应用 + +霍夫曼编码(Huffman Coding) + +Prim 和 Kruskal 最小生成树算法 + +Dijkstra 单源最短路径算法 + +## 分治算法 + +分治算法的核心就是分而治之,也就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,分别解决这些子问题,然后再合并其结果,得到原问题的解。 + +**分治算法是一种处理问题的思想,递归是一种编程技巧**。分治算法一般都比较适合用递归来实现。分治算法的递归实现中,每一层递归都会涉及这样三个操作: + +- 分解:将原问题分解成一系列子问题; +- 解决:递归地求解各个子问题,若子问题足够小,则直接求解; +- 合并:将子问题的结果合并成原问题。 + +分治算法能解决的问题,一般需要满足下面这几个条件: + +- 原问题与分解成的小问题具有相同的模式; +- 原问题分解成的子问题可以独立求解,子问题之间没有相关性,这一点是分治算法跟动态规划的明显区别,等我们讲到动态规划的时候,会详细对比这两种算法; +- 具有分解终止条件,也就是说,当问题足够小时,可以直接求解; +- 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。 + +## 回溯算法 + +### 回溯算法思路 + +**回溯法** 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况: + +- 找到一个可能存在的正确的答案; + +- 在尝试了所有可能的分步方法后宣告该问题没有答案。 + + + +**解决一个回溯问题,实际上就是一个决策树的遍历过程**。 + +- **路径**:也就是已经做出的选择。 +- **选择列表**:也就是你当前可以做的选择。 +- **结束条件**:也就是到达决策树底层,无法再做选择的条件。 + +回溯算法的骨架: + +```text +result = [] +def backtrack(路径, 选择列表): + if 满足结束条件: + result.add(路径) + return + + for 选择 in 选择列表: + 做选择 + backtrack(路径, 选择列表) + 撤销选择 +``` + +**其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」** + +### 回溯算法应用 + +回溯算法典型问题: + +- [46. 全排列(中等)](https://leetcode-cn.com/problems/permutations/) +- [47. 全排列 II(中等)](https://leetcode-cn.com/problems/permutations-ii/) +- [N 皇后(困难)](https://leetcode-cn.com/problems/n-queens/) +37. [解数独(困难)](https://leetcode-cn.com/problems/sudoku-solver/) + +> [知乎:回溯算法套路详解 - labuladong的文章](https://zhuanlan.zhihu.com/p/93530380) + +## 动态规划 + +### 动态规划思路 + +动态规划比较适合用来求解最优问题,比如求最大值、最小值等等。 + +动态规划的应用 + +买卖股票的最佳时机 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- 回溯 +- [知乎:回溯算法套路详解 - labuladong的文章](https://zhuanlan.zhihu.com/p/93530380) diff --git "a/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" "b/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" new file mode 100644 index 0000000..30e2d91 --- /dev/null +++ "b/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" @@ -0,0 +1,42 @@ +# 数据结构 - 树 + +## 二叉树经典题 + +### 深度优先搜索(DFS) + +在这个策略中,我们采用 深度 作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回到达另一个分支。深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为**先序遍历**,**中序遍历**和**后序遍历**。 + +- [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) +- [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal) +- [二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal) + +### 宽度优先搜索(BFS) + +我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。 + +- [二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal) + +### 二叉树和递归 + +- [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree) +- [对称二叉树](https://leetcode-cn.com/problems/symmetric-tree) +- [路径总和](https://leetcode-cn.com/problems/path-sum) + +### 其他 + +- [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) +- [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) +- [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) +- [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +- [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +- [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) + +## 二叉搜索树经典题 + +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +- [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) diff --git a/package.json b/package.json new file mode 100644 index 0000000..47ccf68 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "algorithm-tutorial", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "rimraf docs/.temp", + "start": "vuepress dev docs", + "build": "vuepress build docs", + "deploy": "bash scripts/deploy.sh", + "editFm": "node utils/editFrontmatter.js", + "baiduPush": "node utils/baiduPush.js https://xugaoyi.com && bash baiduPush.sh", + "publish": "cd ./vdoing && npm publish && cd .. && yarn updateTheme", + "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D", + "lint": "markdownlint -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "show-help": "vuepress --help", + "view-info": "vuepress view-info ./ --temp docs/.temp" + }, + "devDependencies": { + "dayjs": "^1.9.7", + "inquirer": "^7.1.0", + "json2yaml": "^1.1.0", + "vuepress": "1.9.5", + "vuepress-plugin-baidu-autopush": "^1.0.1", + "vuepress-plugin-baidu-tongji": "^1.0.1", + "vuepress-plugin-comment": "^0.7.3", + "vuepress-plugin-demo-block": "^0.7.2", + "vuepress-plugin-fulltext-search": "^2.2.1", + "vuepress-plugin-one-click-copy": "^1.0.2", + "vuepress-plugin-thirdparty-search": "^1.0.2", + "vuepress-plugin-zooming": "^1.1.7", + "vuepress-theme-vdoing": "^1.11.2", + "yamljs": "^0.3.0", + "markdownlint-cli": "^0.25.0", + "markdownlint-rule-emphasis-style": "^1.0.1", + "rimraf": "^3.0.1", + "vue-toasted": "^1.1.25" + } +} diff --git a/pom.xml b/pom.xml index a22a98a..9c66df2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,17 +1,17 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - io.github.dunwu - algorithm-tutorial - 1.0.0 - pom - ALGORITHM-TUTORIAL - algorithm-tutorial 示例源码 + io.github.dunwu + algorithm-tutorial + 1.0.0 + pom + ALGORITHM-TUTORIAL + algorithm-tutorial 示例源码 - - codes/algorithm - + + codes/algorithm + diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..eb6bb1f --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,7 @@ +/** + * @see https://prettier.io/docs/en/options.html + * @see https://prettier.io/docs/en/configuration.html + */ +module.exports = { + tabWidth: 2, semi: false, singleQuote: true +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..a89fb1a --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env sh + +# ------------------------------------------------------------------------------ +# gh-pages 部署脚本 +# @author Zhang Peng +# @since 2020/2/10 +# ------------------------------------------------------------------------------ + +# 装载其它库 +ROOT_DIR=$( + cd $(dirname $0)/.. + pwd +) + +# 确保脚本抛出遇到的错误 +set -e + +# 生成静态文件 +npm run build + +# 进入生成的文件夹 +cd ${ROOT_DIR}/docs/.temp + +# 如果是发布到自定义域名 +# echo 'www.example.com' > CNAME + +if [[ ${GITHUB_TOKEN} && ${GITEE_TOKEN} ]]; then + msg='自动部署' + GITHUB_URL=https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/algorithm-tutorial.git + GITEE_URL=https://turnon:${GITEE_TOKEN}@gitee.com/turnon/algorithm-tutorial.git + git config --global user.name "dunwu" + git config --global user.email "forbreak@163.com" +else + msg='手动部署' + GITHUB_URL=git@github.com:dunwu/algorithm-tutorial.git + GITEE_URL=git@gitee.com:turnon/algorithm-tutorial.git +fi +git init +git add -A +git commit -m "${msg}" +# 推送到github gh-pages分支 +git push -f "${GITHUB_URL}" master:gh-pages +git push -f "${GITEE_URL}" master:gh-pages + +cd - +rm -rf ${ROOT_DIR}/docs/.temp diff --git a/utils/config.yml b/utils/config.yml new file mode 100644 index 0000000..6fac6a2 --- /dev/null +++ b/utils/config.yml @@ -0,0 +1,14 @@ +#批量添加和修改、删除front matter配置文件 + +# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[0]/arr[1] ... ) +path: + - docs # 第一个成员必须是docs + +# 要删除的字段 (数组) +delete: + # - test + # - tags + + # 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖) +data: + article: false \ No newline at end of file diff --git a/utils/editFrontmatter.js b/utils/editFrontmatter.js new file mode 100644 index 0000000..8c223f4 --- /dev/null +++ b/utils/editFrontmatter.js @@ -0,0 +1,92 @@ +/** + * 批量添加和修改front matter ,需要配置 ./config.yml 文件。 + */ +const fs = require('fs'); // 文件模块 +const path = require('path'); // 路径模块 +const matter = require('gray-matter'); // front matter解析器 https://github.com/jonschlinkert/gray-matter +const jsonToYaml = require('json2yaml') +const yamlToJs = require('yamljs') +const inquirer = require('inquirer') // 命令行操作 +const chalk = require('chalk') // 命令行打印美化 +const readFileList = require('./modules/readFileList'); +const { type, repairDate} = require('./modules/fn'); +const log = console.log + +const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径 + +main(); + +/** + * 主体函数 + */ +async function main() { + + const promptList = [{ + type: "confirm", + message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'), + name: "edit", + }]; + let edit = true; + + await inquirer.prompt(promptList).then(answers => { + edit = answers.edit + }) + + if(!edit) { // 退出操作 + return + } + + const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象 + + if (type(config.path) !== 'array') { + log(chalk.red('路径配置有误,path字段应该是一个数组')) + return + } + + if (config.path[0] !== 'docs') { + log(chalk.red("路径配置有误,path数组的第一个成员必须是'docs'")) + return + } + + const filePath = path.join(__dirname, '..', ...config.path); // 要批量修改的文件路径 + const files = readFileList(filePath); // 读取所有md文件数据 + + files.forEach(file => { + let dataStr = fs.readFileSync(file.filePath, 'utf8');// 读取每个md文件的内容 + const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{}, ...} + let matterData = fileMatterObj.data; // 得到md文件的front Matter + + let mark = false + // 删除操作 + if (config.delete) { + if( type(config.delete) !== 'array' ) { + log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!')) + } else { + config.delete.forEach(item => { + if (matterData[item]) { + delete matterData[item] + mark = true + } + }) + + } + } + + // 添加、修改操作 + if (type(config.data) === 'object') { + Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象 + mark = true + } + + // 有操作时才继续 + if (mark) { + if(matterData.date && type(matterData.date) === 'date') { + matterData.date = repairDate(matterData.date) // 修复时间格式 + } + const newData = jsonToYaml.stringify(matterData).replace(/\n\s{2}/g,"\n").replace(/"/g,"") + '---\r\n' + fileMatterObj.content; + fs.writeFileSync(file.filePath, newData); // 写入 + log(chalk.green(`update frontmatter:${file.filePath} `)) + } + + }) +} diff --git a/utils/modules/fn.js b/utils/modules/fn.js new file mode 100644 index 0000000..48cbbd1 --- /dev/null +++ b/utils/modules/fn.js @@ -0,0 +1,21 @@ +// 类型判断 +exports.type = function (o){ + var s = Object.prototype.toString.call(o) + return s.match(/\[object (.*?)\]/)[1].toLowerCase() +} + + // 修复date时区格式的问题 + exports.repairDate = function (date) { + date = new Date(date); + return `${date.getUTCFullYear()}-${zero(date.getUTCMonth()+1)}-${zero(date.getUTCDate())} ${zero(date.getUTCHours())}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`; +} + +// 日期的格式 +exports.dateFormat = function (date) { + return `${date.getFullYear()}-${zero(date.getMonth()+1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(date.getMinutes())}:${zero(date.getSeconds())}` +} + +// 小于10补0 +function zero(d){ + return d.toString().padStart(2,'0') +} \ No newline at end of file diff --git a/utils/modules/readFileList.js b/utils/modules/readFileList.js new file mode 100644 index 0000000..8eb97c6 --- /dev/null +++ b/utils/modules/readFileList.js @@ -0,0 +1,43 @@ +/** + * 读取所有md文件数据 + */ +const fs = require('fs'); // 文件模块 +const path = require('path'); // 路径模块 +const docsRoot = path.join(__dirname, '..', '..', 'docs'); // docs文件路径 + +function readFileList(dir = docsRoot, filesList = []) { + const files = fs.readdirSync(dir); + files.forEach( (item, index) => { + let filePath = path.join(dir, item); + const stat = fs.statSync(filePath); + if (stat.isDirectory() && item !== '.vuepress') { + readFileList(path.join(dir, item), filesList); //递归读取文件 + } else { + if(path.basename(dir) !== 'docs'){ // 过滤docs目录级下的文件 + + const fileNameArr = path.basename(filePath).split('.') + let name = null, type = null; + if (fileNameArr.length === 2) { // 没有序号的文件 + name = fileNameArr[0] + type = fileNameArr[1] + } else if (fileNameArr.length === 3) { // 有序号的文件 + name = fileNameArr[1] + type = fileNameArr[2] + } else { // 超过两个‘.’的 + log(chalk.yellow(`warning: 该文件 "${filePath}" 没有按照约定命名,将忽略生成相应数据。`)) + return + } + if(type === 'md'){ // 过滤非md文件 + filesList.push({ + name, + filePath + }); + } + + } + } + }); + return filesList; +} + +module.exports = readFileList; \ No newline at end of file