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;
+
+/**
+ *