持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
LeetCode 75 学习计划适用于想为技术面试做准备但不确定应该聚焦于哪些题目的用户。学习计划中的题目都是经过精心挑选的,Level 1和 Level 2 学习计划是为初级用户和中级用户准备的,题目覆盖了大多数中层公司面试时所必需的数据结构和算法,Level 3 学习计划则是为准备面试顶级公司的用户准备的。来源
第 2 天
同构字符串
难度:简单
题目
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
示例 1:
输入:s = "egg", t = "add"
输出:true
示例 2:
输入:s = "foo", t = "bar"
输出:false
示例 3:
输入:s = "paper", t = "title"
输出:true
解答
第一想法:想要一一映射,就要有相同种类的字母,并且字母个数相同,所以就统计每个字符有几种字符,每个的个数是几个,最后再对比二者是否相等即可。JSON.Stringfy([a:1,b:2]) == JSON.Stringfy([a:1,b:2])。
但是这样,要可能要用到 24 类字母,[a,b,...x,y,z],写太费劲,肯定不明智。
然后想,可以用 map 吗,把字符串的每个字符加在 map 里;
function setObj(str){
let arr = str.split("")
let obj = new Map()
let countVal = []
arr.forEach(item=>{
if(obj.has(item)){
obj.set(item,obj.get(item)+1)
}else{
obj.set(item,1)
}
})
return obj
}
setObj("ssa") // Map(2) {'s' => 2, 'a' => 1}
setObj("ssaab") // Map(3) {'s' => 2, 'a' => 2, 'b' => 1}
如果相同,则 value + 1,最后比较 map 的项的个数,计算一下有几个 0,几个 1......
再用一个 map 记录:
期望是: Map(2) {'s' => 2, 'a' => 1} 变成 {2:1,1:1} 表示2个重复的字符,有 1 个;1 个字符有 1 个;
Map(3) {'s' => 2, 'a' => 2, 'b' => 1} 变成{2:2,1:1} 表示重复 2 次的字符有 2 个;重复 1 次的字符有 1 个;
上述代码改造为:
function countNum(str){
let arr = str.split("")
let obj = new Map()
let countObj= new Map()
arr.forEach(item=>{
if(obj.has(item)){
obj.set(item,obj.get(item)+1)
}else{
obj.set(item,1)
}
})
obj.forEach((val,key)=>{
if(countObj.has(val)){
countObj.set(val,countObj.get(val)+1)
}else{
countObj.set(val ,1)
}
})
return countObj
}
countNum("ssa") // {2 => 1, 1 => 1}
countNum("ssaab") // {2 => 2, 1 => 1}
OK,到这里已经成功一大半了,比较 countNum("ssaab") 和 countNum("xxnnc") 两个对象是否相等即可。
完整算法:
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isIsomorphic = function(s, t) {
function countNum(str){
let arr = str.split("")
let obj = new Map()
let countObj= new Map()
arr.forEach(item=>{
if(obj.has(item)){
obj.set(item,obj.get(item)+1)
}else{
obj.set(item,1)
}
})
obj.forEach((val,key)=>{
if(countObj.has(val)){
countObj.set(val,countObj.get(val)+1)
}else{
countObj.set(val ,1)
}
})
return countObj
}
const resBoolean = JSON.stringify(countNum(s))===JSON.stringify(countNum(t))
return resBoolean
};
纳尼?? 我在控制台输出明明是 false 啊?力扣抽风?
看不出毛病在哪,可以把代码复制在题解中,然后复制在控制台中跑跑看看差异。
最后发现真的:
const a = new Map()
a.set("A",1)
JSON.stringify(a)
这段代码放在 leetcode.cn/problems/is… 这个界面的浏览器打印输出,和其它页面浏览器打印输出不一致!!!
不可不谓不神奇!
先留个坑。
这里至少说明对 Map 用 JSON.stringify 是不可靠的。
把 Map 改成 Object
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isIsomorphic = function(s, t) {
function countNum(str){
let arr = str.split("")
let obj = new Map()
let countObj= new Map()
arr.forEach(item=>{
if(obj.has(item)){
obj.set(item,obj.get(item)+1)
}else{
obj.set(item,1)
}
})
obj.forEach((val,key)=>{
if(countObj.has(val)){
countObj.set(val,countObj.get(val)+1)
}else{
countObj.set(val ,1)
}
})
return countObj
}
const p1 = Object.fromEntries(countNum(s).entries())
const p2 = Object.fromEntries(countNum(t).entries())
return JSON.stringify(p1)===JSON.stringify(p2);
};
敲!再审题:
同时不改变字符的顺序
罢了,计数肯定不行了,看题解吧。。。
var isIsomorphic = function(s, t) {
const s2 = {};
const t2 = {};
const len = s.length;
for (let i = 0; i < len; ++i) {
const x = s[i], y = t[i];
if ((s2[x] && s2[x] !== y) || (t2[y] && t2[y] !== x)) {
return false;
}
s2[x] = y;
t2[y] = x;
}
return true;
};
维护两张哈希表,第一张哈希表 s2 以 s 中字符为键, 映射至 t 的字符为值;第二张哈希表 t2 以 t 中字符为键,映射至 s 的字符为值。
从左至右遍历两个字符串的字符,不断更新两张哈希表,
如果出现冲突,即当前下标 index 对应的字符 s[index] 已经存在映射 且不为 t[index]
或当前下标 index 对应的字符 t[index] 已经存在映射 且不为s[index] 时,说明两个字符串无法构成同构,返回 false
总结
敲,读题吗?顺序不变啊!
不得不说算法很考验细节,一个变量引用错误,整个都跑不通。