JS:二分搜索(3)—— 子序列、信封嵌套、阶乘数的0

本文探讨了二分搜索在解决搜索插入位置和判断子序列问题中的应用,以及动态规划在俄罗斯套娃信封问题和最长递增子序列问题中的解法。还介绍了如何通过二分查找优化查找阶乘后K个零的问题。同时,讲解了如何计算阶乘后零的数量并使用二分查找来找到特定值的前驱和后继。
摘要由CSDN通过智能技术生成

在这里插入图片描述

35. 搜索插入位置 (简单)

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

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

就是普通的二分搜索,目标值不存在时,返回的是left ,因为结束条件是left = right + 1,结束的前一步分情况:left + 1 = right 或者 left = right;然后在具体每种if else情况下,left的下标都是一下特点。
在这里插入图片描述

392. 判断子序列 (简单)

在这里插入图片描述

双指针:

var isSubsequence = function(s, t) {
    let i = j = 0, len = s.length;
    for (; i<t.length && j<len; i++) {
        if(s[j]==t[i]) j++;
    }
    return j==len
};

在这里插入图片描述

进阶二分查找
如果给你一系列字符串s1,s2,…和字符串t。上述解法处理每个s时间复杂度仍然是 O(N),而如果巧妙运用二分查找,可以将时间复杂度降低,大约是 O(MlogN),M 为 s 的长度。
在这里插入图片描述

二分查找的原理

一系列字符串要快的话,内涵的意思就是提前准备一个字典,

主要是对t进行预处理,用一个字典index将每个字符出现的索引位置按顺序存储下来.

在这里插入图片描述
比如对于这个情况,匹配了 “ab”,应该匹配 “c” 了:
在这里插入图片描述

按照之前的解法,我们需要j线性前进扫描字符 “c”。但现在借助index中记录的信息,可以二分搜索index[c]中比 j 大的那个索引,在上图的例子中,就是在[0,2,6]中搜索比 4 大的那个索引:

如何用二分查找计算那个恰好比 4 大的索引呢?答案是,寻找左侧边界的二分搜索就可以做到。

注意for (let index in t) index为字符串

