LeetCode-461-Hamming-Distance

原文首发于 baishusama.github.io,欢迎围观~

肝不动业务代码的时候,就时不时地做个题吧/w\

题目要求

原题目:461. Hamming Distance

维基百科中的“汉明距离”:

在信息论中,两个等长字符串之间的汉明距离(英语:Hamming distance)是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。

题干:现在求两个整数 xy 之间的汉明距离,其中,0 ≤ x, y < 2^31。

思路

根据汉明距离的定义、再结合原题目中的“Explanation”部分,可以得知我们需要对整数 x,y 对应的二进制数逐位累加差异——统计得到的不相同的位的个数,即为所求。

  1. 思路一
    • 我们要么先将整数转换成二进制——但是由于 JavaScript 只有一个 Number 类型用来表示数字,所以二进制数只能用别的方式代为表示——e.g. 4 的二进制数可以表示为字符串 (4).toString(2); // "100",或者数组 [1,0,0] (数组好像没有直接的转换方法?)
    • 再对字符串或者数组中的“二进制值”做求解/求和。
  2. 思路二
    • 虽然 JavaScript 不能很好地支持二进制数的表示,但是 JS 中天然有位操作符——其中,异或 ^ 恰好能满足我们的需求!
      • 如果 x 异或 y 得到 zz = x ^ y),那么 z 对应的二进制表示中 1 的个数即为所求。
    • 故再逐位求和即可:
      • 首先我们来看看,一个变量和 1 做与操作(&)会有什么现象:
        • val & 1,如果 val 的最右一位是 1 那么结果是 1 ;
        • val & 1,如果 val 的最右一位是 0 那么结果是 0 。
      • 此时我们至少能判断最右一位的 01 情况了。
      • 那么再结合右移位操作 >> 来不断使高位逐个变成最右位,我们就能计算一个二进制数的所有位的 01 情况!

其实按照上述思路来看,整体是分成两个步骤的:

  1. 得到差异
  2. 累加差异

两个步骤均可以用一下两种方式二选一解决:

  • 位操作
  • 其他数据结构,比如字符串

所以可以 2 * 2 = 4 组合出四种大致思路。而第一步的“得到差异”个人比较推荐的做法是用异或一步到位。(后续实现中的第一步均采取了异或。)

更多的 JS 相关的位操作符请参考 Bitwise operators @MDN

代码实现

实现一

先异或再转字符串最后通过 match 方法(正则)计数的实现:

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
var hammingDistance = function(x, y) {
    var xor = x ^ y;
    var str = xor.toString(2);
    var match = str.match(/1/g); // 用正则匹配计算个数;match 为 null 或者数组。
    return match ? match.length : 0 ;
};

提交详情:

实现一的提交详情

实现二

先异或再转字符串最后通过 split 方法计数的实现:

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
var hammingDistance = function(x, y) {
    var xor = x ^ y;
    var str = xor.toString(2);
    return str.split('1').length - 1; // 通过 split 计算某个字符(串)出现的个数
};

提交详情:

实现二的提交详情

实现三

完全的位操作实现:

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
var hammingDistance = function(x, y) {
    var xor = x ^ y;
    var sum = 0;
    
    sum += xor & 1;
    while(xor = xor >> 1){
        sum += xor & 1;
    }
    
    return sum;
};

提交详情:

实现三的提交详情

结果分析

其实第一个提交详情的图里的 runtime 和 distribution 不太可信,因为我第一次截实现一的详情图的时候,结果是“Your runtime beats 94.43 % of javascript submissions.”,后来我重新 submit 再打开之后,就变成“99.80 %”了……然后为了满足自己的虚荣心,贴了第二次的图(不要打我)。

其他解法

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
var hammingDistance = function(x, y) {
    let n = x ^ y;
    let count = 0;
    while (n) {
      n = n & (n - 1)
      count++;
    }
    return count;    
};

