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),当然由于当前数组并不是很庞大,执行上其实也并不太大区别,只是尽可能去这么写了。那么关于本文就到这里了。

  • 相关阅读:
    20155305乔磊2016-2017-2《Java程序设计》第三周学习总结
    20155305乔磊2016-2017-2《Java程序设计》第二周学习总结
    20155305乔磊2016-2017-2《Java程序设计》第一周学习总结
    20155305乔磊第三次随笔
    乔磊20155305第二次随笔
    乔磊的第一次随笔
    20155302 2016-2017-2 《Java程序设计》第九周学习总结
    20155302 2016-2017-2 《Java程序设计》第八周学习总结
    20155302 2016-2017-2 《Java程序设计》第七周学习总结
    20155302 2016-2017-2 《Java程序设计》第六周学习总结
  • 原文地址:https://www.cnblogs.com/echolun/p/13971943.html
Copyright © 2011-2022 走看看