算法-二分查找

1. 搜索插入位置

leetcode题目链接:35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

复制代码
输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

复制代码
输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

复制代码
输入: nums = [1,3,5,6], target = 7
输出: 4

解法:

java 复制代码
class Solution {
    /**
     * 在已排序数组中查找目标值的位置,若不存在则返回应插入的位置
     * @param nums 已按升序排列的整型数组
     * @param target 需要查找的目标值
     * @return 目标值的索引(存在时)或应插入的位置(不存在时)
     */
    public int searchInsert(int[] nums, int target) {
        // 初始化二分查找的左右边界指针
        int low = 0;
        int high = nums.length - 1;
        
        // 标准二分查找框架
        while (low <= high) {
            // 计算中间位置,避免整数溢出
            int mid = low + (high - low) / 2;  // 等同于 (low + high)/2
            
            if (nums[mid] == target) {
                // 找到目标值,直接返回索引
                return mid;
            } else if (nums[mid] < target) {
                // 中间值小于目标值,说明目标在右半区间
                // 调整左边界(排除中间位置)
                low = mid + 1;
            } else {
                // 中间值大于目标值,说明目标在左半区间
                // 调整右边界(排除中间位置)
                high = mid - 1;
            }
        }
        
        // 当循环结束时,low指针的位置表示:
        // 1. 数组中第一个大于target的元素位置
        // 2. 当target大于所有元素时,low指向数组末尾(nums.length)
        // 这正是target应该插入的位置
        return low;
    }
}

2. 搜索二维矩阵

leetcode题目链接:74. 搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false

示例 1:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

解法:

java 复制代码
class Solution {
    /**
     * 解法一:先二分查找所在的行,再二分查找所在的列
     */
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        // 先二分查找锁定在哪一行
        int row = -1;
        int startRow = 0;
        int endRow = m-1;
        while(startRow <= endRow){
            int midRow = (startRow+endRow)/2;

            if(target >= matrix[midRow][0] && target <= matrix[midRow][n-1]){
                row = midRow;
            }

            if(target < matrix[midRow][0]){
                endRow = midRow-1;
            }else{
                startRow = midRow+1;
            }
        }
        if(row == -1){
            return false;
        }

        // 锁定行之后,在指定行二分查找对应的列
        int col = -1;
        int startCol = 0;
        int endCol = n-1;
        while(startCol <= endCol){
            int midCol = (startCol+endCol)/2;

            if(target == matrix[row][midCol]){
                return true;
            }

            if(target < matrix[row][midCol]){
                endCol = midCol-1;
            }else{
                startCol = midCol+1;
            }
        }

        return false;
    }

    /**
     * 解法二:把二位矩阵转换为一维大矩阵,再二分查找
     */
    public boolean searchMatrix1(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int left = 0;
        int right = m * n - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            int row = mid / n;
            int col = mid % n;
            int midValue = matrix[row][col];
            
            if (midValue == target) {
                return true;
            } else if (midValue < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return false;
    }


}

3. 在排序数组中查找元素的第一个和最后一个位置

leetcode题目链接:34. 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

复制代码
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

复制代码
输入:nums = [], target = 0
输出:[-1,-1]

解法:

java 复制代码
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[]{-1, -1};
        if (nums == null || nums.length == 0) {
            return result;
        }
        result[0] = findLeftBound(nums, target);
        result[1] = findRightBound(nums, target);
        return result;
    }

    // 寻找目标值的左边界
    private int findLeftBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int index = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2; // 防止整数溢出
            if (nums[mid] == target) {
                index = mid; // 记录当前位置,继续向左寻找更小的索引
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return index;
    }

    // 寻找目标值的右边界
    private int findRightBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int index = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                index = mid; // 记录当前位置,继续向右寻找更大的索引
                left = mid + 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return index;
    }
}

4. 搜索旋转排序数组

leetcode题目链接:33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 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

解法:

