zoukankan      html  css  js  c++  java
  • JS 数组中找到与目标值最接近的数字,记一次工作中关于二分查找的算法优化

    壹 ❀ 引

    在最近的工作中,有一个任务是需要修复富文本编辑器字号显示的BUG。大概情况就是,从WPS中复制不同样式的标题、正文到到项目编辑器中,发现没办法设置选中的文本为正文;而且字体字号都显示为默认的情况下,这些字体大小还表现不同。因为该富文本编辑器是基于ckeditor二次开发,所以也是看了一天的源码才成功定位到问题,最后发现WPS的字体单位使用的是印刷行业的单位,也就是pt,而不是我们熟知的px,这就导致了富文本编辑器无法识别该pt单位,从而产生了后续一系列的bug。

    经过与程序原作者沟通讨论,最终确定的修复方案是将WPS粘贴过来的文本原数据进行单位换算加工。其次还有一个问题,编辑器的字号大小选择范围一般是间断的而非连续的,比如下图:

    如果编辑器能展示的字号范围未能匹配到我们最终拿到的字号大小,那么编辑器就会展示字号为默认,因为匹配不到,那这样就还是会造成13px15px都显示为默认的情况,从而给了用户一种字号都是默认,但字体大小显示不同的疑问。所以我们在换算之后还多了一步操作,我们需要在编辑器的字号范围中找到与换算后的字号最接近的数字,作为最终展示字体大小,那么这就衍生出了一个问题,怎么在数组中找到与目标数字最为接近的数字。

    贰 ❀ 尝试解决

    归纳上面的问题,其实就是在一个数组中找到与目标数字最近接的数字,比如:

    const arr = [1,3,5,6,10];
    const target = 7;
    // 最终找到6,因为7和6的差最小
    
    const arr = [1,3,5,6,10];
    const target = 3;
    // 最终找到3,还有比相等的数字更为接近的?
    

    只要简单分析上述两个例子,我们要找的值其实就是与目标值target的差最小的一个数,考虑到存在差为负数的情况,所以这个差应该是一个绝对值,可用abs()转换。那么我们来尝试实现它:

    const arr = [1, 2, 6, 10, 9];
    const target = 3;
    const findNearestNumber = (arr, target) => {
        // 我们假设最近接的就是数组第第一个数字
        let result = arr[0];
        for (let i = 1, len = arr.length; i < len; i++) {
            if (Math.abs(arr[i] - target) < Math.abs(result - target)) {
                result = arr[i];
            };
        };
        return result;
    };
    console.log(findNearestNumber(arr, target));//2
    

    让我们回顾上述代码,一开始,我们需要提供一个初始值,并拿下一个值参与比较之后决定返回两者中的某一个值,然后继续参与后续遍历,仔细一想,这不就是在做reduce的操作吗,所以我们简化下代码,变成了这样:

    const findNearestNumber = (arr, target) => {
        return arr.reduce((pre, curr) => {
            return Math.abs(pre - target) > Math.abs(curr - target) ? curr : pre;
        })
    };
    

    以上的实现适用于有序以及无序数组,考虑无序的情况,我们最差的情况就是完整遍历一遍,最终发现最后一个数字才是我们想要的,所以站在时间复杂度上来说就是O(n)

    不过我们现在的需求有点不同,很明显字号大小是从小到大排列的,比如这样一个数组:

    const fontSize = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72];
    

    这是一个有序数组,就像肌肉记忆一样,看到有序数组,谈到遍历我们首先想到的就应该是二分查找,其实上篇文章我也提过公司code review会比较严格,对于性能要求比较高。发版那天本来就够忙了,我可不想在提测通过后,因为review没过,又得修改代码再次走测试流程,非常费时,所以保险起见,这里就直接考虑二分查找来做了。

    我们声明左右两个指针,分别指向数组的第0位索引和最后一位索引,然后求出中间索引,拿中间索引所对应的数字与target对比,如果targetarr[mid]大,那说明target肯定在右边范围,这时候只要修改左指针为mid即可,反之修改右指针。

    因为目标值不存在于数组中,所以最终我们得保证左右指针指到相邻的两个数字上,大致实现如下:

    const findNearesttargetber = (arr, target) => {
        let mid;
        let l = 0;
        let r = arr.length - 1;
        // 保证指针最终停留在相邻的两个数,所以这里是判断是否大于1
        while (r - l > 1) {
            mid = Math.floor((l + r) / 2);
            // 如果目标数比中间小,所以范围在左边
            if (target < arr[mid]) {
                r = mid;
            } else {
                l = mid;
            };
        };
        // 最后比较这两个数字的绝对差大小即可。
        return Math.abs(target - arr[l]) <= Math.abs(target - arr[r]) ? arr[l] : arr[r];
    }
    

    由于二分查找每次都能排除掉一半的可能,所以时间复杂度为O(logn),当然由于当前数组并不是很庞大,执行上其实也并不太大区别,只是尽可能去这么写了。那么关于本文就到这里了。

  • 相关阅读:
    C++笔记(2018/2/6)
    2017级面向对象程序设计寒假作业1
    谁是你的潜在朋友
    A1095 Cars on Campus (30)(30 分)
    A1083 List Grades (25)(25 分)
    A1075 PAT Judge (25)(25 分)
    A1012 The Best Rank (25)(25 分)
    1009 说反话 (20)(20 分)
    A1055 The World's Richest(25 分)
    A1025 PAT Ranking (25)(25 分)
  • 原文地址:https://www.cnblogs.com/echolun/p/13971943.html
Copyright © 2011-2022 走看看