From 730f26f950262226baef49b34b8c4c9272fc1d7c Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Thu, 10 Jul 2025 15:32:16 +0800 Subject: [PATCH] =?UTF-8?q?7.7=20=E4=BA=8C=E5=88=B7=20=E4=BA=8C=E5=88=86?= =?UTF-8?q?=E6=9F=A5=E6=89=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 +- src/main/java/backtrack/SolveNQueens.java | 71 ++++++++++++++ .../binary_search/FindMedianSortedArrays.java | 92 +++++++++++-------- src/main/java/binary_search/FindMin.java | 55 +++++++++++ src/main/java/binary_search/Search.java | 32 ++++++- src/main/java/binary_search/SearchInsert.java | 3 + src/main/java/binary_search/SearchMatrix.java | 1 + src/main/java/binary_search/SearchRange.java | 15 +++ src/test/java/array/MaxSubArrayTest.java | 2 +- src/test/java/binary_search/SearchTest.java | 10 ++ 10 files changed, 246 insertions(+), 44 deletions(-) create mode 100644 src/main/java/backtrack/SolveNQueens.java create mode 100644 src/test/java/binary_search/SearchTest.java diff --git a/pom.xml b/pom.xml index 3aa353a..421f504 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,14 @@ junit junit - 4.12 + 4.9 + test + + + + org.hamcrest + hamcrest-core + 1.3 test diff --git a/src/main/java/backtrack/SolveNQueens.java b/src/main/java/backtrack/SolveNQueens.java new file mode 100644 index 0000000..f1a217d --- /dev/null +++ b/src/main/java/backtrack/SolveNQueens.java @@ -0,0 +1,71 @@ +package backtrack; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 题目: 51. N 皇后 (SolveNQueens) + * 描述:按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 + * n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 + * 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + + 示例 1: + 输入:n = 4 + 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] + 解释:如上图所示,4 皇后问题存在两个不同的解法。 + + * 链接:https://leetcode.cn/problems/n-queens/ + */ +//困难题,二刷不会 +public class SolveNQueens { + + /** + * 因为是从上到下 因此只判断是否与左上和右上和正上冲突 下面的内容肯定不冲突 + */ + boolean isValid(char[][] matrix, int row, int col, int n) { + // 1. 同列检查 + for (int i = 0; i < row; i++) { + if (matrix[i][col] == 'Q') return false; + } + // 2. 左上 ↖ 对角线检查 + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (matrix[i][j] == 'Q') return false; + } + // 3. 右上 ↗ 对角线检查 + for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (matrix[i][j] == 'Q') return false; + } + return true; + } + void dfs(int n, int row, List> res, char[][] matrix) { + if (row == n) { + res.add(char2list(matrix)); + return; // 记得加上 return,避免继续往下走 + } + for (int col = 0; col < n; col++) { + if (isValid(matrix, row, col, n)) { + matrix[row][col] = 'Q'; + dfs(n, row + 1, res, matrix); + matrix[row][col] = '.'; + } + } + } + + List char2list(char[][] matrix) { + List res = new ArrayList<>(); + for (char[] row : matrix) { + res.add(new String(row)); // 直接用 String 构造函数 + + } + return res; + } + public List> solveNQueens(int n) { + List> res = new ArrayList<>(); + char[][] matrix = new char[n][n]; + for (int i = 0; i < n; i++) Arrays.fill(matrix[i], '.'); + dfs(n, 0, res, matrix); + return res; + } +} diff --git a/src/main/java/binary_search/FindMedianSortedArrays.java b/src/main/java/binary_search/FindMedianSortedArrays.java index 9788c72..336da1d 100644 --- a/src/main/java/binary_search/FindMedianSortedArrays.java +++ b/src/main/java/binary_search/FindMedianSortedArrays.java @@ -14,51 +14,69 @@ package binary_search; */ //不会 +//二刷不会 public class FindMedianSortedArrays { + public double findMedianSortedArrays(int[] nums1, int[] nums2) { - int m = nums1.length, n = nums2.length; - int total = m + n; - if (total % 2 == 1) { - // 奇数长度,直接取第 (total+1)/2 小 - return getKth(nums1, 0, nums2, 0, (total + 1) / 2); + int length1 = nums1.length, length2 = nums2.length; + int totalLength = length1 + length2; + if (totalLength % 2 == 1) { + int midIndex = totalLength / 2; + double median = getKthElement(nums1, nums2, midIndex + 1); + return median; } else { - // 偶数长度,取第 total/2 和 total/2+1 两个数的平均 - double left = getKth(nums1, 0, nums2, 0, total / 2); - double right= getKth(nums1, 0, nums2, 0, total / 2 + 1); - return (left + right) / 2.0; + int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2; + double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0; + return median; } } - // 返回 nums1[start1...] 和 nums2[start2...] 中,第 k 小的数 - private double getKth(int[] nums1, int start1, int[] nums2, int start2, int k) { - // 如果 nums1 已经“耗尽”,直接从 nums2 里取 - if (start1 >= nums1.length) { - return nums2[start2 + k - 1]; - } - // 如果 nums2 耗尽 - if (start2 >= nums2.length) { - return nums1[start1 + k - 1]; - } - // 如果 k == 1,取两数组当前头部较小者 - if (k == 1) { - return Math.min(nums1[start1], nums2[start2]); - } + public int getKthElement(int[] nums1, int[] nums2, int k) { + /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 + * 这里的 "/" 表示整除 + * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 + * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 + * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 + * 这样 pivot 本身最大也只能是第 k-1 小的元素 + * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 + * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 + * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 + */ - // 各自看 k/2 位置的元素,如果超出边界,则视作 +∞ - int midVal1 = (start1 + k/2 - 1 < nums1.length) - ? nums1[start1 + k/2 - 1] - : Integer.MAX_VALUE; - int midVal2 = (start2 + k/2 - 1 < nums2.length) - ? nums2[start2 + k/2 - 1] - : Integer.MAX_VALUE; + int index1 = 0, index2 = 0; + int len1 = nums1.length, len2 = nums2.length; - // 抛弃较小者及其前面 k/2 个元素 - if (midVal1 < midVal2) { - // 抛弃 nums1[start1 ... start1 + k/2 - 1] - return getKth(nums1, start1 + k/2, nums2, start2, k - k/2); - } else { - // 抛弃 nums2[start2 ... start2 + k/2 - 1] - return getKth(nums1, start1, nums2, start2 + k/2, k - k/2); + while (true) { + // 如果 nums1 已经全部“删除”,第 k 小就是 nums2[index2 + k - 1] + if (index1 == len1) { + return nums2[index2 + k - 1]; + } + // 如果 nums2 已经全部“删除”,第 k 小就是 nums1[index1 + k - 1] + if (index2 == len2) { + return nums1[index1 + k - 1]; + } + // 如果 k == 1,直接返回两数组当前指针处的较小值 + if (k == 1) { + return Math.min(nums1[index1], nums2[index2]); + } + + // 每次比较 nums1 和 nums2 各自第 k/2 个候选(如果不足 k/2,则取末尾) + int half = k / 2; + int newIndex1 = Math.min(index1 + half, len1) - 1; + int newIndex2 = Math.min(index2 + half, len2) - 1; + int pivot1 = nums1[newIndex1]; + int pivot2 = nums2[newIndex2]; + + // 丢弃 pivot 值较小的那半段 + if (pivot1 <= pivot2) { + // 丢弃 nums1[index1 … newIndex1] 这部分,共 newIndex1 - index1 + 1 个元素 + k -= (newIndex1 - index1 + 1); + index1 = newIndex1 + 1; + } else { + // 丢弃 nums2[index2 … newIndex2] 这部分 + k -= (newIndex2 - index2 + 1); + index2 = newIndex2 + 1; + } } } } diff --git a/src/main/java/binary_search/FindMin.java b/src/main/java/binary_search/FindMin.java index c6af274..786efdd 100644 --- a/src/main/java/binary_search/FindMin.java +++ b/src/main/java/binary_search/FindMin.java @@ -16,7 +16,9 @@ package binary_search; *

