剑指 Offer II 065. 最短的单词编码

单词数组 words 的 有效编码 由任意助记字符串 s 和下标数组 indices 组成,且满足:

words.length == indices.length
助记字符串 s 以 ‘#’ 字符结尾
对于每个下标 indices[i] ,s 的一个从 indices[i] 开始、到下一个 ‘#’ 字符结束(但不包括 ‘#’)的 子字符串 恰好与 words[i] 相等
给定一个单词数组 words ,返回成功对 words 进行编码的最小助记字符串 s 的长度 。

示例 1:

输入:words = ["time", "me", "bell"]
输出:10
解释:一组有效编码为 s = "time#bell#" 和 indices = [0, 2, 5] 。
words[0] = "time" ,s 开始于 indices[0] = 0 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[1] = "me" ,s 开始于 indices[1] = 2 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[2] = "bell" ,s 开始于 indices[2] = 5 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"

示例 2:

输入:words = ["t"]
输出:2
解释:一组有效编码为 s = "t#" 和 indices = [0] 。

提示:

1 <= words.length <= 2000
1 <= words[i].length <= 7
words[i] 仅由小写字母组成

思路1 - 字典树

如果某个单词是其它单词的后缀,则不计算该单词的长度。
注意:前缀需要计算。因为从一个位置pos到结束符’#'标识一个单词,例如any和anytime不能写成anytime#[0,3],这标识的是anytimetime两个词,因此只能忽略后缀。

使用字典树的方法就是:

  • 将所有单词翻转,并按长度由大到小排序
  • 构造字典树,结果为字典树中每一条独立路径的长度+独立路径数(#的数量)

例如:

time、me、bell、anytime、anything、any

将这些单词翻转并按长度排序后:

gnihtyna、emityna、emit、lleb、yna、em

如果某些单词是其它单词的后缀,则翻转后的单词是其它单词的前缀,这样构造字典树,字典树中的每一条独立路径就能组成一条有效编码。

构造字典树如下:
在这里插入图片描述
有效编码:助记字符串anything#anytime#bell#any#,下标数组[0, 9, 12, 14, 17, 22]

AC代码

const int TRIE_NODE_SIZE = 26;

// 字典树节点
struct TrieNode {
    TrieNode* next[TRIE_NODE_SIZE];
    bool isEnd;
    int len; // 单词长度
    TrieNode() {
        for (int i = 0; i < TRIE_NODE_SIZE; ++i) {
            next[i] = nullptr;
        }
        isEnd = false;
        len = 0;
    }
};

// 字典树
class Trie {
public:
    // 构造根节点
    Trie() {
        root = new TrieNode();
        sum = 0;
        num = 0;
    }
    
    // 字典树中插入插入word
    void insert(string word) {
        TrieNode* node = root;
        bool flag = false;
        for (const auto& ch : word) {
            if (node->next[ch - 'a'] == nullptr) {
                // 创建节点
                node->next[ch - 'a'] = new TrieNode();
                flag = true;
            }
            node->next[ch - 'a']->len = node->len + 1;
            node = node->next[ch - 'a'];
        }
        // 一次节点都没有创建,则该单词为之前某单词的前缀
        if (!flag) {
            return;
        }
        node->isEnd = true;
        sum += node->len;
        num++;
    }

public:
    TrieNode* root; // 根节点
    int sum; // 叶子节点所代表单词的长度之和
    int num; // 叶子节点数
};

static bool cmp(const string& a, const string& b) {
    return a.length() > b.length();
}

class Solution {
public:
    int minimumLengthEncoding(vector<string>& words) {
        int result = 0;
        vector<string> tmpWords(words);
        // 将所有单词由大到小排序
        sort(tmpWords.begin(), tmpWords.end(), cmp);
        // 将单词翻转构建字典树
        Trie* trie = new Trie();
        for (auto& word : tmpWords) {
            reverse(word.begin(), word.end());
            trie->insert(word);
        }
        return trie->sum + trie->num;
    }
};

思路2 - 常规法

判断后缀的常规法

超时,通过样例27/36

bool cmp(const string& a, const string& b)
{
    return a.length() < b.length();
}

class Solution {
public:
    // 判断str是否是s的前缀
    bool isPrefix(string s, string str)
    {
        if (str.length() > s.length()) {
            return false;
        }
        for (int i = 0; i < str.size(); ++i) {
            if (str[i] != s[i]) {
                return false;
            }
        }
        return true;
    }

    // 判断str是否是s的后缀
    bool isSuffix(string s, string str)
    {
        reverse(s.begin(), s.end());
        reverse(str.begin(), str.end());
        return isPrefix(s, str);
    }

    int minimumLengthEncoding(vector<string>& words) {
        int result = 0;
        sort(words.begin(), words.end(), cmp);
        for (int i = 0; i < words.size(); ++i) {
            bool isSuffixFlag = false;
            for (int j = i + 1; j < words.size(); ++j) {
                // 判断i是否是j的后缀
                if (isSuffix(words[j], words[i])) {
                    isSuffixFlag = true;
                    break;
                }
            }
            if (!isSuffixFlag) {
                result += (words[i].length() + 1);
            }
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值