实现字典树

举报
芒果_Mango 发表于 2022/11/30 23:55:28 2022/11/30
【摘要】 453. 最小操作次数使数组元素相等https://leetcode.cn/problems/minimum-moves-to-equal-array-elements/思路:把问题换一个问法更好理解:有 n 根钉子,每次只能敲一根钉子让高度 - 1,问敲多少下钉子使得所有钉子高度一样? 答案自然就是把所有的钉子敲到跟最矮的那根一样了根据《峡谷相对论》,当对方追着你满地图跑时,敌方加速...

453. 最小操作次数使数组元素相等

https://leetcode.cn/problems/minimum-moves-to-equal-array-elements/

image-20220523190926869

思路:

把问题换一个问法更好理解:有 n 根钉子,每次只能敲一根钉子让高度 - 1,问敲多少下钉子使得所有钉子高度一样? 答案自然就是把所有的钉子敲到跟最矮的那根一样了

根据《峡谷相对论》,当对方追着你满地图跑时,敌方加速 == 我减速,所以每次都有n-1个元素加1 == 每次都有一个元素减1。既然每次只能做减法,那么最少次数就是把所有元素都减到最小的那个元素值

class Solution {
public:
    int minMoves(vector<int>& nums) {
        int minVal = nums[0];//不能初始化为0,防止数组中全为负数
        //1.遍历数组找到数组元素最小值
        for(auto& num:nums)
        {
            minVal = min(minVal,num);
        }
        //2.所有元素到最小值需要减多少次
        int res = 0;
        for(auto & num:nums)
        {
            res+= num - minVal;
        }
        return res;
    }
};

462. 最少移动次数使数组元素相等 II

https://leetcode.cn/problems/minimum-moves-to-equal-array-elements-ii/

image-20220523191434748

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        //中位数是最优解
        //将nums中所有元素都变成nums数组中的中位数时,移动次数最少

        //1.先排序
        sort(nums.begin(),nums.end());
        //2.累加数组所有数到中位数的距离
        int ans = 0;
        int mid = nums[nums.size()/2];
        for(int i = 0;i<nums.size();i++)
        {
            ans += abs(nums[i] - mid);
        }
        return ans;
    }
};

208. 实现 Trie (前缀树)

https://leetcode.cn/problems/implement-trie-prefix-tree/

https://leetcode.cn/problems/QC3q1f/

image-20220526112619546

又称前缀树或字典树

每个节点包含以下字段:pass->标志多少个字符串创建的时候经过这个节点 end->多少个字符串以该节点结尾 nexts:指向子节点的数组/容器

插入字符串

我们从字典树的根开始,插入字符串。对于当前字符对应的子节点,有两种情况:

子节点存在:沿着指针移动到子节点,继续处理下一个字符
子节点不存在:创建一个新的子节点,记录在nexts数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符
重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾

查找前缀

我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:

子节点存在: 沿着指针移动到子节点,继续搜索下一个字符
子节点不存在: 说明字典树中不包含该前缀,返回空指针
重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符

若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的 end >0,则说明字典树中存在该字符串


哈希表unordered_map实现:

用map也可以实现

字母映射表next 的妙用就体现了,vector<TreeNode*> nexts ||unordered_map<char, TrieNode*> nexts; 中保存了对当前结点而言下一个可能出现的所有字符的链接,因此我们可以通过一个父结点来预知它所有子结点的值

struct TrieNode
{
	TrieNode()
		:end(0)
		,pass(0)
	{}
	~TrieNode()
	{
		//释放哈希表的内容
		for (auto& x : nexts)
		{
			delete x.second;//x是pair对象,我们要释放的是第二个成员,指针
			x.second = NULL;
		}
	}
	int end;
	int pass;
	unordered_map<char, TrieNode*> nexts;//字符-节点指针
};

class Trie {
public:
    Trie() {
        _root = new TrieNode();
    }
    