* 链接:https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/ */ +//二刷不会 public class FindMin { + /** * 判断区间是否已经有序 * @@ -50,4 +52,57 @@ public class FindMin { return nums[left]; } + public int findMax(int[] nums) { + int left = 0, right = nums.length - 1; + // 当区间尚未缩到只剩一个元素 + while (left < right) { + // 如果当前区间已经是严格升序(没有经过旋转),那么最大值就是区间末尾 + if (nums[left] < nums[right]) { + return nums[right]; + } + int mid = left + (right - left) / 2; + // 如果 nums[mid] 比 nums[left] 大,说明 [left…mid] 是严格升序的, + // 这段的最大值就是 nums[mid],而且全局上一定存在比 nums[mid] 更小的元素在右边 + // ——但是我们要找的是最大值,此时候,要把搜索区间缩到 [mid…right] + if (nums[mid] > nums[left]) { + left = mid; + } + // 否则,nums[mid] ≤ nums[left],说明旋转点(最大值)在 [left…mid-1] 里 + else { + right = mid - 1; + } + } + // 最终 left == right,指向的就是最大值 + return nums[left]; + } + + private int findMinIndex(int[] nums) { + int left = 0, right = nums.length - 1; + while (left < right) { + // 如果当前区间完全有序 + if (nums[left] < nums[right]) { + return left; + } + int mid = left + (right - left) / 2; + if (nums[mid] >= nums[left]) { + // [left…mid] 单调,最小值一定在 mid+1…right + left = mid + 1; + } else { + // mid 在左侧的旋转段里,min 可能就是 mid + right = mid; + } + } + return left; + } + + /** + * 用一次二分找到最小值,然后取它左边的元素作为最大值 + */ + public int findMax2(int[] nums) { + int n = nums.length; + int pivot = findMinIndex(nums); + // pivot 左边的元素是最大值 + return nums[(pivot - 1 + n) % n]; + } + } diff --git a/src/main/java/binary_search/Search.java b/src/main/java/binary_search/Search.java index 0eca099..c02eec0 100644 --- a/src/main/java/binary_search/Search.java +++ b/src/main/java/binary_search/Search.java @@ -13,35 +13,57 @@ package binary_search; * 链接:https://leetcode.cn/problems/search-in-rotated-sorted-array/ */ //不会 +//二刷不会 public class Search { + + /** + * 在旋转排序数组 nums 中查找目标值 target + * @param nums 旋转排序后的数组 + * @param target 目标值 + * @return 目标值在数组中的下标,如果不存在则返回 -1 + */ public int search(int[] nums, int target) { int n = nums.length; + // 数组为空时,直接返回 -1 if (n == 0) { return -1; } + // 数组长度为 1,直接判断唯一元素是否为 target if (n == 1) { return nums[0] == target ? 0 : -1; } + + // 双指针定义搜索区间 [l, r] int l = 0, r = n - 1; + // 进入二分查找 while (l <= r) { - int mid = (l + r) / 2; + // 防止 (l + r) 溢出,计算中点 + int mid = l + (r - l) / 2; + // 如果中点即为目标,直接返回 if (nums[mid] == target) { return mid; } + + // 判断哪一段是单调升序的: + // 如果 nums[0] <= nums[mid],说明从索引 0 到 mid 是升序的 if (nums[0] <= nums[mid]) { + // 如果 target 在这段升序区间内,则向左继续搜索,否则搜索右半区 if (nums[0] <= target && target < nums[mid]) { - r = mid - 1; + r = mid - 1; // 缩小右边界 } else { - l = mid + 1; + l = mid + 1; // 移动左边界 } } else { + // 否则,右半区 [mid, n-1] 是升序的 + // 如果 target 在右半区范围内,则向右继续搜索,否则搜索左半区 if (nums[mid] < target && target <= nums[n - 1]) { - l = mid + 1; + l = mid + 1; // 移动左边界 } else { - r = mid - 1; + r = mid - 1; // 缩小右边界 } } } + // 未找到目标,返回 -1 return -1; } } diff --git a/src/main/java/binary_search/SearchInsert.java b/src/main/java/binary_search/SearchInsert.java index 332ce54..487de63 100644 --- a/src/main/java/binary_search/SearchInsert.java +++ b/src/main/java/binary_search/SearchInsert.java @@ -12,7 +12,10 @@ package binary_search; * 链接:https://leetcode.cn/problems/search-insert-position/ */ //第一道二分法的题,不熟练 +// 二刷会做 public class SearchInsert { + + public int searchInsert(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { diff --git a/src/main/java/binary_search/SearchMatrix.java b/src/main/java/binary_search/SearchMatrix.java index feb5054..43fe9e6 100644 --- a/src/main/java/binary_search/SearchMatrix.java +++ b/src/main/java/binary_search/SearchMatrix.java @@ -14,6 +14,7 @@ package binary_search; * 链接:https://leetcode.cn/problems/search-a-2d-matrix/ */ +//二刷会做 public class SearchMatrix { public boolean searchMatrix(int[][] matrix, int target) { int r=matrix.length; diff --git a/src/main/java/binary_search/SearchRange.java b/src/main/java/binary_search/SearchRange.java index 6d6af24..0de1f88 100644 --- a/src/main/java/binary_search/SearchRange.java +++ b/src/main/java/binary_search/SearchRange.java @@ -11,6 +11,7 @@ package binary_search; * 链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/ */ +//二刷不会高效方法 public class SearchRange { //极端情况下不是O(log n) public int[] searchRange(int[] nums, int target) { @@ -46,19 +47,33 @@ public class SearchRange { } private int findLeft(int[] nums, int target) { + // 搜索区间为 [left, right] int left = 0, right = nums.length - 1; + // 用来记录 target 最左出现的位置,初始为 -1 表示未找到 int index = -1; + + // 当搜索区间有效时,继续二分 while (left <= right) { + // 计算中点,写法能避免 (left+right) 溢出 int mid = left + (right - left) / 2; + + // 如果中点元素 >= target,说明左边可能还存在 target if (nums[mid] >= target) { + // 收缩右边界到 mid - 1,继续在左半区查找 right = mid - 1; } else { + // nums[mid] < target,说明 target 只能出现在右半区 left = mid + 1; } + + // 如果在 mid 处发现了 target,就更新 index 为 mid + // 但不立即返回,因为还要继续向左看看是否还有更左的 target if (nums[mid] == target) { index = mid; } } + + // 循环结束后,index 要么是最左的 target 位置,要么仍为 -1(没找到) return index; } diff --git a/src/test/java/array/MaxSubArrayTest.java b/src/test/java/array/MaxSubArrayTest.java index 0055491..6ee1aad 100644 --- a/src/test/java/array/MaxSubArrayTest.java +++ b/src/test/java/array/MaxSubArrayTest.java @@ -2,7 +2,7 @@ package array; import org.junit.Test; -import static org.junit.Assert.*; +import java.util.Arrays; public class MaxSubArrayTest { diff --git a/src/test/java/binary_search/SearchTest.java b/src/test/java/binary_search/SearchTest.java new file mode 100644 index 0000000..1d7f5bd --- /dev/null +++ b/src/test/java/binary_search/SearchTest.java @@ -0,0 +1,10 @@ +package binary_search; + +import junit.framework.TestCase; + +public class SearchTest extends TestCase { + + public void testSearch() { + + } +} \ No newline at end of file