【牛客刷题记录】【JAVA】二分查找

(1) 二分查找

链接

二分查找需要序列是有序的,否则二分查找会失效。原理就是如果找的值比mid小,那么[mid,R]的内容就不需要再查找了,反之如果大于mid位置的值,则在[L,mid]内的值也不需要再查找。同时将L/R的值进行修改。注意循环结束的条件为L<=R。

java 复制代码
public int search (int[] nums, int target) {
        // write code here
        if(nums.length==0 || nums.length==1&&nums[0]!=target){
            return -1;
        }
        int L=0, R=nums.length;
        while(L<=R){
            int mid=(L+R)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]<target){
                L=mid+1;
            }else{
                R=mid-1;
            }
        }

        return -1;
    }

(2)寻找峰值

链接

最普通的方法就是O(n),即从头到尾遍历,找到山峰就返回。

java 复制代码
public int findPeakElement (int[] nums) {
        // write code here
        if(nums.length==1){
            return 0;
        }
        int i;
        for(i=0; i<nums.length; i++){
            if(i==0 && nums[i]>nums[i+1]){
                break;
            }else if(i==nums.length-1 && nums[i]>nums[i-1]){
                break;
            }else if(i>0 && nums[i]>nums[i-1] && nums[i]>nums[i+1]){
                break;
            }
        }
       return i;
    }

但如果使用二分查找的方法,就会更快,即O(logN)。当然,我们需要证明一下正确性:

  • 如果 nums[mid] 大于 nums[mid + 1],则在 mid 左侧(包括 mid)必定存在一个峰值。这是因为如果 nums[mid] 是一个下降点,那么在左侧必定存在一个局部最大值。

    例如上图,mid大于mid+1的位置,那么右侧是可能没有峰值的。但左侧必定存在峰值。要么就是mid为峰值,要么就是mid-1更大,而就需要比较mid-2。(有一点递归的意思),而我们的设定是nums[-1] = nums[n] = −∞,因此左侧必定出现峰值。
  • 如果 nums[mid] 小于 nums[mid + 1],则在 mid 右侧(不包括 mid)必定存在一个峰值。这是因为如果 nums[mid] 是一个上升点,那么在右侧必定存在一个局部最大值。
    正确性可以得到了,那么我们就可以写代码,用二分查找的思路:
java 复制代码
public int findPeakElement (int[] nums) {
        // write code here
        int left = 0;
        int right = nums.length - 1;

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

            if (nums[mid] > nums[mid + 1]) {
                // 峰值在左边
                right = mid;
            } else {
                // 峰值在右边
                left = mid + 1;
            }
        }

        return left;
    }

注意,一定会存在峰值,因此不需要我们特殊去判断是否为峰值,结束循环返回结果即可,这样可以让代码非常整洁。注意,和二分查找不一样,我们更新right不能为mid-1,否则可能找不到峰值。如下所示:

(3) 数组中的逆序对

链接

最简单的方法自然就是遍历,但这样的时间复杂度会很大,肯定不是优选。

我们可以用归并排序的思路。

假设我们有以下未排序的数组:

java 复制代码
[8, 4, 2, 1]

我们想要计算这个数组中的逆序对的数量。逆序对是指数组中的两个数字,如果它们的原始位置是i和j,且i < j但A[i] > A[j],那么它们就构成一个逆序对。

让我们使用归并排序的过程来计算逆序对:

  • 第一步:分解

将数组分解为更小的数组:

java 复制代码
[8, 4] [2, 1]

继续分解:

java 复制代码
[8] [4] [2] [1]
  • 第二步:合并并计算逆序对

现在我们开始合并这些数组,并在合并过程中计算逆序对。

合并 [8] 和 [4]:

比较 8 和 4,发现 8 > 4,因此它们构成一个逆序对。我们将逆序对的数量增加 1,得到 1。

合并后的数组是 [4, 8]。
合并 [2] 和 [1]:

比较 2 和 1,发现 2 > 1,因此它们构成一个逆序对。我们将逆序对的数量增加 1,得到 2。

合并后的数组是 [1, 2]。

