《剑指 Offer》专项突破版 - 面试题 32 和 33 : 详解与变位词相关的两道面试题(C++ 实现)

目录

一、有效的变位词

二、变位词组

2.1 - 方法一

2.2 - 方法二


 


一、有效的变位词

题目

给定两个字符串 s 和 t,请判断它们是不是一组变位词。在一组变位词中,它们中的字符及每个字符出现的次数都相同,但字符的顺序不能相同。例如,"anagram" 和 "nagaram" 就是一组变位词。

分析

第 3 章已经讨论过与变位词相关的面试题。由于变位词与字符出现的次数相关,因此可以用一个哈希表来存储每个字符出现的次数。哈希表的键是字符,而值是对应字符出现的次数

代码实现

class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size() || s == t)
            return false;
        
        vector<int> counts(26, 0);  // 用数组模拟哈希表
        for (char ch : s)
        {
            ++counts[ch - 'a'];
        }
        for (char ch : t)
        {
            if (counts[ch - 'a'] == 0)
                return false;
            
            --counts[ch - 'a'];
        }
        return true;
    }
};

 

 


二、变位词组

题目

给定一组单词,请将它们按照变位词分组。例如,输入一组单词 ["eat", "tea", "tan", "ate", "nat", "bat"],这组单词可以分为 3 组,分别是 ["eat", "tea", "ate"]、["tan", "nat"] 和 ["bat"]。假设单词中只包含英文小写字母

分析

解决这个问题,就需要找出一组变位词共同的特性,然后依据此特性把它们分到一组。下面介绍两种方法。

 

2.1 - 方法一

第一种方法是把每个英文小写字母映射到一个质数,如把字母 'a' 映射到数字 2,字母 'b' 映射到数字 3,以此类推,字母 'z' 映射到第 26 个质数 101。每给出一个单词,就把单词中所有字母对应的数字相乘,于是每个单词都可以算出一个数字。例如,单词 "eat" 可以映射到数字 1562(11 * 2 * 71)。

如果两个单词互为变位词,那么它们中的字母及每个字母出现的次数都相同,由于乘法满足交换律,因此上述算法把一组变位词映射到同一个数值。例如,单词 "eat"、"tea" 和 "ate" 都会映射到数字 1562。由于每个字母都是映射到一个质数,因此不互为变位词的两个单词一定会映射到不同的数字

因此,可以定义一个哈希表,哈希表的键是单词中字母映射的数字的乘积,而值为一组变位词

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        int primes[26] = {
            2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
            31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
            73, 79, 83, 89, 97, 101
        };
​
        unordered_map<long, vector<string>> productToAnagram;
        for (const string& str : strs)
        {
            long product = 1;
            for (char ch : str)
            {
                product *= primes[ch - 'a'];
            }
            productToAnagram[product].push_back(str);
        }
​
        vector<vector<string>> result;
        for (const auto& kv : productToAnagram)
        {
            result.push_back(kv.second);
        }
        return result;
    }
};

如果输入 n 个单词,平均每个单词有 m 个字母,那么该算法的时间复杂度是 O(mn)。

该算法有一个潜在的问题:由于把单词映射到数字用到了乘法,因此当该单词非常长时,乘法就有可能溢出

 

2.2 - 方法二

第二种方法是把一组变位词映射到同一个单词。由于互为变位词的单词的字母及每个字母出现的次数都相同,因此如果把单词中的字母排序就会得到相同的字符串

因此可以定义一个哈希表,哈希表的键是把单词排序得到的字符串,而值为一组变位词

keyvalue
"aet"["eat", "tea", "ate"]
"ant"["tan", "nat"]
"abt"["bat"]
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> mp;
        for (const string& str : strs)
        {
            string tmp = str;
            sort(tmp.begin(), tmp.end());
            mp[tmp].push_back(str);
        }
​
        vector<vector<string>> result;
        for (const auto& kv : mp)
        {
            result.push_back(kv.second);
        }
        return result;
    }
};

如果每个单词平均有 m 个字母,排序一个单词需要 O(mlogm) 的时间。假设总共有 n 个单词,该算法总的时间复杂度是 O(nmlogm)。

虽然该方法的时间效率不如前一种方法,但是该方法不用担心乘法可能带来的溢出问题

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值