每日一题:leftpad事件(二分法

408 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

字符串补位leftpad

说起二分,一个典型的事件就是left-pad事件。不了解该事件的可以自行搜索,我们本文的重点是二分法实现一个left-pad功能。leftpad是一个给字符串左侧补位的功能,给一个字符串和一个长度,当字符串长度不够时,使用指定字符在它左侧给它补齐到给的的长度

leftpad(str, len, ch)
// str: 指定字符串
// len:指定长度
// ch:补位的字符

示例:

leftpad('hello', 10, 'a')
输出: 'aaaaahello'
解释: 'hello'字符长度为5,不满足指定长度10,需要用'a'字符在'hello'的左侧进行5次补位,即5'a'拼接上'hello'

一、npm中的leftpad的紧急代替版

module.exports = leftpad;
function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

上面的方法我们可以知道是直接循环拼接字符串的,复杂度为0(n)

二、二分法实现leftpad

二分法思路分析:

  1. 先确定需要补位的位数let length = len = str.length。这时确定了需要补位length个ch字符

  2. 定义补位的字符串total,对length进行循环判断,每次length除以2,ch每次累加自己,total进行累加ch。

    /** 
     * len=10时,需要拼接5次
     * 方法一:循环拼接5次
     * 方法二:记一个缓存值total:
     * 00
     * 00 + 00 + 0
     * 需要拼接2次
    */
    ​
    /** 
     * len=25时,需要拼接20次
     * 方法一:循环拼接20次
     * 方法二:记一个缓存值total:
     *  00
     *  0000
     *  00000000 + 00000000 + 0000
     *  只拼接3次
    */
    
  3. 当length为1时,无法再二分,直接累加str输出

具体实现:

function leftpad2(str, len, ch) {
  let length = len - str.length
  // 二分法
  let total='', cache = ch;
  while (length) {
    // 缓存结果*2处理,剩下的长度/2处理
    cache += cache
    length = parseInt(length / 2)
    // 余1时将这1个值累加
    if (length % 2 === 1) {
      total += cache
    }
    // 最后length可以整除2,无法二分的时候做拼接return处理
    if (length === 1) {
      return total + str
    }
  }
}

当循环次数n量级越大时,两种方法的差异越明显:

// O(n)复杂度执行时间
console.time('leftpad1')
  for(let i=0; i<10000; i++) {
    leftpad1('holle', 10000, 'a')
  }
console.timeEnd('leftpad1')

// 二分法log2(n)复杂度执行时间
console.time('leftpad2')
  for(let i=0; i<10000; i++) {
    leftpad2('holle', 10000, 'a')
  }
console.timeEnd('leftpad2')

输出结果:

image-20220329002140073.png

可以看出当量级上去后,运行时间出现了很大差异,当n越大,运行时间都不是一个数量级了。

三、位运算的优化

其中取余%除法/可以优化一下

function leftpad3(str, len, ch) {
  let length = len = str.length
  // 二分法
  let total = ''
  while(length) {
    // &运算符:按位与(本质上是二进制下的数按位对齐取与操作)
    if (length & 1) {
      total+=ch
    }
    if (length===1) {
      return total+str
    }
    ch+=ch
    // >> 向右位移1位(二进数右位移1位相当于除以2)
    length = length >> 1
  }
}

优化以后当循环和len达到百万的量级时,我们就能看出leftpad3的时间小于leftpad2,但是总体的时间还是同一个量级的。

【拓展】

  1. 按位与:

    例如:25 & 3 = 1
    25 = 0001 1001
    3  = 0000 0011
    按位取与操作为:
    res= 0000 0001
    

    对于上面的if (length & 1),任何数和1按位与为真就说明该数的二进制的最后一位必须是1,二进制的最后一位为1那不就是无法整除2余1吗

    所以if (length & 1)if (length % 2 === 1)是一样的。

    大家可以自己举例几个数和1的按位与,手写下试试哦!

  2. 有符号右移:右移几位就是除以2的几次方

    二进制状态下向右位移1位相当于除以2。右移2位相当于除以4。例如64右移5位结果就是2。