JS 数组中找到与目标值最接近的数字,记一次工作中关于二分查找的算法优化

壹 ❀ 引
在最近的工作中,有一个任务是需要修复富文本编辑器字号显示的BUG。大概情况就是,从WPS中复制不同样式的标题、正文到到项目编辑器中,发现没办法设置选中的文本为正文;而且字体字号都显示为默认的情况下,这些字体大小还表现不同。因为该富文本编辑器是基于ckeditor二次开发,所以也是看了一天的源码才成功定位到问题,最后发现WPS的字体单位使用的是印刷行业的单位,也就是pt,而不是我们熟知的px,这就导致了富文本编辑器无法识别该pt单位,从而产生了后续一系列的bug。
经过与程序原作者沟通讨论,最终确定的修复方案是将WPS粘贴过来的文本原数据进行单位换算加工。其次还有一个问题,编辑器的字号大小选择范围一般是间断的而非连续的,比如下图:

如果编辑器能展示的字号范围未能匹配到我们最终拿到的字号大小,那么编辑器就会展示字号为默认,因为匹配不到,那这样就还是会造成13px与15px都显示为默认的情况,从而给了用户一种字号都是默认,但字体大小显示不同的疑问。所以我们在换算之后还多了一步操作,我们需要在编辑器的字号范围中找到与换算后的字号最接近的数字,作为最终展示字体大小,那么这就衍生出了一个问题,怎么在数组中找到与目标数字最为接近的数字。
贰 ❀ 尝试解决
归纳上面的问题,其实就是在一个数组中找到与目标数字最近接的数字,比如:
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对比,如果target比arr[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),当然由于当前数组并不是很庞大,执行上其实也并不太大区别,只是尽可能去这么写了。那么关于本文就到这里了。
JS 数组中找到与目标值最接近的数字,记一次工作中关于二分查找的算法优化的更多相关文章
- Python实现在给定整数序列中找到和为100的所有数字组合
摘要: 使用Python在给定整数序列中找到和为100的所有数字组合.可以学习贪婪算法及递归技巧. 难度: 初级 问题 给定一个整数序列,要求将这些整数的和尽可能拼成 100. 比如 [17, 1 ...
- 原生js(form)验证,可以借鉴下思路,应用到工作中
我在工作中时常使用form验证,在目前的公司做的表单验证用的angular的form组件,对于一个有追求的前端,或者应用在移动端写个form验证,引入angular或者jquery组件等验证,难免显得 ...
- 旋转数组中的最小数字,剑指offer,P70 二分查找来实现O(logn)的查找
public class MinNumberInRotatedArray { public int getMinNumInRotatedArray(int[] array) { if(array == ...
- 对于一个有序数组,我们通常采用二分查找的方式来定位某一元素,请编写二分查找的算法,在数组中查找指定元素。 给定一个整数数组A及它的大小n,同时给定要查找的元素val,请返回它在数组中的位置(从0开始),若不存在该元素,返回-1。若该元素出现多次,请返回第一次出现的位置。
// ConsoleApplication10.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream& ...
- 记一次工作中的小BUG
今天在调试代码的时候总是遇到一个bug,百思不得其解!先上bug图 我用的webapi 集成的swagger,错误提示是路由名称冲突,可我仔细检查了下并没有冲突的路由地址啊!于是上网查找资料,有位网友 ...
- leetcode:Search for a Range(数组,二分查找)
Given a sorted array of integers, find the starting and ending position of a given target value. You ...
- c:找到出现次数最多的递增数字串
如题,如何在一亿位整数组成的字符串中找到出现次数最多的递增数字串? 答案: #include <stdio.h> #include <string.h> #define MAX ...
- linq介绍及工作中应用两例——左联与内联,linq循环方法
目录 1 linq介绍 1.1 linq产生背景 1.2 linq使用范围 1.3 linq核心程序集 1.4 linq架构图 1.5 linq使用形式对比 1.5.1 linq To Objects ...
- Git-【技术干货】工作中Git的使用实践
Git-[技术干货]工作中Git的使用实践 置顶 2019-09-17 21:02:16 web洋仔 阅读数 11444更多 分类专栏: Git 版权声明:本文为博主原创文章,遵循CC 4.0 B ...
- LeetCode第十六题-找出数组中三数之和最接近目标值的答案
3Sum Closest 问题简介: 给定n个整数的数组nums和整数目标,在nums中找到三个整数,使得总和最接近目标,返回三个整数的总和,可以假设每个输入都只有一个解决方案 举例: 给定数组:nu ...
随机推荐
- 搬运 |(有刀🔪慎入)kwdnx、谷雨、更漏与我
本打算把文案投到官微,但官微说文案太长了,只能放删减版: 那就把文案放博客上吧 写于 癸卯年 戊午月 己亥日. 四年前的冬天我们初见,大家都是青涩的未成年人:在少年班团建的喧闹中,谷雨谈论高数竞赛,k ...
- 解决在Edge浏览器中使用不了(找不到)new bing的情况
1.问题 我们有时候看不到下图圈出部分的信息,无法找到New Bing的入口(这边是空的) 2.解决方式 1.选择右上角的三条杠,并选择其中的settings 2.将其中的country一项改为外国即 ...
- [转帖]GC日志解读,这次别再说看不懂GC日志了
https://juejin.cn/post/7029130033268555807 测试环境:机器内存16G,JDK 8,12核CPU 测试用例,从网上找的示例,视情况修改即可: java ...
- [转帖]记druid 连接池没满,但超时问题 GetConnectionTimeoutException active 5, maxActive 100
记druid 连接池没满,但超时问题 GetConnectionTimeoutException active 5, maxActive 100 问题说明 线上服务突然出现报错,通过日志查找发现是因为 ...
- [转帖]《Linux性能优化实战》笔记(二)—— CPU 上下文切换(上)
上一篇的最后一个例子,在多个进程竞争CPU时,我们看到每个进程实际上%usr部分只有20%多,70%多是在wait,但是load远远高于单个进程使用CPU达到100%. 这让我想到之前看的RWP公开课 ...
- [转帖]iometer - 性能压力测试工具
<存储工具系列文章>主要介绍存储相关的测试和调试工具,包括不限于dd.fio.vdbench.iozone.iometer.cosbench等性能负载工具,及strace等调试工具. 1 ...
- [转帖]Sosreport:收集系统日志和诊断信息的工具
https://zhuanlan.zhihu.com/p/39259107 如果你是 RHEL 管理员,你可能肯定听说过 Sosreport :一个可扩展.可移植的支持数据收集工具.它是一个从类 Un ...
- 【转帖】bpftrace 指南
文章目录 0. bpftrace 0.1 bpftrace组件 0.2 bpftrace 帮助信息 0.3 bpftrace 工具速览表 0.4 bpftrace 探针 0.4.1 tracepoin ...
- Docker 运行 Redis Rabbitmq seata-server ftp 的简单办法
公司里面用到了很多组件, 发现安装二进制太麻烦了, 所以想用Docker 进行安装. 这里面简单给总结一下就不在折腾了.. 1. redis docker run -d -p 6379:6379 -- ...
- ESXi重置密码以及修改网络IP地址的方法
Study From https://www.cnblogs.com/mk21/p/15784082.html 前期公司有部分虚拟化的服务器因为只通过vCenter进行管理. 导致密码遗失. 最近因为 ...