【LeetCode】720. Longest Word in Dictionary(C++)

本文探讨了LeetCode上的一个问题,即寻找字典中最长的单词,该单词能通过逐字符构建的方式形成。文章提供了三种解决方案,包括使用哈希表、Trie树以及另一种基于哈希表的遍历方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

地址:https://leetcode.com/problems/longest-word-in-dictionary/

题目:

Given a list of strings words representing an English Dictionary, find the longest word in words that can be built one character at a time by other words in words. If there is more than one possible answer, return the longest word with the smallest lexicographical order.

Example 1:

Input: words = [“w”,“wo”,“wor”,“worl”, “world”]
Output: “world”
Explanation: The word “world” can be built one character at a time by “w”, “wo”, “wor”, and “worl”.

Example 2:

Input: words = [“a”, “banana”, “app”, “appl”, “ap”, “apply”, “apple”]
Output: “apple”
Explanation: Both “apply” and “apple” can be built from other words in the dictionary. However, “apple” is lexicographically smaller than “apply”.

Note:

All the strings in the input will only contain lowercase letters.

  • The length of words will be in the range [1, 1000].
  • The length of words[i] will be in the range [1, 30].

理解:

承认愚蠢的我并没有看懂题目。。遂google之
是这样的,找到words中最长的那个单词,他可以通过其他的words每次加一个字母得到。比如Example 1中,world可以依次由"w",“wo”,“wor”,"worl"添加’o’,‘r’,‘l’,'d’得到。如果有多个可能的答案,返回按字典序最小的,也就是最后一个字母最小的,比如apple和apply,字典序apple在前,返回apple。

实现1:

看到discussion里的Trie+DFS,感觉略麻烦,要去吃饭了,先看一种简单的实现。
首先把words按字典序排序,从而保证字典序小的word在前,然后遍历。创建一个unordered_set

  • 如果长度为1,就加入s,因为可能是其他单词的前缀,并更新res
  • 如果这个单词word的前word.size()-1项在set中,表明word可以由words中的单词添加一个字母构成,就更新res,并加入s
  • 注意更新res的时候,只有长度大于现有的res才更新,这样保证了,res为同长度下字典序最小的字符串。

实现如下

class Solution {
public:
	string longestWord(vector<string>& words) {
		string res;
		unordered_set<string> s;
		sort(words.begin(), words.end());
		for (string word : words) {
			if (word.size() == 1 || s.count(word.substr(0, word.size() - 1))) {
				res = word.size() > res.size() ? word : res;
				s.insert(word);
			}
		}
        return res;
	}
};

先去吃饭了,下午再补复杂的方法。

实现2

先补一些基础知识
trie:又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。Trie-维基百科
基本性质:

  • 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
  • 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符互不相同。
    一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。
    上面的Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。来源:Trie树(Prefix Tree)介绍
    因此,插入所有单词,构成一个Tier即可。实现如下:
//Definition of TrieNode
class TrieNode {
public:
	bool last;
	vector<TrieNode*> children;
	TrieNode():last(false),children(26,nullptr){}
};

class Trie {
public:
	TrieNode* root;
	Trie():root(new TrieNode()){}
	void insert(string s) {
		TrieNode* curr = root;
		for (char c : s) {
			int index = c - 'a';
			if (!curr->children[index]) {
				curr->children[index] = new TrieNode();
			}
			curr = curr->children[index];
		}
		curr->last = true;
	}
	void dfs(TrieNode* curr, string& res, string& s) {
		if (curr->last) {
			if (s.size() > res.size())
				res = s;
		}
		for (int index = 0; index < 26;++index) {
			if (curr->children[index]&& curr->children[index]->last) {
				s.push_back(index + 'a');
				dfs(curr->children[index], res, s);
				s.pop_back();
			}
		}
	}

	string dfsSearch() {
		string res, s;
		dfs(root, res, s);
		return res;
	}
};

class Solution {
public:
	string longestWord(vector<string>& words) {
		Trie t;
		for (string word : words)
			t.insert(word);
		return t.dfsSearch();
	}
};

实现3

提交完了以后,还看到一种简单粗暴的方法。
对words建立一个unordered_set,然后遍历words,对每个word,

  • 若比res短,或者相同,但字典序大于等于res时,跳过
  • 否则,循环去判断,每去掉一个后缀,剩余的是不是在哈希表里,如果有不在的就不满足,如果都在,则标记为新的res
