之前一直对位运算十分畏惧,但是最近刷题的时候发现好的解法通通的离不开位运算,于是乎分析起了一位大佬的代码(梦璃夜·天星 LeetCode大佬的名字),题目如下:
给你一个由英文字母组成的字符串 s ,请你找出并返回 s 中的 最好 英文字母。返回的字母必须为大写形式。如果不存在满足条件的字母,则返回一个空字符串。
最好 英文字母的大写和小写形式必须 都 在 s 中出现。
英文字母 b 比另一个英文字母 a 更好 的前提是:英文字母表中,b 在 a 之 后 出现。
示例 1:
输入:s = "lEeTcOdE"
输出:"E"
解释:
字母 'E' 是唯一一个大写和小写形式都出现的字母。
示例 2:
输入:s = "arRAzFif"
输出:"R"
解释:
字母 'R' 是大写和小写形式都出现的最好英文字母。
注意 'A' 和 'F' 的大写和小写形式也都出现了,但是 'R' 比 'F' 和 'A' 更好。
示例 3:
输入:s = "AbCdEfGhIjK"
输出:""
解释:
不存在大写和小写形式都出现的字母。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/greatest-english-letter-in-upper-and-lower-case
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
一个简单的题目,硬算也是没问题的。但是,如果用上位运算,嘎嘎快了!WC
上大佬的代码!
class Solution {
public:
string greatestLetter(string s) {
int64_t t = 0;
for(char c: s) t |= 1ll << (c - 'A');
t = t & (t >> ('a'-'A'));
return t ? string(1, 63-__builtin_clzll(t)+'A') : "";
}
};
也就几行的代码,接下来,图文并茂按照自己的理解来解释下这些语句啥意思
int64_t t = 0;
这个就没啥好说的,64位的一个数,内容如下:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
for(char c: s) t |= 1ll << (c - 'A');
这句代码就比较关键了,他可分为四步走(个人想法):
Step1: for循环里面的操作
将字符串S中的数据遍历一遍
Step2: 对S遍历的同时,算出其与大写字母A的ASCLL码的差值
Step3: 对值为1(可能说法有点问题)的64位long long 数进行左移 左移的位数就是Step2算的差值
此时可以发现,如果说遍历的字母为A,那么他们的差值应该不会改变,所以也就不需要左移。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
上表表示已经识别了字母A,如果说遍历的字母是B,那么位表示便是:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
由此可以发现,此番操作后,遍历的字母基本可以按照zyx...cba...ZYX...CBA这样的表示了,是从大到小的一个顺序(按照ascll码)。
Step4: 对进行左移了的数进行或运算
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
上表是假设遍历字母是B的位表示(自己这么叫的,我也不知道该叫啥),下表示初始值。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
进行对应位或操作,容易看出,结果如下:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
t = t & (t >> ('a'-'A'));
这句代码就是一个求结果代码,分三步走:
Step1: 首先求大小写字母他们的距离差值,应该是对应大小写(A与a)他们之间的位置差值为32.
Step2: 对遍历完的数据进行右移操作,为了将小写字母移动到其对应的大写字母的对应位上。
展示一个不是很恰当的例子:
z | y | x | 0 | 0 | 0 | Z | Y | X | 0 | 0 | u |
Table1
上表是原始数据,然后进行右移6位操作
0 | 0 | 0 | 0 | 0 | 0 | z | y | x | 0 | 0 | 0 |
Table2
我们所举例子是移动6位便可以实现大小写字母位置对齐操作(小写字母对齐大写字母),代码里面计算的'a'-'A'也是一样的作用。
Step3: 最后一步,进行与操作,判断是否存在大小写字母都存在的情况
如Table1和Table2所示,他们的与操作可以表示为:
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 |
Table3
Table3中的1代表该位置的大小写字母均存在,最后一步便是求该位置的具体表示值了。
return t ? string(1, 63-__builtin_clzll(t)+'A') : "";
如果t为0,那表示不存在大小写均存在的字母,则输出"";如果存在这么个都存在的情况,那么他们的对应位应该是1,接下来就是找这个1的位置了。
__builtin_clzll()这个函数,可以返回从左往右数第一个1值得相对位置(对于最左边数据讲,也就是其左边有多少个0),对于Table3来说就是6。
从上上一句代码的第2,3步可知,首先对原始数据进行了一个右移操作,那么其左边位置(高位)均为0,那部分位置包含小写字母的映射位置。即原来小写字母映射位置全部都为0了,然后这些小写字母的映射位置进行了偏移,偏到了大写字母的映射位置。最后数据应该就变成了像Table3那样了。
至于代码中的63,则是对1位置与其所对应的字母之间关系的修正系数了。
Exam:
一共有64位,小写字母z映射的位置为第58位(从右往左数),因为z的ascll码值为122,A为65,差值为57,但是下标是从1开始的,那么z对应的位置就是58。
往右偏移32位后,z到了Z的初始映射位置,此时其下标应该是26,__builtin_clzll()函数返回的值就是38。这是要根据这个值来推算Z的值,以A为基准,如果要得到Z那么应该比A大25。
所有应该有 n - 38 = 25,可以求得n =63。
Ps:写的不好的地方还请多多包容!