1
头图

也许你我素未谋面,但很可能相见恨晚,我是前端胖头鱼

前言

金三银四大潮如期到来,在这个裁员降薪倒闭无处不在的寒冷季节里,咱们都没有太多选择的余地。唯有努力提高自身能力,才能在寒风凛冽中站住脚跟,抵御随时到来的风险。

最近有个朋友在面字节时遇到了一道有意思的题,按照他的话说,差点就因为这个题寄了,不过好在他在最后几分钟,充分调动体内的洪荒之力,解出了答案,朝字节又迈进了一步。

要不要一起来做做这道题!!!

1.# 62进制的大数相加


// 实现两个62进制数的大数加法
// 输入:两个 62 进制数,String 类型,仅考虑整数
// 输出:两数之和,String 类型
// 62 进制数:按照 1-9,a-z,A-Z 递增

function sum (a, b) {}

不知道你们看到这道题时是什么感受!!!

我当时的想法是:“好像只听过2进制、8进制、16进制、32进制,62进制是什么鬼?

终于在看到这段信息后恍然大悟:62 进制数:按照 1-9,a-z,A-Z 递增

我掰着手加脚指头数了数...

  1. 0 ~ 9 是10个数
  2. a ~ z 是26个字母
  3. A ~ Z 是26个字母

加起来就是62个数,刚好凑齐62进制。

1.1 10进制大数相加(整数)

突然看到求62进制的两数之和,我内心多少是有点懵逼的,懵逼的点在哪?

就是字母咋相加啊?比如 1a(10进制的72) + 2 = 1c(10进制的74)

不过作为接受过9年义务教育的社会主义接班人,一年级咱们就学会了加法(也就是所谓的10进制加法)

回顾一下小学知识: 19 + 23?

1. 个位数相加:9 + 3 = 12。写下 2,进位 1。
  19
+ 23
-----
   2
   
2. 十位数相加并加上进位:1 + 2 + 1(进位)= 4。
  19
+ 23
-----
  42

非常简单是不是?咱们尝试一下用程序来计算两个10进制数的加法。

别担心,朋友,这段代码虽然看起来长了点,但它完全模拟的小学加法,很容易看懂。

敲黑板:10进制的大数相加也是面试的热点

const addBigNumbers = (a, b) => {
  // 从尾部往前计算
  let i = a.length - 1
  let j = b.length - 1
  // 进位标志,1或者0
  let carry = 0
  let result = ''

  while (i >= 0 || j >= 0 || carry) {
    const n1 = +(a[ i ] || 0)
    const n2 = +(b[ j ] || 0)
    // 当前位相加(包含进位部分)
    let sum = n1 + n2 + carry
    // 当前位相加如果>=10,说明要往前进1,否则要重置进位标志
    if (sum >= 10) {
      sum -= 10
      carry = 1
    } else {
      carry = 0
    }
    // 递减往前算
    i--
    j--
    // 当前位 + 前面低位的结果
    result = sum + '' + result
  }

  return result
}

console.log(addBigNumbers('19', '23')) // 42
console.log(addBigNumbers('100', '1024')) // 1124

1.2# 62进制与10进制之间如何转换?

其实咱们知道如何实现10进制大数相加之后,62进制的大数相加也就完成了一大半,只要解决这2个问题,面试官就该给你过了。

  1. 62进制转化为10进制
  2. 10进制转化为62进制

想想只要能将62进制转化为10进制进行相加,再把结果转化为62进制,问题不就迎刃而解了吗?

1a (10进制的72) + 2 = 1c (10进制的74)

1.3# 62进制转化为10进制

让我们一起来回顾一下计算机基础知识:62进制转化为10进制的规则

设 62 进制数为 d[n]d[n-1]...d[1]d[0],其中 d[i] 表示第 i 位的数字(从右向左,最低位为0)。

则该数的十进制值为:

d[0] * 62^0 + d[1] * 62^1 + ... + d[n] * 62^n

举个例子

1a = 1 * 62^1 + 10 * 62^0 // 62进制中的a代表10进制中的10,b代表的是11以此类推
   = 1 * 62 + 10 * 1
   = 62 + 10
   = 72

有了这个规律用代码实现就很方便了.

const base62ToDecimal = (str) => {
  const base = 62
  const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let len = str.length
  let sum = 0
  let n =0 // 最后一位是0,倒数第二位是1...

  while (len--) {
    let digit = digits.indexOf(str[ len ]) // 获取当前位的10进制数,例如a表示10进制的10,b表示11,c表示12...
    sum += digit * Math.pow(base, n)
    n++
  }

  return sum
}

// 示例
console.log(base62ToDecimal('1a')) // 72
console.log(base62ToDecimal('1c')) // 74

1.4# 10进制转化为62进制

同样 10进制转化为62进制也有规律可言:

  1. 用输入的十进制数除以 62,得到商和余数。
  2. 将余数对应的 62 进制字符放在结果的最低位。
  3. 将商作为新的输入,重复步骤 1 和 2,直到商为 0。
  4. 将得到的所有余数按照逆序排列,就是最终的 62 进制表示。