class Solution {
public:
	string longestWord(vector<string>& words) {
		unordered_set<string> s(words.size());
		for (auto &w : words) s.insert(w);

		string rs;
		for (auto &w : words) {
			if (w.size() < rs.size() || (w.size() == rs.size() && w >= rs)) continue;

			bool found = true;
			string temp = w;
			while (temp.size() > 1) {
				temp.pop_back();
				if (!s.count(temp)) { found = false; break; }
			}

			if (found) rs = w;
		}
		return rs;
	}
};
<think>好的,我现在需要帮用户整理关于前缀树的知识点,针对他的情况:大二下学期,学过数据结构但基础薄弱,目标是能够做练习题。首先,用户之前的对话已经给出了一些建议,我需要根据他的当前需求进一步细化。 用户现在明确说自己没有教材,需要整合全网的内容,针对之前提到的核心知识点,整理出一个适合他当前水平的知识点列表。他的目标是能够进行练习,所以知识点需要结构清晰,由浅入深,覆盖必要的基础和常见应用场景。 首先,我需要回顾之前提到的知识点,确保不遗漏关键内容。之前提到的包括基础概念、操作、变种和应用。现在需要将这些内容更系统地组织,分成不同的学习阶段,适合基础较弱的用户。 用户的基础是学过数据结构但几乎零基础,所以需要从最基础的部分开始,逐步推进。可能需要先解释前缀树是什么,为什么使用它,然后结构设计,基本操作,再到优化和变种,最后练习题。 需要考虑用户使用C++,所以在知识点中需要强调C++相关的实现细节,比如指针管理、内存泄漏等。同时,练习题部分需要推荐适合他水平的LeetCode题目,并给出一些实现技巧。 另外,用户可能需要一些学习资源,比如中文的博客、视频教程,以及具体的代码示例。之前提到的资源如GeeksforGeeks、知乎文章、B站视频都需要再次推荐,并确保链接有效。 需要注意避免过于理论化的内容,保持实用,侧重应用和代码实现。例如,删除操作和压缩Trie可能对初学者有一定难度,可以放在进阶部分,先掌握基础插入、搜索和前缀匹配。 还要提醒用户注意常见错误,比如指针错误、节点共享问题,以及如何调试。这些实际编码中的问题对于提升代码能力很重要。 最后,练习题需要按照难度排序,从易到难,逐步提升。推荐的题目包括LeetCode 208、211、212等,需要给出链接和简要说明,帮助用户选择。 总结下来,结构应该是:核心概念 -> 节点结构 -> 基本操作 -> C++实现要点 -> 常见问题 -> 练习题推荐 -> 学习资源。每个部分需要简明扼要,适合用户当前水平,并帮助他逐步提升到能做练习题的程度。</think>好的!根据你的背景(大二下学期,数据结构基础薄弱,目标是能上手做题),我会为你整理一份**精简、循序渐进的前缀树知识点清单**,覆盖核心概念、实现细节和必备题型,并附上可直接参考的免费资源链接。 --- ### **一、必须掌握的核心知识点清单** #### **1. 基础概念(理解“是什么”和“为什么”)** - **定义**:前缀树(Trie)是一种**树形数据结构**,用于高效存储和检索字符串集合,核心利用字符串的**公共前缀**减少查询时间。 - **核心优势**: - **快速前缀匹配**:比哈希表更适合处理“查找是否有以某前缀开头的字符串”这类问题。 - **避免重复存储**:共享公共前缀的字符串不会重复存储前缀字符。 - **典型应用场景**: - 搜索引擎的输入提示(输入“app”自动补全“apple”)。 - 拼写检查、IP路由表的最长前缀匹配。 #### **2. 节点结构设计(理解“如何存数据”)** - **基本节点结构**(C++实现): ```cpp class TrieNode { public: bool isEnd; // 标记是否为单词结尾 TrieNode* children[26]; // 假设仅处理小写字母 TrieNode() : isEnd(false) { memset(children, 0, sizeof(children)); // 初始化子节点指针为nullptr } }; ``` - **关键点**: - **字符到子节点的映射**:通常用数组(如`children[26]`)或哈希表实现。 - **结束标记**:`isEnd`标记当前节点是否为一个单词的结尾(例如:插入“app”后,第二个‘p’节点的`isEnd`为true)。 #### **3. 基本操作(手写代码实现)** 1. **插入(Insert)**: - 从根节点开始,逐个字符向下创建节点。 - 时间复杂度:**O(L)**(L为单词长度)。 ```cpp void insert(TrieNode* root, string word) { TrieNode* node = root; for (char c : word) { int idx = c - 'a'; if (!node->children[idx]) { node->children[idx] = new TrieNode(); } node = node->children[idx]; } node->isEnd = true; } ``` 2. **搜索完整单词(Search)**: - 从根节点向下逐字符查找,且最后一个节点必须有`isEnd == true`。 ```cpp bool search(TrieNode* root, string word) { TrieNode* node = root; for (char c : word) { int idx = c - 'a'; if (!node->children[idx]) return false; node = node->children[idx]; } return node->isEnd; } ``` 3. **前缀匹配(StartsWith)**: - 只需检查是否存在该前缀路径,无需检查`isEnd`。 ```cpp bool startsWith(TrieNode* root, string prefix) { TrieNode* node = root; for (char c : prefix) { int idx = c - 'a'; if (!node->children[idx]) return false; node = node->children[idx]; } return true; } ``` #### **4. C++实现要点(避免踩坑)** - **内存管理**: - 插入时用`new`创建节点,需在析构函数中递归`delete`(否则内存泄漏)。 - 示例析构函数: ```cpp ~TrieNode() { for (int i = 0; i < 26; ++i) { if (children[i]) delete children[i]; } } ``` - **扩展字符集**:若要支持更多字符(如大写字母、数字),需调整`children`数组大小(例如ASCII码范围)。 #### **5. 常见问题与调试技巧** - **指针未初始化**:`children`数组必须初始化为`nullptr`。 - **节点共享问题**:删除操作时需确保其他单词不共享该节点(进阶内容,初期可不学)。 - **调试方法**: - 打印Trie树结构(递归输出节点层级)。 - 使用调试器观察`isEnd`和`children`数组状态。 --- ### **二、练习题清单(按难度排序)** 先从模板题开始,逐步过渡到变种题型: | 题目 | 链接 | 关键点 | 难度 | |------|------|--------|------| | 208. 实现 Trie | [LeetCode 208](https://leetcode.cn/problems/implement-trie-prefix-tree/) | 手写完整模板,理解基础操作 | 简单 | | 720. 词典中最长的单词 | [LeetCode 720](https://leetcode.cn/problems/longest-word-in-dictionary/) | 前缀存在性验证 + 排序技巧 | 中等 | | 211. 添加与搜索单词 | [LeetCode 211](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | 通配符`.`处理(DFS回溯) | 中等 | | 648. 单词替换 | [LeetCode 648](https://leetcode.cn/problems/replace-words/) | 前缀最短匹配 | 中等 | | 212. 单词搜索 II | [LeetCode 212](https://leetcode.cn/problems/word-search-ii/) | Trie + DFS回溯(二维网格) | 困难 | --- ### **三、学习资源推荐(全部免费)** #### **1. 图文教程** - [Trie树详解(含代码实现)](https://zhuanlan.zhihu.com/p/34747612)(知乎专栏,适合快速入门) - [GeeksforGeeks Trie Tutorial](https://www.geeksforgeeks.org/trie-insert-and-search/)(英文,代码详细) #### **2. 视频教程** - [B站:Trie树从原理到实现](https://www.bilibili.com/video/BV1yJ411s7dk)(15分钟快速掌握) - [LeetCode 208官方题解视频](https://leetcode.cn/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode-ti5o/)(代码逐行讲解) #### **3. 实战代码参考** - [C++ Trie模板代码](https://github.com/huihut/interview/blob/master/DataStructure/Trie.cpp)(GitHub开源项目) --- ### **四、学习计划建议** 1. **Day 1-2**:理解概念 + 手写基础插入、搜索、前缀匹配代码。 2. **Day 3**:实现LeetCode 208题,通过测试用例。 3. **Day 4-5**:尝试LeetCode 720和648,巩固前缀验证逻辑。 4. **Day 6-7**:挑战LeetCode 211(通配符匹配),学习DFS与Trie结合。 --- 通过以上知识点和练习,你可以在1-2周内掌握前缀树的核心内容,并能解决大部分中等难度题型。遇到卡点时,多画图理解节点关系,并善用调试工具!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值