题目描述(简单难度)
290、Word Pattern
Given a pattern
and a string str
, find if str
follows the same pattern.
Here follow means a full match, such that there is a bijection between a letter in pattern
and a non-empty word in str
.
Example 1:
Input: pattern = "abba", str = "dog cat cat dog"
Output: true
Example 2:
Input:pattern = "abba", str = "dog cat cat fish"
Output: false
Example 3:
Input: pattern = "aaaa", str = "dog cat cat dog"
Output: false
Example 4:
Input: pattern = "abba", str = "dog dog dog dog"
Output: false
Notes:
You may assume pattern
contains only lowercase letters, and str
contains lowercase letters that may be separated by a single space.
判断字符串是否符合某个模式,类比于汉字,abb
型,就是喜洋洋,胖乎乎,每个汉字对应题目中的每个单词。
可以先做一下 205 题,基本上和这个题一模一样了,下边的思路也都是基于 205 题 去写了。
解法一
最简单的思路,利用 HashMap
,pattern
的每个字母作为 key
,str
的每个单词作为 value
。第一次遇到的 key
就加入到 HashMap
中,第二次遇到同一个 key
,那就判断它的value
和当前单词是否一致。举个例子。
pattern = "abba", str = "dog cat cat dog"
a b b a
dog cat cat dog
^
i
第一次遇到 a, 加入到 HashMap
HashMap = {a:dog}
a b b a
dog cat cat dog
^
i
第一次遇到 b, 加入到 HashMap
HashMap = {a: dog, b: cat}
a b b a
dog cat cat dog
^
i
HashMap = {a: dog, b: cat}
第二次遇到 b, 判断 HashMap 中 b 的 value 和当前的单词是否符合
符合的话继续判断, 不符合就返回 false
a b b a
dog cat cat dog
^
i
HashMap = {a: dog, b: cat}
第二次遇到 a, 判断 HashMap 中 a 的 value 和当前的单词是否符合
符合的话继续判断, 不符合就返回 false
遍历结束,返回 true
可以看一下相应的代码。
public boolean wordPattern(String pattern, String str) {
HashMap<Character, String> map = new HashMap<>();
String[] array = str.split(" ");
if (pattern.length() != array.length) {
return false;
}
for (int i = 0; i < pattern.length(); i++) {
char key = pattern.charAt(i);
//当前 key 是否存在
if (map.containsKey(key)) {
if (!map.get(key).equals(array[i])) {
return false;
}
} else {
map.put(key, array[i]);
}
}
return true;
}
但上边的代码还是有问题的,我们只是完成了 pattern
到 str
的映射,如果对于下边的例子是有问题的。
pattern = "abba"
str = "dog dog dog dog"
最直接的方法,在添加新的 key
的时候判断一下相应的 value
是否已经用过了。
public boolean wordPattern(String pattern, String str) {
HashMap<Character,String> map = new HashMap<>();
String[] array = str.split(" ");
if(pattern.length() != array.length){
return false;
}
for(int i = 0; i < pattern.length();i++){
char key = pattern.charAt(i);
if(map.containsKey(key)){
if(!map.get(key).equals(array[i])){
return false;
}
}else{
//判断 value 中是否存在
if(map.containsValue(array[i])){
return false;
}
map.put(key, array[i]);
}
}
return true;
}
虽然可以 AC 了,但还有一个问题,containsValue
的话,需要遍历一遍 value
,会导致时间复杂度增加。最直接的解决方法,我们可以把 HashMap
中的 value
存到 HashSet
中。
public boolean wordPattern(String pattern, String str) {
HashMap<Character, String> map = new HashMap<>();
HashSet<String> set = new HashSet<>();
String[] array = str.split(" ");
if (pattern.length() != array.length) {
return false;
}
for (int i = 0; i < pattern.length(); i++) {
char key = pattern.charAt(i);
if (map.containsKey(key)) {
if (!map.get(key).equals(array[i])) {
return false;
}
} else {
// 判断 value 中是否存在
if (set.contains(array[i])) {
return false;
}
map.put(key, array[i]);
set.add(array[i]);
}
}
return true;
}
当然还有另外一种思路,我们只判断了 pattern
到 str
的映射,我们只需要再判断一次 str
到 pattern
的映射就可以了,这样就保证了一一对应。
两次判断映射的逻辑是一样的,所以我们可以抽象出一个函数,但由于 pattern
只能看成 char
数组,这样的话会使得两次的 HashMap
不一样,一次是 HashMap<Character, String>
,一次是 HashMap<String, Character>
。所以我们提前将 pattern
转成 String
数组。
public boolean wordPattern(String pattern, String str) {
String[] array1 = str.split(" ");
if (array1.length != pattern.length()) {
return false;
}
String[] array2 = pattern.split("");
//两个方向的映射
return wordPatternHelper(array1, array2) && wordPatternHelper(array2, array1);
}
//array1 到 array2 的映射
private boolean wordPatternHelper(String[] array1, String[] array2) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < array1.length; i++) {
String key = array1[i];
if (map.containsKey(key)) {
if (!map.get(key).equals(array2[i])) {
return false;
}
} else {
map.put(key, array2[i]);
}
}
return true;
}
解法二
205 题 还介绍了另一种思路。
解法一中,我们通过对两个方向分别考虑来解决的。
这里的话,我们找一个第三方来解决。即,按照单词出现的顺序,把两个字符串都映射到另一个集合中。
第一次出现的单词(字母)映射到 1
,第二次出现的单词(字母)映射到 2
... 依次类推,这样就会得到一个新的字符串了。两个字符串都通过这样方法去映射,然后判断新得到的字符串是否相等 。
举个现实生活中的例子,一个人说中文,一个人说法语,怎么判断他们说的是一个意思呢?把中文翻译成英语,把法语也翻译成英语,然后看最后的英语是否相同即可。举个例子。
pattern = "abba", str = "dog cat cat dog"
对于 abba
a -> 1
b -> 2
最终的得到的字符串就是 1221
对于 dog cat cat dog
dog -> 1
cat -> 2
最终的得到的字符串就是 1221
最终两个字符串都映射到了 1221, 所以 str 符合 pattern
代码的话,我们同样封装一个函数,返回映射后的结果。
public boolean wordPattern(String pattern, String str) {
String[] array = str.split(" ");
if(array.length!=pattern.length()){
return false;
}
//判断映射后的结果是否相等
return wordPatternHelper(pattern.split("")).equals(wordPatternHelper(array));
}
private String wordPatternHelper(String[] array) {
HashMap<String, Integer> map = new HashMap<>();
int count = 1;
//保存映射后的结果
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) {
//是否已经映射过
if (map.containsKey(array[i])) {
sb.append(map.get(array[i]));
} else {
sb.append(count);
map.put(array[i], count);
count++;
}
}
return sb.toString();
}
为了方便,我们也可以将当前单词(字母)直接映射为当前单词(字母)的下标,省去 count
变量。
public boolean wordPattern(String pattern, String str) {
String[] array = str.split(" ");
if(array.length!=pattern.length()){
return false;
}
//判断映射后的结果是否相等
return wordPatternHelper(pattern.split("")).equals(wordPatternHelper(array));
}
private String wordPatternHelper(String[] array) {
HashMap<String, Integer> map = new HashMap<>();
//保存映射后的结果
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) {
//是否已经映射过
if (map.containsKey(array[i])) {
sb.append(map.get(array[i]));
} else {
sb.append(i);
map.put(array[i], i);
}
}
return sb.toString();
}
上边我们是调用了两次函数,将字符串整体转换后来判断。我们其实可以一个单词(字母)一个单词(字母)的判断。第一次遇到的单词(字母)就给它一个 value
,也就是把下标给它。如果第二次遇到,就判断它们的 value
是否一致。
怎么判断是否是第一次遇到,我们可以通过判断 key
是否存在,但这样判断起来会比较麻烦。为了统一,我们可以给还不存在的 key
一个默认的 value
,这样代码写起来比较统一。
public boolean wordPattern(String pattern, String str) {
String[] array1 = str.split(" ");
if (array1.length != pattern.length()) {
return false;
}
char[] array2 = pattern.toCharArray();
HashMap<String, Integer> map1 = new HashMap<>();
HashMap<Character, Integer> map2 = new HashMap<>();
for (int i = 0; i < array1.length; i++) {
String c1 = array1[i];
char c2 = array2[i];
// 当前的映射值是否相同
int a = map1.getOrDefault(c1, -1);
int b = map2.getOrDefault(c2, -1);
if (a != b) {
return false;
} else {
map1.put(c1, i);
map2.put(c2, i);
}
}
return true;
}
同样的思路,然后看一下 StefanPochmann 大神的 代码。
public boolean wordPattern(String pattern, String str) {
String[] words = str.split(" ");
if (words.length != pattern.length())
return false;
Map index = new HashMap();
for (Integer i = 0; i < words.length; ++i)
if (index.put(pattern.charAt(i), i) != index.put(words[i], i))
return false;
return true;
}
他利用了 put
的返回值,如果 put
的 key
不存在,那么就插入成功,返回 null
。
如果 put
的 key
已经存在了,返回 key
是之前对应的 value
。
所以第一次遇到的单词(字母)两者都会返回 null
,不会进入 return false
。
第二次遇到的单词(字母)就判断之前插入的 value
是否相等。也有可能其中一个之前还没插入值,那就是 null
,另一个之前已经插入了,会得到一个 value
,两者一定不相等,就会返回 false
。
还有一点需要注意,for
循环中我们使用的是 Integer i
,而不是 int i
。是因为 map
中的 value
只能是 Integer
。
如果我们用 int i
的话,java
会自动装箱,转成 Integer
。这样就会带来一个问题,put
返回的是一个 Integer
,判断对象相等的话,相当于判断的是引用的地址,那么即使 Integer
包含的值相等,那么它俩也不会相等。我们可以改成 int i
后试一试。
public boolean wordPattern(String pattern, String str) {
String[] words = str.split(" ");
if (words.length != pattern.length())
return false;
Map index = new HashMap();
for (int i = 0; i < words.length; ++i)
if (index.put(pattern.charAt(i), i) != index.put(words[i], i))
return false;
return true;
}
改成 int i
以后,就不能 AC
了。但你会发现当 pattern
的长度比较小时,代码是没有问题的,这又是为什么呢?
是因为当数字在 [-128,127]
的范围内时,对于同一个值,Integer
对象是共享的,举个例子。
Integer a = 66;
Integer b = 66;
System.out.println(a == b); // ?
Integer c = 166;
Integer d = 166;
System.out.println(c == d); // ?
大家觉得上边会返回什么?
是的,是 true
和 false
。当不在 [-128,127]
的范围内时,即使 Integer
包含的值相等,但由于是对象之间比较,依旧会返回 false
。
回到之前的问题,如果你非常想用 int
,比较两个值的时候,你可以拆箱去比较。但返回的有可能是 null
,所以需要多加几个判断条件。
public boolean wordPattern(String pattern, String str) {
String[] words = str.split(" ");
if (words.length != pattern.length())
return false;
Map index = new HashMap();
for (int i = 0; i < words.length; ++i) {
Object a = index.put(pattern.charAt(i), i);
Object b = index.put(words[i], i);
if (a == null && b == null)
continue;
if (a == null || b == null)
return false;
if ((int) a != (int) b) {
return false;
}
}
return true;
}
也可以通过 Object.equals
来判断两个 Integer
是否相等。
public boolean wordPattern(String pattern, String str) {
String[] words = str.split(" ");
if (words.length != pattern.length())
return false;
Map index = new HashMap();
for (int i=0; i<words.length; ++i)
if (!Objects.equals(index.put(pattern.charAt(i), i),
index.put(words[i], i)))
return false;
return true;
}
总
其实和 205 题 用到的方法是一模一样的。此外,就是对 java
的装箱拆箱有了更多的了解。