【牛客刷题记录】【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;
    }
相关推荐
钱多多_qdd6 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
waicsdn_haha8 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
_WndProc10 分钟前
C++ 日志输出
开发语言·c++·算法
Q_192849990618 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
qq_4335545419 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
Code_流苏20 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
数据小爬虫@38 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.40 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy1 小时前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader1 小时前
全面解析 Golang Gin 框架
开发语言·golang·gin