LeetCode之路:171. Excel Sheet Column Number

一、引言

这道题相对来说比较简单,但是背后涉及到的东西确实挺有意思的:

我才知道 Excel 的列的表示是用的类似于二十六进制表示法来表示的

每做一题,总会有或多或少的收获,或是技术上的,或是经验上的。嗯,还是直接看题吧:

Given a column tilte as appear in an Excel sheet, return its corresponding column number.

For example:
A -> 1
B -> 2
C -> 3

Z -> 26
AA -> 27
AB -> 28

简单翻译一下:

给定一个在 Excel 表格中出现的列标题,请返回此列标题对应的列号

这里还是需要再说一下, Excel 中是怎么标记列的呢?

AZ to BA

  1. 首先,1 ~ 26 列正好对应了 A ~ Z 的26个字母,这没什么问题

  2. 然后,第 27 列怎么记录呢?很简单,我们再多一个位来表示,那么就是 AA,也就是说从 27 ~ 52 列我们使用 AA ~ AZ 来表示,这应该也没有什么问题吧

  3. 那么,现在问题来了,第 53 列怎么表示?是再多一位吗?非也,经过查看 Excel 发现第 53 列是使用的 BA 表示的;也就是说,A ~ Z 其实就是数位上的 26 个数字表示而已,而进位则以 26 为基数

  4. 最后,我们再在 Excel 中查看 ZZ 到 AAA 的转换,证实了我们在第 3 个步骤中推理的思路

ZZ to AAA

最后我们得到结论:

Excel 列的表示使用的是一种类似于二十六进制表示法的标记法,其每个数位上的表示 A ~ Z 正好对应了数字 1 ~ 26

根据我们的分析,我们已经可以很容易地完成这道题了。

二、实现:公式的简单翻译

根据我们在引言里分析的逻辑,我们可以抽离出这么一个公式:

number=i=0n(str[i]×26i)

这里简单解释下:

  1. number 表示列标题转换出来的列号,str 表示传入的字符表示的数值,后面的 26i 表示当前位的权值,比如最低位的权值为 260 也就是 1,倒数第二位的权值为 261 也就是 26

  2. 我们只需要遍历整个字符串数组,将每个字母对应的数字取出来,再乘以当前位的权值进行累加计算即可

那么现在剩下的问题就是如何将类似于 A 这样的大写字母转化为数字 1 了:

了解 ASCII 码的我们知道,大写字母 A ~ Z 的 ASCII 码是相邻的,也就是说我们通过当前字母减去 ‘A’ 的码值就能得知当前字母相对于 ‘A’ 的位置,即可推算当前字母的值

现在公式也有了,关键的转化问题也解决了,我的第一个版本的代码也就出来了:

// my solution 1 , runtime = 6 ms
class Solution1 {
public:
    int titleToNumber(string s) {
        int result = 0;
        for (int i = 0; i < s.size(); ++i) {
            int temp = s[s.size() - 1 - i] - 'A' + 1;
            result += temp * pow(26, i);
        }
        return result;
    }
};

在这个版本的方法中,我使用了一个 for 循环遍历了整个字符数组,从右边开始每次取出一个数位进行累加计算;另外,这里使用了 std::pow 方法进行 26i 的计算,非常方便。

当然我追求代码简洁的我,对于这段代码并不满意,觉得行数太多,并且代码逻辑并不是非常直观,于是有了下一段代码:

// my solution 2 , runtime = 6 ms
class Solution2 {
public:
    int titleToNumber(string s) {
        int result = 0, i = s.size() - 1;
        for (auto c : s) result += pow(26, i--) * (c - 'A' + 1);
        return result;
    }
};

在这段代码中,我使用了范围 for 循环,每次循环可以直接取到对应数位值,与上一个版本的代码不同的是,遍历权值的 i 值初始化为字符串的长度 - 1,正好可以取到最高权位值,这是因为范围 for 循环是从最高位开始循环的。

这段代码简洁明了,只用了 3 行代码,清晰高效地表达了程序逻辑,我还是很喜欢自己写的这个版本的代码的。

三、观光:看看最高票答案

最高票答案其实也没有其他的思路可选,处理思路都是那个公式,只是编写代码的方式不同而已:

int result = 0;
for (int i = 0; i < s.size(); result = result * 26 + (s.at(i) - 'A' + 1), i++);
return result;

其实并没有觉得比我的简洁到哪里去,^_^,还是固执地认为我写的更好。

不过还是有值得学习的地方,比如没有使用 std::pow 方法,而是使用上一个 result 来乘以 26 计算;另外对于遍历变量 i 使用了 std::string::at 函数来进行访问,那么此方法与普通的 std::string::operator[] 有什么不同呢:

std::string::at

这里直接贴出来 C++在线参考手册之 std::string::at 中对于 std::string::at 的解释:

也就是说,std::sting::at 会对访问进行边界检查,如果进行了越界的访问,则会抛出异常 std::out_of_range

我自己写了一个程序,尝试使用 std::string::at 访问了无效的位置,果然弹出了这样的异常:

out of range

以后想安全的访问的话,就可以尝试使用这样的方法。

四、总结

这道题还是比较简单的,有意思的是我才发现 Excel 使用了这样奇怪的表示方法,不知道微软的产品经理是怎么考虑的,居然思考出来了这么神奇的表示列的方法。

简单的题能够清晰地进行:

分析问题 –> 解决问题 –> 验证解决方案

的路径来解决问题,其实还是比较练人的,毕竟太多太多新人都会比较急迫,要么想要跳过分析问题的步骤快点写代码,要么就是分析好了因为不熟悉语言写不出简洁优雅的代码。

LeetCode 之路还在继续:

To be Stronger!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值