举个例子: 将10进制的72转化为64进制是啥?


第一步,将 72 不断除以 62,得到的余数就是 62 进制数的低位数字。依次计算如下:

1. 72 ÷ 62 = 1 ... 10 (余数为 10,10对应的62进制字符为 'a')
2. 1 ÷ 62 = 0 ... 1 (余数为 1,1对应的62进制字符为 '1')

第二步,将每次得到的余数按照逆序排列,就是最终的62进制表示:

72(10进制)= '1a'(62进制)

根据规律聪明的你也能很快用程序来实现10进制到62进制的转换

const decimalToBase62 = (decimal) => {
  const base = 62
  const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = []

  while (decimal > 0) {
    const remainder = decimal % base // 取余

    result.push(digits[ remainder ]) // 将余数真正对应62进制字符存入数组

    decimal = Math.floor(decimal / base) // 计算商,用于下次计算
  }

  return result.reverse().join('') || '0' // 反转结果
}

console.log(decimalToBase62('72')) // 1a
console.log(decimalToBase62('74')) // 1c

1.5 解题啦!!!

啰嗦了这么多,大家有没有发现解决62进制大数相加所需的知识点实在是太基础了

  1. 会小学加法
  2. 会进制转换

最后请丢出这份代码,亮瞎面试官的眼吧!!!

// 62进制转10进制
const base62ToDecimal = (str) => {
  const base = 62
  const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let len = str.length
  let sum = 0
  let n =0 // 最后一位是0,倒数第二位是1...

  while (len--) {
    let digit = digits.indexOf(str[ len ]) // 获取当前位的10进制数,例如a表示10进制的10,b表示11,c表示12...
    sum += digit * Math.pow(base, n)
    n++
  }

  return sum
}
// 10进制转62进制
const decimalToBase62 = (decimal) => {
  const base = 62
  const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = []

  while (decimal > 0) {
    const remainder = decimal % base // 取余

    result.push(digits[ remainder ]) // 将余数真正对应62进制字符存入数组

    decimal = Math.floor(decimal / base) // 计算商,用于下次计算
  }

  return result.reverse().join('') || '0' // 反转结果
}


const addBigBase62Numbers = (a, b) => {
  // 转化为10进制的字符串
  a = '' + base62ToDecimal(a)
  b = '' + base62ToDecimal(b)

  // 从尾部往前计算
  let i = a.length - 1
  let j = b.length - 1
  // 进位标志,1或者0
  let carry = 0
  let result = ''

  while (i >= 0 || j >= 0 || carry) {
    const n1 = +(a[ i ] || 0)
    const n2 = +(b[ j ] || 0)
    // 当前位相加(包含进位部分)
    let sum = n1 + n2 + carry
    // 当前位相加如果>=10,说明要往前进1,否则要重置进位标志
    if (sum >= 10) {
      sum -= 10
      carry = 1
    } else {
      carry = 0
    }
    // 递减往前算
    i--
    j--
    // 当前位 + 前面的
    result = sum + '' + result
  }

  return decimalToBase62(result) // 将10进制再转化为62进制
}

console.log(addBigBase62Numbers('1a', '2')) // 1c
console.log(addBigBase62Numbers('Z', '1')) // 10

2.# 另一种解法

咱们废了老大劲,写了一大坨的代码才实现这个功能,有没有其他更简便一点的解法呢?

其实62进制的数学性质和10进制类似,只是基数(62)不同而已,在10进制中是逢十进一,62进制中就是逢62进一了。所以在我们知道10进制的大数相加如何解之后,仅仅需要做少量的修改就可以变成62进制的大数相加。

搞起!!!

const addBigBase62Numbers = (a, b) => {
  const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let i = a.length - 1
  let j = b.length - 1
  let carry = 0
  let result = ''

  while (i >= 0 || j >= 0 || carry) {
    const n1 = digits.indexOf(a[i] || 0) // 获取当前位的10进制表示,如a表示10,b表示11 
    const n2 = digits.indexOf(b[j] || 0) // 获取当前位的10进制表示,如a表示10,b表示11 
    let sum = n1 + n2 + carry
    // 62进1
    if (sum >= 62) {
      sum -= 62
      carry = 1
    } else {
      carry = 0
    }
    // digits[sum] 将当前位转换回62进制
    result = digits[sum] + result

    i--
    j--
  }

  return result
}


console.log(addBigBase62Numbers('1a', '2')) // 1c
console.log(addBigBase62Numbers('Z', '1')) // 10

好舒畅、代码一下子少了一大坨,又可以和面试官吹牛逼了...

3.# 举一反三

文中我们只处理了整数的62进制大数相加,聪明你可以尝试一下这几种场景

  1. 带小数的62进制大数相加?
  2. n进制的大数相加?

原理都是类似的,期待你在评论区写下你的答案,一起交流。


前端胖头鱼
3.7k 声望6.2k 粉丝