[哈希表] 290. 单词规律(哈希表实现双向映射:containsValue、两个哈希表、借助中转数字字符串)

290. 单词规律

题目链接:https://leetcode-cn.com/problems/word-pattern/


分类:

  • 哈希表,记录字母 ↔ 单词的双向映射关系的方法:
    • 一个哈希表记录单向映射 + containsValue函数(思路1);
    • 两个哈希表分别记录字母→单词、单词→字母的映射关系(思路2);
    • 两个哈希表记录单词→数字,字母→数字,比较数字是否相等(借助中转数字字符串,思路3、思路4)

在这里插入图片描述

题目分析

题目要求的完全匹配,就是pattern的字母 → str的单词和str的单词 → pattern的字母这两个方向都要有且只有一个映射。

也就是说,pattern的一种字母只能唯一对应str的一种单词,同时str的一种单词也只能对应pattern的一种字母。

如果只使用一个map记录字母→单词的映射关系,则字母作为key,只能保证key不重复,value中的单词可能出现重复。

例如:
pattern="abba" s="dog dog dog dog"
pattern[0]='a',s[0]=dog,所以map={a-dog}
pattern[0]='b',s[0]=dog,因为map中的key不包含b,所以继续存入map={a-dog,b-dog}
最后会输出true,但实际是false,原因就在于一个map只维护了一个方向的映射关系,无法保证value的唯一性。

所以我们要在此基础上增加从单词 → 字母的映射关系:

  • 思路1:使用map的containsValue函数;
  • 思路2:使用两个哈希表分别记录字母→单词、单词→字母的映射关系;
  • 思路3:使用一个哈希表 + 字母、单词都转换成数字;
  • 思路4:思路3的改进,利用两个哈希表 + 字母、单词边转换成数字边比较.

思路1:一个哈希表 + containsValue函数

哈希表保存pattern每个字母 → str每个单词的映射关系:key=pattern字母,value=单词;

为了实现单词→字母的映射,对于每个单词,如果与之对应的字母在map中不存在,我们还需要进一步判断map中是否包含该单词,如果包含该单词,但当前字母又不存在,则说明单词→字母的映射和字母→单词的映射相矛盾,返回false。

实现遇到的问题:特殊用例
pattern = "aaa"  s = "aa aa aa aa"

字母个数 != 单词个数,直接返回false。

实现代码:

class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character, String> map = new HashMap<>();
        String[] words = s.split(" ");
        //特殊用例:如果字母数量和单词数量不相等,则直接返回false
        if(pattern.length() != words.length) return false;
        for(int i = 0; i < words.length; i++){
            char ch = pattern.charAt(i);
            if(!map.containsKey(ch)){
            	//如果包含该单词,但当前字母又不存在,返回false
                if(map.containsValue(words[i])) return false;
                map.put(ch, words[i]);
            }
            else{
                if(!map.get(ch).equals(words[i])) return false;
            }
        }
        return true;
    } 
}
  • 存在的问题:代码可以AC,但调用containsValue会导致时间复杂度增加。

思路2:两个哈希表记录双向映射关系(推荐)

设置两个哈希表,分别存放pattern每个字母 → str每个单词的映射关系和str每个单词p → attern每个字母的映射关系。

如果出现不满足其中一个哈希表的映射关系,就返回false。

其中,另一个哈希表可以用HashSet来代替,用set来保存出现过的单词,使用方法和思路1的containsValue类似,只是将思路1中的containsValue替换成set.contains,可以降低时间复杂度。

实现代码:

class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character, String> map = new HashMap<>();
        Set<String> set = new HashSet<>();
        String[] words = s.split(" ");
        //特殊用例:如果字母数量和单词数量不相等,则直接返回false
        if(pattern.length() != words.length) return false;
        for(int i = 0; i < words.length; i++){
            char ch = pattern.charAt(i);
            if(!map.containsKey(ch)){
                if(set.contains(words[i])) return false;
                map.put(ch, words[i]);
                set.add(words[i]);
            }
            else{
                if(!map.get(ch).equals(words[i])) return false;
            }
        }
        return true;
    } 
}

思路3:一个哈希表 + 字母、单词都转换成数字(推荐)

参考题解

将pattern上的每个字母都转换成相应的下标,s上的每个单词也转换成相应的下标,这些数字下标又组成两个字符串,比较这两个字符串是否相等,就能同时解决双向映射关系。

例如:pattern = "abba", str = "dog cat cat fish"
a=0,b=1,b=1,a=0,所以pattern="0110"
dog=0,cat=1,cat=1,fish=3,所以str="0113"
因为两个数字字符串不相等,所以返回false。

字母、单词转数字,采用的数字就是每个字母本身所对应的下标,转换过程同样需要哈希表的帮助,哈希表存放已经转换的键值对(需要两个哈希表分别存放key=ch,value=下标和key=word,value=下标),对于已经转换过的字母或单词,直接从map中获取对应的value,没有转换过的才取下标作为它对应的value写入map中。

最后,拿生成的两个数字字符串做equals比较。

实现代码:

class Solution {
    public boolean wordPattern(String pattern, String s) {
        String[] words = s.split(" ");
        //特殊用例:如果字母数量和单词数量不相等,则直接返回false
        if(pattern.length() != words.length) return false;
        
        //分别生成p和s对应的数字字符串进行比较
        return getNumStr(pattern.split("")).equals(getNumStr(words));
    }
    //输入字符串数组生成对应的数字字符串(参数设置为字符串数组,所以p,s都需要先转换成字符串数组)
    public String getNumStr(String[] strs){
        StringBuilder sb = new StringBuilder();
        Map<String, Integer> map = new HashMap<>();
        for(int i = 0; i < strs.length; i++){
            //已经转换过的字母或单词,直接从map中获取对应的value
            if(map.containsKey(strs[i])){
                int value = map.get(strs[i]);
                sb.append(value);
            }
            //没有转换过的字母或单词,取下标作为它对应的value写入map,加入sb末尾
            else{
                map.put(strs[i], i);
                sb.append(i);
            }
        }
        return sb.toString();
    }
}

思路4:两个哈希表 + 字母、单词都转换成数字(对思路3的改进,推荐)

参考题解

思路3是把两个数字字符串都生成完毕再做比较,但实际上可以在转换过程中边转换边比较每个字母、单词对应的数字是否相等,如果不相等直接返回false。

因此需要两个哈希表,分别存放p和s的数字映射关系。

为了代码能够统一处理,在比较字母、单词转换的数字a,b时,我们对不存在于map中的key,就取默认值-1作为它的value。

实现代码:

class Solution {
    public boolean wordPattern(String pattern, String s) {
        String[] words = s.split(" ");

        //特殊用例:如果字母数量和单词数量不相等,则直接返回false
        if(pattern.length() != words.length) return false;
        
        Map<String, Integer> sMap = new HashMap<>();
        Map<Character, Integer> pMap = new HashMap<>();
        for(int i = 0; i < words.length; i++){
        	//如果key不存在map中就取-1
            int sValue = sMap.getOrDefault(words[i], -1);
            int pValue = pMap.getOrDefault(pattern.charAt(i), -1);
            //每转换一个字母和一个单词,就立即比较对应的数字
            if(sValue != pValue) return false;
            else{
                sMap.put(words[i], i);
                pMap.put(pattern.charAt(i), i);
            }
        }
        return true;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值