请教一个算法问题,把数字转换为A,B,C……,如何实现?

image.png
现在我想根据数字转换为英文字母:比如 A->1,B->2,……AA->27,AB->28。
我的思路本来是想维护一个数组,然后根据辗转相除法,然后生成对应的下标,但是有问题:

function numToLetter(originNum = 53) {
  let letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  let num = originNum; // 随意输入一个数字
  let numArr = []; // 得到num对应的字母下标
  // 辗转相除,然后取余数
  while (num > 0) {
    numArr.push(num % 26);
    num = Math.floor(num / 26);
  }
  console.log('**********************************')
  // 输出对应数字的字母下标
  console.log(`${originNum}对应的字母下标为`, numArr.reverse());
  let str = ''; // 数字对应的字符串
  numArr.forEach(v => {
    str += letter[v-1]
  })
  console.log('这个字符串为:', str)
}
numToLetter(53)
numToLetter(52)

image.png

53 = 26^1 * 2 + 26^0 * 1
52 = 26^1 * 2 + 26^0 * 1

26 进制是这样算的,但是转换为下标的话,这样写有问题,不知道如何优化,或者别的思路求数字转字母

阅读 5k
5 个回答

首先,这个“26 进制”不是标准 26 进制,是双射 26 进制(Bijective positional notation [wiki]

在双射 k 进制下,
$$\overline{a_na_{n−1} \cdots a_1a_0} = a_nk^n + a_{n-1}k^{n-1} + \cdots + a_1k + a_0 $$

其中 $$\{1, 2, \cdots, k \}$$ 是双射 k 进制使用的数字。

所以,直接取余才会遇到没法处理余数是 0 的情况。

用我们更为熟悉的十进制来类比一下

在双射十进制中,假定使用 1-9A 这 10 个数字。

  • 10 -> (1, 0) -> A
  • 11 -> (1, 1) -> 11
  • 20 -> (2, 0) -> 1A
  • ...
  • 100 -> (1, 0, 0) -> 9A
  • 110 -> (1, 1, 0) -> AA
  • 111 -> (1, 1, 1) -> 111

那么,遇到余数是 0 怎么办?以双射十进制下表达 100 为例(手算过程,伪代码)

X = 100
MOD_1 = 100 % 10
if (! MOD_1) { // 余数是 0,但没有数字 0,怎么办?
    X -= 10 // 只好从上一位“借走”10
    MOD_1 = 10 // 这样,这一位就是数字 A 了
}
X /= 10 // 90 / 10 == 9

MOD_2 = 9 % 10
// 数字 9,没问题
X /= 10 // 9 / 10 == 0,结束

Y = MOD_2 ++ MOD_1 // 9A,没问题

现在我们可以改进原来的代码:

function numToLetter(originNum = 53) {
  let letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  let num = originNum; // 随意输入一个数字
  let numArr = []; // 得到num对应的字母下标
  // 辗转相除,然后取余数
  while (num > 0) {
+   let mod = num % 26;
+    if (! mod) {
+     num -= 26;
+     mod = 26;
+   }
-   numArr.push(num % 26);
+   numArr.push(mod)
    num = Math.floor(num / 26);
  }
  console.log('**********************************')
  // 输出对应数字的字母下标
  console.log(`${originNum}对应的字母下标为`, numArr.reverse());
  let str = ''; // 数字对应的字符串
  numArr.forEach(v => {
    str += letter[v-1]
  })
  console.log('这个字符串为:', str)
}

结果:

numToLetter(52)
// **********************************
// 676对应的字母下标为 
// Array [ 1, 26 ]
// 这个字符串为: AZ

numToLetter(26 * 26) // 676
// **********************************
// 676对应的字母下标为 
// Array [ 25, 26 ]
// 这个字符串为: YZ

numToLetter(26 * 26 + 26) // 702
// **********************************
// 702对应的字母下标为 
// Array [ 26, 26 ]
// 这个字符串为: ZZ

楼主的代码可以只修改两个地方:

- numArr.push(num % 26);
+ numArr.push(--num % 26);

- str += letter[v-1]
+ str += letter[v]

总体思路和 @ForkKILLET 一样,减 1 把 1~26 映射为 0~25 即可


和这题一模一样:Excel表列名称
反向转换:Excel 表列序号
可以点进去看题解复制一下学习一下

利用字符编码会更简洁优雅,参考代码

数字转字母

const convertToTitle = n => {
    let codes = []
    while (n > 0) {
        codes.push(65 + --n % 26)
        n = n / 26 | 0
    }
    return String.fromCharCode(...codes.reverse())
}

字母转数字

const titleToNumber = s => {
    let n = 0
    for (const c of s) {
        n = n * 26 + c.charCodeAt() - 64
    }
    return n
}

之前写过一个PHP版本根据列数,生成excel的列名。
https://segmentfault.com/a/11...

改造一下:

// originNum 为 >=1 的正整数
function numberToLetter(originNum = 53)
{
    let letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    let num = originNum - 1;
    if (num < 0) {
        num = 0;
    }
    // 这里算是一个小优化,可以按照需要,加上更多的可以直接映射的
    if (num < 26) {
        return letter[num];
    }

    let str = '';
    while (true) {
        remain = num % 26;
        str = letter[remain] + str;
        num = Math.floor(num / 26);
        if (num == 0) {
            break;
        }
        num--;
    }
    return str;
}

$ numberToLetter(1)
'A'
$ numberToLetter(26)
'Z'
$ numberToLetter(27)
'AA'
$ numberToLetter(53)
'BA'

image.png
希望这个能帮到你

新手上路,请多包涵

可以利用ascii码转换

推荐问题
宣传栏