第一眼看到这个排名极靠前、完全位操作实现的解法的时候,虽然看不懂,但是可以看出这个 accepted 的解法中的 while 在最少的循环次内就得到了结果。

因为在我的完全位操作的实现(实现三)中,当最高位是 1 的那一位在越高位的时候,while 就会循环越多次,如果最高位的 1 在第 M 位(M >= 0),那么循环条件将执行 M+1 次,循环体将执行 M 次,即很多是 0 的位也被列入总和—— 0 虽然并不会对总和产生影响,但是多执行的代码会增加时间上的开销。

而上面这个解法中能够只执行 count 次就结束循环,堪称完美!那么,现在我们来看看最为关键的代码 n = n & (n - 1) 有什么奥秘。

二进制数减一是一个奇妙的操作——当一个二进制数减一的时候,低位的 0 会变成 1,直到遇到一个最低位的 1 被减成 0。假设这个数 n 中最低位的 1 位于第 m 位(m >= 0),最高位的 1 位于第 M 位,最高位为第 N 位。那么此时,0~m 位各位上的数字都做了取反操作(包含一个 m 位的 1 和 0 ~ m-1 位的所有 0),而 m+1 ~ N 位各位上的数字都保持不变,即数 n 与上 (n - 1) 会导致 0~m 位均变成 0 ,这个过程中影响到了最低位(m 位上的一个 1)。即,做一次 n = n & (n - 1) 的操作会使得二进制数少一个最低位上的 1

特别的,二进制数中只有一个 1 的时候,n & (n - 1) // == 0。由此 n > 0 && (n & (n - 1)) 也常用于判断整数 n 是不是 2 的指数:

function isPowerOfTwo(n){
    // better judge if n is an int at first..
    if(n <= 0) return false;
    return !(n&(n-1));
}

关于 JS 中整数的判断,ES5 及以前请看 How to check if a variable is an integer in JavaScript? @SO,ES6 及以后可以用 Number.isInteger()

相关补充

刚好最近在看《Effective JavaScript》这本书,书中第二条——“理解 JavaScript 的浮点数”,有一些相关知识。

JS 中的数字

JavaScript 中的数字(number)都是 64 位双精度浮点数,即 double。JS 中的整数仅仅是其一个子集,整数的范围在 [-2^53, 2^53]。

Safe Integer

Safe integer 是符合如下描述的整数:

  • 能被精确地表示为一个 IEEE-754 双精度浮点数
  • 这个表示不能是其他整数的舍入结果

所以,2^53 虽然能被 IEEE-754 双精度浮点数精确表示,但是由于 2^53 + 1 在向零舍入和就近舍入中会被舍入为 2^53 ,所以不符合 safe integer 的要求,用 Number.isSafeInteger 判断会得到 false :

Math.isSafeInteger

JS 中的位运算

位运算符的工作原理:

标准的 JS 浮点数 =隐式转换=> 32 位的有符号整数 =做位运算后返回=> 标准的 JS 浮点数

说到 32 位有符号整数,比较容易想到 C 语言中的 int 类型。那么也就稍微可以理解下题目中给出的 xy 的范围 [0,2^31) ( 32 位有符号整数的非负整数范围)了。

小结

这虽然是 leetcode 上最简单的一道题目,但是我还是有所收获,特别是对 二进制数中 1 的个数的求解方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,300评论 18 399
  • 1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不...
    曲终人散Li阅读 3,245评论 0 19
  • 坐在大一写过社团期望的位置,我有点焦虑,也很平和的一步步翻找着有用的信息,掐指一算,其实日子不难熬,我每天安心学习...
    绿豆冰沙老干妈阅读 259评论 0 0
  • 发现定一些计划,强迫自己去完成还是比较有意思的,尤其是自己最后完成的情况下。今天是彻底发现自己的笔记本电脑太卡了,...
    空也不空阅读 203评论 0 0