题目描述(简单难度)
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
的装箱拆箱有了更多的了解。