    void insert(string word) {
        if (word == "") return;
		TrieNode* cur = _root;//从根位置开始遍历
		cur->pass++;//根节点的pass值++
		for (int i = 0; i < word.size(); i++)
		{
			char ch = word[i];//字符
			//检测cur->nexts[ch]得到的对应的节点,判断是否存在
			if (cur->nexts[ch] == nullptr)	//说明节点不存在,要新开辟一个节点
			{
				cur->nexts[ch] = new TrieNode();//相当于在哈希表新插入路径<ch,TrieNode*>
			}
			cur = cur->nexts[ch];
			cur->pass++;
		}
		//最后一个位置的end值++
		cur->end++;
    }
    
    bool search(string word) {
        if (word == "") return false;
		TrieNode* cur = _root;//从根位置往下找
		for (int i = 0; i < word.size(); i++)
		{
			char ch = word[i];
			if (cur->nexts[ch] == nullptr)
			{
				//说明该字符串不存在
				return false;
			}
			cur = cur->nexts[ch];//到下一个位置考察
		}
		//如果结尾位置的end值>0说明字符串存在, 否则不存在
		//不能直接return true,如:找abc  而只有abcd,这样就错了!
		//因为c位置,end值不为1
		return cur->end > 0;
    }
    
    bool startsWith(string prefix) {
		if (prefix == "")
		{
			return 0;
		}
		TrieNode* cur = _root;//从根位置开始找
		for (int i = 0; i < prefix.size(); i++)
		{
			char ch = prefix[i];

			if (cur->nexts[ch] == NULL)//说明没有以pre为前缀的字符串
			{
				return 0;
			}
			cur = cur->nexts[ch];//到达下一个位置
		}
		return cur->pass;//返回pre最后一个字符对应的pass值,即多少个字符串构建的时候是以pre为前缀的
    }
    TrieNode* _root;
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

vector实现:

//前缀树的每个节点
struct TrieNode
{
	TrieNode()
	{
		pass = 0;
		end = 0;
		nexts.resize(26, nullptr);
	}
	int pass;//pass:经过该节点的路径有几条
	int end;//end:以该节点结尾的路径有几条
	//下级的节点
	vector<TrieNode*> nexts;//字符a-z 相对映射在vector,下标为0-25的位置
};

class Trie {
public:
    Trie() {
        root = new TrieNode();
    }
    
    void insert(string word) {
        //如果words是空串就不插入
		if (word == "") return;
		TrieNode* cur = root;//从根节点出发
		cur->pass++;//沿途节点的pass值要++
		int path = 0;//标识当前字符应该映射在哪个位置
		//遍历字符串
		for (int i = 0; i < word.size(); i++)
		{
			path = word[i] - 'a';// 映射在vector的位置
			//如果当前节点的vector中path下标的这个位置为空,就在path下标处新开辟一个节点
			if (cur->nexts[path] == nullptr)
			{
				cur->nexts[path] = new TrieNode();//
			}
			cur = cur->nexts[path];//node去下一个节点
			cur->pass++;//沿途节点的pass值++
		}

		//此时cur就是在字符串的结束节点处,该节点end值++
		cur->end++;
    }
    
    bool search(string word) {
        if (word == "") return false;
		TrieNode* cur = root;
		int path = 0;
		for (int i = 0; i < word.size(); i++)
		{
			path = word[i] - 'a';
			if (cur->nexts[path] == nullptr)
			{
				return false;
			}
			cur = cur->nexts[path];
		}
		return cur->end>0;
    }
    
    bool startsWith(string prefix) {
        if (prefix == "") return 0;
		TrieNode* cur = root;
		int path = 0;
		//遍历字符串
		for (int i = 0; i < prefix.size(); i++)
		{
			path = prefix[i] - 'a';
			//如果当前节点的nexts数组中path下标处的节点是空->字符串不存在
			if (cur->nexts[path] == nullptr)
			{
				return 0;
			}
			cur = cur->nexts[path];//到达下一个位置
		}
		//返回此时字符串pre结尾节点的pass的值,就知道多少个字符串是以pre这个字符串作为前缀的
		return cur->pass;
    }
    TrieNode* root;//前缀树的根节点
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。