[字符串 哈希表] 205. 同构字符串(利用哈希表寻找双向映射关系)
205.同构字符串
题目链接:https://leetcode-cn.com/problems/isomorphic-strings/
分类:
- 字符串(给定新概念“同构”,总结出具体要求和实现方法,提取出关键词:双向映射);
- 哈希表(记录两个字符串上字母的映射关系);
- Set(统计字符串上的字母种类个数)
题目分析
同构的要求题目已经很明确的给出了:
- 所有出现的字符都必须用另一个字符替换
- 两个字符不能映射到同一个字符上;
- 字符可以映射到本身;
- 映射后保留字符的顺序。
使用map就能解决上述的要求,但注意,一个map中key=s上的字母,value=映射到t上的字母,所以一个map只能提供单向的s -> t映射关系,而t -> s的映射关系没有考虑在内,所以这题的难点在于:
- 是否意识到存在两个方向的映射关系(s->t,t->s);
- 如何实现s->t,t->s两个方向的映射:
- 思路1采用的是map+set+字母种类数比较,只用了一个map用来记录s->t的映射关系,set用来统计t的字母种类数,如果同时满足:s->t字母映射关系合法,且s,t字母种类数相等,则s,t同构。
- 思路2采用的是一个map + 调换参数执行两次函数
- 思路3采用的是两个数组分别记录s->t,t->s的映射关系
思路1:map + set + 字母种类数比较(自己想到的,推荐)
算法设计
1、要求1可以转化为:s,t中字母的种类和字母数量要对应。
示例1:s = “egg”, t = “add”
s中有两种字母e和g,t中也有两种字母a和d;
s中有一个个数为1的字母,有一个个数为2的字母,相应的t中也有一个个数为1的字母,一个个数为2的字母,所以两个字符串满足要求1。
又因为s,t长度相同,所以只要字母种类相同,它们的数量也就自然能够满足对应关系。
如何统计s,t上字母的种类数量?
在后面使用了map以后,map的键值对数量=s的字母种类数,需要再开辟一个set来存放t的无重复字母,最后set的大小 == t的字母种类数。
2、要求2可以用map来实现,key=s上的字母,value=映射到t上的字母
一旦出现同一个key而value不同,说明 t 上的两个字符映射到s上的同一个字符了,不满足要求1,直接返回false;
但是这个map只提供了s -> t的映射,如果出现:s=ab,t=cc,a->c,b->c合法,但t->s时c同时映射了两个字母,所以是不合法的:
- 解决方法1:map结合算法设计1里的处理,统计字母的种类数量;(思路1)
- 解决方法2:分别以s,t和t,s作为参数执行两次,就能分别处理s->t和t->s两个映射,最后两个函数的返回值相与的结果就是最终结果。(见思路2)
- 解决方法3:不用map,设置两个数组提供map的效果,分别存放s->t和t->s的字母映射关系,(见思路3)
3、要求3不需要对映射规则做任何限制,就能满足字符可以映射到本身的要求;
4、要求4只需要做到同时遍历s,t同一个索引位置上的字符就能满足
使用了map后,我们遍历两个字符串都从0开始访问同一个下标的字母,同一个下标处s上的字母和t上的字母两两配对,所以相对顺序在遍历过程中被隐性保存下来了。
实现代码
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character, Character> map = new HashMap<>();
Set<Character> set = new HashSet<>();//统计t字符串上的字母种类数
//s的字母种类数不用统计,就等于map.size()
for(int i = 0; i < s.length(); i++){
char sCh = s.charAt(i), tCh = t.charAt(i);
if(map.containsKey(sCh)){
if(tCh != map.get(sCh)) return false;
}
else{
map.put(sCh, tCh);
}
set.add(tCh);
}
return map.size() == set.size();
}
}
思路2:map + 调换参数执行两次
设置一个check函数用来判断一个方向的映射关系是否合法。
check函数中只使用了一个map,作用和思路1相同,记录单个方向上字母的映射关系。
调用两次check函数,相当于考虑了两个方向上的映射关系:
check(s, t) && check(t, s),
最后,返回两个函数返回值相与的结果即可。
实现代码:
class Solution {
public boolean isIsomorphic(String s, String t) {
return check(s, t) && check(t, s);
}
public boolean check(String s, String t){
Map<Character, Character> map = new HashMap<>();
for(int i = 0; i < s.length(); i++){
char sCh = s.charAt(i), tCh = t.charAt(i);
if(map.containsKey(sCh)){
if(tCh != map.get(sCh)) return false;
}
else{
map.put(sCh, tCh);
}
}
return true;
}
}
思路3:两个数组记录s->t,t->s(较简洁,推荐)
设置两个数组分别记录两个方向的映射关系,数组下标对应字符的ASCII值,元素值存放与之对应的字符的ASCII码;
因为要拿下标来对应ASCII,而ASCII一共128个字符,所以开辟的数组大小为128:
int[] sTot = new int[128];
int[] tTos = new int[128];
因为s.charAt(i)放到数组的下标处就会自动转换为ASCII码,所以数组实际使用时如下:
- sTot[s.charAt(i)]表示s上第i个字符对应在t上的第i个字符的ASCII;
- tTos[t.charAt(i)]表示t上第i个字符对应在s上的第i个字符的ASCII;
如果sTot[s.charAt(i)] != t.charAt(i),说明s->t的映射是不合法的;同理,
如果tTos[t.charAt(i)] != s.charAt(i),说明t->s的映射是不合法的。
知识点:
1、char字符写在数组的下标处时会自动转换为ASCII码值。
2、int数组存放char型数据时,char型数据会自动转成ASCII码存入。
实现代码
class Solution {
public boolean isIsomorphic(String s, String t) {
int[] sTot = new int[128];//记录s->t的映射关系
int[] tTos = new int[128];//记录t->s的映射关系
for(int i = 0; i < s.length(); i++){
int sCh = s.charAt(i), tCh = t.charAt(i);
if(sTot[sCh] == 0) sTot[sCh] = tCh;
else if(sTot[sCh] != tCh) return false;
if(tTos[tCh] == 0) tTos[tCh] = sCh;
else if(tTos[tCh] != sCh) return false;
}
return true;
}
}