手摸手提桶跑路——LeetCode剑指 Offer II 066. 单词之和

198 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

题目描述

实现一个 MapSum 类,支持两个方法,insert 和 sum

MapSum() 初始化 MapSum 对象 void insert(String key, int val) 插入 key-val 键值对,字符串表示键 key ,整数表示值 val 。如果键 key 已经存在,那么原来的键值对将被替代成新的键值对。 int sum(string prefix) 返回所有以该前缀 prefix 开头的键 key 的值的总和。

示例:

输入:
inputs = ["MapSum", "insert", "sum", "insert", "sum"]
inputs = [[], ["apple", 3], ["ap"], ["app", 2], ["ap"]]
输出:
[null, null, 3, null, 5]

解释:
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);  
mapSum.sum("ap");           // return 3 (apple = 3)
mapSum.insert("app", 2);    
mapSum.sum("ap");           // return 5 (apple + app = 3 + 2 = 5)

本题同 LeetCode677. 键值映射 是一样的。

解题思路——hashmap

遇事不决,hashmap

这道题要求我们对于给定的前缀去计算属于该前缀的总和。那么我们怎么知道有哪些单词有这些前缀呢?

javascript 提供了一个 startsWith 方法,用于匹配给定的字符串是否是由另一个字符串开始。那么有了这个方法后,我们将所有的单词都存入 Map 集合中,后续调用 sum 方法时,只要 逐个 判断是否以给定的 prefix 打头,然后进行累加即可。

更多关于 startsWith 的用法,见MDN:String.prototype.startsWith() | MDN (mozilla.org)

题解

var MapSum = function() {
    this.mp = new Map();
};

MapSum.prototype.insert = function(key, val) {
    this.mp.set(key, val);
};

MapSum.prototype.sum = function(prefix) {
  let sum = 0;
  for(const [k,v] of this.mp) {
      k.startsWith(prefix) && (sum+=v);
  }
  return sum;
};

hashmap.png

解题思路—— Trie 树

老生常谈了,看到这种什么 insert 的,单词啊,前缀的,就要想到字典树了。

老观众应该都知道啥是字典树了,不懂的看看这篇文章:

水电费费

Trie 树的结构

这道题中我们除了往字典树中插入单词外,还需要有一个属性来保存 val 值,方便后续计算给定 prefixsum 时使用。所以字典树的结构就出来了:

class Trie {
    constructor() {
        this.data = {};
        this.val = 0;
    }
}

Trie 树的 insert 方法

有了 Trie 树的结构后,我们来思考一下 insert 的逻辑。

插入的时候,除了单词以外,还需要保存 val 值。这道题我们最后会使用 DFS 进行 sum 值计算,也就是说,我们会深度遍历所有以 prefix 为前缀的单词(必须是结束单词),那么这里的 val 值我们就存在单词的结束位置,和结束标记一个层级。

insert(word, val) {
    let r = this;
    for (const w of word) {
        if (r.data[w] === void 0) {
            r.data[w] = new Trie();
        }
        r = r.data[w];
    }
    r.val = val; // 同结束标记一个层级
    r['#'] = true;
}

Trie 树的 sum 方法

这道题有个需要注意的地方是,后续如果有重复单词插入的话,val 值是需要更新的。这边我用 DFS 统计以 prefix 开头的所有 val 值。

我们先将指针移动到 prefix 这个层级。然后对于该层开始的每个 Trie 节点,都累加 val 值,最后输出。

题解

这个 DFS 写的丑陋了点,后续看看能不能优化一下了。

class Trie {
    constructor() {
        this.data = {};
        this.val = 0;
    }

    insert(word, val) {
        let r = this;
        for (const w of word) {
            if (r.data[w] === void 0) {
                r.data[w] = new Trie();
            }
            r = r.data[w];
        }
        r.val = val;
        r['#'] = true;
    }

    sum(prefix) {
        let r = this;
        for (const w of prefix) {
            if(!r) return 0;
            r = r.data[w];
        }

        let sum = 0;
        const dfs = (node) => {
            if (!node) {
                return;
            }
            sum += node.val;
            for (const k in node.data) {
                dfs(node.data[k])
            }
        }
        dfs(r);
        return sum;
    }
}

var MapSum = function () {
    this.trie = new Trie();
};

MapSum.prototype.insert = function (key, val) {
    this.trie.insert(key, val);
};

MapSum.prototype.sum = function (prefix) {
    return this.trie.sum(prefix);
};

微信截图_20220818171842.png