();
- for (int i = 0; i < nums.length; i++) {
- if (map.containsKey(target - nums[i])) {
- result[1] = i;
- result[0] = map.get(target - nums[i]);
- return result;
- }
- map.put(nums[i], i);
- }
- return result;
- }
-
- public static void main(String[] args) {
- int[] nums = new int[]{2, 7, 11, 15};
- int target = 18;
- int[] result = twoSum(nums, target);
- System.out.println(result);
- }
-}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java"
new file mode 100644
index 0000000..7e1cb56
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.array.base;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 485. 最大连续 1 的个数
+ *
+ * @author Zhang Peng
+ * @since 2018-11-05
+ */
+public class 最大连续1的个数 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(3, s.findMaxConsecutiveOnes(new int[] { 1, 1, 0, 1, 1, 1 }));
+ Assertions.assertEquals(2, s.findMaxConsecutiveOnes(new int[] { 1, 0, 1, 1, 0, 1 }));
+ }
+
+ static class Solution {
+
+ public int findMaxConsecutiveOnes(int[] nums) {
+ int max = 0;
+ int cnt = 0;
+ for (int num : nums) {
+ if (num == 1) {
+ cnt++;
+ max = Math.max(max, cnt);
+ } else {
+ cnt = 0;
+ }
+ }
+ return max;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java"
new file mode 100644
index 0000000..cd5ceb9
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.array.base;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 747. 至少是其他数字两倍的最大数
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 至少是其他数字两倍的最大数 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(1, s.dominantIndex(new int[] { 3, 6, 1, 0 }));
+ Assertions.assertEquals(-1, s.dominantIndex(new int[] { 1, 2, 3, 4 }));
+ Assertions.assertEquals(0, s.dominantIndex(new int[] { 1, 0 }));
+ }
+
+ static class Solution {
+
+ public int dominantIndex(int[] nums) {
+ int second = -1, max = 0;
+ for (int i = 1; i < nums.length; i++) {
+ if (nums[i] > nums[max]) {
+ second = max;
+ max = i;
+ } else if (second == -1 || nums[i] > nums[second]) {
+ second = i;
+ }
+ }
+ return nums[max] >= 2 * nums[second] ? max : -1;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java"
new file mode 100644
index 0000000..1a47983
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java"
@@ -0,0 +1,38 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 704. 二分查找
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 二分查找 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(4, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 9));
+ Assertions.assertEquals(-1, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 2));
+ }
+
+ static class Solution {
+
+ public int search(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] == target) {
+ return mid;
+ } else if (nums[mid] < target) {
+ left = mid + 1;
+ } else {
+ right = mid - 1;
+ }
+ }
+ return -1;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java"
new file mode 100644
index 0000000..0c3678b
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java"
@@ -0,0 +1,66 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 410. 分割数组的最大值
+ *
+ * @author Zhang Peng
+ * @date 2025-10-16
+ */
+public class 分割数组的最大值 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(18, s.splitArray(new int[] { 7, 2, 5, 10, 8 }, 2));
+ Assertions.assertEquals(9, s.splitArray(new int[] { 1, 2, 3, 4, 5 }, 2));
+ Assertions.assertEquals(4, s.splitArray(new int[] { 1, 4, 4 }, 3));
+ }
+
+ static class Solution {
+
+ public int splitArray(int[] nums, int k) {
+ return shipWithinDays(nums, k);
+ }
+
+ public int shipWithinDays(int[] weights, int days) {
+ int max = 0, sum = 0;
+ for (int weight : weights) {
+ max = Math.max(max, weight);
+ sum += weight;
+ }
+
+ int left = max, right = sum;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (f(weights, mid) <= days) {
+ // 需要让 f(x) 的返回值大一些
+ right = mid - 1;
+ } else if (f(weights, mid) > days) {
+ // 需要让 f(x) 的返回值小一些
+ left = mid + 1;
+ }
+ }
+ return left;
+ }
+
+ // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物
+ // f(x) 随着 x 的增加单调递减
+ int f(int[] weights, int x) {
+ int days = 0;
+ for (int i = 0; i < weights.length; ) {
+ // 尽可能多装货物
+ int cap = x;
+ while (i < weights.length) {
+ if (cap < weights[i]) break;
+ else cap -= weights[i];
+ i++;
+ }
+ days++;
+ }
+ return days;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java"
new file mode 100644
index 0000000..04d948d
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java"
@@ -0,0 +1,62 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1011. 在 D 天内送达包裹的能力
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 在D天内送达包裹的能力 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(15, s.shipWithinDays(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 5));
+ Assertions.assertEquals(6, s.shipWithinDays(new int[] { 3, 2, 2, 4, 1, 4 }, 3));
+ Assertions.assertEquals(3, s.shipWithinDays(new int[] { 1, 2, 3, 1, 1 }, 4));
+ }
+
+ static class Solution {
+
+ public int shipWithinDays(int[] weights, int days) {
+ int max = 0, sum = 0;
+ for (int weight : weights) {
+ max = Math.max(max, weight);
+ sum += weight;
+ }
+
+ int left = max, right = sum;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (f(weights, mid) <= days) {
+ // 需要让 f(x) 的返回值大一些
+ right = mid - 1;
+ } else if (f(weights, mid) > days) {
+ // 需要让 f(x) 的返回值小一些
+ left = mid + 1;
+ }
+ }
+ return left;
+ }
+
+ // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物
+ // f(x) 随着 x 的增加单调递减
+ int f(int[] weights, int x) {
+ int days = 0;
+ for (int i = 0; i < weights.length; ) {
+ // 尽可能多装货物
+ int cap = x;
+ while (i < weights.length) {
+ if (cap < weights[i]) break;
+ else cap -= weights[i];
+ i++;
+ }
+ days++;
+ }
+ return days;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java"
new file mode 100644
index 0000000..fb14865
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java"
@@ -0,0 +1,79 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 34.在排序数组中查找元素的第一个和最后一个位置
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 在排序数组中查找元素的第一个和最后一个位置 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+
+ Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 3));
+ Assertions.assertEquals(0, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 5));
+ Assertions.assertEquals(5, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 10));
+ Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 12));
+ Assertions.assertEquals(1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 7));
+
+ Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 3));
+ Assertions.assertEquals(0, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 5));
+ Assertions.assertEquals(5, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 10));
+ Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 12));
+ Assertions.assertEquals(2, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 7));
+
+ Assertions.assertArrayEquals(new int[] { 3, 4 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 8));
+ Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 6));
+ Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] {}, 0));
+ Assertions.assertArrayEquals(new int[] { 0, 0 }, s.searchRange(new int[] { 1 }, 1));
+ }
+
+ static class Solution {
+
+ public int[] searchRange(int[] nums, int target) {
+ int left = searchLeft(nums, target);
+ int right = searchRight(nums, target);
+ return new int[] { left, right };
+ }
+
+ public int searchLeft(int[] nums, int target) {
+ int res = -1;
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] == target) {
+ res = mid;
+ right = mid - 1;
+ }
+ }
+ return res;
+ }
+
+ public int searchRight(int[] nums, int target) {
+ int res = -1;
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] == target) {
+ res = mid;
+ left = mid + 1;
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java"
new file mode 100644
index 0000000..bdc2bb9
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java"
@@ -0,0 +1,41 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 35. 搜索插入位置
+ *
+ * @author Zhang Peng
+ * @since 2020-07-29
+ */
+public class 搜索插入位置 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(0, s.searchInsert(new int[] { 1 }, 1));
+ Assertions.assertEquals(2, s.searchInsert(new int[] { 1, 3, 5, 6 }, 5));
+ Assertions.assertEquals(1, s.searchInsert(new int[] { 1, 3, 5, 6 }, 2));
+ Assertions.assertEquals(4, s.searchInsert(new int[] { 1, 3, 5, 6 }, 7));
+ Assertions.assertEquals(0, s.searchInsert(new int[] { 1, 3, 5, 6 }, 0));
+ }
+
+ static class Solution {
+
+ public int searchInsert(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] == target) {
+ return mid;
+ } else if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ }
+ }
+ return left;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java"
new file mode 100644
index 0000000..c7b5f17
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java"
@@ -0,0 +1,55 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 875. 爱吃香蕉的珂珂
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 爱吃香蕉的珂珂 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(4, s.minEatingSpeed(new int[] { 3, 6, 7, 11 }, 8));
+ Assertions.assertEquals(30, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 5));
+ Assertions.assertEquals(23, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 6));
+ Assertions.assertEquals(2, s.minEatingSpeed(new int[] { 312884470 }, 312884469));
+ Assertions.assertEquals(3, s.minEatingSpeed(new int[] { 805306368, 805306368, 805306368 }, 1000000000));
+ Assertions.assertEquals(14, s.minEatingSpeed(
+ new int[] { 332484035, 524908576, 855865114, 632922376, 222257295, 690155293, 112677673, 679580077,
+ 337406589, 290818316, 877337160, 901728858, 679284947, 688210097, 692137887, 718203285, 629455728,
+ 941802184 }, 823855818));
+ }
+
+ static class Solution {
+
+ public int minEatingSpeed(int[] piles, int h) {
+ int left = 1, right = 1_000_000_000;
+
+ // right 是闭区间,所以这里改成 <=
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (f(piles, mid) <= h) {
+ // right 是闭区间,所以这里用 mid - 1
+ right = mid - 1;
+ } else if (f(piles, mid) > h) {
+ left = mid + 1;
+ }
+ }
+ return left;
+ }
+
+ long f(int[] nums, int x) {
+ long h = 0;
+ for (int num : nums) {
+ h += num / x;
+ if (num % x > 0) { h++; }
+ }
+ return h;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java"
new file mode 100644
index 0000000..c0266ae
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java"
@@ -0,0 +1,52 @@
+package io.github.dunwu.algorithm.array.bsearch;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * LCR 172. 统计目标成绩的出现次数
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 统计目标成绩的出现次数 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(3, s.countTarget(new int[] { 2, 2, 3, 4, 4, 4, 5, 6, 6, 8 }, 4));
+ Assertions.assertEquals(0, s.countTarget(new int[] { 1, 2, 3, 5, 7, 9 }, 6));
+ }
+
+ static class Solution {
+
+ public int countTarget(int[] scores, int target) {
+ int leftBound = searchLeft(scores, target);
+ if (leftBound == -1) { return 0; }
+ int cnt = 1;
+ for (int i = leftBound + 1; i < scores.length; i++) {
+ if (scores[i] == target) {
+ cnt++;
+ }
+ }
+ return cnt;
+ }
+
+ public int searchLeft(int[] nums, int target) {
+ int res = -1;
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] == target) {
+ right = mid - 1;
+ res = mid;
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java"
new file mode 100644
index 0000000..1787652
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java"
@@ -0,0 +1,104 @@
+package io.github.dunwu.algorithm.array.demo;
+
+import java.util.Arrays;
+
+/**
+ * 1) 数组的插入、删除、按照下标随机访问操作; 2)数组中的数据是int类型的;
+ *
+ * Author: Zheng modify: xing, Gsealy
+ */
+public class 模拟ArrayList1 {
+
+ //定义整型数据data保存数据
+ public int data[];
+ //定义数组长度
+ private int n;
+ //定义中实际个数
+ private int count;
+
+ //构造方法,定义数组大小
+ public 模拟ArrayList1(int capacity) {
+ this.data = new int[capacity];
+ this.n = capacity;
+ this.count = 0;//一开始一个数都没有存所以为0
+ }
+
+ //根据索引,找到数据中的元素并返回
+ public int find(int index) {
+ if (index < 0 || index >= count) return -1;
+ return data[index];
+ }
+
+ //插入元素:头部插入,尾部插入
+ public boolean insert(int index, int value) {
+ //数组中无元素
+
+ //if (index == count && count == 0) {
+ // data[index] = value;
+ // ++count;
+ // return true;
+ //}
+
+ // 数组空间已满
+ if (count == n) {
+ System.out.println("动态扩容");
+ data = Arrays.copyOf(data, n << 1);
+ }
+ // 如果count还没满,那么就可以插入数据到数组中
+ // 位置不合法
+ if (index < 0 || index > count) {
+ System.out.println("位置不合法");
+ return false;
+ }
+ // 位置合法
+ for (int i = count; i > index; --i) {
+ data[i] = data[i - 1];
+ }
+ data[index] = value;
+ ++count;
+ return true;
+ }
+
+ //根据索引,删除数组中元素
+ public boolean delete(int index) {
+ if (index < 0 || index >= count) return false;
+ //从删除位置开始,将后面的元素向前移动一位
+ for (int i = index + 1; i < count; ++i) {
+ data[i - 1] = data[i];
+ }
+ //删除数组末尾元素 这段代码不需要也可以
+ /*int[] arr = new int[count-1];
+ for (int i=0; i {
+
+ private T[] data;
+ private int size;
+
+ // 根据传入容量,构造Array
+ public 模拟ArrayList2(int capacity) {
+ data = (T[]) new Object[capacity];
+ size = 0;
+ }
+
+ // 无参构造方法,默认数组容量为10
+ public 模拟ArrayList2() {
+ this(10);
+ }
+
+ // 获取数组容量
+ public int getCapacity() {
+ return data.length;
+ }
+
+ // 获取当前元素个数
+ public int count() {
+ return size;
+ }
+
+ // 判断数组是否为空
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ // 修改 index 位置的元素
+ public void set(int index, T e) {
+ checkIndex(index);
+ data[index] = e;
+ }
+
+ // 获取对应 index 位置的元素
+ public T get(int index) {
+ checkIndex(index);
+ return data[index];
+ }
+
+ // 查看数组是否包含元素e
+ public boolean contains(T e) {
+ for (int i = 0; i < size; i++) {
+ if (data[i].equals(e)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 获取对应元素的下标, 未找到,返回 -1
+ public int find(T e) {
+ for (int i = 0; i < size; i++) {
+ if (data[i].equals(e)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ // 在 index 位置,插入元素e, 时间复杂度 O(m+n)
+ public void add(int index, T e) {
+ checkIndexForAdd(index);
+ // 如果当前元素个数等于数组容量,则将数组扩容为原来的2倍
+ if (size == data.length) {
+ resize(2 * data.length);
+ }
+
+ for (int i = size - 1; i >= index; i--) {
+ data[i + 1] = data[i];
+ }
+ data[index] = e;
+ size++;
+ }
+
+ // 向数组头插入元素
+ public void addFirst(T e) {
+ add(0, e);
+ }
+
+ // 向数组尾插入元素
+ public void addLast(T e) {
+ add(size, e);
+ }
+
+ // 删除 index 位置的元素,并返回
+ public T remove(int index) {
+ checkIndex(index);
+
+ T ret = data[index];
+ for (int i = index + 1; i < size; i++) {
+ data[i - 1] = data[i];
+ }
+ size--;
+ data[size] = null;
+
+ // 缩容
+ if (size == data.length / 4 && data.length / 2 != 0) {
+ resize(data.length / 2);
+ }
+
+ return ret;
+ }
+
+ // 删除第一个元素
+ public T removeFirst() {
+ return remove(0);
+ }
+
+ // 删除末尾元素
+ public T removeLast() {
+ return remove(size - 1);
+ }
+
+ // 从数组中删除指定元素
+ public void removeElement(T e) {
+ int index = find(e);
+ if (index != -1) {
+ remove(index);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length));
+ builder.append('[');
+ for (int i = 0; i < size; i++) {
+ builder.append(data[i]);
+ if (i != size - 1) {
+ builder.append(", ");
+ }
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
+ // 扩容方法,时间复杂度 O(n)
+ private void resize(int capacity) {
+ T[] newData = (T[]) new Object[capacity];
+
+ for (int i = 0; i < size; i++) {
+ newData[i] = data[i];
+ }
+ data = newData;
+ }
+
+ private void checkIndex(int index) {
+ if (index < 0 || index >= size) {
+ throw new IllegalArgumentException("Add failed! Require index >=0 and index < size.");
+ }
+ }
+
+ private void checkIndexForAdd(int index) {
+ if (index < 0 || index > size) {
+ throw new IllegalArgumentException("remove failed! Require index >=0 and index <= size.");
+ }
+ }
+
+ public void printAll() {
+ for (int i = 0; i < this.size; ++i) {
+ System.out.print(data[i] + " ");
+ }
+ System.out.println();
+ }
+
+ public static void main(String[] args) {
+ 模拟ArrayList2 array = new 模拟ArrayList2<>(5);
+ array.printAll();
+ array.add(0, 3);
+ array.printAll();
+ array.add(0, 4);
+ array.printAll();
+ array.add(1, 5);
+ array.printAll();
+ array.add(3, 9);
+ array.printAll();
+ array.add(3, 10);
+ array.printAll();
+ // array.add(0, 3);
+ // array.printAll();
+ array.resize(10);
+ array.add(0, 3);
+ array.printAll();
+ array.remove(array.count() - 1);
+ array.printAll();
+ array.remove(0);
+ array.printAll();
+ array.removeElement(4);
+ array.printAll();
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java"
new file mode 100644
index 0000000..2a10b4a
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java"
@@ -0,0 +1,65 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 151. 反转字符串中的单词
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 反转字符串中的单词 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals("blue is sky the", s.reverseWords("the sky is blue"));
+ Assertions.assertEquals("world hello", s.reverseWords(" hello world "));
+ Assertions.assertEquals("example good a", s.reverseWords("a good example"));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertEquals("blue is sky the", s2.reverseWords("the sky is blue"));
+ Assertions.assertEquals("world hello", s2.reverseWords(" hello world "));
+ Assertions.assertEquals("example good a", s2.reverseWords("a good example"));
+ }
+
+ // 利用库函数
+ static class Solution {
+
+ public String reverseWords(String s) {
+ String[] arr = s.trim().split(" ");
+ StringBuilder sb = new StringBuilder();
+ for (int i = arr.length - 1; i >= 0; i--) {
+ if (arr[i].equals("")) {
+ continue;
+ }
+ sb.append(arr[i]).append(" ");
+ }
+ return sb.toString().trim();
+ }
+
+ }
+
+ // 双指针
+ static class Solution2 {
+
+ public String reverseWords(String s) {
+ // 删除首尾空格
+ s = s.trim();
+ int l = s.length() - 1, r = l;
+ StringBuilder res = new StringBuilder();
+ while (l >= 0) {
+ // 左指针偏移,直到遇到空格
+ while (l >= 0 && s.charAt(l) != ' ') { l--; }
+ // 添加单词
+ res.append(s.substring(l + 1, r + 1)).append(' ');
+ // 左指针偏移,直到遇到非空格
+ while (l >= 0 && s.charAt(l) == ' ') { l--; }
+ // 右指针对齐左指针
+ r = l;
+ }
+ return res.toString().trim();
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java"
new file mode 100644
index 0000000..9d3c672
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java"
@@ -0,0 +1,64 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 498. 对角线遍历
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 对角线遍历 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] input = { { 1, 2 }, { 3, 4 } };
+ int[] expect = { 1, 2, 3, 4 };
+
+ int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
+ int[] expect2 = { 1, 2, 4, 7, 5, 3, 6, 8, 9 };
+
+ int[][] input3 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } };
+ int[] expect3 = { 1, 2, 5, 9, 6, 3, 4, 7, 10, 13, 14, 11, 8, 12, 15, 16 };
+
+ Assertions.assertArrayEquals(expect, s.findDiagonalOrder(input));
+ Assertions.assertArrayEquals(expect2, s.findDiagonalOrder(input2));
+ Assertions.assertArrayEquals(expect3, s.findDiagonalOrder(input3));
+ }
+
+ static class Solution {
+
+ // 1. 同一对角线上的元素,满足 i + j = k
+ // 2. k 的大小,满足递增,从 0 到 m + n - 2
+ // 3. 由于,i + j = k -> i = k - j
+ // i = m - 1 时最大,j 最小;而 k - (m - 1) 必须大于 0 => minJ = max(0, k - (m - 1))
+ // i = 0 时最小,j 最大,但不能超过 n - 1 => maxJ = Math.max(k, n -1)
+ public int[] findDiagonalOrder(int[][] mat) {
+
+ // base case
+ if (mat == null || mat.length == 0) { return new int[0]; }
+
+ int idx = 0;
+ int m = mat.length, n = mat[0].length;
+ int[] res = new int[m * n];
+ for (int k = 0; k < m + n - 1; k++) {
+ int minJ = Math.max(k - (m - 1), 0);
+ int maxJ = Math.min(k, n - 1);
+ if (k % 2 == 0) {
+ for (int j = minJ; j <= maxJ; j++) {
+ res[idx++] = mat[k - j][j];
+ }
+ } else {
+ for (int j = maxJ; j >= minJ; j--) {
+ res[idx++] = mat[k - j][j];
+ }
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java"
new file mode 100644
index 0000000..b4bdd6d
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java"
@@ -0,0 +1,55 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import cn.hutool.core.util.ArrayUtil;
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * LCR 006. 两数之和 II - 输入有序数组
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 旋转图像 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[][] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
+ s.rotate(matrix);
+ int[][] expect = { { 7, 4, 1 }, { 8, 5, 2 }, { 9, 6, 3 } };
+ Assertions.assertTrue(ArrayUtil.equals(expect, matrix));
+
+ int[][] matrix2 = { { 5, 1, 9, 11 }, { 2, 4, 8, 10 }, { 13, 3, 6, 7 }, { 15, 14, 12, 16 } };
+ s.rotate(matrix2);
+ int[][] expect2 = { { 15, 13, 2, 5 }, { 14, 3, 4, 1 }, { 12, 6, 8, 9 }, { 16, 7, 10, 11 } };
+ Assertions.assertTrue(ArrayUtil.equals(expect2, matrix2));
+ }
+
+ static class Solution {
+
+ public void rotate(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return; }
+ int n = matrix.length;
+ // 沿对角线置换
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
+ int temp = matrix[i][j];
+ matrix[i][j] = matrix[j][i];
+ matrix[j][i] = temp;
+ }
+ }
+
+ for (int i = 0; i < n; i++) {
+ int left = 0, right = n - 1;
+ while (left < right) {
+ int temp = matrix[i][left];
+ matrix[i][left] = matrix[i][right];
+ matrix[i][right] = temp;
+ left++;
+ right--;
+ }
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java"
new file mode 100644
index 0000000..e0085df
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java"
@@ -0,0 +1,74 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 54. 螺旋矩阵
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 螺旋矩阵 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ List output = s.spiralOrder(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } });
+ Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output.toArray());
+ List output2 = s.spiralOrder(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } });
+ Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2.toArray());
+ }
+
+ static class Solution {
+
+ public List spiralOrder(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) {
+ return new LinkedList<>();
+ }
+
+ int m = matrix.length, n = matrix[0].length;
+ int up = 0, down = m - 1, left = 0, right = n - 1;
+ List res = new LinkedList<>();
+ while (res.size() < m * n) {
+ // 向右
+ if (up <= down) {
+ for (int i = left; i <= right; i++) {
+ res.add(matrix[up][i]);
+ }
+ up++;
+ }
+ // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向下
+ if (left <= right) {
+ for (int i = up; i <= down; i++) {
+ res.add(matrix[i][right]);
+ }
+ right--;
+ }
+ // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向左
+ if (up <= down) {
+ for (int i = right; i >= left; i--) {
+ res.add(matrix[down][i]);
+ }
+ down--;
+ }
+ // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向上
+ if (left <= right) {
+ for (int i = down; i >= up; i--) {
+ res.add(matrix[i][left]);
+ }
+ left++;
+ }
+ // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res));
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java"
new file mode 100644
index 0000000..785f236
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java"
@@ -0,0 +1,66 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 54. 螺旋矩阵
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 螺旋矩阵2 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[][] output = s.generateMatrix(3);
+ Assertions.assertArrayEquals(new int[][] { { 1, 2, 3 }, { 8, 9, 4 }, { 7, 6, 5 } }, output);
+ int[][] output2 = s.generateMatrix(1);
+ Assertions.assertArrayEquals(new int[][] { { 1 } }, output2);
+ }
+
+ static class Solution {
+
+ public int[][] generateMatrix(int n) {
+ int cnt = 0;
+ int[][] res = new int[n][n];
+ int left = 0, right = n - 1, top = 0, bottom = n - 1;
+ while (cnt < n * n) {
+
+ // 向右
+ if (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ res[top][i] = ++cnt;
+ }
+ top++;
+ }
+
+ // 向下
+ if (left <= right) {
+ for (int i = top; i <= bottom; i++) {
+ res[i][right] = ++cnt;
+ }
+ right--;
+ }
+
+ // 向左
+ if (top <= bottom) {
+ for (int i = right; i >= left; i--) {
+ res[bottom][i] = ++cnt;
+ }
+ bottom--;
+ }
+
+ // 向上
+ if (left <= right) {
+ for (int i = bottom; i >= top; i--) {
+ res[i][left] = ++cnt;
+ }
+ left++;
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java"
new file mode 100644
index 0000000..c39ed1a
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java"
@@ -0,0 +1,84 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 54. 螺旋矩阵
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 螺旋遍历二维数组 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[] output = s.spiralArray(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } });
+ Assertions.assertArrayEquals(new int[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output);
+ int[] output2 = s.spiralArray(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } });
+ Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2);
+ }
+
+ static class Solution {
+
+ public int[] spiralArray(int[][] array) {
+ List list = spiralOrder(array);
+ int[] res = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ res[i] = list.get(i);
+ }
+ return res;
+ }
+
+ public List spiralOrder(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) {
+ return new LinkedList<>();
+ }
+
+ int m = matrix.length, n = matrix[0].length;
+ int up = 0, down = m - 1;
+ int left = 0, right = n - 1;
+ List res = new LinkedList<>();
+ while (res.size() < m * n) {
+ // 向右
+ if (up <= down) {
+ for (int i = left; i <= right; i++) {
+ res.add(matrix[up][i]);
+ }
+ up++;
+ }
+ // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向下
+ if (left <= right) {
+ for (int i = up; i <= down; i++) {
+ res.add(matrix[i][right]);
+ }
+ right--;
+ }
+ // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向左
+ if (up <= down) {
+ for (int i = right; i >= left; i--) {
+ res.add(matrix[down][i]);
+ }
+ down--;
+ }
+ // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // 向上
+ if (left <= right) {
+ for (int i = down; i >= up; i--) {
+ res.add(matrix[i][left]);
+ }
+ left++;
+ }
+ // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right);
+ // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res));
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java"
new file mode 100644
index 0000000..6c7d535
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java"
@@ -0,0 +1,54 @@
+package io.github.dunwu.algorithm.array.matrix;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.LinkedList;
+
+/**
+ * 08. 零矩阵
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 零矩阵 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[][] input = {
+ { 1, 1, 1 },
+ { 1, 0, 1 },
+ { 1, 1, 1 }
+ };
+ int[][] expect = {
+ { 1, 0, 1 },
+ { 0, 0, 0 },
+ { 1, 0, 1 }
+ };
+ s.setZeroes(input);
+ Assertions.assertArrayEquals(expect, input);
+ }
+
+ static class Solution {
+
+ public void setZeroes(int[][] matrix) {
+ int m = matrix.length, n = matrix[0].length;
+ LinkedList queue = new LinkedList<>();
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ if (matrix[i][j] == 0) {
+ queue.offer(new int[] { i, j });
+ }
+ }
+ }
+
+ while (!queue.isEmpty()) {
+ int[] point = queue.poll();
+ int x = point[0], y = point[1];
+ for (int i = 0; i < n; i++) { matrix[x][i] = 0; }
+ for (int i = 0; i < m; i++) { matrix[i][y] = 0; }
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java"
new file mode 100644
index 0000000..61ece6f
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java"
@@ -0,0 +1,52 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 303. 区域和检索 - 数组不可变
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 二维区域和检索_矩阵不可变 {
+
+ public static void main(String[] args) {
+ NumMatrix numMatrix = new NumMatrix(new int[][] {
+ { 3, 0, 1, 4, 2 },
+ { 5, 6, 3, 2, 1 },
+ { 1, 2, 0, 1, 5 },
+ { 4, 1, 0, 1, 7 },
+ { 1, 0, 3, 0, 5 }
+ });
+ Assertions.assertEquals(8, numMatrix.sumRegion(2, 1, 4, 3));
+ Assertions.assertEquals(11, numMatrix.sumRegion(1, 1, 2, 2));
+ Assertions.assertEquals(12, numMatrix.sumRegion(1, 2, 2, 4));
+ }
+
+ static class NumMatrix {
+
+ // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和
+ private int[][] preSum;
+
+ public NumMatrix(int[][] matrix) {
+ int m = matrix.length, n = matrix[0].length;
+ if (m == 0 || n == 0) return;
+ // 构造前缀和矩阵
+ preSum = new int[m + 1][n + 1];
+ for (int i = 1; i <= m; i++) {
+ for (int j = 1; j <= n; j++) {
+ // 计算每个矩阵 [0, 0, i, j] 的元素和
+ preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1];
+ }
+ }
+ }
+
+ // 计算子矩阵 [x1, y1, x2, y2] 的元素和
+ public int sumRegion(int x1, int y1, int x2, int y2) {
+ // 目标矩阵之和由四个相邻矩阵运算获得
+ return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java"
new file mode 100644
index 0000000..276664c
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 303. 区域和检索 - 数组不可变
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 区域和检索_数组不可变 {
+
+ public static void main(String[] args) {
+ NumArray numArray = new NumArray(new int[] { -2, 0, 3, -5, 2, -1 });
+ Assertions.assertEquals(1, numArray.sumRange(0, 2));
+ Assertions.assertEquals(-1, numArray.sumRange(2, 5));
+ Assertions.assertEquals(-3, numArray.sumRange(0, 5));
+ }
+
+ static class NumArray {
+
+ private int[] preSum;
+
+ public NumArray(int[] nums) {
+ preSum = new int[nums.length + 1];
+ for (int i = 1; i <= nums.length; i++) {
+ preSum[i] = preSum[i - 1] + nums[i - 1];
+ }
+ }
+
+ public int sumRange(int left, int right) {
+ return preSum[right + 1] - preSum[left];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java"
new file mode 100644
index 0000000..c7d8c6b
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java"
@@ -0,0 +1,66 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 724. 寻找数组的中心索引
+ *
+ * @author Zhang Peng
+ * @since 2018-11-04
+ */
+public class 寻找数组的中心索引 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(3, s.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 }));
+ Assertions.assertEquals(-1, s.pivotIndex(new int[] { 1, 2, 3 }));
+ Assertions.assertEquals(0, s.pivotIndex(new int[] { 2, 1, -1 }));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertEquals(3, s2.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 }));
+ Assertions.assertEquals(-1, s2.pivotIndex(new int[] { 1, 2, 3 }));
+ Assertions.assertEquals(0, s2.pivotIndex(new int[] { 2, 1, -1 }));
+ }
+
+ static class Solution {
+
+ public int pivotIndex(int[] nums) {
+ for (int mid = 0; mid < nums.length; mid++) {
+ int leftSum = 0, rightSum = 0;
+ for (int i = 0; i < mid; i++) {
+ leftSum += nums[i];
+ }
+ for (int i = mid + 1; i < nums.length; i++) {
+ rightSum += nums[i];
+ }
+ if (leftSum == rightSum) {
+ return mid;
+ }
+ }
+ return -1;
+ }
+
+ }
+
+ static class Solution2 {
+
+ public int pivotIndex(int[] nums) {
+ int total = 0;
+ for (int num : nums) {
+ total += num;
+ }
+
+ int leftSum = 0;
+ for (int i = 0; i < nums.length; i++) {
+ int rightSum = total - leftSum - nums[i];
+ if (leftSum == rightSum) {
+ return i;
+ }
+ leftSum += nums[i];
+ }
+ return -1;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java"
new file mode 100644
index 0000000..43dca82
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java"
@@ -0,0 +1,58 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1013.将数组分成和相等的三个部分
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 将数组分成和相等的三个部分 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1 }));
+ Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 3, 3, 6, 5, -2, 2, 5, 1, -9, 4 }));
+ Assertions.assertFalse(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, 7, 9, -1, 2, 0, 1 }));
+ }
+
+ static class Solution {
+
+ public boolean canThreePartsEqualSum(int[] arr) {
+ int n = arr.length;
+ NumArray preSum = new NumArray(arr);
+ for (int i = 1; i < n; i++) {
+ for (int j = i + 1; j < n; j++) {
+ int leftSum = preSum.sumRange(0, i - 1);
+ int midSum = preSum.sumRange(i, j - 1);
+ int rightSum = preSum.sumRange(j, n - 1);
+ if (leftSum == midSum && midSum == rightSum) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static class NumArray {
+
+ private final int[] preSum;
+
+ public NumArray(int[] arr) {
+ preSum = new int[arr.length + 1];
+ preSum[0] = 0;
+ for (int i = 1; i <= arr.length; i++) {
+ preSum[i] = preSum[i - 1] + arr[i - 1];
+ }
+ }
+
+ public int sumRange(int left, int right) {
+ return preSum[right + 1] - preSum[left];
+ }
+
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java"
new file mode 100644
index 0000000..6ea1646
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java"
@@ -0,0 +1,94 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1094. 屁车
+ *
+ * @author Zhang Peng
+ * @date 2025-10-17
+ */
+public class 拼车 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[][] input = { { 2, 1, 5 }, { 3, 3, 7 } };
+ Assertions.assertFalse(s.carPooling(input, 3));
+ int[][] input2 = { { 1, 2, 10 }, { 2, 2, 15 } };
+ Assertions.assertTrue(s.carPooling(input2, 5));
+ int[][] input3 = { { 2, 1, 5 }, { 3, 5, 7 } };
+ Assertions.assertTrue(s.carPooling(input3, 3));
+ }
+
+ static class Solution {
+
+ public boolean carPooling(int[][] trips, int capacity) {
+ // 最多有 1000 个车站
+ int[] nums = new int[1001];
+ // 构造差分解法
+ Difference df = new Difference(nums);
+
+ for (int[] trip : trips) {
+ // 乘客数量
+ int val = trip[0];
+ // 第 trip[1] 站乘客上车
+ int i = trip[1];
+ // 第 trip[2] 站乘客已经下车,
+ // 即乘客在车上的区间是 [trip[1], trip[2] - 1]
+ int j = trip[2] - 1;
+ // 进行区间操作
+ df.increment(i, j, val);
+ }
+
+ int[] res = df.result();
+
+ // 客车自始至终都不应该超载
+ for (int i = 0; i < res.length; i++) {
+ if (capacity < res[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // 差分数组工具类
+ class Difference {
+
+ // 差分数组
+ private int[] diff;
+
+ // 输入一个初始数组,区间操作将在这个数组上进行
+ public Difference(int[] nums) {
+ assert nums.length > 0;
+ diff = new int[nums.length];
+ // 根据初始数组构造差分数组
+ diff[0] = nums[0];
+ for (int i = 1; i < nums.length; i++) {
+ diff[i] = nums[i] - nums[i - 1];
+ }
+ }
+
+ // 给闭区间 [i, j] 增加 val(可以是负数)
+ public void increment(int i, int j, int val) {
+ diff[i] += val;
+ if (j + 1 < diff.length) {
+ diff[j + 1] -= val;
+ }
+ }
+
+ // 返回结果数组
+ public int[] result() {
+ int[] res = new int[diff.length];
+ // 根据差分数组构造结果数组
+ res[0] = diff[0];
+ for (int i = 1; i < diff.length; i++) {
+ res[i] = res[i - 1] + diff[i];
+ }
+ return res;
+ }
+
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java"
new file mode 100644
index 0000000..bb51080
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java"
@@ -0,0 +1,76 @@
+package io.github.dunwu.algorithm.array.range;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1109. 航班预订统计
+ *
+ * @author Zhang Peng
+ * @date 2025-10-17
+ */
+public class 航班预订统计 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] bookings = { { 1, 2, 10 }, { 2, 3, 20 }, { 2, 5, 25 } };
+ Assertions.assertArrayEquals(new int[] { 10, 55, 45, 25, 25 }, s.corpFlightBookings(bookings, 5));
+
+ int[][] bookings2 = { { 1, 2, 10 }, { 2, 2, 15 } };
+ Assertions.assertArrayEquals(new int[] { 10, 25 }, s.corpFlightBookings(bookings2, 2));
+ }
+
+ static class Solution {
+
+ public int[] corpFlightBookings(int[][] bookings, int n) {
+ int[] nums = new int[n];
+ Difference df = new Difference(nums);
+ for (int[] booking : bookings) {
+ int first = booking[0], last = booking[1], seat = booking[2];
+ df.increment(first - 1, last - 1, seat);
+ }
+ return df.result();
+ }
+
+ // 差分数组工具类
+ static class Difference {
+
+ // 差分数组
+ private final int[] diff;
+
+ // 输入一个初始数组,区间操作将在这个数组上进行
+ public Difference(int[] nums) {
+ assert nums.length > 0;
+ diff = new int[nums.length];
+ // 根据初始数组构造差分数组
+ diff[0] = nums[0];
+ for (int i = 1; i < nums.length; i++) {
+ diff[i] = nums[i] - nums[i - 1];
+ }
+ }
+
+ // 给闭区间 [i, j] 增加 val(可以是负数)
+ public void increment(int i, int j, int val) {
+ diff[i] += val;
+ if (j + 1 < diff.length) {
+ diff[j + 1] -= val;
+ }
+ }
+
+ // 返回结果数组
+ public int[] result() {
+ int[] res = new int[diff.length];
+ // 根据差分数组构造结果数组
+ res[0] = diff[0];
+ for (int i = 1; i < diff.length; i++) {
+ res[i] = res[i - 1] + diff[i];
+ }
+ return res;
+ }
+
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java"
new file mode 100644
index 0000000..7f21032
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java"
@@ -0,0 +1,70 @@
+package io.github.dunwu.algorithm.array.template;
+
+/**
+ * 二分查找模板
+ *
+ * @author Zhang Peng
+ * @date 2025-12-08
+ */
+public class 二分查找模板 {
+
+ int binary_search(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] == target) {
+ // 直接返回
+ return mid;
+ }
+ }
+ // 直接返回
+ return -1;
+ }
+
+ int left_bound(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] == target) {
+ // 别返回,锁定左侧边界
+ right = mid - 1;
+ }
+ }
+ // 判断 target 是否存在于 nums 中
+ if (left < 0 || left >= nums.length) {
+ return -1;
+ }
+ // 判断一下 nums[left] 是不是 target
+ return nums[left] == target ? left : -1;
+ }
+
+ int right_bound(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ if (nums[mid] < target) {
+ left = mid + 1;
+ } else if (nums[mid] > target) {
+ right = mid - 1;
+ } else if (nums[mid] == target) {
+ // 别返回,锁定右侧边界
+ left = mid + 1;
+ }
+ }
+ // 由于 while 的结束条件是 right == left - 1,且现在在求右边界
+ // 所以用 right 替代 left - 1 更好记
+ if (right < 0 || right >= nums.length) {
+ return -1;
+ }
+ return nums[right] == target ? right : -1;
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java"
new file mode 100644
index 0000000..7c37f55
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java"
@@ -0,0 +1,65 @@
+package io.github.dunwu.algorithm.array.template;
+
+/**
+ * 前缀和数组代码模板
+ *
+ * @author Zhang Peng
+ * @date 2025-10-20
+ */
+public class 前缀和数组代码模板 {
+
+ /**
+ * 一维前缀和
+ */
+ static class NumArray {
+
+ // 前缀和数组
+ private final int[] preSum;
+
+ // 输入一个数组,构造前缀和
+ public NumArray(int[] nums) {
+ // preSum[0] = 0,便于计算累加和
+ preSum = new int[nums.length + 1];
+ // 计算 nums 的累加和
+ for (int i = 1; i < preSum.length; i++) {
+ preSum[i] = preSum[i - 1] + nums[i - 1];
+ }
+ }
+
+ // 查询闭区间 [left, right] 的累加和
+ public int sumRange(int left, int right) {
+ return preSum[right + 1] - preSum[left];
+ }
+
+ }
+
+ /**
+ * 二维前缀和
+ */
+ static class NumMatrix {
+
+ // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和
+ private int[][] preSum;
+
+ public NumMatrix(int[][] matrix) {
+ int m = matrix.length, n = matrix[0].length;
+ if (m == 0 || n == 0) return;
+ // 构造前缀和矩阵
+ preSum = new int[m + 1][n + 1];
+ for (int i = 1; i <= m; i++) {
+ for (int j = 1; j <= n; j++) {
+ // 计算每个矩阵 [0, 0, i, j] 的元素和
+ preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1];
+ }
+ }
+ }
+
+ // 计算子矩阵 [x1, y1, x2, y2] 的元素和
+ public int sumRegion(int x1, int y1, int x2, int y2) {
+ // 目标矩阵之和由四个相邻矩阵运算获得
+ return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java"
new file mode 100644
index 0000000..bb6a01c
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java"
@@ -0,0 +1,49 @@
+package io.github.dunwu.algorithm.array.template;
+
+/**
+ * 差分数组代码模板
+ *
+ * @author Zhang Peng
+ * @date 2025-10-20
+ */
+public class 差分数组代码模板 {
+
+ // 差分数组工具类
+ static class Difference {
+
+ // 差分数组
+ private final int[] diff;
+
+ // 输入一个初始数组,区间操作将在这个数组上进行
+ public Difference(int[] nums) {
+ assert nums.length > 0;
+ diff = new int[nums.length];
+ // 根据初始数组构造差分数组
+ diff[0] = nums[0];
+ for (int i = 1; i < nums.length; i++) {
+ diff[i] = nums[i] - nums[i - 1];
+ }
+ }
+
+ // 给闭区间 [i, j] 增加 val(可以是负数)
+ public void increment(int i, int j, int val) {
+ diff[i] += val;
+ if (j + 1 < diff.length) {
+ diff[j + 1] -= val;
+ }
+ }
+
+ // 返回结果数组
+ public int[] result() {
+ int[] res = new int[diff.length];
+ // 根据差分数组构造结果数组
+ res[0] = diff[0];
+ for (int i = 1; i < diff.length; i++) {
+ res[i] = res[i - 1] + diff[i];
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java"
new file mode 100644
index 0000000..66a46d4
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java"
@@ -0,0 +1,46 @@
+package io.github.dunwu.algorithm.array.template;
+
+/**
+ * 滑动窗口模板
+ *
+ * @author Zhang Peng
+ * @date 2025-11-20
+ */
+public class 滑动窗口模板 {
+
+ // 滑动窗口算法伪码框架
+ // void slidingWindow(String s) {
+ // // 用合适的数据结构记录窗口中的数据,根据具体场景变通
+ // // 比如说,我想记录窗口中元素出现的次数,就用 map
+ // // 如果我想记录窗口中的元素和,就可以只用一个 int
+ // Object window = ...
+ //
+ // int left = 0, right = 0;
+ // while (right < s.length()) {
+ // // c 是将移入窗口的字符
+ // char c = s[right];
+ // window.add(c)
+ // // 增大窗口
+ // right++;
+ // // 进行窗口内数据的一系列更新
+ // // ...
+ //
+ // // *** debug 输出的位置 ***
+ // // 注意在最终的解法代码中不要 print
+ // // 因为 IO 操作很耗时,可能导致超时
+ // printf("window: [%d, %d)\n", left, right);
+ // // ***********************
+ //
+ // // 判断左侧窗口是否要收缩
+ // while (left < right && window needs shrink){
+ // // d 是将移出窗口的字符
+ // char d = s[left];
+ // window.remove(d)
+ // // 缩小窗口
+ // left++;
+ // // 进行窗口内数据的一系列更新
+ // ...
+ // }
+ // }
+ // }
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java"
new file mode 100644
index 0000000..bb147b7
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java"
@@ -0,0 +1,66 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * 三数之和
+ *
+ * @author Zhang Peng
+ * @since 2020-01-18
+ */
+public class 三数之和 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ List> input = s.threeSum(new int[] { -1, 0, 1, 2, -1, -4 });
+ List> expect = new ArrayList<>();
+ expect.add(Arrays.asList(-1, -1, 2));
+ expect.add(Arrays.asList(-1, 0, 1));
+ Assertions.assertArrayEquals(expect.toArray(), input.toArray());
+ }
+
+ static class Solution {
+
+ public List> threeSum(int[] nums) {
+ if (nums == null || nums.length < 3) { return new ArrayList<>(); }
+
+ // 数组排序
+ Arrays.sort(nums);
+
+ List> res = new ArrayList<>();
+ for (int i = 0; i < nums.length; i++) {
+
+ // 跳过重复元素
+ if (i > 0 && nums[i] == nums[i - 1]) { continue; }
+
+ // 双指针,目标是找到 nums[l] + nums[r] = -nums[i]
+ int target = -nums[i];
+ int l = i + 1, r = nums.length - 1;
+
+ while (l < r) {
+ int sum = nums[l] + nums[r];
+ if (sum == target) {
+ res.add(Arrays.asList(nums[i], nums[l], nums[r]));
+ l++;
+ r--;
+ // 跳过重复元素
+ while (l < r && nums[l] == nums[l - 1]) l++;
+ while (l < r && nums[r] == nums[r + 1]) r--;
+ } else if (sum > target) {
+ r--;
+ } else if (sum < target) {
+ l++;
+ }
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java"
new file mode 100644
index 0000000..27e751f
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java"
@@ -0,0 +1,67 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 1. 两数之和
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 两数之和 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9));
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 3, 2, 4 }, 6));
+ Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 3, 3 }, 6));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9));
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 3, 2, 4 }, 6));
+ Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 3, 3 }, 6));
+ }
+
+ /**
+ * 两次 for 循环暴力求解,时间复杂度 o(n^2)
+ */
+ static class Solution {
+
+ public int[] twoSum(int[] nums, int target) {
+ int n = nums.length;
+ for (int i = 0; i < n; i++) {
+ for (int j = i + 1; j < n; j++) {
+ if (nums[i] + nums[j] == target) {
+ return new int[] { i, j };
+ }
+ }
+ }
+ return new int[0];
+ }
+
+ }
+
+ /**
+ * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target
+ */
+ static class Solution2 {
+
+ public int[] twoSum(int[] nums, int target) {
+ Map map = new HashMap<>(nums.length);
+ for (int i = 0; i < nums.length; i++) {
+ int diff = target - nums[i];
+ if (map.containsKey(diff)) {
+ return new int[] { map.get(diff), i };
+ } else {
+ map.put(nums[i], i);
+ }
+ }
+ return new int[0];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java"
new file mode 100644
index 0000000..2585dda
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java"
@@ -0,0 +1,69 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 167. 两数之和 II - 输入有序数组
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 两数之和2 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9));
+ Assertions.assertArrayEquals(new int[] { 1, 3 }, s.twoSum(new int[] { 2, 3, 4 }, 6));
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { -1, 0 }, -1));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9));
+ Assertions.assertArrayEquals(new int[] { 1, 3 }, s2.twoSum(new int[] { 2, 3, 4 }, 6));
+ Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { -1, 0 }, -1));
+ }
+
+ /**
+ * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target
+ */
+ static class Solution {
+
+ public int[] twoSum(int[] nums, int target) {
+ Map map = new HashMap<>(nums.length);
+ for (int i = 0; i < nums.length; i++) {
+ int diff = target - nums[i];
+ if (map.containsKey(diff)) {
+ return new int[] { map.get(diff), i + 1 };
+ } else {
+ map.put(nums[i], i + 1);
+ }
+ }
+ return new int[0];
+ }
+
+ }
+
+ /**
+ * 双指针
+ */
+ static class Solution2 {
+
+ public int[] twoSum(int[] nums, int target) {
+ int left = 0, right = nums.length - 1;
+ while (left < right) {
+ if (nums[left] + nums[right] == target) {
+ return new int[] { left + 1, right + 1 };
+ } else if (nums[left] + nums[right] < target) {
+ left++;
+ } else {
+ right--;
+ }
+ }
+ return new int[0];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java"
new file mode 100644
index 0000000..1e7c9e1
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java"
@@ -0,0 +1,92 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 1260. 二维网格迁移
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 二维网格迁移 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] grid1 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
+ List> res1 = s.shiftGrid(grid1, 1);
+ Assertions.assertNotNull(res1);
+ Assertions.assertArrayEquals(new Integer[] { 9, 1, 2 }, res1.get(0).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 3, 4, 5 }, res1.get(1).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 6, 7, 8 }, res1.get(2).toArray(new Integer[0]));
+
+ int[][] grid2 = new int[][] { { 3, 8, 1, 9 }, { 19, 7, 2, 5 }, { 4, 6, 11, 10 }, { 12, 0, 21, 13 } };
+ List> res2 = s.shiftGrid(grid2, 4);
+ Assertions.assertNotNull(res2);
+ Assertions.assertArrayEquals(new Integer[] { 12, 0, 21, 13 }, res2.get(0).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 3, 8, 1, 9 }, res2.get(1).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 19, 7, 2, 5 }, res2.get(2).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 4, 6, 11, 10 }, res2.get(3).toArray(new Integer[0]));
+
+ int[][] grid3 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
+ List> res3 = s.shiftGrid(grid3, 9);
+ Assertions.assertNotNull(res3);
+ Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, res3.get(0).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 4, 5, 6 }, res3.get(1).toArray(new Integer[0]));
+ Assertions.assertArrayEquals(new Integer[] { 7, 8, 9 }, res3.get(2).toArray(new Integer[0]));
+
+ int[][] grid4 = new int[][] { { 1 }, { 2 }, { 3 }, { 4 }, { 7 }, { 6 }, { 5 } };
+ List> res4 = s.shiftGrid(grid4, 23);
+ Assertions.assertNotNull(res4);
+ }
+
+ static class Solution {
+
+ public List> shiftGrid(int[][] grid, int k) {
+ for (int i = 0; i < k; i++) {
+ shift(grid);
+ }
+
+ int m = grid.length, n = grid[0].length;
+ List> res = new ArrayList<>();
+ for (int i = 0; i < m; i++) {
+ List list = new ArrayList<>();
+ res.add(list);
+ for (int j = 0; j < n; j++) {
+ list.add(grid[i][j]);
+ }
+ }
+ return res;
+ }
+
+ public void shift(int[][] grid) {
+ int m = grid.length, n = grid[0].length;
+ int last = get(grid, m * n - 1);
+ for (int i = m * n - 1; i > 0; i--) {
+ int prev = get(grid, i - 1);
+ set(grid, i, prev);
+ }
+ set(grid, 0, last);
+ }
+
+ public int get(int[][] grid, int index) {
+ int n = grid[0].length;
+ int i = index / n;
+ int j = index % n;
+ return grid[i][j];
+ }
+
+ public void set(int[][] grid, int index, int val) {
+ int n = grid[0].length;
+ int i = index / n;
+ int j = index % n;
+ grid[i][j] = val;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java"
new file mode 100644
index 0000000..812604b
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java"
@@ -0,0 +1,46 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 67. 二进制求和
+ *
+ * @author Zhang Peng
+ * @date 2025-01-21
+ */
+public class 二进制求和 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals("100", s.addBinary("11", "1"));
+ Assertions.assertEquals("10101", s.addBinary("1010", "1011"));
+ }
+
+ static class Solution {
+
+ public String addBinary(String a, String b) {
+ int i = a.length() - 1;
+ int j = b.length() - 1;
+ int carry = 0;
+ StringBuilder sb = new StringBuilder();
+ while (i >= 0 || j >= 0) {
+ int numA = i < 0 ? 0 : a.charAt(i--) - '0';
+ int numB = j < 0 ? 0 : b.charAt(j--) - '0';
+ int sum = numA + numB + carry;
+ if (sum > 1) {
+ carry = 1;
+ sb.append(sum % 2);
+ } else {
+ carry = 0;
+ sb.append(sum);
+ }
+ }
+ if (carry > 0) {
+ sb.append(carry);
+ }
+ return sb.reverse().toString();
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java"
new file mode 100644
index 0000000..ce195a8
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 26. 删除有序数组中的重复项
+ *
+ * @author Zhang Peng
+ * @since 2018-11-05
+ */
+public class 删除排序数组中的重复项 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 1, 2 }));
+ Assertions.assertEquals(5, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 2, 2, 3, 3, 4 }));
+ Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 2 }));
+ Assertions.assertEquals(1, s.removeDuplicates(new int[] { 2, 2 }));
+ }
+
+ static class Solution {
+
+ public int removeDuplicates(int[] nums) {
+ int slow = 0, fast = 1;
+ while (fast < nums.length) {
+ if (nums[fast] != nums[slow]) {
+ slow++;
+ nums[slow] = nums[fast];
+ }
+ fast++;
+ }
+ return slow + 1;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java"
new file mode 100644
index 0000000..ad47707
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java"
@@ -0,0 +1,45 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 80. 删除有序数组中的重复项 II
+ *
+ * @author Zhang Peng
+ * @since 2018-11-05
+ */
+public class 删除排序数组中的重复项2 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(5, s.removeDuplicates(new int[] { 1, 1, 1, 2, 2, 3 }));
+ Assertions.assertEquals(7, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 1, 2, 3, 3 }));
+ }
+
+ static class Solution {
+
+ public int removeDuplicates(int[] nums) {
+ int slow = 0, fast = 1;
+ int cnt = 1;
+ while (fast < nums.length) {
+ if (nums[fast] != nums[slow]) {
+ cnt = 1;
+ slow++;
+ nums[slow] = nums[fast];
+ } else {
+ if (cnt < 2) {
+ slow++;
+ nums[slow] = nums[fast];
+ }
+ cnt++;
+ }
+ fast++;
+ }
+ // System.out.printf("slow: %d, fast: %d, nums: %s\n", slow, fast,
+ // JSONUtil.toJsonStr(ArrayUtil.sub(nums, 0, slow + 1)));
+ return slow + 1;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java"
new file mode 100644
index 0000000..75e2bc1
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java"
@@ -0,0 +1,39 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 344. 反转字符串
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 反转字符串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ char[] arr1 = new char[] { 'h', 'e', 'l', 'l', 'o' };
+ s.reverseString(arr1);
+ Assertions.assertArrayEquals(new char[] { 'o', 'l', 'l', 'e', 'h' }, arr1);
+
+ char[] arr2 = new char[] { 'H', 'a', 'n', 'n', 'a', 'h' };
+ s.reverseString(arr2);
+ Assertions.assertArrayEquals(new char[] { 'h', 'a', 'n', 'n', 'a', 'H' }, arr2);
+ }
+
+ static class Solution {
+
+ public void reverseString(char[] s) {
+ int left = 0, right = s.length - 1;
+ while (left < right) {
+ char temp = s[left];
+ s[left] = s[right];
+ s[right] = temp;
+ left++;
+ right--;
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java"
new file mode 100644
index 0000000..7465ea6
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java"
@@ -0,0 +1,67 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 88. 合并两个有序数组
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 合并两个有序数组 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[] nums1 = new int[] { 1, 2, 3, 0, 0, 0 };
+ int[] nums2 = new int[] { 2, 5, 6 };
+ s.merge(nums1, 3, nums2, 3);
+ Assertions.assertArrayEquals(new int[] { 1, 2, 2, 3, 5, 6 }, nums1);
+
+ int[] nums3 = new int[] { 1 };
+ int[] nums4 = new int[] {};
+ s.merge(nums3, 1, nums4, 0);
+ Assertions.assertArrayEquals(new int[] { 1 }, nums3);
+
+ int[] nums5 = new int[] { 0 };
+ int[] nums6 = new int[] { 1 };
+ s.merge(nums5, 0, nums6, 1);
+ Assertions.assertArrayEquals(new int[] { 1 }, nums5);
+
+ int[] nums7 = new int[] { 4, 5, 6, 0, 0, 0 };
+ int[] nums8 = new int[] { 1, 2, 3 };
+ s.merge(nums7, 3, nums8, 3);
+ Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 6 }, nums7);
+ }
+
+ static class Solution {
+
+ public void merge(int[] nums1, int m, int[] nums2, int n) {
+ // 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿)
+ int i = m - 1, j = n - 1;
+ // 生成排序的结果(类似拉链的拉锁)
+ int p = nums1.length - 1;
+ // 从后向前生成结果数组,类似合并两个有序链表的逻辑
+ while (i >= 0 && j >= 0) {
+ if (nums1[i] >= nums2[j]) {
+ nums1[p] = nums1[i];
+ i--;
+ } else {
+ nums1[p] = nums2[j];
+ j--;
+ }
+ p--;
+ }
+ // 可能其中一个数组的指针走到尽头了,而另一个还没走完
+ while (i >= 0) {
+ nums1[p--] = nums1[i--];
+ }
+ while (j >= 0) {
+ nums1[p--] = nums2[j--];
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java"
new file mode 100644
index 0000000..d424a3e
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java"
@@ -0,0 +1,62 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 56. 合并区间
+ *
+ * @author Zhang Peng
+ * @since 2020-07-29
+ */
+public class 合并区间 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] input = new int[][] { { 1, 4 }, { 2, 3 } };
+ int[][] expect = new int[][] { { 1, 4 } };
+ Assertions.assertArrayEquals(expect, s.merge(input));
+
+ int[][] input2 = new int[][] { { 1, 3 }, { 2, 6 }, { 8, 10 }, { 15, 18 } };
+ int[][] expect2 = new int[][] { { 1, 6 }, { 8, 10 }, { 15, 18 } };
+ Assertions.assertArrayEquals(expect2, s.merge(input2));
+
+ int[][] input3 = new int[][] { { 1, 4 }, { 4, 5 } };
+ int[][] expect3 = new int[][] { { 1, 5 } };
+ Assertions.assertArrayEquals(expect3, s.merge(input3));
+ }
+
+ static class Solution {
+
+ public int[][] merge(int[][] intervals) {
+
+ // base case
+ if (intervals == null || intervals.length <= 1) { return intervals; }
+
+ // 先按区间下限排序
+ Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
+
+ // 设置双指针,扫描 intervals
+ List merged = new ArrayList<>();
+ for (int[] interval : intervals) {
+ int l = interval[0], r = interval[1];
+ int last = merged.size() - 1;
+ if (last == -1 || merged.get(last)[1] < l) {
+ merged.add(new int[] { l, r });
+ } else {
+ l = merged.get(last)[0];
+ r = Math.max(merged.get(last)[1], r);
+ merged.set(last, new int[] { l, r });
+ }
+ }
+ return merged.toArray(new int[merged.size()][2]);
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java"
new file mode 100644
index 0000000..cabacab
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java"
@@ -0,0 +1,70 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PriorityQueue;
+
+/**
+ * 1329. 将矩阵按对角线排序
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 将矩阵按对角线排序 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] input1 = { { 3, 3, 1, 1 }, { 2, 2, 1, 2 }, { 1, 1, 1, 2 } };
+ int[][] expected1 = { { 1, 1, 1, 1 }, { 1, 2, 2, 2 }, { 1, 2, 3, 3 } };
+ int[][] output1 = s.diagonalSort(input1);
+ Assertions.assertArrayEquals(expected1, output1);
+
+ int[][] input2 = { { 11, 25, 66, 1, 69, 7 }, { 23, 55, 17, 45, 15, 52 }, { 75, 31, 36, 44, 58, 8 },
+ { 22, 27, 33, 25, 68, 4 }, { 84, 28, 14, 11, 5, 50 } };
+ int[][] expected2 = { { 5, 17, 4, 1, 52, 7 }, { 11, 11, 25, 45, 8, 69 }, { 14, 23, 25, 44, 58, 15 },
+ { 22, 27, 31, 36, 50, 66 }, { 84, 28, 75, 33, 55, 68 } };
+ int[][] output2 = s.diagonalSort(input2);
+ Assertions.assertArrayEquals(expected2, output2);
+ }
+
+ static class Solution {
+
+ public int[][] diagonalSort(int[][] mat) {
+
+ int m = mat.length, n = mat[0].length;
+
+ // 在同一个对角线上的元素,其横纵坐标之差是相同的
+ // 存储所有对角线的元素列表,利用 PriorityQueue 自动对对角线元素排序
+ Map> map = new HashMap<>();
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ // 横纵坐标之差可以作为一条对角线的 ID
+ int diff = i - j;
+ if (!map.containsKey(diff)) {
+ map.put(diff, new PriorityQueue<>(Comparator.comparingInt(a -> mat[a[0]][a[1]])));
+ }
+ map.get(diff).add(new int[] { i, j });
+ }
+ }
+
+ // 把排序结果回填二维矩阵
+ int[][] res = new int[m][n];
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ int diff = i - j;
+ PriorityQueue queue = map.get(diff);
+ int[] point = queue.poll();
+ res[i][j] = mat[point[0]][point[1]];
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java"
new file mode 100644
index 0000000..008fb47
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java"
@@ -0,0 +1,49 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 14. 最长公共前缀
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 最长公共前缀 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+ String[] input1 = { "flower", "flow", "flight" };
+ String expect1 = "fl";
+ String output1 = s.longestCommonPrefix(input1);
+ Assertions.assertEquals(expect1, output1);
+
+ String[] input2 = { "dog", "racecar", "car" };
+ String expect2 = "";
+ String output2 = s.longestCommonPrefix(input2);
+ Assertions.assertEquals(expect2, output2);
+ }
+
+ static class Solution {
+
+ public String longestCommonPrefix(String[] strs) {
+ int m = strs.length;
+ // 以第一行的列数为基准
+ int n = strs[0].length();
+ for (int col = 0; col < n; col++) {
+ for (int row = 1; row < m; row++) {
+ String cur = strs[row], prev = strs[row - 1];
+ // 判断每个字符串的 col 索引是否都相同
+ if (col >= cur.length() || col >= prev.length() ||
+ cur.charAt(col) != prev.charAt(col)) {
+ // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀
+ return strs[row].substring(0, col);
+ }
+ }
+ }
+ return strs[0];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java"
new file mode 100644
index 0000000..c42a437
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java"
@@ -0,0 +1,84 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 5. 最长回文子串
+ *
+ * @author Zhang Peng
+ * @date 2025-11-10
+ */
+public class 最长回文子串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals("bab", s.longestPalindrome("babad"));
+ Assertions.assertEquals("bb", s.longestPalindrome("cbbd"));
+ Assertions.assertEquals("a", s.longestPalindrome("a"));
+ Assertions.assertEquals("bb", s.longestPalindrome("bb"));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertEquals("aba", s2.longestPalindrome("babad"));
+ Assertions.assertEquals("bb", s2.longestPalindrome("cbbd"));
+ Assertions.assertEquals("a", s2.longestPalindrome("a"));
+ Assertions.assertEquals("bb", s2.longestPalindrome("bb"));
+ }
+
+ /**
+ * 双指针判断回文串 + 暴力解决,时间复杂度 o(n^2)
+ */
+ static class Solution {
+
+ public String longestPalindrome(String s) {
+ String res = s.substring(0, 1);
+ for (int i = 0; i < s.length(); i++) {
+ for (int j = i + 1; j < s.length(); j++) {
+ if (isPalindrome(s, i, j)) {
+ int len = j - i + 1;
+ if (len > res.length()) {
+ res = s.substring(i, j + 1);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ public boolean isPalindrome(String s, int left, int right) {
+ while (left < right) {
+ if (s.charAt(left) != s.charAt(right)) {
+ return false;
+ }
+ left++;
+ right--;
+ }
+ return true;
+ }
+
+ }
+
+ static class Solution2 {
+
+ public String longestPalindrome(String s) {
+ String res = "";
+ for (int i = 0; i < s.length(); i++) {
+ String s1 = palindrome(s, i, i);
+ String s2 = palindrome(s, i, i + 1);
+ res = res.length() > s1.length() ? res : s1;
+ res = res.length() > s2.length() ? res : s2;
+ }
+ return res;
+ }
+
+ public String palindrome(String s, int l, int r) {
+ while (l >= 0 && r < s.length()
+ && s.charAt(l) == s.charAt(r)) {
+ l--;
+ r++;
+ }
+ return s.substring(l + 1, r);
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java"
new file mode 100644
index 0000000..e9324ca
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java"
@@ -0,0 +1,49 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 977. 有序数组的平方
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 有序数组的平方 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[] input1 = { -4, -1, 0, 3, 10 };
+ int[] expect1 = { 0, 1, 9, 16, 100 };
+ int[] output1 = s.sortedSquares(input1);
+ Assertions.assertArrayEquals(expect1, output1);
+
+ int[] input2 = { -7, -3, 2, 3, 11 };
+ int[] expect2 = { 4, 9, 9, 49, 121 };
+ int[] output2 = s.sortedSquares(input2);
+ Assertions.assertArrayEquals(expect2, output2);
+ }
+
+ public static class Solution {
+
+ public int[] sortedSquares(int[] nums) {
+ int p = nums.length - 1;
+ int i = 0, j = nums.length - 1;
+ int[] res = new int[nums.length];
+ while (i <= j) {
+ if (Math.abs(nums[i]) > Math.abs(nums[j])) {
+ res[p] = nums[i] * nums[i];
+ i++;
+ } else {
+ res[p] = nums[j] * nums[j];
+ j--;
+ }
+ p--;
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java"
new file mode 100644
index 0000000..59232f3
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java"
@@ -0,0 +1,64 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.PriorityQueue;
+
+/**
+ * 378. 有序矩阵中第 K 小的元素
+ *
+ * @author Zhang Peng
+ * @date 2025-01-21
+ */
+public class 有序矩阵中第K小的元素 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[][] matrix = { { 1, 5, 9 }, { 10, 11, 13 }, { 12, 13, 15 } };
+ Assertions.assertEquals(13, s.kthSmallest(matrix, 8));
+
+ int[][] matrix2 = { { -5 } };
+ Assertions.assertEquals(-5, s.kthSmallest(matrix2, 1));
+
+ int[][] matrix3 = { { 1, 2 }, { 1, 3 } };
+ Assertions.assertEquals(1, s.kthSmallest(matrix3, 2));
+
+ int[][] matrix4 = { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } };
+ Assertions.assertEquals(3, s.kthSmallest(matrix4, 8));
+ }
+
+ static class Solution {
+
+ public int kthSmallest(int[][] matrix, int k) {
+ // 存储二元组 (matrix[i][j], i, j)
+ // i, j 记录当前元素的索引位置,用于生成下一个节点
+ PriorityQueue pq = new PriorityQueue<>((a, b) -> {
+ // 按照元素大小升序排序
+ return a[0] - b[0];
+ });
+
+ // 初始化优先级队列,把每一行的第一个元素装进去
+ for (int i = 0; i < matrix.length; i++) {
+ pq.offer(new int[] { matrix[i][0], i, 0 });
+ }
+
+ int res = -1;
+ while (!pq.isEmpty() && k > 0) {
+ int[] cur = pq.poll();
+ res = cur[0];
+ k--;
+
+ // 链表中的下一个节点加入优先级队列
+ int i = cur[1], j = cur[2];
+ if (j + 1 < matrix[i].length) {
+ pq.add(new int[] { matrix[i][j + 1], i, j + 1 });
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java"
new file mode 100644
index 0000000..89cfd6a
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java"
@@ -0,0 +1,52 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import cn.hutool.json.JSONUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+/**
+ * 373. 查找和最小的 K 对数字
+ *
+ * @author Zhang Peng
+ * @date 2025-01-21
+ */
+public class 查找和最小的K对数字 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ List> expectList1 = new ArrayList<>();
+ expectList1.add(Arrays.asList(1, 2));
+ expectList1.add(Arrays.asList(1, 4));
+ expectList1.add(Arrays.asList(1, 6));
+ List> list1 = s.kSmallestPairs(new int[] { 1, 7, 11 }, new int[] { 2, 4, 6 }, 3);
+ System.out.println(JSONUtil.toJsonStr(list1));
+
+ List> list2 = s.kSmallestPairs(new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, 2);
+ System.out.println(JSONUtil.toJsonStr(list2));
+ }
+
+ static class Solution {
+
+ public List> kSmallestPairs(int[] nums1, int[] nums2, int k) {
+ PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a[0] + a[1])));
+ for (int i = 0; i < nums1.length; i++) {
+ for (int j = 0; j < nums2.length; j++) {
+ queue.offer(new int[] { nums1[i], nums2[j] });
+ }
+ }
+
+ List> res = new ArrayList<>();
+ for (int i = 0; i < k; i++) {
+ int[] element = queue.poll();
+ res.add(Arrays.asList(element[0], element[1]));
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java"
new file mode 100644
index 0000000..626bd5b
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java"
@@ -0,0 +1,39 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * LCR 179. 查找总价格为目标值的两个商品
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 查找总价格为目标值的两个商品 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertArrayEquals(new int[] { 3, 15 }, s.twoSum(new int[] { 3, 9, 12, 15 }, 18));
+ Assertions.assertArrayEquals(new int[] { 27, 34 }, s.twoSum(new int[] { 8, 21, 27, 34, 52, 66 }, 61));
+ }
+
+ static class Solution {
+
+ public int[] twoSum(int[] nums, int target) {
+ Set set = new HashSet<>();
+ for (int num : nums) {
+ int diff = target - num;
+ if (set.contains(diff)) {
+ return new int[] { num, diff };
+ } else {
+ set.add(num);
+ }
+ }
+ return new int[0];
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java"
new file mode 100644
index 0000000..b682999
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java"
@@ -0,0 +1,50 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 283. 移动零
+ *
+ * @author Zhang Peng
+ * @since 2018-11-05
+ */
+public class 移动零 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[] arr1 = { 0, 1, 0, 3, 12 };
+ s.moveZeroes(arr1);
+ Assertions.assertArrayEquals(new int[] { 1, 3, 12, 0, 0 }, arr1);
+
+ int[] arr2 = { 0, 0, 1 };
+ s.moveZeroes(arr2);
+ Assertions.assertArrayEquals(new int[] { 1, 0, 0 }, arr2);
+
+ int[] arr3 = { 0 };
+ s.moveZeroes(arr3);
+ Assertions.assertArrayEquals(new int[] { 0 }, arr3);
+ }
+
+ public static class Solution {
+
+ public void moveZeroes(int[] nums) {
+ // slow 指针维护所有不为 0 的元素
+ int slow = 0, fast = 0;
+ while (fast < nums.length) {
+ if (nums[fast] != 0) {
+ nums[slow] = nums[fast];
+ slow++;
+ }
+ fast++;
+ }
+ // 后续补零
+ for (int i = slow; i < nums.length; i++) {
+ nums[i] = 0;
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java"
new file mode 100644
index 0000000..52992a1
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java"
@@ -0,0 +1,43 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 27. 移除元素
+ *
+ * @author Zhang Peng
+ * @since 2018-11-05
+ */
+public class 移除元素 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[] arr1 = { 3, 2, 2, 3 };
+ Assertions.assertEquals(2, s.removeElement(arr1, 3));
+
+ int[] arr2 = { 0, 1, 2, 2, 3, 0, 4, 2 };
+ Assertions.assertEquals(5, s.removeElement(arr2, 2));
+
+ int[] arr3 = { 1 };
+ Assertions.assertEquals(0, s.removeElement(arr3, 1));
+ }
+
+ static class Solution {
+
+ public int removeElement(int[] nums, int val) {
+ int slow = 0, fast = 0;
+ while (fast < nums.length) {
+ if (nums[fast] != val) {
+ nums[slow] = nums[fast];
+ slow++;
+ }
+ fast++;
+ }
+ return slow;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java"
new file mode 100644
index 0000000..92104fe
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java"
@@ -0,0 +1,41 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1329. 将矩阵按对角线排序
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 转置矩阵 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ int[][] input1 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
+ int[][] expect1 = { { 1, 4, 7 }, { 2, 5, 8 }, { 3, 6, 9 } };
+ int[][] output1 = s.transpose(input1);
+ Assertions.assertArrayEquals(expect1, output1);
+
+ int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 } };
+ int[][] expect2 = { { 1, 4 }, { 2, 5 }, { 3, 6 } };
+ int[][] output2 = s.transpose(input2);
+ Assertions.assertArrayEquals(expect2, output2);
+ }
+
+ public static class Solution {
+
+ public int[][] transpose(int[][] matrix) {
+ int m = matrix.length, n = matrix[0].length;
+ int[][] res = new int[n][m];
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ res[j][i] = matrix[i][j];
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java"
new file mode 100644
index 0000000..d8d6d83
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java"
@@ -0,0 +1,53 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import cn.hutool.json.JSONUtil;
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 75. 颜色分类
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 颜色分类 {
+
+ public static void main(String[] args) {
+
+ Solution s = new Solution();
+
+ int[] nums1 = { 2, 0, 2, 1, 1, 0 };
+ s.sortColors(nums1);
+ Assertions.assertArrayEquals(new int[] { 0, 0, 1, 1, 2, 2 }, nums1);
+
+ int[] nums2 = { 2, 0, 1 };
+ s.sortColors(nums2);
+ Assertions.assertArrayEquals(new int[] { 0, 1, 2 }, nums2);
+ }
+
+ static class Solution {
+
+ public void sortColors(int[] nums) {
+ moveToTail(nums, 1);
+ System.out.println("nums = " + JSONUtil.toJsonStr(nums));
+ moveToTail(nums, 2);
+ System.out.println("nums = " + JSONUtil.toJsonStr(nums));
+ }
+
+ public void moveToTail(int[] nums, int val) {
+ if (nums == null || nums.length == 0) { return; }
+ int slow = 0, fast = 0;
+ while (fast < nums.length) {
+ if (nums[fast] != val) {
+ nums[slow] = nums[fast];
+ slow++;
+ }
+ fast++;
+ }
+ for (int i = slow; i < nums.length; i++) {
+ nums[i] = val;
+ }
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java"
new file mode 100644
index 0000000..4c1597e
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java"
@@ -0,0 +1,74 @@
+package io.github.dunwu.algorithm.array.two_pointer;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 125. 验证回文串
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 验证回文串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.isPalindrome("A man, a plan, a canal: Panama"));
+ Assertions.assertFalse(s.isPalindrome("race a car"));
+ Assertions.assertTrue(s.isPalindrome(" "));
+ Assertions.assertTrue(s.isPalindrome("ab_a"));
+
+ Solution2 s2 = new Solution2();
+ Assertions.assertTrue(s2.isPalindrome("A man, a plan, a canal: Panama"));
+ Assertions.assertFalse(s2.isPalindrome("race a car"));
+ Assertions.assertTrue(s2.isPalindrome(" "));
+ Assertions.assertTrue(s2.isPalindrome("ab_a"));
+ }
+
+ static class Solution {
+
+ public boolean isPalindrome(String s) {
+ String format = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
+ return isPalindrome(format, 0, format.length() - 1);
+ }
+
+ public boolean isPalindrome(String s, int left, int right) {
+ while (left < right) {
+ if (s.charAt(left) != s.charAt(right)) {
+ return false;
+ }
+ left++;
+ right--;
+ }
+ return true;
+ }
+
+ }
+
+ static class Solution2 {
+
+ public boolean isPalindrome(String s) {
+ int left = 0, right = s.length() - 1;
+ while (left < right) {
+ if (!Character.isLetterOrDigit(s.charAt(left))) {
+ left++;
+ continue;
+ }
+ if (!Character.isLetterOrDigit(s.charAt(right))) {
+ right--;
+ continue;
+ }
+
+ char l = Character.toLowerCase(s.charAt(left));
+ char r = Character.toLowerCase(s.charAt(right));
+ if (l != r) {
+ return false;
+ }
+ left++;
+ right--;
+ }
+ return true;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java"
new file mode 100644
index 0000000..6a6d857
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java"
@@ -0,0 +1,50 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 713. 乘积小于 K 的子数组
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 乘积小于K的子数组 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(8, s.numSubarrayProductLessThanK(new int[] { 10, 5, 2, 6 }, 100));
+ Assertions.assertEquals(0, s.numSubarrayProductLessThanK(new int[] { 1, 2, 3 }, 0));
+ }
+
+ static class Solution {
+
+ public int numSubarrayProductLessThanK(int[] nums, int k) {
+ int left = 0, right = 0;
+ // 滑动窗口,初始化为乘法单位元
+ int windowProduct = 1;
+ // 记录符合条件的子数组个数
+ int count = 0;
+
+ while (right < nums.length) {
+ // 扩大窗口,并更新窗口数据
+ windowProduct = windowProduct * nums[right];
+ right++;
+
+ while (left < right && windowProduct >= k) {
+ // 缩小窗口,并更新窗口数据
+ windowProduct = windowProduct / nums[left];
+ left++;
+ }
+ // 现在必然是一个合法的窗口,但注意思考这个窗口中的子数组个数怎么计算:
+ // 比方说 left = 1, right = 4 划定了 [1, 2, 3] 这个窗口(right 是开区间)
+ // 但不止 [left..right] 是合法的子数组,[left+1..right], [left+2..right] 等都是合法子数组
+ // 所以我们需要把 [3], [2,3], [1,2,3] 这 right - left 个子数组都加上
+ count += right - left;
+ }
+
+ return count;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java"
new file mode 100644
index 0000000..22f6ea9
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java"
@@ -0,0 +1,59 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 567. 字符串的排列
+ *
+ * @author Zhang Peng
+ * @since 2025-08-06
+ */
+public class 字符串的排列 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.checkInclusion("ab", "eidbaooo"));
+ Assertions.assertFalse(s.checkInclusion("ab", "eidboaoo"));
+ }
+
+ static class Solution {
+
+ public boolean checkInclusion(String t, String s) {
+ Map need = new HashMap<>();
+ Map window = new HashMap<>();
+ for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1);
+
+ int left = 0, right = 0;
+ int valid = 0;
+ while (right < s.length()) {
+ char c = s.charAt(right);
+ right++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(c)) {
+ window.put(c, window.getOrDefault(c, 0) + 1);
+ if (window.get(c).equals(need.get(c))) { valid++; }
+ }
+
+ // 判断左侧窗口是否要收缩
+ while (right - left >= t.length()) {
+ // 在这里判断是否找到了合法的子串
+ if (valid == need.size()) { return true; }
+ char d = s.charAt(left);
+ left++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(d)) {
+ if (window.get(d).equals(need.get(d))) { valid--; }
+ window.put(d, window.get(d) - 1);
+ }
+ }
+ }
+ // 未找到符合条件的子串
+ return false;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java"
new file mode 100644
index 0000000..8bf319d
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java"
@@ -0,0 +1,38 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 217. 存在重复元素
+ *
+ * @author Zhang Peng
+ * @since 2020-06-05
+ */
+public class 存在重复元素 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 2, 3, 1 }));
+ Assertions.assertFalse(s.containsDuplicate(new int[] { 1, 2, 3, 4 }));
+ Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 1, 1, 3, 3, 4, 3, 2, 4, 2 }));
+ }
+
+ static class Solution {
+
+ public boolean containsDuplicate(int[] nums) {
+ Set set = new HashSet<>();
+ for (int num : nums) {
+ if (set.contains(num)) {
+ return true;
+ }
+ set.add(num);
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java"
new file mode 100644
index 0000000..2e6b279
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java"
@@ -0,0 +1,50 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashSet;
+
+/**
+ * 219. 存在重复元素 II
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 存在重复元素2 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1 }, 3));
+ Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 0, 1, 1 }, 1));
+ Assertions.assertFalse(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1, 2, 3 }, 2));
+ Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 99, 99 }, 2));
+ }
+
+ static class Solution {
+
+ public boolean containsNearbyDuplicate(int[] nums, int k) {
+
+ // base case
+ if (nums == null || nums.length < 2) { return false; }
+
+ int left = 0, right = 0;
+ HashSet window = new HashSet<>();
+ // 滑动窗口算法框架,维护一个大小为 k 的窗口
+ while (right < nums.length) {
+ // 扩大窗口
+ if (window.contains(nums[right])) { return true; }
+ window.add(nums[right]);
+ right++;
+
+ if (right - left > k) {
+ // 当窗口的大小大于 k 时,缩小窗口
+ window.remove(nums[left]);
+ left++;
+ }
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java"
new file mode 100644
index 0000000..8265957
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java"
@@ -0,0 +1,55 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.TreeSet;
+
+/**
+ * 220. 存在重复元素 III
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 存在重复元素3 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 3, 1 }, 3, 0));
+ Assertions.assertFalse(s.containsNearbyAlmostDuplicate(new int[] { 1, 5, 9, 1, 5, 9 }, 2, 3));
+ Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 2, 3, 4, 5 }, 3, 0));
+ }
+
+ static class Solution {
+
+ public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
+ TreeSet window = new TreeSet<>();
+ int left = 0, right = 0;
+ while (right < nums.length) {
+ // 为了防止 i == j,所以在扩大窗口之前先判断是否有符合题意的索引对 (i, j)
+ // 查找略大于 nums[right] 的那个元素
+ Integer ceiling = window.ceiling(nums[right]);
+ if (ceiling != null && (long) ceiling - nums[right] <= t) {
+ return true;
+ }
+ // 查找略小于 nums[right] 的那个元素
+ Integer floor = window.floor(nums[right]);
+ if (floor != null && (long) nums[right] - floor <= t) {
+ return true;
+ }
+
+ // 扩大窗口
+ window.add(nums[right]);
+ right++;
+
+ if (right - left > k) {
+ // 缩小窗口
+ window.remove(nums[left]);
+ left++;
+ }
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java"
new file mode 100644
index 0000000..d39d1c8
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java"
@@ -0,0 +1,65 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1658. 将 x 减到 0 的最小操作数
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 将x减到0的最小操作数 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(2, s.minOperations(new int[] { 1, 1, 4, 2, 3 }, 5));
+ Assertions.assertEquals(-1, s.minOperations(new int[] { 5, 6, 7, 8, 9 }, 4));
+ Assertions.assertEquals(5, s.minOperations(new int[] { 3, 2, 20, 1, 1, 3 }, 10));
+ Assertions.assertEquals(16, s.minOperations(new int[] { 8828, 9581, 49, 9818, 9974, 9869, 9991,
+ 10000, 10000, 10000, 9999, 9993, 9904, 8819, 1231, 6309 }, 134365));
+ }
+
+ static class Solution {
+
+ // 【思路】
+ // 从边缘删除掉和为 x 的元素,那剩下来的是什么?剩下来的是不是就是 nums 中的一个子数组?
+ // 让你尽可能少地从边缘删除元素说明什么?是不是就是说剩下来的这个子数组大小尽可能的大?
+ // 所以,这道题等价于让你寻找 nums 中元素和为 sum(nums) - x 的最长子数组。
+
+ // 1、当窗口内元素之和小于目标和 target 时,扩大窗口,让窗口内元素和增加。
+ // 2、当窗口内元素之和大于目标和 target 时,缩小窗口,让窗口内元素和减小。
+ // 3、当窗口内元素之和等于目标和 target 时,找到一个符合条件的子数组,我们想找的是最长的子数组长度。
+ public int minOperations(int[] nums, int x) {
+ int n = nums.length, sum = 0;
+ for (int num : nums) { sum += num; }
+ // 滑动窗口需要寻找的子数组目标和
+ int target = sum - x;
+
+ int left = 0, right = 0;
+ // 记录窗口内所有元素和
+ int windowSum = 0;
+ // 记录目标子数组的最大长度
+ int maxLen = Integer.MIN_VALUE;
+ // 开始执行滑动窗口框架
+ while (right < nums.length) {
+ // 扩大窗口
+ windowSum += nums[right];
+ right++;
+
+ while (windowSum > target && left < right) {
+ // 缩小窗口
+ windowSum -= nums[left];
+ left++;
+ }
+ // 寻找目标子数组
+ if (windowSum == target) {
+ maxLen = Math.max(maxLen, right - left);
+ }
+ }
+ // 目标子数组的最大长度可以推导出需要删除的字符数量
+ return maxLen == Integer.MIN_VALUE ? -1 : n - maxLen;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java"
new file mode 100644
index 0000000..2a6bb20
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java"
@@ -0,0 +1,69 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 438. 找到字符串中所有字母异位词
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 找到字符串中所有字母异位词 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertArrayEquals(new Integer[] { 0, 6 }, s.findAnagrams("cbaebabacd", "abc").toArray());
+ Assertions.assertArrayEquals(new Integer[] { 0, 1, 2 }, s.findAnagrams("abab", "ab").toArray());
+ }
+
+ static class Solution {
+
+ public List findAnagrams(String s, String t) {
+ Map need = new HashMap<>();
+ Map window = new HashMap<>();
+ for (char c : t.toCharArray()) {
+ need.put(c, need.getOrDefault(c, 0) + 1);
+ }
+
+ int left = 0, right = 0;
+ int valid = 0;
+ // 记录结果
+ List res = new ArrayList<>();
+ while (right < s.length()) {
+ char c = s.charAt(right);
+ right++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(c)) {
+ window.put(c, window.getOrDefault(c, 0) + 1);
+ if (window.get(c).equals(need.get(c))) {
+ valid++;
+ }
+ }
+ // 判断左侧窗口是否要收缩
+ while (right - left >= t.length()) {
+ // 当窗口符合条件时,把起始索引加入 res
+ if (valid == need.size()) {
+ res.add(left);
+ }
+ char d = s.charAt(left);
+ left++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(d)) {
+ if (window.get(d).equals(need.get(d))) {
+ valid--;
+ }
+ window.put(d, window.get(d) - 1);
+ }
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java"
new file mode 100644
index 0000000..52f0955
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java"
@@ -0,0 +1,52 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 3. 无重复字符的最长子串
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 无重复字符的最长子串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(3, s.lengthOfLongestSubstring("abcabcbb"));
+ Assertions.assertEquals(1, s.lengthOfLongestSubstring("bbbbb"));
+ Assertions.assertEquals(3, s.lengthOfLongestSubstring("pwwkew"));
+ Assertions.assertEquals(2, s.lengthOfLongestSubstring("aab"));
+ }
+
+ static class Solution {
+
+ public int lengthOfLongestSubstring(String s) {
+ Map window = new HashMap<>();
+
+ int left = 0, right = 0;
+ // 记录结果
+ int res = 0;
+ while (right < s.length()) {
+ char c = s.charAt(right);
+ right++;
+ // 进行窗口内数据的一系列更新
+ window.put(c, window.getOrDefault(c, 0) + 1);
+ // 判断左侧窗口是否要收缩
+ while (window.get(c) > 1) {
+ char d = s.charAt(left);
+ left++;
+ // 进行窗口内数据的一系列更新
+ window.put(d, window.get(d) - 1);
+ }
+ // 在这里更新答案
+ res = Math.max(res, right - left);
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java"
new file mode 100644
index 0000000..58f6ba0
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java"
@@ -0,0 +1,56 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 424. 替换后的最长重复字符
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 替换后的最长重复字符 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(4, s.characterReplacement("ABAB", 2));
+ Assertions.assertEquals(4, s.characterReplacement("AABABBA", 1));
+ Assertions.assertEquals(4, s.characterReplacement("AAAA", 2));
+ }
+
+ static class Solution {
+
+ public int characterReplacement(String s, int k) {
+ int left = 0, right = 0;
+ // 统计窗口中每个字符的出现次数
+ int[] windowCharCount = new int[26];
+ // 记录窗口中字符的最多重复次数
+ // 记录这个值的意义在于,最划算的替换方法肯定是把其他字符替换成出现次数最多的那个字符
+ int windowMaxCount = 0;
+ // 记录结果长度
+ int res = 0;
+
+ // 开始滑动窗口模板
+ while (right < s.length()) {
+ // 扩大窗口
+ int c = s.charAt(right) - 'A';
+ windowCharCount[c]++;
+ windowMaxCount = Math.max(windowMaxCount, windowCharCount[c]);
+ right++;
+
+ // 这个 while 换成 if 也可以
+ while (right - left - windowMaxCount > k) {
+ // 杂牌字符数量 right - left - windowMaxCount 多于 k
+ // 此时,k 次替换已经无法把窗口内的字符都替换成相同字符了
+ // 必须缩小窗口
+ windowCharCount[s.charAt(left) - 'A']--;
+ left++;
+ }
+ // 经过收缩后,此时一定是一个合法的窗口
+ res = Math.max(res, right - left);
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java"
new file mode 100644
index 0000000..531e688
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java"
@@ -0,0 +1,40 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 1004. 最大连续1的个数 III
+ *
+ * @author Zhang Peng
+ * @date 2025-10-14
+ */
+public class 最大连续1的个数3 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(6, s.longestOnes(new int[] { 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, 2));
+ Assertions.assertEquals(10,
+ s.longestOnes(new int[] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, 3));
+ }
+
+ static class Solution {
+
+ public int longestOnes(int[] nums, int k) {
+ int cnt = 0, len = 0;
+ int left = 0, right = 0;
+ while (right < nums.length) {
+ if (nums[right] == 0) { cnt++; }
+ right++;
+
+ while (cnt > k) {
+ if (nums[left] == 0) { cnt--; }
+ left++;
+ }
+ len = Math.max(len, right - left);
+ }
+ return len;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java"
new file mode 100644
index 0000000..81b42a2
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java"
@@ -0,0 +1,74 @@
+package io.github.dunwu.algorithm.array.window;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 76. 最小覆盖子串
+ *
+ * @author Zhang Peng
+ * @date 2025-01-10
+ */
+@Slf4j
+public class 最小覆盖子串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals("BANC", s.minWindow("ADOBECODEBANC", "ABC"));
+ Assertions.assertEquals("a", s.minWindow("a", "a"));
+ Assertions.assertEquals("", s.minWindow("a", "aa"));
+ }
+
+ static class Solution {
+
+ public String minWindow(String s, String t) {
+ Map need = new HashMap<>();
+ Map window = new HashMap<>();
+ for (char c : t.toCharArray()) {
+ need.put(c, need.getOrDefault(c, 0) + 1);
+ }
+
+ int valid = 0;
+ int left = 0, right = 0;
+ // 记录最小覆盖子串的起始索引及长度
+ int start = 0, len = Integer.MAX_VALUE;
+ while (right < s.length()) {
+ // c 是将移入窗口的字符
+ char c = s.charAt(right);
+ // 扩大窗口
+ right++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(c)) {
+ window.put(c, window.getOrDefault(c, 0) + 1);
+ if (window.get(c).equals(need.get(c))) { valid++; }
+ }
+
+ // 判断左侧窗口是否要收缩
+ while (valid == need.size()) {
+
+ // 在这里更新最小覆盖子串
+ if (right - left < len) {
+ start = left;
+ len = right - left;
+ }
+ // d 是将移出窗口的字符
+ char d = s.charAt(left);
+ // 缩小窗口
+ left++;
+ // 进行窗口内数据的一系列更新
+ if (need.containsKey(d)) {
+ if (window.get(d).equals(need.get(d))) { valid--; }
+ window.put(d, window.get(d) - 1);
+ }
+ }
+ }
+ // 返回最小覆盖子串
+ return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java"
new file mode 100644
index 0000000..5a8994b
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java"
@@ -0,0 +1,86 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 395. 至少有 K
+ * 个重复字符的最长子串
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 至少有K个重复字符的最长子串 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(3, s.longestSubstring("aaabb", 3));
+ Assertions.assertEquals(5, s.longestSubstring("ababbc", 2));
+ Assertions.assertEquals(6, s.longestSubstring("aaabbb", 3));
+ }
+
+ public static class Solution {
+
+ public int longestSubstring(String s, int k) {
+ int len = 0;
+ for (int i = 1; i <= 26; i++) {
+ // 限制窗口中只能有 i 种不同字符
+ len = Math.max(len, longestKLetterSubstring(s, k, i));
+ }
+ return len;
+ }
+
+ // 寻找 s 中含有 count 种字符,且每种字符出现次数都大于 k 的子串
+ public int longestKLetterSubstring(String s, int k, int count) {
+
+ // 记录答案
+ int res = 0;
+ // 快慢指针维护滑动窗口,左闭右开区间
+ int left = 0, right = 0;
+ // 题目说 s 中只有小写字母,所以用大小 26 的数组记录窗口中字符出现的次数
+ int[] windowCount = new int[26];
+ // 记录窗口中存在几种不同的字符(字符种类)
+ int windowUniqueCount = 0;
+ // 记录窗口中有几种字符的出现次数达标(大于等于 k)
+ int windowValidCount = 0;
+ // 滑动窗口代码模板
+ while (right < s.length()) {
+ // 移入字符,扩大窗口
+ int c = s.charAt(right) - 'a';
+ if (windowCount[c] == 0) {
+ // 窗口中新增了一种字符
+ windowUniqueCount++;
+ }
+ windowCount[c]++;
+ if (windowCount[c] == k) {
+ // 窗口中新增了一种达标的字符
+ windowValidCount++;
+ }
+ right++;
+
+ // 当窗口中字符种类大于 count 时,缩小窗口
+ while (windowUniqueCount > count) {
+ // 移出字符,缩小窗口
+ int d = s.charAt(left) - 'a';
+ if (windowCount[d] == k) {
+ // 窗口中减少了一种达标的字符
+ windowValidCount--;
+ }
+ windowCount[d]--;
+ if (windowCount[d] == 0) {
+ // 窗口中减少了一种字符
+ windowUniqueCount--;
+ }
+ left++;
+ }
+
+ // 当窗口中字符种类为 count 且每个字符出现次数都满足 k 时,更新答案
+ if (windowValidCount == count) {
+ res = Math.max(res, right - left);
+ }
+ }
+ return res;
+ }
+
+ }
+
+}
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java"
new file mode 100644
index 0000000..b636134
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java"
@@ -0,0 +1,44 @@
+package io.github.dunwu.algorithm.array.window;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 209. 长度最小的子数组
+ *
+ * @author Zhang Peng
+ * @date 2025-10-15
+ */
+public class 长度最小的子数组 {
+
+ public static void main(String[] args) {
+ Solution s = new Solution();
+ Assertions.assertEquals(2, s.minSubArrayLen(7, new int[] { 2, 3, 1, 2, 4, 3 }));
+ Assertions.assertEquals(1, s.minSubArrayLen(4, new int[] { 1, 4, 4 }));
+ Assertions.assertEquals(0, s.minSubArrayLen(11, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }));
+ }
+
+ public static class Solution {
+
+ public int minSubArrayLen(int target, int[] nums) {
+ int left = 0, right = 0;
+ // 维护窗口内元素之和
+ int windowSum = 0;
+ int res = Integer.MAX_VALUE;
+
+ while (right < nums.length) {
+ // 扩大窗口
+ windowSum += nums[right];
+ right++;
+ while (windowSum >= target && left < right) {
+ // 已经达到 target,缩小窗口,同时更新答案
+ res = Math.min(res, right - left);
+ windowSum -= nums[left];
+ left++;
+ }
+ }
+ return res == Integer.MAX_VALUE ? 0 : res;
+ }
+
+ }
+
+}
diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/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/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java
similarity index 83%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java
index c8b6203..259df53 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* In computer science, a heap is a specialized tree-based data structure that satisfies the heap property: If A is a
@@ -7,66 +7,74 @@
* the root node (this kind of heap is called max heap) or the keys of parent nodes are less than or equal to those of
* the children (min heap).
*
+ *
* @author Justin Wetherell
- * @see Heap (Wikipedia)
- *
+ * @see Heap (Wikipedia)
*/
public interface IHeap {
/**
* Add value to the heap.
+ *
* @param value to add to the heap.
* @return True if added to the heap.
*/
- public boolean add(T value);
+ boolean add(T value);
/**
* Get the value of the head node from the heap.
+ *
* @return value of the head node.
*/
- public T getHeadValue();
+ T getHeadValue();
/**
* Remove the head node from the heap.
+ *
* @return value of the head node.
*/
- public T removeHead();
+ T removeHead();
/**
* Remove the value from the heap.
+ *
* @param value to remove from heap.
* @return True if value was removed form the heap;
*/
- public T remove(T value);
+ T remove(T value);
/**
* Clear the entire heap.
*/
- public void clear();
+ void clear();
/**
* Does the value exist in the heap. Warning this is a O(n) operation.
+ *
* @param value to locate in the heap.
* @return True if the value is in heap.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Get size of the heap.
+ *
* @return size of the heap.
*/
- public int size();
+ int size();
/**
* Validate the heap according to the invariants.
+ *
* @return True if the heap is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get this Heap as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java
similarity index 76%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java
index 613b630..6869238 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java
@@ -1,63 +1,70 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* A list or sequence is an abstract data type that implements an ordered collection of values, where the same value may
* occur more than once.
*
+ *
* @author Justin Wetherell
- * @see List (Wikipedia)
- *
+ * @see List (Wikipedia)
*/
public interface IList {
/**
* Add value to list.
+ *
* @param value to add.
* @return True if added.
*/
- public boolean add(T value);
+ boolean add(T value);
/**
* Remove value from list.
+ *
* @param value to remove.
* @return True if removed.
*/
- public boolean remove(T value);
+ boolean remove(T value);
/**
* Clear the entire list.
*/
- public void clear();
+ void clear();
/**
* Does the list contain value.
+ *
* @param value to search list for.
* @return True if list contains value.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Size of the list.
+ *
* @return size of the list.
*/
- public int size();
+ int size();
/**
* Validate the list according to the invariants.
+ *
* @return True if the list is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get this List as a Java compatible List
+ *
* @return Java compatible List
*/
- public java.util.List toList();
+ java.util.List toList();
/**
* Get this List as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java
similarity index 78%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java
index 458ac28..3860756 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java
@@ -1,65 +1,73 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* In computer science, an associative array, map, or dictionary is an abstract data type composed of a collection of
* (key, value) pairs, such that each possible key appears at most once in the collection.
*
+ *
* @author Justin Wetherell
- * @see Associative Array (Wikipedia)
- *
+ * @see Associative Array
+ * (Wikipedia)
*/
public interface IMap {
/**
* Put key->value pair in the map.
- * @param key to be inserted.
+ *
+ * @param key to be inserted.
* @param value to be inserted.
* @return V previous value or null if none.
*/
- public V put(K key, V value);
+ V put(K key, V value);
/**
* Get value for key.
+ *
* @param key to get value for.
* @return value mapped to key.
*/
- public V get(K key);
+ V get(K key);
/**
* Remove key and value from map.
+ *
* @param key to remove from the map.
* @return True if removed or False if not found.
*/
- public V remove(K key);
+ V remove(K key);
/**
* Clear the entire map.
*/
- public void clear();
+ void clear();
/**
* Does the map contain the key.
+ *
* @param key to locate in the map.
* @return True if key is in the map.
*/
- public boolean contains(K key);
+ boolean contains(K key);
/**
* Number of key/value pairs in the hash map.
+ *
* @return number of key/value pairs.
*/
- public int size();
+ int size();
/**
* Validate the map according to the invariants.
+ *
* @return True if the map is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Wraps this map in a Java compatible Map
+ *
* @return Java compatible Map
*/
- public java.util.Map toMap();
+ java.util.Map toMap();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java
similarity index 81%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java
index 8cef376..0a5c724 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* A queue is a particular kind of abstract data type or collection in which the entities in the collection are kept in
@@ -6,72 +6,82 @@
* position and removal of entities from the front terminal position. This makes the queue a First-In-First-Out (FIFO)
* data structure. In a FIFO data structure, the first element added to the queue will be the first one to be removed.
*
+ *
* @author Justin Wetherell
- * @see Queue (Wikipedia)
- *
+ * @see Queue
+ * (Wikipedia)
*/
public interface IQueue {
/**
* Add a value to the beginning of the queue.
+ *
* @param value to add to queue.
* @return True if added to queue.
*/
- public boolean offer(T value);
+ boolean offer(T value);
/**
* Remove a value from the tail of the queue.
+ *
* @return value from the tail of the queue.
*/
- public T poll();
+ T poll();
/**
* Get but do not remove tail of the queue.
+ *
* @return value from the tail of the queue.
*/
- public T peek();
+ T peek();
/**
* Remove the value from the queue.
+ *
* @param value to remove from the queue.
* @return True if the value was removed from the queue.
*/
- public boolean remove(T value);
+ boolean remove(T value);
/**
* Clear the entire queue.
*/
- public void clear();
+ void clear();
/**
* Does the queue contain the value.
+ *
* @param value to find in the queue.
* @return True if the queue contains the value.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Get the size of the queue.
+ *
* @return size of the queue.
*/
- public int size();
+ int size();
/**
* Validate the queue according to the invariants.
+ *
* @return True if the queue is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get this Queue as a Java compatible Queue
+ *
* @return Java compatible Queue
*/
- public java.util.Queue toQueue();
+ java.util.Queue toQueue();
/**
* Get this Queue as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java
similarity index 79%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java
index 75fbe59..550d0a8 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* In computer science, a set is an abstract data structure that can store certain values, without any particular order,
@@ -6,60 +6,68 @@
* other collection types, rather than retrieving a specific element from a set, one typically tests a value for
* membership in a set.
*
+ *
* @author Justin Wetherell
- * @see Set (Wikipedia)
- *
+ * @see Set
+ * (Wikipedia)
*/
public interface ISet {
/**
* Add value to set.
+ *
* @param value to add.
* @return True if added.
*/
- public boolean add(T value);
+ boolean add(T value);
/**
* Remove value from set.
+ *
* @param value to remove.
* @return True if removed.
*/
- public boolean remove(T value);
+ boolean remove(T value);
/**
* Clear the entire set.
*/
- public void clear();
+ void clear();
/**
* Does the set contain value.
+ *
* @param value to search set for.
* @return True if set contains value.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Size of the set.
+ *
* @return size of the set.
*/
- public int size();
+ int size();
/**
* Validate the set according to the invariants.
+ *
* @return True if the set is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get this Set as a Java compatible Set
+ *
* @return Java compatible Set
*/
- public java.util.Set toSet();
+ java.util.Set toSet();
/**
* Get this Set as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java
similarity index 81%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java
index 9a517f3..5904d27 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* A stack is a last in, first out (LIFO) abstract data type and linear data structure. A stack can have any abstract
@@ -7,71 +7,81 @@
* contain enough space to accept the given item, the stack is then considered to be in an overflow state. The pop
* operation removes an item from the top of the stack.
*
+ *
* @author Justin Wetherell
- * @see Stack (Wikipedia)
- *
+ * @see Stack
+ * (Wikipedia)
*/
public interface IStack {
/**
* Push value on top of stack
+ *
* @param value to push on the stack.
*/
- public boolean push(T value);
+ boolean push(T value);
/**
* Pop the value from the top of stack.
+ *
* @return value popped off the top of the stack.
*/
- public T pop();
+ T pop();
/**
* Peek the value from the top of stack.
+ *
* @return value popped off the top of the stack.
*/
- public T peek();
+ T peek();
/**
* Remove value from stack.
+ *
* @param value to remove from stack.
* @return True if value was removed.
*/
- public boolean remove(T value);
+ boolean remove(T value);
/**
* Clear the entire stack.
*/
- public void clear();
+ void clear();
/**
* Does stack contain object.
+ *
* @param value object to find in stack.
* @return True is stack contains object.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Size of the stack.
+ *
* @return size of the stack.
*/
- public int size();
+ int size();
/**
* Validate the stack according to the invariants.
+ *
* @return True if the stack is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get this Stack as a Java compatible Queue
+ *
* @return Java compatible Queue
*/
- public java.util.Queue toLifoQueue();
+ java.util.Queue toLifoQueue();
/**
* Get this Stack as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java
similarity index 86%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java
index 151e500..af24df2 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
import java.util.Set;
@@ -7,6 +7,7 @@
* containing all the suffixes of the given text as their keys and positions in the text as their values. Suffix trees
* allow particularly fast implementations of many important string operations.
*
+ *
* @author Justin Wetherell
* @see Suffix Tree (Wikipedia)
*
@@ -15,14 +16,17 @@ public interface ISuffixTree {
/**
* Does the sub-sequence exist in the suffix tree.
+ *
* @param sub-sequence to locate in the tree.
* @return True if the sub-sequence exist in the tree.
*/
- public boolean doesSubStringExist(C sub);
+ boolean doesSubStringExist(C sub);
/**
* Get all the suffixes in the tree.
+ *
* @return set of suffixes in the tree.
*/
- public Set getSuffixes();
+ Set getSuffixes();
+
}
diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java
similarity index 84%
rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java
rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java
index 14c415e..6036192 100644
--- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.ds.common;
+package io.github.dunwu.algorithm.common;
/**
* A tree can be defined recursively (locally) as a collection of nodes (starting at a root node), where each node is a
@@ -6,6 +6,7 @@
* node is duplicated. A tree can be defined abstractly as a whole (globally) as an ordered tree, with a value assigned
* to each node.
*
+ *
* @author Justin Wetherell
* @see Tree (Wikipedia)
*
@@ -14,46 +15,52 @@ public interface ITree {
/**
* Add value to the tree. Tree can contain multiple equal values.
+ *
* @param value to add to the tree.
* @return True if successfully added to tree.
*/
- public boolean add(T value);
+ boolean add(T value);
/**
* Remove first occurrence of value in the tree.
+ *
* @param value to remove from the tree.
* @return T value removed from tree.
*/
- public T remove(T value);
+ T remove(T value);
/**
* Clear the entire stack.
*/
- public void clear();
+ void clear();
/**
* Does the tree contain the value.
+ *
* @param value to locate in the tree.
* @return True if tree contains value.
*/
- public boolean contains(T value);
+ boolean contains(T value);
/**
* Get number of nodes in the tree.
+ *
* @return Number of nodes in the tree.
*/
- public int size();
+ int size();
/**
* Validate the tree according to the invariants.
+ *
* @return True if the tree is valid.
*/
- public boolean validate();
+ boolean validate();
/**
* Get Tree as a Java compatible Collection
+ *
* @return Java compatible Collection
*/
- public java.util.Collection toCollection();
+ java.util.Collection toCollection();
}
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/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/dfs/\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"
new file mode 100644
index 0000000..9799f09
--- /dev/null
+++ "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"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.dfs;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * https://leetcode-cn.com/problems/reverse-string/
+ *
+ * @author Zhang Peng
+ * @since 2020-07-08
+ */
+public class 反转字符串 {
+
+ public static void main(String[] args) {
+ char[] str = { 'h', 'e', 'l', 'l', 'o' };
+ reverseString(str);
+ Assertions.assertArrayEquals(new char[] { 'o', 'l', 'l', 'e', 'h' }, str);
+
+ char[] str2 = { 'H', 'a', 'n', 'n', 'a', 'h' };
+ reverseString(str2);
+ Assertions.assertArrayEquals(new char[] { 'h', 'a', 'n', 'n', 'a', 'H' }, str2);
+ }
+
+ public static void reverseString(char[] s) {
+ if (s == null || s.length == 0) return;
+ int l = 0, r = s.length - 1;
+ recursive(s, l, r);
+ }
+
+ private static void recursive(char[] s, int l, int r) {
+ if (l >= r) return;
+ char temp = s[l];
+ s[l] = s[r];
+ s[r] = temp;
+ recursive(s, l + 1, r - 1);
+ }
+
+}
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/divide/N\346\254\241\345\271\202.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/N\346\254\241\345\271\202.java"
new file mode 100644
index 0000000..80a3464
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/N\346\254\241\345\271\202.java"
@@ -0,0 +1,75 @@
+package io.github.dunwu.algorithm.divide;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 50. Pow(x, n) 求 N次幂
+ *
+ * @author Zhang Peng
+ * @see 50. Pow(x, n)
+ * @since 2020-07-02
+ */
+public class N次幂 {
+
+ public static void main(String[] args) {
+ Assertions.assertEquals(1024.00000, myPow(2.00000, 10));
+ Assertions.assertEquals(9.261000000000001, myPow(2.10000, 3));
+ Assertions.assertEquals(0.25000, myPow(2.00000, -2));
+
+ Assertions.assertEquals(1024.00000, myPow2(2.00000, 10));
+ Assertions.assertEquals(9.261000000000001, myPow2(2.10000, 3));
+ Assertions.assertEquals(0.25000, myPow2(2.00000, -2));
+ }
+
+ /**
+ * 分治算法
+ *
+ * x^n 可以视为 y^2(n为偶数) 或 x*y^2(n为奇数)
+ *
+ * 时间复杂度:O(log N)
+ */
+ public static double myPow(double x, int n) {
+ if (n > 0) return helper(x, n);
+ return 1.00000 / helper(x, -n);
+ }
+
+ public static double helper(double x, int n) {
+ if (n == 0) {
+ return 1.00000;
+ }
+
+ double y = helper(x, n / 2);
+ if (n % 2 == 0) {
+ return y * y;
+ } else {
+ return x * y * y;
+ }
+ }
+
+ /**
+ * 暴力破解法
+ *
+ * 时间复杂度:O(N)
+ */
+ public static double myPow2(double x, int n) {
+ double result = 1.00000;
+
+ if (n == 0) {
+ return 1.00000;
+ }
+
+ int cnt = n > 0 ? n : -n;
+ for (int i = 0; i < cnt; i++) {
+ result *= x;
+ }
+
+ if (n < 0) result = 1.00000 / result;
+ return result;
+ }
+
+ // 库函数
+ public static double myPow3(double x, int n) {
+ return Math.pow(x, n);
+ }
+
+}
diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java
new file mode 100644
index 0000000..f051be8
--- /dev/null
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 分治算法
+ *
+ * @author Zhang Peng
+ * @since 2020-07-02
+ */
+package io.github.dunwu.algorithm.divide;
diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java"
new file mode 100644
index 0000000..a93d092
--- /dev/null
+++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java"
@@ -0,0 +1,48 @@
+package io.github.dunwu.algorithm.divide;
+
+import org.junit.jupiter.api.Assertions;
+
+import java.util.Arrays;
+
+/**
+ * 多数元素
+ *
+ * @author Zhang Peng
+ * @see 多数元素
+ * @since 2020-07-02
+ */
+public class 多数元素 {
+
+ public static void main(String[] args) {
+ Assertions.assertEquals(3, majorityElement(new int[] { 3, 2, 3 }));
+ Assertions.assertEquals(2, majorityElement(new int[] { 2, 2, 1, 1, 1, 2, 2 }));
+ Assertions.assertEquals(6, majorityElement(new int[] { 6, 6, 6, 7, 7 }));
+ }
+
+ // 暴力破解法
+ // 时间复杂度:O(N) + O(log N)
+ public static int majorityElement(int[] nums) {
+ Arrays.sort(nums);
+ int max = 1;
+ int count = 0;
+ int currElem = nums[0];
+ int maxElem = nums[0];
+ for (int i = 0; i < nums.length; i++) {
+ if (nums[i] != currElem) {
+ count = 1;
+ currElem = nums[i];
+ } else {
+ count++;
+ if (maxElem == currElem) {
+ max = count;
+ } else {
+ if (max < count) {
+ maxElem = currElem;
+ }
+ }
+ }
+ }
+ return maxElem;
+ }
+
+}
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/dp/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java
new file mode 100644
index 0000000..48674a2
--- /dev/null
+++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 动态规划算法
+ *
+ * @author Zhang Peng
+ * @since 2020-03-06
+ */
+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/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" "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"
new file mode 100644
index 0000000..98b90e7
--- /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\2722.java"
@@ -0,0 +1,38 @@
+package io.github.dunwu.algorithm.dp.state;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 122. 买卖股票的最佳时机 II
+ *
+ * @author Zhang Peng
+ * @since 2020-07-05
+ */
+public class 买卖股票的最佳时机2 {
+
+ public static void main(String[] args) {
+ int[] prices = { 7, 1, 5, 3, 6, 4 };
+ int[] prices2 = { 1, 2, 3, 4, 5 };
+ Assertions.assertEquals(7, maxProfit(prices));
+ Assertions.assertEquals(4, maxProfit(prices2));
+ }
+
+ public static int maxProfit(int[] prices) {
+ if (prices == null || prices.length == 0) return 0;
+
+ int max = 0;
+ final int days = prices.length;
+ final int[][] dp = new int[days][2];
+
+ dp[0][0] = 0;
+ dp[0][1] = -prices[0];
+
+ for (int i = 1; i < days; i++) {
+ dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
+ max = Math.max(dp[i][0], dp[i][1]);
+ }
+ return max;
+ }
+
+}
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\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"
new file mode 100644
index 0000000..916188a
--- /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\272III.java"
@@ -0,0 +1,46 @@
+package io.github.dunwu.algorithm.dp.state;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Zhang Peng
+ * @see 123. 买卖股票的最佳时机 III
+ * @since 2020-07-05
+ */
+public class 买卖股票的最佳时机III {
+
+ public static void main(String[] args) {
+ int[] prices = { 3, 3, 5, 0, 0, 3, 1, 4 };
+ int[] prices2 = { 1, 2, 3, 4, 5 };
+ Assertions.assertEquals(6, maxProfit(prices));
+ Assertions.assertEquals(4, maxProfit(prices2));
+ }
+
+ public static int maxProfit(int[] prices) {
+ if (prices == null || prices.length == 0) return 0;
+ final int days = prices.length;
+ final int deal = 2; // 交易笔数
+
+ // 定义二维数组
+ // 一维表示第 i 天
+ // 二维表示交易笔数,最多 2 笔
+ // 三维表示是否持有股票:0/1(持有)
+ int[][][] dp = new int[days][deal + 1][2];
+
+ // 第一天数据初始化
+ for (int k = 0; k <= deal; k++) {
+ dp[0][k][0] = 0;
+ dp[0][k][1] = -prices[0];
+ }
+
+ for (int i = 1; i < days; i++) { // 扫描天数
+ for (int k = deal; k >= 1; k--) {
+ dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
+ dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
+ }
+ }
+
+ return dp[days - 1][deal][0];
+ }
+
+}
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\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"
new file mode 100644
index 0000000..f2bb072
--- /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\272IV.java"
@@ -0,0 +1,64 @@
+package io.github.dunwu.algorithm.dp.state;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Zhang Peng
+ * @see 188. 买卖股票的最佳时机 IV
+ * @since 2020-07-05
+ */
+public class 买卖股票的最佳时机IV {
+
+ public static void main(String[] args) {
+ int[] prices = { 2, 4, 1 };
+ int[] prices2 = { 3, 2, 6, 5, 0, 3 };
+ Assertions.assertEquals(2, maxProfit(2, prices));
+ Assertions.assertEquals(7, maxProfit(2, prices2));
+ }
+
+ public static int maxProfit(final int k, int[] prices) {
+ if (prices == null || prices.length == 0) return 0;
+ final int days = prices.length;
+ if (k > days / 2) return maxProfit(prices);
+
+ // 定义二维数组
+ // 一维表示第 i 天
+ // 二维表示交易笔数,最多 2 笔
+ // 三维表示是否持有股票:0/1(持有)
+ int[][][] dp = new int[days][k + 1][2];
+
+ // 第一天数据初始化
+ for (int j = 0; j <= k; j++) {
+ dp[0][j][0] = 0;
+ dp[0][j][1] = -prices[0];
+ }
+
+ for (int i = 1; i < days; i++) { // 扫描天数
+ for (int j = k; j >= 1; j--) {
+ dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
+ dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
+ }
+ }
+
+ return dp[days - 1][k][0];
+ }
+
+ public static int maxProfit(int[] prices) {
+ if (prices == null || prices.length == 0) return 0;
+
+ int max = 0;
+ final int days = prices.length;
+ final int[][] dp = new int[days][2];
+
+ dp[0][0] = 0;
+ dp[0][1] = -prices[0];
+
+ for (int i = 1; i < days; i++) {
+ dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
+ max = Math.max(dp[i][0], dp[i][1]);
+ }
+ return max;
+ }
+
+}
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\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"
new file mode 100644
index 0000000..134b337
--- /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\345\220\253\346\211\213\347\273\255\350\264\271.java"
@@ -0,0 +1,36 @@
+package io.github.dunwu.algorithm.dp.state;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Zhang Peng
+ * @see 714.
+ * 买卖股票的最佳时机含手续费
+ * @since 2020-07-05
+ */
+public class 买卖股票的最佳时机含手续费 {
+
+ public static void main(String[] args) {
+ int[] prices = { 1, 3, 2, 8, 4, 9 };
+ Assertions.assertEquals(8, maxProfit(prices, 2));
+ }
+
+ public static int maxProfit(int[] prices, int fee) {
+ if (prices == null || prices.length == 0) return 0;
+
+ int max = 0;
+ final int days = prices.length;
+ final int[][] dp = new int[days][2];
+
+ dp[0][0] = 0;
+ dp[0][1] = -prices[0];
+
+ for (int i = 1; i < days; i++) {
+ dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
+ dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
+ max = Math.max(dp[i][0], dp[i][1]);
+ }
+ return max;
+ }
+
+}
diff --git "a/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" "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"
new file mode 100644
index 0000000..9836d29
--- /dev/null
+++ "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"
@@ -0,0 +1,40 @@
+package io.github.dunwu.algorithm.dp.state;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Zhang Peng
+ * @see 309. 最佳买卖股票时机含冷冻期
+ * @since 2020-07-05
+ */
+public class 最佳买卖股票时机含冷冻期 {
+
+ public static void main(String[] args) {
+ int[] prices = { 1, 2, 3, 0, 2 };
+ Assertions.assertEquals(3, maxProfit(prices));
+ }
+
+ public static int maxProfit(int[] prices) {
+ if (prices == null || prices.length == 0) return 0;
+
+ int max = 0;
+ final int days = prices.length;
+ final int[][][] dp = new int[days][2][2];
+
+ dp[0][0][0] = 0;
+ dp[0][0][1] = 0;
+ dp[0][1][0] = -prices[0];
+
+ for (int i = 1; i < days; i++) {
+ dp[i][0][0] = Math.max(dp[i - 1][0][0], dp[i - 1][0][1]);
+ dp[i][0][1] = dp[i - 1][1][0] + prices[i];
+ dp[i][1][0] = Math.max(dp[i - 1][0][0] - prices[i], dp[i - 1][1][0]);
+
+ int temp1 = Math.max(dp[i][0][0], dp[i][0][1]);
+ int temp2 = Math.max(dp[i][1][0], temp1);
+ max = Math.max(max, temp2);
+ }
+ return max;
+ }
+
+}
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/dp/subseq/\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"
new file mode 100644
index 0000000..22b2183
--- /dev/null
+++ "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"
@@ -0,0 +1,37 @@
+package io.github.dunwu.algorithm.dp.subseq;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 53. 最大子序和
+ *
+ * @author Zhang Peng
+ * @since 2020-07-06
+ */
+public class 最大子序和 {
+
+ public static void main(String[] args) {
+ int[] nums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
+ Assertions.assertEquals(6, maxSubArray(nums));
+ Assertions.assertEquals(-1, maxSubArray(new int[] { -1 }));
+ }
+
+ public static int maxSubArray(int[] nums) {
+ if (nums == null || nums.length == 0) return 0;
+
+ int[] dp = new int[nums.length + 1];
+ dp[0] = nums[0];
+ int max = dp[0];
+ for (int i = 1; i < nums.length; i++) {
+ dp[i] = dp[i - 1] >= 0 ? dp[i - 1] + nums[i] : nums[i];
+ }
+
+ for (int i = 0; i < nums.length; i++) {
+ if (max < dp[i]) {
+ max = dp[i];
+ }
+ }
+ return max;
+ }
+
+}
diff --git "a/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" "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"
new file mode 100644
index 0000000..d7ff0fe
--- /dev/null
+++ "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"
@@ -0,0 +1,35 @@
+package io.github.dunwu.algorithm.dp.subseq;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * 300. 最长上升子序列
+ *
+ * @author Zhang Peng
+ * @since 2020-07-06
+ */
+public class 最长上升子序列 {
+
+ public static void main(String[] args) {
+ int[] nums = { 10, 9, 2, 5, 3, 7, 101, 18 };
+ Assertions.assertEquals(4, lengthOfLIS(nums));
+ Assertions.assertEquals(1, lengthOfLIS(new int[] { 0 }));
+ }
+
+ public static int lengthOfLIS(int[] nums) {
+ if (nums == null || nums.length == 0) return 0;
+ int max = 1;
+ final int[] dp = new int[nums.length + 1];
+ for (int i = 0; i < nums.length; i++) dp[i] = 1;
+ for (int i = 1; i < nums.length; i++) {
+ for (int j = 0; j < i; j++) {
+ if (nums[j] < nums[i]) {
+ dp[i] = Math.max(dp[i], dp[j] + 1);
+ }
+ }
+ max = Math.max(max, dp[i]);
+ }
+ return max;
+ }
+
+}
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/dp/\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"
new file mode 100644
index 0000000..3f7dff2
--- /dev/null
+++ "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"
@@ -0,0 +1,49 @@
+package io.github.dunwu.algorithm.dp;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Zhang Peng
+ * @since 2020-07-05
+ */
+public class 乘积最大子数组 {
+
+ public static void main(String[] args) {
+ int[] nums = { 2, 3, -2, 4 };
+ int[] nums2 = { -2, 0, -1 };
+ // Assertions.assertEquals(6, maxProduct(nums));
+ Assertions.assertEquals(6, maxProduct2(nums));
+ Assertions.assertEquals(0, maxProduct2(nums2));
+ }
+
+ public static int maxProduct(int[] nums) {
+ return backtrack(nums, 0, 0, 1, 0);
+ }
+
+ // 递归 + 回溯 暴力破解
+ // 时间复杂度 O(2^N)
+ public static int backtrack(int[] nums, int begin, int end, int res, int max) {
+ if (end >= nums.length || begin > end) return max;
+ res *= nums[end];
+ if (res > max) {
+ return backtrack(nums, begin, end + 1, res, res);
+ } else {
+ return backtrack(nums, end + 1, end + 1, 1, max);
+ }
+ }
+
+ public static int maxProduct2(int[] nums) {
+ int min = nums[0];
+ int max = nums[0];
+ int res = nums[0];
+ for (int i = 1; i < nums.length; i++) {
+ int currMax = Math.max(Math.max(nums[i] * max, nums[i] * min), nums[i]);
+ int currMin = Math.min(Math.min(nums[i] * max, nums[i] * min), nums[i]);
+ res = Math.max(currMax, res);
+ max = currMax;
+ min = currMin;
+ }
+ return res;
+ }
+
+}
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/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