哈希表里的“垃圾元素”,为什么有时必须删除?

哈希表里的“垃圾元素”,为什么有时必须删除?

◆ 博主名称: 晓此方-ZEEKLOG博客

大家好,欢迎来到晓此方的博客。

⭐️C++系列个人专栏:

Re:从零开始的C++_晓此方的博客-ZEEKLOG博客

 ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰


目录

一、什么是哈希表中的垃圾元素

二、什么时候必须清理垃圾元素

2.1情况1:需要比较两个哈希表是否相等

2.2情况2:算法依赖 map.size()

三、什么时候不需要清理

四、从刷题中总结出来的实战经验


一、什么是哈希表中的垃圾元素

        在做滑动窗口 + 哈希统计时,经常会出现一种现象:哈希表中存在 value = 0 的键值对。这些元素通常被称为 “垃圾元素”

        当我们做频率统计时,经常写这样的代码:

mp[x]--; 

        如果x原来是1,执行后变成了0但在map / unordered_map中:(xxx,0)仍然是一个真实存在的键值对,并不会自动删除。这种 key 仍存在,但 value 已经没有意义 的元素,就可以称为:哈希表垃圾元素(value = 0 的 key)


二、什么时候必须清理垃圾元素

        先说结论,核心原则:

        只要你的算法依赖“哈希表结构”进行判断,就必须清理。

2.1情况1:需要比较两个哈希表是否相等

典型例题:

LCR 015. 找到字符串中所有字母异位词 - 力扣(LeetCode)https://leetcode.cn/problems/VabMRr/description/本人有两解:

