二分查找之习题分析

一、在旋转有序的数组中搜索

(一)、题目需求

​ 给你一个升序排列的整数数组 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:目标值小于中间值,同时目标值小于开始值。所以寻找的目标位于中间值的后面,因此将开始值往后移动。
  • 假如中间值小于开始值:则意味着旋转发生在中间值的前面。
    • 情况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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值