打卡第28天: 单词的压缩编码

1. 题目描述

给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A
例如,如果这个列表是 ["time", "me", "bell"],我们就可以将其表示为 S = "time#bell#" 和 indexes = [0, 2, 5]
对于每一个索引,我们可以通过从字符串 S 中索引的位置开始读取字符串,直到 "#" 结束,来恢复我们之前的单词列表。
那么成功对给定单词列表进行编码的最小字符串长度是多少呢?

示例:

输入: words = ["time", "me", "bell"]
输出: 10
说明: S = "time#bell#" , indexes = [0, 2, 5] 。

提示:

1 <= words.length <= 2000
1 <= words[i].length <= 7
每个单词都是小写字母 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/short-encoding-of-words

2. 解题思路

今天是一道中等难度的题,兴奋~~因为每一道中等难度以上的题都会有一个知识点,通过近一个月的打卡,我大概了解了贪心算法,动态规划,深/广度优先搜索,虽然有些知识点没有没掌握,但是我相信通过多次练习和分析,我也一定会掌握其中的奥妙。

今天这道题其实也不是很难,用到了一种经典的数据结构--字典树,字典树也被称为前缀树,用这种结构来存储语言类的信息会非常省空间并且方便查找,参考资料,这篇文章写的比较详细,不了解的小伙伴可以参考。

不知道这道题的题意小伙伴们看懂了没,我就来解释下这个示例吧,func(["time", "me", "bell"]) = 10, 为什么会输出10呢,因为metime拥有公共后缀me,所以在压缩后的字符串中我们可以通过time就能把me表现出来,我这里画个图:

微信图片_20200328212500.jpg

从索引0开始读,读到#结束为time,从索引2开始读,读到#me
我的想法是,先把words数组从大到小排序,这样先让最大长度的单词去建立索引,这样方便对#的个数进行统计,然后在给字典树中插入单词的时候需要把单词做一个反转,因为我们的目的是找出公共后缀,在插入单词的时候,如果我们发现这个单词的不能和已有分支吻合,需要插入新的节点,那么我们就把新插入节点的个数记下来,然后返回,最后把插入每一个单词新增的长度加起来就是我们最终压缩字符串的长度了。

2.1 代码

/**
 * @param {string[]} words
 * @return {number}
 */
var minimumLengthEncoding = function(words) {
    const root = new Node('', 0)
    let res = 0
    words.sort((a, b) => b.length -a.length)
    for (let item of words) {
        const len = root.addWord(item)
        res += len ? len + 1 : 0
    }
    return res
};

class Node { // 定义字典树的类
    constructor (val, depth) { // 构造函数,这里保存一个depth,因为如果另起一个分支的时候增长的长度要把前面的节点也算上
        this.value = val
        this.depth = depth
        this.children = {}
    }
    addWord (word) {
        let {children, depth} = this
        let res = 0
        for (let i = word.length - 1; i > -1; i--) {
            if (!(word[i] in children)) {   // 如果这个字母不在下一层节点中,需要新起一个分支
                if (!Object.keys(children).length) res += 1  // 如果是叶子节点,那么只需要自增1就行
                else res = res + depth + 1  // 如果新起分支的节点不是叶子节点,那么需要把前面的也统计上
                children[word[i]] = new Node(word[i], depth + 1)
            }
            depth = children[word[i]].depth
            children = children[word[i]].children
        }
        return res
    }
}

2.2 性能表现

占用内存过多,需要对结构进行调整~


image.png

3. 更新解法

不用字典树的话也很好解的,首先说一下这种解法的关键思路就是(A[A.length - 1] == B[B.length - 1]) && A.includes(B)推导出AB的公共后缀是B。就是说当两个单词的最后一位相等且一个单词包含另一个单词的时候就说明他们两个有公共后缀!

我们先创建一个一个哈希表,然后用单词的最后一位来做keyvalue存放一个数组,这个数组存放的内容是以该key结尾的单词,然后我们还是先把单词数组排序,之后遍历这个单词数组,如果单词的最后一位已经存在哈希表中,那么我们就去哈希表中把那个数组拿出来,然后用字符串的includes来判断包含关系。

3.1 代码

/**
 * @param {string[]} words
 * @return {number}
 */
var minimumLengthEncoding = function(words) {
    const hash = {}
    let res = ''
    words.sort((a, b) => b.length - a.length)
    for (let item of words) {
        const last = item[item.length - 1]
        if (last in hash) {
            if (hash[last].some(_item => _item.includes(item))) continue
            res += `${item}#`
            hash[last].push(item)
        }
        else {
            hash[last] = [item]
            res += `${item}#`
        }
    }
    return res.length
};

3.2 性能表现

image.png

你可能感兴趣的:(打卡第28天: 单词的压缩编码)