7.7 二刷 二分查找

This commit is contained in:
zhangsan 2025-07-10 15:32:16 +08:00
parent ce64f93c76
commit 730f26f950
10 changed files with 246 additions and 44 deletions

View File

@ -16,7 +16,14 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.9</version>
<scope>test</scope>
</dependency>
<!-- 显式添加 Hamcrest 核心库 -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version> <!-- 或者用最新的 2.2 -->
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -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<List<String>> 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<String> char2list(char[][] matrix) {
List<String> res = new ArrayList<>();
for (char[] row : matrix) {
res.add(new String(row)); // 直接用 String 构造函数
}
return res;
}
public List<List<String>> solveNQueens(int n) {
List<List<String>> 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;
}
}

View File

@ -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];
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 的值减去删除的数的个数
*/
int index1 = 0, index2 = 0;
int len1 = nums1.length, len2 = nums2.length;
while (true) {
// 如果 nums1 已经全部删除 k 小就是 nums2[index2 + k - 1]
if (index1 == len1) {
return nums2[index2 + k - 1];
}
// 如果 nums2 耗尽
if (start2 >= nums2.length) {
return nums1[start1 + k - 1];
// 如果 nums2 已经全部删除 k 小就是 nums1[index1 + k - 1]
if (index2 == len2) {
return nums1[index1 + k - 1];
}
// 如果 k == 1取两数组当前头部较小者
// 如果 k == 1直接返回两数组当前指针处的较小值
if (k == 1) {
return Math.min(nums1[start1], nums2[start2]);
return Math.min(nums1[index1], nums2[index2]);
}
// 各自看 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;
// 每次比较 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];
// 抛弃较小者及其前面 k/2 个元素
if (midVal1 < midVal2) {
// 抛弃 nums1[start1 ... start1 + k/2 - 1]
return getKth(nums1, start1 + k/2, nums2, start2, k - k/2);
// 丢弃 pivot 值较小的那半段
if (pivot1 <= pivot2) {
// 丢弃 nums1[index1 newIndex1] 这部分 newIndex1 - index1 + 1 个元素
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
// 抛弃 nums2[start2 ... start2 + k/2 - 1]
return getKth(nums1, start1, nums2, start2 + k/2, k - k/2);
// 丢弃 nums2[index2 newIndex2] 这部分
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}

View File

@ -16,7 +16,9 @@ package binary_search;
* <p>
* 链接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] 说明 [leftmid] 是严格升序的
// 这段的最大值就是 nums[mid]而且全局上一定存在比 nums[mid] 更小的元素在右边
// 但是我们要找的是最大值此时候要把搜索区间缩到 [midright]
if (nums[mid] > nums[left]) {
left = mid;
}
// 否则nums[mid] nums[left]说明旋转点最大值 [leftmid-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]) {
// [leftmid] 单调最小值一定在 mid+1right
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];
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -2,7 +2,7 @@ package array;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.Arrays;
public class MaxSubArrayTest {

View File

@ -0,0 +1,10 @@
package binary_search;
import junit.framework.TestCase;
public class SearchTest extends TestCase {
public void testSearch() {
}
}