现在我们有 [4, 8] 和 [1, 2] 两个已排序的数组,接下来合并这两个数组:

比较 4 和 1、4 和 2,逆序数加2。

比较 8 和 1、8 和 2,逆序数夹2.因此最终逆序数为6.

最终合并后的数组是 [1, 2, 4, 8]。

java 复制代码
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param nums int整型一维数组
     * @return int整型
     */private static final int MOD = 1000000007;

    public int InversePairs(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[] copy = new int[nums.length];
        System.arraycopy(nums, 0, copy, 0, nums.length);
        return (int) mergeSort(nums, copy, 0, nums.length - 1);
    }

    private long mergeSort(int[] nums, int[] copy, int start, int end) {
        if (start >= end) {
            return 0;
        }
        int mid = start + (end - start) / 2;
        long left = mergeSort(copy, nums, start, mid) % MOD;
        long right = mergeSort(copy, nums, mid + 1, end) % MOD;
        int i = mid, j = end;
        int copyIndex = end;
        long count = 0;
        while (i >= start && j > mid) {
            if (nums[i] > nums[j]) {
                count += j - mid;
                copy[copyIndex--] = nums[i--];
                if (count >= MOD) {
                    count %= MOD;
                }
            } else {
                copy[copyIndex--] = nums[j--];
            }
        }
        while (i >= start) {
            copy[copyIndex--] = nums[i--];
        }
        while (j > mid) {
            copy[copyIndex--] = nums[j--];
        }
        for (i = start; i <= end; i++) {
            nums[i] = copy[i];
        }
        return (left + right + count) % MOD;
    }

}

(4) 旋转数组的最小数字

链接

我们可以把数组分为两部分,如图所示:

旋转数组的特点这两个子数组也是非递减的,这也就可以推断出,mid的值可能大于最右端的值,也可以小于(先不讨论等于)。

  • 如果大于最右端的值,则意味着现在的mid指向的位置在第一个子数组中
  • 如果小于最右端的值,则意味着现在的mid指向的位置在第二个子数组中。
    如果是第一种情况,则需要移动left,这样就可以逼近最小值。如果是第二种情况,就移动right,这样可以逼近最小值。
    如果出现相同元素,例如:
java 复制代码
2,2,2,1,2

这样是无法确定怎么移动的,我们可以将right左移。

java 复制代码
public int minNumberInRotateArray (int[] nums) {
        // write code here
        int left = 0, right = nums.length - 1, mid = 0;
        while (left <= right) {
            mid = (left + right) / 2;
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] < nums[right]) {
                right = mid;
            } else {
                // 当 nums[mid] 和 nums[right] 相等时,无法确定最小值的位置
                // 将右指针左移一位
                right--;
            }
        }
        return nums[mid];
    }

(5) 比较版本号

链接

可以用双指针,在while里比较每两个.之间值的大小。每次都循环直到达到最大长度或者遇到.,然后计算出两个.中间值的大小,并且进行比较。如果一个版本已经遍历完但另一个还没有,也可以继续循环,这就相当于遍历完的版本号后面多出的0,因为我们的tmp设置都为0,所以不影响比较。

java 复制代码
version1:2.0.0.1
version2:2

比如这个用例,在version2遍历完后仍可以继续执行whileversion1中间的两个0在计算时得到的tmp1都为0,这与tmp2相等,因此相当于version2也为2.0.0.0

java 复制代码
public int compare (String version1, String version2) {
        // write code here
        int len1=version1.length(), len2=version2.length();
        int i=0, j=0;

        while(i<len1 || j<len2){
            long tmp1=0;
            while(i<len1 && version1.charAt(i)!='.'){
                tmp1= tmp1*10 + (version1.charAt(i)-'0');
                i++;
            }
            
            long tmp2=0;
            while(j<len2 && version2.charAt(j)!='.'){
                tmp2= tmp2*10 + (version2.charAt(j)-'0');
                j++;
            }
            i++;j++; // 跳过'.'
            if(tmp1!=tmp2){
                return tmp1<tmp2? -1: 1;
            }

        }

        return 0;
    }
相关推荐
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸4 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象4 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·5 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it5 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
懒洋洋大魔王5 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq