程序员面试金典 - 判定字符串是否唯一

59 阅读2分钟

题目来源:leetcode

题目描述:

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

题解:

看到问题,第一反应是使用循环+新建map数据结构进行解答,代码如下

解法1:

var isUnique = function(astr) {    // 思路1    let isAllSame = true;    const strMap = {}    for(let i of astr) {        if(strMap[i]) {            strMap[i] += 1        } else {            strMap[i] = 1        }    }    for(let k in strMap) {        if(strMap[k] > 1) {            isAllSame = false;            break;        }    }}

时间复杂度:O(n)

空间复杂度:O(n)

思考过后,其实这里map可以替换成Set数据结构,大致思路也是如此,就不多写了。

那能不能不新建新的数据解构呢?

解法2:循环嵌套

    // 思路2    for(let i = 0 ; i < astr.length; i++) {        for(let j = i + 1; j < astr.length; j++) {            if(astr[i] === astr[j]) {                return false            }        }    }

时间复杂度:O(n2)

空间复杂度:O(1)

上面的步骤虽然不需要新建数据结构,但是时间复杂度还是不友好,有没有更友好的方式呢?

解法3:位运算

    let bitMask = 0;    for(let i of astr) {        let move_bit = i.charCodeAt(0) - 'a'.charCodeAt(0)        let charBit = 1 << move_bit        if((charBit & bitMask) > 0) {            return false        }        bitMask |= charBit    }
    return true

题解分析:

Js位运算介绍可参考:按位与

题目告诉我们,这里都是字符串,并且都是小写字母,总共26个,那我们能不能创建一个26位的数字,00...01,从低位到高位每一个位置分别代表一个字母,如果字符串中出现了字母,就把对应位置上的值改为1;

如字符串a,对应的数字为:00....1;字符串b,对应的数字为:00....10

如果字符串中某个字母出现了多次,那对应的两个数字做与运算,一定大于0;

反之,如果字符串中所有的字母都只出现了依次,那与运算的结果,一定是0;

好,按照上面的说法,有三个问题要解决。

问题1:如果用0000..1表示a,0000..10表示b,如果有26个字母,我岂不是要建一个map表,然后再一一对应获取?

这样太麻烦了,太不优雅了吧?

怎么办?

位移运算由此而生:位移

我们发现,如果用阿拉伯数字1表示a,2表示b,它们之间的相差1;

相应的,只需要把0000..1左位移1,是不是就得到了0000..10。

惊喜!!!

也就是说我们只需要得到当前字母相相对于a的编码差,就能够获取到对应的位运算数值了。

Js中获取编码的方式

let move_bit = i.charCodeAt(0) - 'a'.charCodeAt(0)
let charBit = 1 << move_bit

**问题2:**与运算的左右应该是什么?

左边我们知道,是当前字母对应的位数值,那右边呢?

右边应该是当前字母之前的所有字母的位数值表达?

那问题来了,如何获取。

首先想到的是循环,对字符串进行循环

for(let i of astr) {}

循环体中做什么呢?

或运算应运而生。

根据或运算特性,如字符串abc

第一个字母a,表示为000...1

第二个字母b,表示为00...10

那000...1 | 00...10 = 00...11,完美。

所以,我们可以在循环体的末尾,对当前字母位运算值和之前所有字母的位运算值做或运算。

bitMask |= charBit

问题3:如何判断是否重复?

经过上线的分析,我们就好判断了,也就是终极武器:与运算。

如果字符串为aa,那也就是000..1 & 000..1,结果一定是大于0的;

如果字符串为ab,那也就是000..1 & 00..10,结果一定是等于0的;

所以,只需要通过与运算进行判断即可,

        if((charBit & bitMask) > 0) {            return false        }

综上,全部的代码为:

    let bitMask = 0;    
    for(let i of astr) {        
        let move_bit = i.charCodeAt(0) - 'a'.charCodeAt(0) 
        let charBit = 1 << move_bit
        if((charBit & bitMask) > 0) {           
            return false        
        } 
        bitMask |= charBit    
    }
    return true

时间复杂度:O(n)

空间复杂度:O(1)