Leftpad快速幂与位运算 | 青训营笔记

192 阅读2分钟

Leftpad快速幂 与 快速求是否为4的幂

这是我参与「第四届青训营 」笔记创作活动的的第3天。

昨天,跟着月影学JavaScript,讲了几个算法,我本身对算法也比较有兴趣,所以记录一下课堂上讲的几个算法。

Leftpad

Leftpad源码

根据新浪微博上的消息,有一位开发者不满NPM(Node Package Manager)的做法,收回了自己的开源代码,其中包括一个叫left-pad的模块,就是这个模块把javascript里面的React/Babel干瘫痪了。

这是个什么样的模块?就是在字符串前填充一些东西到一定的长度。例如用0去填充字符串12,使之长度为10,调用left-pad的结果就应该是0000000012

当年作者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;
  } 

当时源码一出,引发了不少人对程序代码风格、性能的吐槽与评论。

优化做法

其实乍一看还真没发现有什么问题所在,性能感觉也没得说,为 O(n) ,但是仔细一看,根据快速幂的思路,不难想到 O(lgn) 的做法。

这个while里面的代码基于快速幂思想,这里源码的实现思路是这样的,每次循环,len & 1,如果是1,就将pad + ch,否则ch变为原来两倍,然后len左移一位,变成了原来的一半,那么循环次数相应减少到原来的一半,这样每次循环都将len减少到原来的一半。这样就将原本O(n)的复杂度减少到了O(log(n))。

举个栗子🍑:我们要添加9个0,即 9 * (0) = (0) + 2 * ( 2 * ( 2 * (0) ) )...

讨论

老师上课给了份代码为:

  function leftpad(str, len, ch) {
      str = "" + str;
      const padLen = len - str.length;
      if(padLen <= 0) {
        return str;
      }
      return (""+ch).repeat(padLen)+str;
  } 

其中 repeat 作用为重复padLen次括号里的内容,然后我看了下repeat的源码为:

    /**
     * Returns a String value that is made from count copies appended together. If count is 0,
     * the empty string is returned.
     * @param count number of copies to append
     */
    repeat(count: number): string;

明显,其做法也为一次次重复,时间复杂度也为 O(n) , 当然此份代码较源码更清晰简洁,同时我们调用leftpad方法也很少会补齐很多,通常10位以内,那么其实O(n) 与 O(lgn)着实没太大区别


4的幂

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。

整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4^x

-2^31 <= n <= 2^31 - 1

LeetCode 原题连接:342. 4的幂 - 力扣(LeetCode)

基础解法

不难想到:

    bool isPowerOfFour(int n) {
        if(n == 0)  return false;     // 特判
        while(1){
            if(n == 1)  return true;
            if(n & 3)
                return false;
            else   
                n = n >> 2;
        }
    }

这也是我第一眼的做法,每次余4,判断能否整除,能的话就整除,否则返回false,直到最后是否为1为止。

这里 n & 3 就等价于 n % 4,n >> 2 也等价于 n / 4,用位运算是感觉更快一些。

另: 其实C++底层来说。若常量≠2^n,无优化;否则,除法将被转换为右移运算。由于由右移运算实现的整除实质上是向下取整,所以编译器会通过一些附加的指令在不产生分支结构的情况下将向下取整转换为向零取整。

O(1) 解法

这里有一份跟老师解法不大一样的: 如果那个数是4的幂,那么其余3一定为1

因为

            n = (4 * 4 * 4 * 4 ... * 4) % 3
            = (4 % 3 * 4 % 3 * ... * 4 % 3)
            = 1

如果 n 是 2 的幂却不是 4 的幂,那么它可以表示成 2 * 4 ^ x 的形式,此时它除以 3 的余数一定为 2。

故:

     bool isPowerOfFour(int n) {
        return n > 0 && (n & (n - 1)) == 0 && n % 3 == 1;
    }

n & (n - 1) 判断是否为2的幂

根据位运算也能得出,老师代码如下:

     bool isPowerOfFour(int n) {
        return n > 0 && (n & (n - 1)) == 0 && (n & 0xAAAAAAAA) == 0;
     }

如有不严谨之处,欢迎指出~