钱塘江上潮信来 今日|

Appletree24

园龄:2年10个月粉丝:25关注:0

2023-01-25 15:32阅读: 56评论: 0推荐: 0

二分查找的三种形式&两道力扣

前言

过年前刷Leetcode的时候遇到这样一道题目:
354. 俄罗斯套娃信封问题 - 力扣(Leetcode)
其中使用patience sorting这个算法的做法中,因为牌堆顶是有序数组,所以可以使用二分查找把时间复杂度降到对数级别,但是这个并不是需要查找某个数字所出现的位置,而是需要查找当前数字需要插入到何处,思考了大佬的代码后恍然大悟,AC后想系统性整理一下二分查找的基本用法和拓展,于是有了此文。

二分查找基本用法(寻找某数位置)

//用kotlin是为了练一下语法……几乎和java是一样的,应该没有阅读压力
class Solution {
fun binarySearch(nums: IntArray, target: Int): Int {
var left: Int = 0
//right可以是nums.size-1,也可以是nums.size,具体情况写到下文
var right: Int = nums.size - 1
//while中的条件是一个关键点,什么时候取等什么时候不取等
while (left <= right) {
//经典操作,以防直接相加导致的越界
val mid = left + (right - left) / 2
//每次调整搜索范围时,什么时候加1什么时候不加1也是一个关键
if (nums[mid] < target) left = mid + 1
else if (nums[mid] > target) right = mid - 1
else if (nums[mid] == target) return mid
}
return -1
}
}

在阅读labuladong大佬的文章前,对二分查找也存在很多疑问点,也就是我在代码注释中写出来的部分。因为本身也不经常刷算法,而接触二分查找又是大一入学阶段,所以导致当时的疑问就这么堆着,如果现在的话,最好的做法就是带着疑问,用多组testcase进行debug,一步步分析代码。

1.while中何时取等何时不取等

这个问题的关键在于right变量的初始化

  • right变量初始化为nums.size-1时,就可以取等,反之则不能
    如果我们当前查找的区间形式是左闭右开,那么就可以取nums.size,反之则不能(很好想)。例如上述代码中查找区间形式为左闭右闭,while的终止条件为left>right(其实是right+1),这时闭区间内绝对不会存在数字,是一个非法区间,也就代表可以终止了,而终止的结果正是没有找到。
    如果我们使用左闭右开区间,while的终止条件就是left>=right(其实就是right),这时假如区间左右端点相等,那么区间内还存在一个数字,但while循环却终止了,明显是错误的。可以如下改正
//改成right也可以,因为此时left和right相同了
return nums[left] == target ? left : -1;

2.left和right什么时候+1 or -1,什么时候不需要

这个问题的关键就承接第一个问题。当我们取的是闭区间的时候,每次搜索,mid这个索引下的数字都是被搜索了的,所以下一次我们理所应当应该取mid的左或右,而当我们取开区间时,mid这个索引下的数字存在未被搜索的情况,对应其未被搜索时,就需要从mid这个索引开始下一次搜索

寻找左侧边界的二分查找

fun binarySearchForLeftBound(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val mid = left + (right - left) / 2
if (nums[mid] > target) right = mid - 1
else if (nums[mid] < target) left = mid + 1
else if (nums[mid] == target) right = mid - 1
}
if (left == nums.size) return -1
return if (nums[left] == target) left
else -1
}

1.while中何时取等

这个问题的解答和上面那个是相同的,不再赘述了

2.为什么上述代码可以搜索到左侧边界

因为每当nums[mid]==target时,我们都不直接返回当前下标,而是将右边界缩小,相当于不断向左进行收缩,进而寻找左侧边界

寻找右侧边界的二分查找

fun binarySearchForRightBound(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val mid = left + (right - left) / 2
if (nums[mid] > target) right = mid - 1
else if (nums[mid] < target) left = mid + 1
else if (nums[mid] == target) left = mid + 1
}
if (right < 0) return -1
return if (nums[right] == target) nums[right]
else -1
}

