LeetCode刷题Day01——数组(二分查找)

简介: 二分查找

一、二分查找

题目链接:704.二分查找


/**
 * <pre>
 * <p>最朴素的二分查找问题</p>
 * 
 * 可以采用while循环进行查找,也可以采用递归进行查找
 * 原理都一样,每次选择中间值进行判断,不断缩小查找的区间
 * 左右指针不断靠拢,直到左右指针重叠时如果还找不到,这时要么左指针+1大于右指针,要么右指针-1小于左指针,这时便会结束循环或结束递归
 * 查找区间分为左闭右闭和左闭右开,具体差别会在下面解释
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/2 11:44
 */
public class 二分查找704 {
    public int search1(int[] nums, int target) {
        // 需要注意的是这里右边界是长度-1,因为数组下标从0开始
        // 这里维护的是一个左闭右闭的区间
        return bisection(nums, 0, nums.length-1, target);
    }
    // 递归方式
    public int bisection(int[] nums, int left, int right, int target) {
        // 左指针已经位于右指针右边,表示查询不到结果
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            return mid;  
        } else if (nums[mid] < target) {
            return bisection(nums, mid+1, right, target);
        } else {
            return bisection(nums, left, mid-1, target);
        }
    }
    // 循环方式——左闭右闭(更好理解)
    public int search2(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        // 1.这里之所以使用的是<=号是因为维护的是左闭右闭的区间,所以两者相等时的值是有意义的
        // 2.如果是左闭右开区间,则上面的right指针不需要-1,直接等于长度
        while (left <= right) {
            int mid = (right - left) / 2 + left;
            int num = nums[mid];
            if (num == target) {
                return mid;
            } else if (num > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
    // 循环方式——左闭右开
    public int search3(int[] nums, int target) {
        int left = 0, right = nums.length;
        // 这里的长度不需要-1,因为维护的是左闭右开区间
        // 这时候当num<target的时候,right=mid,同理也不需要-1
        // 当num>target的时候,left=mid+1,因为左闭且mid值已经判断过了,可以判断下一个,所以需要加1
        // 循环结束条件:
        //      因为右边是开区间,所以其实在这个位置的值是无效的,所以只需要left<right不需要等于
        //      当相等时已经是取到无效值了就不用继续了
        //      如何理解呢?
        //          出现left=right有两种可能,要么是左边界=mid+1后等于右边界,要么是右边界=mid后等于左边界
        //          如果是左边界=mid+1后等于右边界,因为右边界是开区间,所以其实已经超过范围了,显然就应该结束了
        //          如果是右边界=mid后等于左边界,则mid是在前一轮就判断过的,故也不需要继续这新的一轮循环
        while (left < right) {
            int mid = (right - left) / 2 + left;
            int num = nums[mid];
            if (num == target) {
                return mid;
            } else if (num > target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}



二、搜索插入位置

题目链接:35.搜索插入位置(opens new window)


/**
 * <pre>
 * <p>此题其实就是二分查找后对结果进行一定的处理</p>
 * 
 * 需要将mid值进行保存,循环结束后mid值其实就是最后一次查询时指针所在的位置(即进行判断的位置)
 * 如果该位置正好是答案那么自然返回该位置就行了
 * 如果该位置不是答案,那么只有两种情况
 * 一种是最后一次判断的值比要找的数大,那么这个位置自然就是插入的位置
 * 最后一次判断的值小的话,自然就要将目标值放在它后面,即+1再返回
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/2 15:17
 */
public class 搜索插入位置35 {
    public int searchInsert(int[] nums, int target) {
        int left=0, right=nums.length-1, mid=0;
        while (left <= right) {
            mid = (right - left) / 2 + left;
            if (nums[mid] == target) {
                break;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        if (nums[mid] >= target) { // 如果mid位置所在的就是目的值则返回当前值,如果比目的值大则将目的值插在此处,原值后移
            return mid;
        } else {
            return mid + 1; // 如果mid所在值比目的值小,则将目的值插在mid后面一位
        }
    }
}



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

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

/**
 * <pre>
 * <p>此题其实就是二分查询左右两个边界,找到目标值时不像一般的二分查找直接返回,而是继续查询直到找到最边缘的值</p>
 * 
 * 可以采用两次二分,一次查询左边界,一次查询右边界
 * 也可以采用一次二分,在找到目标值时向左右同时扩展
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/2 12:11
 */
public class 在排序数组中查找元素的第一个和最后一个位置34 {
    public int[] searchRange(int[] nums, int target) {
        int[] result = {-1, -1};
        bisection(nums, 0, nums.length-1, target, result);
        return result;
    }
    public void bisection(int[] nums, int left, int right, int target, int[] result) {
        if (left > right) {
            return;
        }
        int mid = (right - left) / 2 + left;
        if (nums[mid] == target) {
            if (result[0] == -1) { // 第一次找到相同的值,则对两个位置全赋值
                result[0] = mid;
                result[1] = mid;
            } else if (mid < result[0]) { // 不是第一次找到而且找到的位置比之前找到的左边界还要小,则扩展左边界
                result[0] = mid;
            } else if(mid > result[1]) { // 不是第一次找到而且找到的位置比之前的右边界还要大,则扩展右边界
                result[1] = mid;
            }
            // 找到一个值后需要向左右两处继续查询是否还有其他相等情况以扩充边界
            bisection(nums, mid+1, right, target, result);
            bisection(nums, left, mid-1, target, result);
        } else if (nums[mid] < target) {
            bisection(nums, mid+1, right, target, result);
        } else {
            bisection(nums, left, mid-1, target, result);
        }
    }
    // 使用两个二分分开查找左边界和右边界
    public int[] searchRange2(int[] nums, int target) {
        int[] res = new int[] {-1, -1};
        res[0] = binarySearch(nums, target, true);
        res[1] = binarySearch(nums, target, false);
        return res;
    }
    public int binarySearch(int[] nums, int target, boolean leftOrRight) {
        int res = -1;
        int left = 0, right = nums.length - 1, mid;
        while(left <= right) {
            mid = left + (right - left) / 2;
            if(target < nums[mid])
                right = mid - 1;
            else if(target > nums[mid])
                left = mid + 1;
            else {
                res = mid;
                // 当前查询的是左边界
                if(leftOrRight)
                    right = mid - 1;
                else
                    left = mid + 1;
            }
        }
        return res;
    }
}



四、x 的平方根

题目链接: 69.x 的平方根


/**
 * <pre>
 * <p>求x的平方根,实际也是二分查找的一个变形</p>
 * 搜索区间从给定数组,编程1到x的区间
 * 不断二分该区间,对每次取的mid值进行平方,与x值进行对比决定下一个区间变换
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/2 15:47
 */
public class x的平方根69 {
    public int mySqrt(int x) {
        int left = 1, right = x, mid = 0; // mid=0解决不进入循环时(即x=0)的情况
        long pow = 0; // 需要设置为long类型防止溢出
        while (left <= right) {
            mid = (right - left) / 2 + left;
            pow = (long) mid * mid; // 需要先将一个mid转换为long类型,long*int才会为long类型,不然是溢出后的结果赋值给pow
            if (pow == x) {
                return mid;
            } else if (pow < x) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        if (pow <= x) {
            return mid;
        } else {
            return mid - 1;
        }
    }
}



五、有效的完全平方数

题目链接:367.有效的完全平方数


/**
 * <pre>
 * <p>求x平方根的简化版</p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/3 10:30
 */
public class 有效的完全平方数367 {
    public boolean isPerfectSquare(int num) {
        int left = 0, right = num;
        long mid, pow;
        while (left <= right) {
            mid = (right - left) / 2 + left;
            pow = mid * mid;
            if (pow == num) {
                return true;
            } else if (pow < num) {
                left = (int) (mid + 1);
            } else {
                right = (int) (mid - 1);
            }
        }
        return false;
    }
}



相关文章
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
25天前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
21 1
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
20 4
|
1月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
19 0
Leetcode第三十三题(搜索旋转排序数组)
|
1月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
60 0
|
1月前
|
C++
【LeetCode 12】349.两个数组的交集
【LeetCode 12】349.两个数组的交集
17 0
|
1月前
【LeetCode 01】二分查找总结
【LeetCode 01】二分查找总结
14 0
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
57 6