class Solution { public: vector<int> findAnagrams(string s, string p) { vector<int> v; map<char, int> sc1; map<char, int> sc2; for (auto e : p) { sc2[e]++; } string::iterator left = s.begin(); string::iterator right = s.begin(); for (int i = 0; i < p.size(); i++) { if (right == s.end()) return v; sc1[*right]++; right++; } while (right != s.end()) { if (sc1 == sc2) { v.push_back(left - s.begin()); } sc1[*right]++; right++; sc1[*left]--; if (sc1[*left] == 0) sc1.erase(*left); left++; } if (sc1 == sc2) { v.push_back(left - s.begin()); } return v; } };
class Solution { public: vector<int> findAnagrams(string s, string p) { map<char,int> mp1; map<char,int> mp2; for(auto e:p){mp2[e]++;} int m=p.size(); int n=s.size(); string ::iterator left=s.begin(); string ::iterator right=s.begin(); int count=0; vector<int> v; while(right!=s.end()) { mp1[*right]++; if(mp2.find(*right)!=mp2.end()&&mp1[*right]<=mp2[*right]) count++; while((right-left+1)>m) { if(mp2.find(*left)!=mp2.end()&&mp1[*left]<=mp2[*left]) count--; mp1[*left]--; if(mp1[*left]==0) mp1.erase(*left); left++; } if(count==m) v.push_back(left-s.begin()); right++; } return v; } };

        我们看第一解中的片段:if (sc1 == sc2) {v.push_back(left - s.begin()); 如果窗口移动时只写:sc1[*left]--;left++;没有删除:if (sc1[*left] == 0) sc1.erase(*left);就可能出现:

mp1: a -> 1 b -> 1 mp2: a -> 1 b -> 1 c -> 0 

        虽然频率逻辑是正确的,map 判断时:mp1 != mp2。因为mp2多了一个 key。结果:算法会 漏掉正确答案。因此:value == 0 必须 erase。

2.2情况2:算法依赖 map.size()

        如果代码中出现:mp.size()来判断窗口状态,例如:窗口内不同字符数量那么:value = 0 的 key,会导致:size 统计错误。例如:

a -> 1 b -> 1 c -> 0 

        此时:size = 3。但实际上窗口里只有:2 个有效字符,因此必须删除。

if (mp[x] == 0) mp.erase(x); 

三、什么时候不需要清理

        如果你的算法 只关心频率值,例如:

mp[a] <= mp[b] mp[a] >= mp[b] mp[a]++ mp[a]-- 

不比较 map 结构,通常可以不删除。典型例题:

30. 串联所有单词的子串 - 力扣(LeetCode)https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/本人有一解:(这里故意修改了一些代码方便下面讲解)

class Solution { public: vector<int> findSubstring(string s, vector<string>& words) { vector<int> v; map<string, int> mp1; map<string, int> mp2; for (auto e : words) { mp1[e]++; } int len = words[0].size(); int n = words.size(); int lengthen = len * n; int count = 0; for (int i = 0; i < len; i++) { int right = i, left = i; while (right < s.size()) { mp2[s.substr(right, len)]++; if (mp1.find(s.substr(right, len)) != mp1.end() && mp2[s.substr(right, len)] <= mp1[s.substr(right, len)]) { count++; } if (right - left + len > lengthen) { if (mp1.find(s.substr(left, len)) != mp1.end() && mp2[s.substr(left, len)] <= mp1[s.substr(left, len)]) { count--; } mp2[s.substr(left, len)]--; if (mp2[s.substr(left, len)]==0) { mp2.erase(s.substr(left, len)); } left += len; } if (count == n) v.push_back(left); right += len; } mp2.clear(); count = 0; } return v; } };

        在这题中常见判断是:即使存在:s.substr(right, len) -> 0也不会影响逻辑正确性。因此 删除只是优化,不是必须

mp2[s.substr(right, len)] <= mp1[s.substr(right, len)] 

        实际上这里的判断和删除逻辑并非必须,不需要像我一样这么冗余,但是加上mp1.find(s.substr(left, len)) != mp1.end()的判断效率会高很多。

if (mp1.find(s.substr(left, len)) != mp1.end() && mp2[s.substr(left, len)] <= mp1[s.substr(left, len)]) { count--; } mp2[s.substr(left, len)]--; if (mp2[s.substr(left, len)]==0) { mp2.erase(s.substr(left, len)); }

四、从刷题中总结出来的实战经验

        写滑动窗口哈希表时,可以记住一个简单规则:如果代码中出现:map1 == map2或者是map.size()。就必须写:

if (mp[key] == 0) mp.erase(key); 

        否则很容易出现隐藏 bug。如果只是做 频率比较,通常可以不删。

当算法依赖 哈希表结构(keys) 时必须去污;
当算法只依赖 哈希表数值(values) 时通常不必去污。

好了,本期内容到此结束,我是此方,我们下期再见。バイバイ!

Read more

Qt与Web混合编程:CEF与QCefView深度解析

Qt与Web混合编程:CEF与QCefView深度解析

Qt与Web混合编程:CEF与QCefView深度解析 * 1. 引言:现代GUI开发的融合趋势 * 2. Qt与Web集成方案对比 * 3. CEF核心架构解析 * 4. QCefView:Qt与CEF的桥梁 * 5. 实战案例:智能家居控制面板 * 6. 性能优化策略 * 7. 调试技巧大全 * 8. 安全加固方案 * 9. 未来展望:WebComponent集成 * 10. 结语 1. 引言:现代GUI开发的融合趋势 在当今的桌面应用开发领域,本地GUI框架与Web技术的融合已成为不可逆转的趋势。Qt作为成熟的跨平台C++框架,与Web技术的结合为开发者提供了前所未有的灵活性: * 本地性能 + Web动态性 = 最佳用户体验 * 快速迭代的Web前端 + 稳定可靠的本地后端 * 跨平台一致性 + 现代UI效果 35%25%20%20%混合应用优势分布开发效率UI表现力跨平台性性能平衡 2. Qt与Web集成方案对比 方案优点缺点适用场景Qt WebEngine官方支持,

By Ne0inhk
前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

前端异常捕获与统一格式化:从 console.log(error) 到服务端上报

🧑 博主简介:ZEEKLOG博客专家,「历代文学网」(公益文学网,PC端可以访问:https://lidaiwenxue.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,首席架构师,也是联合创始人!16年工作经验,精通Java编程,高并发设计,分布式系统架构设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。 🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图 ” 前端异常捕获与统一格式化:从 console.log(error) 到服务端上报 引言 在前端开发中,异常监控是保证应用稳定性的重要一环。当用户遇到页面白屏、功能不可用等问题时,如果能及时收集到详细的错误信息(包括堆栈、

By Ne0inhk
惊叹数据结构之美,品味排序算法之妙:对计排、桶排的详细介绍

惊叹数据结构之美,品味排序算法之妙:对计排、桶排的详细介绍

大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在ZEEKLOG这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 本文目录 * 引言 * 正文 * 一、计数排序(Counting Sort) * 二、基数排序(Radix Sort) * 三、总结 * 快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!! 引言 排序算法中的基数排序和计数排序都是非基于传统比较的排序方法,它们各自有着独特的实现原理和应用场景。下面小编将从代码实现的角度对这两种排序算法进行详细介绍。 那接下来就让我们开始遨游在知识的海洋! 正文 一、计数排序(Counting Sort) 原理概述: 计数排序是一种适用于元素范围较小的排序算法。它利用一个额外的计数数组来记录待排序数组中每个元素出现的次数,然后根据这些次数来确定每个元素在最终排序数组中的位置。 代码实现步骤: 1. 确定元素范围:找出待排序数组中的最小值和最大值,记为min和max。2. 创建计数数组:创建

By Ne0inhk
Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析 前言 在鸿蒙(OpenHarmony)应用开发中,数据结构的选择往往决定了逻辑的成败。当标准的 List、Set、Map 无法满足更高级的需求(例如:需要一个自动按优先级排序的任务队列,或者需要判断两个深度嵌套的 Map 是否完全一致)时,开发者就需要引入更强大的集合支持。 collection 是 Dart 官方维护的最核心基础库之一。它不仅补充了大量缺失的容器类型(如 PriorityQueue、Heap),还为原生集合提供了极其丰富的扩展工具类(如 ListEquality、CanonicalizedMap)。在 Flutter for OpenHarmony 的底层架构实践中,它是处理复杂业务逻辑、优化检索效率的必备“基石”。 一、原理解析 / 概念介绍

By Ne0inhk