二分查找之习题分析
一、在旋转有序的数组中搜索
(一)、题目需求
给你一个升序排列的整数数组
nums
,和一个整数
target
。
假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组
[0,1,2,4,5,6,7]
可能变为
[4,5,6,7,0,1,2]
)。
请你在数组中搜索
target
,如果数组中存在这个目标值,则返回它的索引,否则返回
-1
。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
```
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
```
示例 3:
输入:nums = [1], target = 0
输出:-1
```
(二)、解法
public class Solution {
public static void main(String[] args) {
System.out.println(Solution.search(new int[]{4, 5, 6, 7, 0, 1, 2}, 0));
}
public static int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] > nums[start]) {
if (nums[mid] >= target && target >= nums[start]) {
end = mid;
} else {
start = mid;
}
} else {
if (nums[mid] <= target && target <= nums[end]) {
start = mid;
} else {
end = mid;
}
}
}
if (nums[start] == target) {
return start;
}
if (nums[end] == target) {
return end;
}
return -1;
}
}
(三)、代码解析
1、设置开始指针、结束指针、中间指针
int start = 0;
int end = nums.length - 1;
int mid;
2、计算mid值,通过该方式计算可避免大数的情况
mid = start + (end - start) / 2;
if (nums[mid] == target) {
return mid;
}
3、进行判断:
-
假如中间值大于开始值:则意味着旋转发生在中间值的后面。
-
情况1:目标值小于中间值,同时目标值大于开始值。所以寻找的目标位于中间值的前面,因此将结束值往前移动。
-
情况2:目标值大于中间值。所以寻找的目标位于中间值的后面,因此将开始值往后移动。
- 情况3:目标值小于中间值,同时目标值小于开始值。所以寻找的目标位于中间值的后面,因此将开始值往后移动。
-
情况2:目标值大于中间值。所以寻找的目标位于中间值的后面,因此将开始值往后移动。
-
情况1:目标值小于中间值,同时目标值大于开始值。所以寻找的目标位于中间值的前面,因此将结束值往前移动。
-
假如中间值小于开始值:则意味着旋转发生在中间值的前面。
- 情况1:目标值大于中间值,同时目标值小于开始值。所以寻找的目标位于中间值的后面,因此将开始值往后移动。
- 情况2:目标值小于中间值。所以寻找的目标位于中间值的前面,因此将结束值往前移动。
- 情况3:目标大于中间值,同时目标值大于开始值。所以寻找的目标位于中间值的前面,因此将结束值往前移动。
if (nums[mid] > nums[start]) {
if (nums[mid] >= target && target >= nums[start]) {
end = mid;
} else {
start = mid;
}
} else {
if (nums[mid] <= target && target <= nums[end]) {
start = mid;
} else {
end = mid;
}
}
二、在旋转有序的数组中寻找最小数值
(一)、题目需求
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组
[0,1,2,4,5,6,7]
可能变为
[4,5,6,7,0,1,2]
)。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
(二)、解法
public class Solution {
public static void main(String[] args) {
Solution.findMin(
new int[]{5, 1, 2, 3, 4}
);
}
public static int findMin(int[] nums) {
if (nums == null || nums.length == 0) {
return -1;
}
if (nums[0] <= nums[nums.length - 1]) {
return nums[0];
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (nums[mid] >= nums[end]) {
start = mid;
} else {
end = mid;
}
}
return nums[start] <= nums[end] ? nums[start] : nums[end];
}
}
(三)、代码解析
1、首先判断是否有序,如果第一个值小于等于最后一个值,说明无发生旋转,直接返回第一个值即可。
if (nums[0] <= nums[nums.length - 1]) {
return nums[0];
}
2、设置开始指针、结束指针、中间指针
int start = 0;
int end = nums.length - 1;
int mid;
3、计算中间值,同时判断中间值和结束值的大小。
- 情况1:中间值大于结束值,说明旋转发生在中间值后面,所以将开始值往后移动。
- 情况2:中间值小于结束值,说明旋转发生在中间值前面,所以将结束值往前移动。
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (nums[mid] >= nums[end]) {
start = mid;
} else {
end = mid;
}
}
三、寻找峰值
(一)、题目需求
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组
nums
,其中
nums[i] ≠ nums[i+1]
,找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设
nums[-1] = nums[n] = -∞
。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
(二)、解法
public class Solution {
public int findPeakElement(int[] nums) {
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]) {
return mid;
}
if (nums[mid] < nums[mid + 1]) {
start = mid;
}
if (nums[mid] > nums[mid + 1]) {
end = mid;
}
}
return nums[start] > nums[end] ? start : end;
}
}
(三)、代码解析
1、设置开始指针、结束指针、中间指针
int start = 0;
int end = nums.length - 1;
int mid;
2、设置中间值,并进行判断
- 情况1:如果中间值大于它的前一个值,同时大于它的后一个值。说明该中间值为峰值,直接返回。
- 情况2:如果中间值小于它的后一个值,说明它处于一个山峰的上坡中,所以将开始值往后移动,移至中间值的位置。
- 情况3:如果中间值大于它的后一个值,说明它处于一个山峰的下坡中,所以将结束值往前移动,移至中间值的位置。
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]) {
return mid;
}
if (nums[mid] < nums[mid + 1]) {
start = mid;
}
if (nums[mid] > nums[mid + 1]) {
end = mid;
}
}
四、切木头
(一)、题目需求
给定一个输入数组
nums
以及规定木头的段数,找出
nums
中全部数值可以给出最大的数。
示例 1:
输入: nums = [232,124,456]; k = 7
输出: 114
解释: 需要7段木头,232,124,456分别按照114进行切分可以得到7段木头的同时,每一段木头为最大值
示例 2:
输入: nums = [232,124,456]; k = 1
输出: 456
解释: 需要1段木头,232,124,456分别按照456进行切分可以得到1段木头的同时,每一段木头为最大值
(二)、解法
public class WoodCut {
public static void main(String[] args) {
int[] L = new int[]{232, 124, 456};
System.out.println(new WoodCut().woodCut(L, 7));
}
public int woodCut(int[] L, int k) {
if (L == null || L.length == 0) {
return 0;
}
int start = 0;
int end = getMax(L);
int mid;
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (getPieces(L, mid) >= k) {
start = mid;
} else {
end = mid;
}
}
if (getPieces(L, end) >= k) {
return end;
}
if (getPieces(L, start) >= k) {
return start;
}
return 0;
}
private int getPieces(int[] l, int mid) {
int pieces = 0;
for (int i = 0; i < l.length; i++) {
pieces += l[i] / mid;
}
return pieces;
}
private int getMax(int[] l) {
int max = l[0];
for (int i = 1; i < l.length; i++) {
if (l[i] > max) {
max = l[i];
}
}
return max;
}
}
(三)、代码解析
1、计算出该数组中的最大值
private int getMax(int[] l) {
int max = l[0];
for (int i = 1; i < l.length; i++) {
if (l[i] > max) {
max = l[i];
}
}
return max;
}
2、计算段数,该数组按照mid值进行切分,得出的段数数量
private int getPieces(int[] l, int mid) {
int pieces = 0;
for (int i = 0; i < l.length; i++) {
pieces += l[i] / mid;
}
return pieces;
}
3、设置开始指针、结束指针、中间指针
int start = 0;
int end = getMax(L);
int mid;
4、计算中间值,并进行判断
- 情况1:如果按照该中间值得出的段数大于给定的段数,则说明中间值还可以取更大值,因此开始值往后移动。
- 情况2:如果按照该中间值得出的段数小于给定的段数,则说明中间值需要取更小值,因此结束值往前移动。
while (start + 1 < end) {
mid = start + (end - start) / 2;
if (getPieces(L, mid) >= k) {
start = mid;
} else {
end = mid;
}
}