题目:
Given two strings s and t, determine if they are isomorphic.
Two strings are isomorphic if the characters in s can be replaced to get t.
All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.
Example 1:
Input: s ="egg",
t ="add"
Output: true
Example 2:
Input: s ="foo",
t ="bar"
Output: false
Example 3:
Input: s ="paper",
t ="title"
Output: true
Note:
You may assume both s and t have the same length.
这道题要求两个字符串有没有相同的pattern,并且要一个字母只map一个字母,而且是双向的map。
思路很自然的想到了用map存储每个字母对应的新字母,然后用C++实现了一下,submit发现WA卡在了"ab", "aa"这一个test case上。在第一个字符时a映射到a,而第二个字符b也映射到a,这就不对了。然鹅,unordered_map并不提供直接查找value的操作(搜了一下好像没搜到?),于是想用C++写的话就必须得自己造个数据结构了(羡慕隔壁Java玩家)。
然后就看了一下discussion,模仿着自己造了两个长度为128的数组,初始化为0,用来存放每一个ASCII字符对应的mapping字符,并且两个数组是对称的,比如现在遇到了s: egg和t: add,那么在关于s的数组里要存放e对应的是a,并在关于t的数组里存放a对应的是e。每次循环时判断当前这个字符在不在两个数组里,不在就加进去,在的话如果两边对应关系不一致就return false。代码的复杂度是O(n),运行时间8ms,86.83%,空间复杂度O(n),9.1M,92%:
class Solution {
public:
bool isIsomorphic(string s, string t) {
char s_char[128] = {0};
char t_char[128] = {0};
for (int i = 0; i < s.length(); i++) {
if (s_char[s[i]] == 0 && t_char[t[i]] == 0) {
s_char[s[i]] = t[i];
t_char[t[i]] = s[i];
}
else {
if (s_char[s[i]] != t[i] || t_char[t[i]] != s[i]) {
return false;
}
}
}
return true;
}
};
就,还是不会做呗,其实挺简单一题,但就是脑子不好使。两个string各自对应一个长度为ASCII code长度的数组,然后在各自字符对应的位置存下另一个string上对应的字符。如果两个对不上,就return false。
class Solution {
public boolean isIsomorphic(String s, String t) {
int[] map1 = new int[256];
Arrays.fill(map1, -1);
int[] map2 = new int[256];
Arrays.fill(map2, -1);
for (int i = 0; i < s.length(); i++) {
char sc = s.charAt(i);
char tc = t.charAt(i);
if (map1[sc] == -1 && map2[tc] == -1) {
map1[sc] = tc;
map2[tc] = sc;
} else if (map1[sc] != tc || map2[tc] != sc) {
return false;
}
}
return true;
}
}
上面这种做法是一种非常非常传统的想法,还有另一种改进版就比较有技巧性,不是死板地把一个char map到另一个char。它的思想是把每个字母map到一个index,这个index就是这个字母第一次出现的位置,对两个string都是一样的操作。在遍历整个string的时候,如果发现这两个string对应的char对应到的index不一样的话,就return false;如果一样且不为零的话,将它map到现在的位置,如果为零的话其实也可以map到现在的位置,两种情况可以合在一起。
这种方法有一点点难想,但是代码写起来比上一种简洁多了。底下存当前位置的时候要+1是因为最开始初始化为0了。时间4ms,99.63%,时间上要快好多,空间9.1M,90%:
class Solution {
public:
bool isIsomorphic(string s, string t) {
char s_char[128] = {0};
char t_char[128] = {0};
for (int i = 0; i < s.length(); i++) {
if (s_char[s[i]] != t_char[t[i]]) {
return false;
}
else {
s_char[s[i]] = i + 1;
t_char[t[i]] = i + 1;
}
}
return true;
}
};
下面又是另一种方法,就是把原始的string transform成另一种形式,比如把相同的字母都map到同一个数字,最后形成一个list/string,然后比较这两个list/string看是否相等。
class Solution {
public boolean isIsomorphic(String s, String t) {
return stringToList(s).equals(stringToList(t));
}
private List<Integer> stringToList(String s) {
List<Integer> list = new ArrayList<>();
Map<Character, Integer> map = new HashMap<>();
int count = 0;
for (char c : s.toCharArray()) {
if (!map.containsKey(c)) {
map.put(c, count);
}
int index = map.get(c);
list.add(index);
count++;
}
return list;
}
}
还看到了别人的解法,用了一个map存s到t的mapping,用一个set存在t里见过的char。遍历的时候只需要判断这个char是否存在在这个map里,如果存在的话判断一下是不是之前的mapping,不存在的话判断一下t对应的char是否已经存在。
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character, Character> map = new HashMap<>();
Set<Character> set = new HashSet<>();
for (int i = 0; i < s.length(); i++) {
char sc = s.charAt(i);
char tc = t.charAt(i);
// if char in s is not in the map but already appeared in t
if (!map.containsKey(sc) && set.contains(tc)) {
return false;
}
// if char is in s but the mapping is not to t
if (map.containsKey(sc) && map.get(sc) != tc) {
return false;
}
map.put(sc, tc);
set.add(tc);
}
return true;
}
}