二分查找之习题分析

  • Post author:
  • Post category:其他




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



(一)、题目需求

​ 给你一个升序排列的整数数组

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



版权声明:本文为hyyyya原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。