[每日一题] 163. 单词的压缩编码(暴力、各种炫技、巧妙解法)

1. 题目来源

链接:820. 单词的压缩编码
来源:LeetCode

2. 题目说明

在这里插入图片描述

3. 题目解析

方法一:暴力+模拟+常规解法

这是一道 Medium 问题。一开始直观上就想着能不能将这个 压缩编码串重建出来,具体解法思路如下:

  • 先处理长单词,因为短单词可能是长单词的子串,给单词数组按长度排序一下就行,自己重写一个 comparator 即可
  • 然后遍历排序字符串数组,对于每个单词,都在字符串进行查找
  • 如果没有,直接加上这个单词,再加一个 #
  • 如果有,就可以得到出现的位置。比如在 time# 中查找 me,得到 found = 2,然后要验证该单词后面是否紧跟着一个#号,所以我们直接访问 found + word.size() 这个位置,如果不是 # 号,说明不能合并,我们还是要加上这个单词和 # 号。最后返回编码字符串的长度即可
  • 最后就可以把压缩编码串重建,返回它的长度即可

我觉得这是第一时间该想到的方法,但是一开始看到该问题时意识到了可能是 字典树 相关,前缀树、后缀树相关,但是这方面确实欠缺,而白白空想很长时间,意义不大。

参见代码如下:

// 执行用时 :528 ms, 在所有 C++ 提交中击败了32.03%的用户
// 内存消耗 :12.6 MB, 在所有 C++ 提交中击败了83.16%的用户

class Solution {
public:
    int minimumLengthEncoding(vector<string>& words) {
        string str = "";
        sort(words.begin(), words.end(), [](string& a, string& b){return a.size() > b.size();});
        for (string word : words) {
            int found = str.find(word);
            if (found == string::npos || str[found + word.size()] != '#') {
                str += word + "#";
            }
        }
        return str.size();
    }
};

方法二:翻转+模拟+巧妙解法

能够意识到,这个问题就是 找字符串后缀问题,那么上面采用 comparator 方法解决了。那么换一种思路,不采用 comparator 方法来处理该问题。这个思想很重要,将一个 后缀问题进行抽象简化为前缀问题,具体思路如下:

  • 首先每个单词翻转,time 变成 emitme 变成 em
  • 这样只要用默认的 sort 顺序排,就可以得到 ememit 的顺序,那么能合并的单词就放到一起了,而且一定是当前的合并到后面一个
  • 只要判读当前单词是否是紧跟着的下一个单词的前缀
  • 是的话就加 0
  • 不是的话就要加上当前单词的长度并再加 1,多加的 1 是 #
  • 判断前缀的方法很简单,直接在后面的单词中 substr 取相同长度的前缀比较即可
  • 由于每次都要取下一个单词,为了防止越界,只处理到倒数第二个单词,那么就要把最后一个单词的长度加入结果 res,并再加 1 即可

参见代码如下:

// 执行用时 :112 ms, 在所有 C++ 提交中击败了78.32%的用户
// 内存消耗 :12.1 MB, 在所有 C++ 提交中击败了91.58%的用户

class Solution {
public:
    int minimumLengthEncoding(vector<string>& words) {
        int res = 0, n = words.size();
        for (int i = 0; i < n; ++i) reverse(words[i].begin(), words[i].end());
        sort(words.begin(), words.end());
        for (int i = 0; i < n - 1; ++i) 
            res += (words[i] == words[i + 1].substr(0, words[i].size())) ? 0  : words[i].size() + 1;
        return res + words.back().size() + 1;
    }
};

方法三:HashSet+巧妙解法

思路来自官方题解。

这种方法也很巧妙,用了一个 HashSet,将所有的单词先放到这个 HashSet 中。原理是对于每个单词,我们遍历其所有的后缀,比如 time,那么就遍历 imemee,然后看 HashSet 中是否存在这些后缀,有的话就删掉,那么 HashSet 中的 me 就会被删掉,这样保证了留下来的单词不可能再合并了,最后再加上每个单词的长度到结果 res,并且同时要加上 # 号的长度。

参见代码如下:

// 执行用时 :120 ms, 在所有 C++ 提交中击败了77.12%的用户
// 内存消耗 :15.3 MB, 在所有 C++ 提交中击败了47.37%的用户

class Solution {
public:
    int minimumLengthEncoding(vector<string>& words) {
        int res = 0;
        unordered_set<string> st(words.begin(), words.end());
        for (string word : st) {
            for (int i = 1; i < word.size(); ++i) {
                st.erase(word.substr(i));
            }
        }
        for (string word : st) res += word.size() + 1;
        return res;
    }
};

方法四:字符串哈希+字典树+前缀树+后缀树+巧妙解法

看了看题解区大佬的解法,涉及到知识盲区了 😦😃,不过能学习到新知识也是很开心满足的。在此我不做总结,因为也不熟练,相关知识听是听说过,但没有好好使用刷过几道题,也没总结相关用法。仅贴出链接,供大家学习分享:

前额叶没长好:手撕字典树(Trie 树) C++ 另附暴力哈希解法

Sweetiee 🍬: Trie 吐血攻略,包教包会

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值