java 复制代码
class Solution {
        public int search(int[] nums, int target) {
            int left = 0, right = nums.length - 1;
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] == target) {
                    return mid;
                }
                // 判断左半部分是否有序
                if (nums[left] <= nums[mid]) {
                    // 如果目标在左半有序区间内
                    if (target >= nums[left] && target < nums[mid]) {
                        right = mid - 1;
                    } else {
                        left = mid + 1;
                    }
                } else { // 右半部分有序
                    // 如果目标在右半有序区间内
                    if (target > nums[mid] && target <= nums[right]) {
                        left = mid + 1;
                    } else {
                        right = mid - 1;
                    }
                }
            }
            return -1;
        }
    }

5. 寻找旋转排序数组中的最小值

leetcode题目链接:153. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

复制代码
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

复制代码
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

解法:

java 复制代码
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int result = nums[0];

        while(left <= right){
            int mid = left + (right - left)/2;

            // 左半侧是有序的,找到左边的最小值,然后再去找右半侧
            if(nums[left] <= nums[mid]){
                result = Math.min(result, nums[left]);
                left = mid+1;
                // 右半侧是有序的,找到右边的最小值,然后再去找左半侧
            }else{
                result = Math.min(result, nums[mid]);
                right = mid-1;
            }
        }

        return result;
    }
}

6. 寻找两个正序数组的中位数

leetcode题目链接:4. 寻找两个正序数组的中位数

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

示例 1:

复制代码
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

复制代码
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

解法:hard,没看懂

java 复制代码
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int total = m + n;
        if (total % 2 == 1) {
            // 总长度为奇数,返回第 (total+1)/2 小的元素
            return findKth(nums1, 0, nums2, 0, (total + 1) / 2);
        } else {
            // 总长度为偶数,返回第 total/2 和 total/2 +1 小的元素的平均值
            int left = findKth(nums1, 0, nums2, 0, total / 2);
            int right = findKth(nums1, 0, nums2, 0, total / 2 + 1);
            return (left + right) / 2.0;
        }
    }

    // 辅助函数:找到两个有序数组中第k小的元素
    private int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
        // 如果nums1的起始位置超过数组长度,说明nums1已全部排除,直接取nums2的第k个元素
        if (i >= nums1.length) {
            return nums2[j + k - 1];
        }
        // 同理处理nums2的情况
        if (j >= nums2.length) {
            return nums1[i + k - 1];
        }
        // 当k=1时,返回两个数组当前起始位置的较小值
        if (k == 1) {
            return Math.min(nums1[i], nums2[j]);
        }

        // 计算两个数组的中间位置,用于比较
        int mid1 = i + k / 2 - 1;
        int mid2 = j + k / 2 - 1;

        // 处理越界情况,若越界则取最大值,确保排除另一数组的部分元素
        int val1 = (mid1 < nums1.length) ? nums1[mid1] : Integer.MAX_VALUE;
        int val2 = (mid2 < nums2.length) ? nums2[mid2] : Integer.MAX_VALUE;

        // 比较中间值,排除较小值所在数组的前k/2个元素
        if (val1 <= val2) {
            // 排除nums1的前k/2个元素,更新起始位置和k值
            return findKth(nums1, mid1 + 1, nums2, j, k - k / 2);
        } else {
            // 排除nums2的前k/2个元素
            return findKth(nums1, i, nums2, mid2 + 1, k - k / 2);
        }
    }
}
相关推荐
-Thinker6 小时前
贪心算法解决找零钱问题
算法·贪心算法
sin_hielo6 小时前
leetcode 2054(排序 + 单调栈,通用做法是 DP)
数据结构·算法·leetcode
晨晖26 小时前
直接插入排序
c语言·数据结构·c++·算法
HUST6 小时前
C 语言 第七讲:数组和函数实践:扫雷游戏
c语言·开发语言·数据结构·vscode·算法·游戏·c#
玖剹6 小时前
字符串相关题目
c语言·c++·算法·leetcode
llz_1126 小时前
图(邻接表)-(DFS/BFS)-Dijkstra
算法·深度优先·dijkstra·宽度优先
派葛穆6 小时前
机器人-六轴机械臂的逆运动学
算法·机器学习·机器人
那雨倾城6 小时前
用 YOLO Pose + Segmentation 在PiscCode构建“语义佛光”:一次实时视觉语义融合实验
图像处理·python·opencv·算法·yolo·计算机视觉·视觉检测
nnerddboy6 小时前
解决传统特征波段选择的局限性:1.对偶学习
学习·算法·机器学习