其实上述代码中最后nums[right]==target部分中有些人习惯将right写为left-1,当while循环结束后,left=right+1,所以二者可以替换。但是这里为什么不去判断left而是判断left-1,就是一个疑惑点,这是因为每次我们都需要将mid这个下标排除出搜索区间,当循环结束时,左侧边界不断靠右且每次移动都有mid=left-1这个关系,最后left下标处一定不是target,反而是left-1处可能是target。

Leetcode 34

image.png

class Solution {
    fun searchRange(nums: IntArray, target: Int): IntArray {
        return if (nums.isEmpty()) intArrayOf(-1, -1)
        else intArrayOf(searchLeftBound(nums, target), searchRightBound(nums, target))
    }
    private fun searchLeftBound(nums: IntArray, target: Int): Int {
        var left = 0
        var right = nums.size - 1
        while (left <= right) {
            val mid = left + (right - left) / 2
            if (nums[mid] < target) left = mid + 1
            else if (nums[mid] > target) right = mid - 1
            else if (nums[mid] == target) right = mid - 1
        }
        if (left == nums.size) return -1
        return if (nums[left] == target) left
        else -1
    }
    private fun searchRightBound(nums: IntArray, target: Int): Int {
        var left = 0
        var right = nums.size - 1
        while (left <= right) {
            val mid = left + (right - left) / 2
            if (nums[mid] < target) left = mid + 1
            else if (nums[mid] > target) right = mid - 1
            else if (nums[mid] == target) left = mid + 1
        }
        if (left - 1 < 0) return -1
        return if (nums[left - 1] == target) left - 1
        else -1
    }
}

Leetcode 354

image.png

import java.util.Arrays;
/**
 * @author Zzh
 * @date 2023/1/7
 * @time 16:03
 */
class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        Arrays.sort(envelopes, (o1, o2) -> o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0]);
        int[] height = new int[envelopes.length];
        for (int i = 0; i < envelopes.length; i++) {
            height[i] = envelopes[i][1];
        }
        return lengthOfLIS(height);
    }
    private int lengthOfLIS(int[] nums) {
        if (nums.length == 0) return 0;
        int[] top = new int[nums.length];
        int piles = 0;
        for (int poker :
                nums) {
            int left = 0;
            int right = piles - 1;
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (top[mid] < poker) left = mid + 1;
                else if (top[mid] > poker) right = mid - 1;
                else if (top[mid] == poker) right = mid - 1;
            }
            if (left == piles) piles++;
            top[left] = poker;
        }
        return piles;
    }
}

本文作者:Appletree24

本文链接:https://www.cnblogs.com/appletree24/p/17066997.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Appletree24  阅读(56)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 愛錠 (Edit ver.) LiSA
愛錠 (Edit ver.) - LiSA
00:00 / 00:00
An audio error has occurred.

話してしまえば 思い出

隠してしまえば 幸せ

時間がすべてを奪ってく

What do you think?do you think?

愛してしまえば 地獄で

離れてしまえば 孤独だ

もう戻れない

ああ、想う 想うほど 絡まる愛錠

この手を繋ぐ鎖のように

ただ目の前の明日を信じられるのならば

それだけでいい

今そっと手を伸ばした

もたれてしまえば 2倍で

壊してしまえば それぞれ

時間このまま過ぎ去って

What do you think?do you think?

願ってしまえば 欲しくて

叶ってしまえば 足りない

知っている

もう戻れはしない

あの日には

今夜すべてを置き去りに

独りで逃げ出したって

きっと私はいつまでも

後悔に縛られたまま

ずっときょうを恨みながら

罪責と悪夢に魘されるのでしょう

解けないわ

鉄と鉄が擦れ合い

指と指の温もりも

今ここに貴方を感じられるカギ

ああ、想う 想うほど 絡まる愛錠

この手を繋ぐ鎖のように

ただ目の前の明日を信じられるのならば

それだけでいいよ 今は

ああ、巡る 巡るほど 絡まる愛錠

切れない絆 確かめながら

どんな見えない明日も貴方が傍にいるのなら

それだけでいい

強く確かな愛情