function left_bound(arr, target) {
    let left=0, right=arr.length-1;
    while (left<=right) {
        let mid = Math.floor((right-left)/2) + left;
        const val = arr[mid]
        if(val == target) {
            right = mid - 1
        } else if(val < target) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return left;
}
var isSubsequence = function(s, t) {
    let dic = {}
    for (let index in t) {
        let c = t[index]
        if (!dic[c]) {
            dic[c] = []
        }
        dic[c].push(index - 0) // index为字符串 转成整型
    }
    // console.log(dic)
    let need = 0
    for (let c of s) {
        // 不存在字符c
        if (!dic[c]) return false;
        // 查找字符c在字典的位置
        let pos = left_bound(dic[c], need)
        // 二分查找没有大于j的下标
        if (pos == dic[c].length) return false;
        // j在t字符串的下一个开始, 问题所在 上面的index为字符串
        need = dic[c][pos] + 1
    }
    return true;
};

354. 俄罗斯套娃信封问题 (困难)

在这里插入图片描述

给我感觉动态规划多一点。先看解析,该题是 动规 + 二分法 ,需要排序后进行处理。
在这里插入图片描述

最长递增子序列之信封嵌套问题,这道题目其实是最长递增子序列(Longes Increasing Subsequence,简写为 LIS)的一个变种,因为很显然,每次合法的嵌套是大的套小的,相当于找一个最长递增的子序列,其长度就是最多能嵌套的信封个数。

这道题的解法是比较巧妙的:
先对宽度w进行升序排序(处理了一维),如果遇到w相同的情况,则按照高度h降序排序。之后把所有的h作为一个数组,在这个数组上计算 LIS 的长度就是答案。

将二维问题转化为一维,构造新的序列
在这里插入图片描述

二分 + 动规,这样我(连动态规划都没掌握)就先不学了,使用时间复杂度 O(N^2)的动态规划。安慰一下自己。
在这里插入图片描述


那就先解决 300. 最长递增子序列 ,给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
在这里插入图片描述
因为 d p [ i ] dp[i] dp[i] 可以有不同的含义,所以跟着文章来。按照一下步骤:

  1. dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。
  2. dp数组全部初始化为 1,因为子序列最少也要包含自己,所以长度最小为 1。
  3. 状态转移: L [ j ] = 1 + m a x ( L [ i ] ) , i < j 且 a [ i ] < a [ j ] L[j]=1+{max(L[i]) , i < j且a[i] < a[j]} L[j]=1+max(L[i])i<ja[i]<a[j]
    在这里插入图片描述
  4. 最终结果应该是 dp 数组中的最大值。

在这里插入图片描述
LIS代码——leetcode第300题。动态规划,

var lengthOfLIS = function(nums) {
    let len = nums.length
    let dp = new Array(len).fill(1)
    for (let i=1; i<len; i++) {
        for (let j=0; j<i; j++) {
            if (nums[i] > nums[j])
                dp[i] = Math.max(dp[i], dp[j]+1)
        }
    }
    return Math.max(...dp)
};

在这里插入图片描述


信封嵌套问题代码——leetcode第354题。动态规划,官方题解都超时,所有语言都是,艹
先记录下,到时在学

var maxEnvelopes = function(envelopes) {
    // 按宽度升序排列,如果宽度相同,按高度逆序排列
    envelopes.sort((a, b) => {
        return a[0]==b[0] ? b[1]-a[1] : a[0]-b[0]
    })
    let len = envelopes.length
    const height = new Array(len)
    for (let i = 0; i < len; i++) {
        height[i] = envelopes[i][1];
    }
    // LIS问题 
    let dp = new Array(len).fill(1)
    for (let i=1; i<len; i++) {
        for (let j=0; j<i; j++) {
            if (height[i] > height[j]) // 只比较高度
                dp[i] = Math.max(dp[i], dp[j]+1)
        }
    }
    return Math.max(...dp)
};

793. 阶乘函数后 K 个零 (困难)

相关文章:阶乘相关的算法题
在这里插入图片描述
第一反应好像在贪心算法时,做过一道以2,3,5底数作为指针,依次搜索。
在这里插入图片描述

末尾是 0 应该与2,5这两个因数有关。

又是先从简单题过度


172. 阶乘后的零
在这里插入图片描述

比如说n = 25,那么25!最多可以分解出几个 2 和 5 相乘?这个主要取决于能分解出几个因子 5,因为每个偶数都能分解出因子 2,因子 2 肯定比因子 5 多得多。
问题转化为:n!最多可以分解出多少个因子 5?

难度在于高效计算因子5,
125!,125 第一次除以 5 = 25,说明起码有一个25个数能提高一个因子5
125 除以 25 = 5,说明还有5个数能提高两个因子5,但前面已经提供了一次,剩一个。
125 除以 125,说明还有1个数能提高三个因子5,前面提高了两次,剩一个。

代码:

var trailingZeroes = function(n) {
    let ans = 0, base = 5;
    while (base <= n) {
        ans += Math.floor(n / base)
        base *= 5
    }
    return ans;
};

随着n的增加,trailingZeroes(n!)肯定也是递增的。对于这种具有单调性的函数,用 for 循环遍历,可以用二分查找进行降维打击。

在这里插入图片描述

二分查找需要给一个搜索区间
在这里插入图片描述
注意:当二分搜索不存在时,left和right的含义
在这里插入图片描述

var preimageSizeFZF = function(k) {
    let left = 0, right = Number.MAX_SAFE_INTEGER;
    // 左侧边界
    while (left <= right) {
        let mid = Math.floor((right-left)/2) + left;
        const val = trailingZeroes(mid)
        if(val == k){
            right = mid - 1
        } else if(val > k){
            right = mid - 1
        } else{
            left = mid + 1
        }
    }
    let first = left;
    left = 0, right = Number.MAX_SAFE_INTEGER;
    // 右侧边界
    while (left <= right) {
        let mid = Math.floor((right-left)/2) + left;
        const val = trailingZeroes(mid)
        if(val == k){
            left = mid + 1
        } else if(val > k){
            right = mid - 1
        } else{
            left = mid + 1
        }
    }
    // 不存在时 first=x,right=x-1的取值范围
    return right - first + 1;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值