训练营第六天| 哈希表理论基础 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和 | Sundri

防火墙学习5---虚拟系统和跟系统通过路由表互访 虚拟系统与跟系统互访可分为虚拟系统访问根系统和跟系统访问虚拟系统。 阅读详情

哈希表理论基础

常见的三种哈希结构

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。

  • 数组
  • set (集合)
  • map(映射)

242.有效的字母异位词(哈希数组)

# 解题思路:

1.创建一个长度为26的数组 arr,所有index对应的值都初始化为0

2.遍历字符串s,找到每个字母在数组里面的相对位置,数量每次增加1: arr [ord(s[i]) - ord('a')] += 1

3.遍历字符串t,找到每个字母在数组里面的相对位置,数量每次减少1, arr [ord(t[i]) - ord('a')] -= 1

4. 遍历字符串,如果找到一个非0的值,就说明s和t不是异位词,否则返回true

# 复杂度分析:

T: O(N) N是字符串长度

S: O(S) S=26,相当于字符集的大小,可以看成是O1)

def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t): return False
        bucket = [0] * 26
        for i in range(len(s)):
            bucket[ord(s[i]) - ord('a')] += 1
            bucket[ord(t[i]) - ord('a')] -= 1
        for i in range(len(bucket)):
            if bucket[i] != 0:
                return False 
        return True

349. 两个数组的交集

# 面试备注

2022年11月linkedin面试题,面试官给我的是两个已经升序排列的数组,让我求交集;

follow up问题是:如果数组里面有重复的元素,如何能够做到去重

# 方法一:哈希表法 

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        d = collections.defaultdict()
        res = []
        for num in nums1:
            if num not in d:
                d[num] = 1
        for num in nums2:
            if num in d and d[num] != 0:
                res.append(num)
                d[num] -= 1
        return res

# 时间空间复杂度:

T:max(m,n) 时间复杂度是两个数组里面取最大值

S:max(m,n) 空间复杂度是把其中一个数组放入哈希表的值,也是两个数组的size中取最大值

# 方法二:集合 set

# 解题思路

如果使用哈希集合存储元素,则可以在 O(1) 的时间内判断一个元素是否在集合中,从而降低时间复杂度。

首先使用两个集合分别存储两个数组中的元素,然后遍历较的集合,判断其中的每个元素是否在另一个集合中,如果元素也在另一个集合中,则将该元素添加到返回值。该方法的时间复杂度可以降低到 O(m+n)。

def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        set1 = set(nums1)
        set2 = set(nums2)
    
        if len(set1) > len(set2):
            return [num for num in set1 if num in set2]
        return [num for num in set2 if num in set1]

# 为什么是遍历较小的集合?

因为这样能够让遍历的时间复杂度尽可能的降低。遍历较小的集合(O(m)),查询较大的集合(O(1)),时间复杂度就是较小的集合。如果反过来,时间复杂度就会取决于较大的集合。所以用集合做这个题目的时候,一定要去思考:两个数组的大小关系是怎么样的。

# 方法三:排序+双指针(最应该掌握的方法)

# 解题思路

如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。可以预见的是加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 pre 表示上一次加入答案数组的元素(在python里面,pre 可以用 res[-1]来进行表示,回想下 time schedule的题目例子)。

初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,且该数字不等于 pre ,将该数字添加到答案并更新 pre 变量,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。

# 复杂度分析

时间复杂度:

O(mlogm+nlogn),其中 m 和 n 分别是两个数组的长度。对两个数组排序的时间复杂度分别是 O(mlogm) 和 O(nlogn),双指针寻找交集元素的时间复杂度是O(m+n),因此总时间复杂度是 O(mlogm+nlogn)

空间复杂度:

O(logm+logn),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间。(如果题目已经完成排序,空间复杂度就是 O(1),不算输出数组的大小)

# 如果排序的两个方法是并列的,时间复杂度应该是相加,还是两者取较大值?

在leetcode给的官方题解里面,因为两个sort是平行的关系,所以总的时间复杂度应该是两个sort的时间复杂度之和。可以思考成线程的一个关系,程序要先执行这个条件,再执行下一个条件,所以时间一定是相加,而不是取两个sort时间里面的较大值!一定要更正这个思维误区!总而言之:平行关系的时间复杂度,在数学上是相加的关系!

def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1.sort()
        nums2.sort()
        idx1, idx2 = 0,0
        res = []
        while idx1 < len(nums1) and idx2 < len(nums2):
            if nums1[idx1] < nums2[idx2]:
                if not res or res[-1] != nums1[idx1]:
                    res.append(nums1[idx1])
                    idx1 += 1
            elif nums1[idx1] > nums2[idx2]:
                if not res or res[-1] != nums1[idx2]:
                    res.append(nums2[idx2])
                    idx2 += 1
            else:
                res.append(nums1[idx1])
                idx1 += 1
                idx2 += 1
        return res

202. 快乐数(** 易错题,双指针做法好秀!)

# 方法一:集合

class Solution:
    def isHappy(self, n: int) -> bool:
        seen = set()
        while n != 1:
            number = self.calculate_sum(n)
            if number in seen:
                return False
            if number == 1:
                return True 
            if number not in seen and number != 1:
                seen.add(number)
            n = number
        return True # 最后还是要return true,因为要考虑到 n = 1 的情况! 
        
    
    def calculate_sum(self, n):
        cur_sum = 0
        while n:
            cur_sum += (n % 10) * (n % 10)
            n //= 10
        return cur_sum

# 易错点:

1. 这个题我做了很多次都做错,主要原因是在于,这个n有两个功能:

一、需要一个while循环把n遍历到0为止,这样可以把n所有位置上面的digit的平方相加,计算出新一层所需要的n。 -- 这个部分就是 getNext() 函数所实现的功能

二、需要把新的这个n重新放到一个while循环去遍历,并且每次都对这个n进行判断:

a)n == 1?如果相等,直接返回 true

b) n != 1 但是 n 已经出现过? 说明已经陷入了死循环,直接返回 False

c)n != 1 且 n 没有出现过:把 n 加入到 seen 的集合当中,继续重复遍历

class Solution:
    def isHappy(self, n: int) -> bool:
        seen = set()
        while n != 1:
            number = self.getNext(n)
            if number in seen:
                return False
            if number == 1:
                return True 
            if number not in seen and number != 1:
                seen.add(number)
            n = number
        return True # 最后还是要return true,因为要考虑到 n = 1 的情况! 
        
    
    def getNext(self, n):
        cur_sum = 0
        while n:
            cur_sum += (n % 10) * (n % 10)
            n //= 10
        return cur_sum

# 方法二:双指针(卧槽,被秀到了...)

通过反复调用 getNext(n) 得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针,但数据仍然形成链表结构。起始数字是链表的头 “节点”,链中的所有其他数字都是节点。next 指针是通过调用 getNext(n) 函数获得。

意识到我们实际有个链表,那么这个问题就可以转换为检测一个链表是否有环。因此我们在这里可以使用弗洛伊德循环查找算法。这个算法是两个奔跑选手,一个跑的快,一个跑得慢。在龟兔赛跑的寓言中,跑的慢的称为 “乌龟”,跑得快的称为 “兔子”。

不管乌龟和兔子在循环中从哪里开始,它们最终都会相遇。这是因为兔子每走一步就向乌龟靠近一个节点(在它们的移动方向上)。

# 实现的关键点:

1.slow指针从 n 开始,fast指针从 getNext(n) 开始

2. slow指针每次走一步, fast指针每次走两步 (getNext(getNext(n)))

def isHappy(self, n):
        def hasNext(n):
            cur_sum = 0
            while n:
                cur_sum += (n % 10) * (n % 10)
                n //= 10
            return cur_sum
        
        slow, fast = n, hasNext(n)
        while slow != 1 and fast !=1 and slow != fast:
            # print("slow",slow,"fast",fast)
            slow = hasNext(slow) # slow 指针走一步
            fast = hasNext(hasNext(fast)) # fast指针走两步
            if slow == fast:
                return False
        return True

# 时间复杂度:

O(logn)。该分析建立在对前一种方法的分析的基础上,但是这次我们需要跟踪两个指针而不是一个指针来分析,以及在它们相遇前需要绕着这个循环走多少次。
如果没有循环,那么快跑者将先到达 1,慢跑者将到达链表中的一半。我们知道最坏的情况下,成本是 O(logn)。
一旦两个指针都在循环中,在每个循环中,快跑者将离慢跑者更近一步。一旦快跑者落后慢跑者一步,他们就会在下一步相遇。假设循环中有 k 个数字。如果他们的起点是相隔 k-1 的位置(这是他们可以开始的最远的距离),那么快跑者需要 k-1步才能到达慢跑者,这对于我们的目的来说也是不变的。因此,主操作仍然在计算起始 n 的下一个值,即O(logn)。


# 空间复杂度:

O(1),对于这种方法,我们不需要哈希集来检测循环。指针需要常数的额外空间。


1. 两数之和

def twoSum(self, nums: List[int], target: int) -> List[int]:
        d = collections.defaultdict()
        res = []
        for idx, num in enumerate(nums):
            if target - num in d:
                res.append(idx)
                res.append(d[target-num])
                return res
            else:
                d[num] = idx

深入解析Unity3D中PostLateUpdate突然占用大量时间的原因 在Unity3D的生命周期中,是一个在LateUpdate之后被调用的方法。它通常用于处理一些需要在所有更新(包括物理更新和动画更新)之后进行的逻辑。由于它在游戏循环的后期阶段被调用,因此它通常用于处理与渲染直接相关的任务,如相机调整、UI更新等。突然占用大量时间的问题可能由多种原因引起,包括复杂的逻辑处理、频繁的资源加载、不恰当的渲染优化、插件或第三方库的副作用以及内存泄漏或垃圾回收等。为了解决这个问题,开发者需要仔细审查和优化中的代码,同时利用Unity提供的工具来监控和分析游戏性能。 阅读详情

相关推荐

中级软考(软件攻城狮)第3章知识点——数据结构据运算(线性结构+非线性结构)

本文系统梳理了数据结构中的线性与非线性结构。线性部分详细介绍了线性表(顺序/链式存储及时间复杂度对比)、栈(后进先出特性及应用)、队列(循环队列与双端队列)以及串(空串与子串概念)。非线性部分涵盖数组、矩阵、广义表,重点解析了树结构(二叉树性质、存储方式及遍历方法),并深入讲解了最优二叉树(哈夫曼树)的构建原理与编码应用。全文通过图示对比和实例说明,清晰呈现了各类数据结构的特点、存储方式及典型应用场景。

睡不醒的每天 167

GXF 3.2.0 to 4.0 CUDA 12 路径 API 与数据结构增量分析

分析范围:以仓库中与分支对比为基础,聚焦)可用的公共 API 与数据结构变化。由于 GXF 核心头文件在各 CUDA 版本间共享,下列新增/修改的接口在 CUDA 12 构建上同样生效。对比链接:https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nitros/compare/release-3.2…release-4.0。

eloudy的专栏 257

bestAdmin后台管理系统html模板_管理系统登录模板html

bestAdmin后台管理系统html模板

数据结构】图的应用:最小生成树(Prim / Kruskal)

摘要:最小生成树(MST)是带权连通无向图中边权总和最小的生成树,常用于网络优化问题。Prim算法从顶点出发,逐步选择连接已选和未选顶点集的最小边,适合稠密图(时间复杂度O(n²))。Kruskal算法按边权排序后依次选择不构成环的边,适合稀疏图(时间复杂度O(e log e))。两者均基于贪心策略,但Prim侧重顶点扩张,Kruskal侧重边合并。关键区别在于数据结构(Prim用优先队列,Kruskal用并查集)和适用场景。典型例题需掌握选边过程,记忆口诀为“稠密Prim,稀疏Kruskal”。软考中常考

bianyuan172的博客 352

数据结构 --- 二叉树&哈希表

本篇数据结构学习笔记分为二叉树、哈希表两大板块。二叉树部分讲解树形基础概念、链式存储方式、前 / 中 / 后序遍历算法,介绍带占位符字符串递归创建二叉树的方法,配套结点计、深度计算、内存销毁等递归代码实现,补充二叉树核心学性质。哈希表板块梳理哈希函、哈希冲突概念,对比开放定址法、链地址法两种冲突解决方案,分析操作的时间复杂度。最后完整给出拉链哈希表五大功能模块的 C 语言实现代码,讲解插入、查找、打印、销毁的执行流程,同时点明现有代码的缺陷与优化方向。

2401_86288082的博客 721

数据结构】排序算法:归并排序与基排序

本文介绍了归并排序和基排序两种非比较型排序算法。归并排序采用分治策略,通过递归分割与合并实现排序,时间复杂度稳定为O(nlogn),空间复杂度O(n),具有稳定性。基排序则基于字位进行分配收集,时间复杂度O(d(n+r)),适用于固定位的整或字符串排序。文章通过示例详细说明了两者的执行过程,并对比了八大排序算法的关键特性,包括时间复杂度、空间复杂度和稳定性。最后通过典型例题展示了两种排序的应用场景和解题思路,强调归并排序的分治思想和基排序的LSD实现是考试重点。(149字)

bianyuan172的博客 298

数据结构】C语言实现链式二叉树

本文系统介绍了链式二叉树的实现与操作,包括节点创建、遍历算法(先序、中序、后序、层序)、节点统计(总、叶子节点、左右子节点)、高度计算、层级节点查询、节点查找、完全二叉树判断、二叉树翻转和销毁等16个核心功能。重点阐述了递归思想在二叉树操作中的应用,并展示了队列在层序遍历和完全二叉树判断中的关键作用。所有操作均通过C语言实现,提供了完整的代码示例,为理解和实现链式二叉树数据结构提供了全面参考。

Jupiter1007的博客 339

嵌入式C语言常见考察点(2)

本文介绍了循环队列实现、 冒泡排序优化、二分查找、 位运算应用、volatile关键字重要性、大小端判断等

2301_79475128的博客 174

第 4 章 数据结构总览:SDMA 在驱动里的实体

本章将硬件概念映射到SDMA驱动的C数据结构,主要包含三层嵌套关系:芯片级amdgpu_sdma管理所有SDMA实例,实例级amdgpu_sdma_instance对应物理引擎(含GFX和Page两条队列),队列级amdgpu_ring处理具体命令。关键字段包括固件管理、队列状态、中断源、硬件差异抽象(函指针)等,完整反映了"多实例→双队列"的硬件模型。通过这种层级化设计,驱动实现了对SDMA引擎的精细化控制。

图形/计算GPU的驱动与虚拟化技术分享园地 367

数据结构哈希表:构造与冲突处理

文章摘要: 哈希表是一种通过哈希函直接定位据的查找结构,核心在于设计哈希函(如除留余法)和处理冲突(开放地址法/链地址法)。重点包括:1)除留余法中质p的选择;2)线性探测易产生堆积,链地址法效率更稳;3)装填因子α影响查找效率;4)ASL计算(成功/失败)是软考高频考点,需掌握比较次统计。典型例题展示ASL计算流程,口诀帮助记忆要点。建议重点掌握冲突处理和ASL计算,以应对软考选择与应用题。

bianyuan172的博客 272

数据结构】排序(快排,选择,直接插入,希尔)

快速排序是基于分治思想的经典交换排序算法,它选定一个元素作为基准值,通过双指针遍历将数组划分为两部分,使基准左侧元素全部小于等于它、右侧元素全部大于等于它,再递归对划分得到的左右子区间重复上述操作,不断缩小待排序范围,最终得到整体有序的数组,平均时间复杂度为 O (nlogn),最坏情况下可达 O (n²),属于不稳定排序。2).Hoare快排选取区间最左侧下标 keyi 作为基准位置,将left 指针移到基准的下一位开始遍历;在外层循环下,使 right先移动,right 从区间右端向左遍历,当。

Jupiter1007的博客 292

数据结构:二叉树

树:由根节点和若干个子节点构成的具有一对多关系的据的集合,称为树形结构。完全二叉树:在满二叉树基础上,按照从左到右,从上到下依次增加节点的树形结构。二叉树:树的广度为2的树形结构,且各节点的左右子节点不能交换。满二叉树:在不增加层的情况下,无法再增加一个节点。树的度(的广度):树中节点最大的度是该的广度。K层满二叉树:第K层的节点个:2^(K-1)叶子节点(终端节点):向后没有子节点的节点。分支节点(非终端节点):有子节点的节点。节点的度:节点的子节点个。叶子节点的节点的度为0。

yxg0505的博客 514

数据结构】图的遍历:广度优先搜索(BFS)

摘要: 广度优先搜索(BFS)是一种基于队列实现的图遍历算法,核心思想是“层层推进”,先访问起点邻接顶点,再逐层扩展。与DFS的纵向深入不同,BFS横向扩展,适合求解无权图最短路径。算法关键点包括:入队时立即标记已访问,避免重复入队;时间复杂度邻接表为O(n+e),邻接矩阵为O(n²)。BFS生成树具有“矮胖”特性,根到任意顶点的路径为最短路径。典型应用包括连通性判断、二分图检测等。需重点掌握队列实现逻辑及与DFS的对比(如数据结构、遍历顺序等)。软考常考BFS序列模拟及陷阱(如访问标记时机)。

bianyuan172的博客 274

数据结构】图的应用:最短路径(Dijkstra / Floyd)

摘要:最短路径问题是图论经典应用,分为单源(Dijkstra算法)和多源(Floyd算法)两类。Dijkstra采用贪心策略,每次选择距离起点最近的未访问顶点进行松弛操作,时间复杂度O(n²),但不能处理负权边。Floyd通过动态规划逐步允许中转点优化路径,需三层循环(k在外层),时间复杂度O(n³),支持负权边但不支持负权环。两种算法对比鲜明:Dijkstra适合单源查询,Floyd适用于多源且顶点较少的情况。备考需重点掌握Dijkstra的dist数组更新和Floyd的三层循环顺序。

bianyuan172的博客 443

学嵌入式和C语言编程数据结构|学习日记Day22:栈,二叉树,哈希存储

功能:存储人名和电话号码,以名字首字符作为key计算哈希地址,冲突则挂在对应位置的单链表。哈希存储:在据关键字与存储位置之间建立映射关系,依据映射快速存、取据。底层使用单链表实现栈,链表头部作为栈顶,入栈出栈都操作表头,效率O(1)。顺序栈基于数组实现,分为4种状态:满增栈、空增栈、满减栈、空减栈。不同关键字,经过哈希计算得到同一个存储位置,就产生哈希冲突。树:由根节点和若干子节点构成,具备一对多关系的集合。二叉树:树的度为2的树形结构,左右子节点不能互换。①忘记销毁栈/二叉树/队列,出现内存泄漏;

kkkkkkkkkk_Z的博客 165

学嵌入式和C语言编程数据结构|学习日记Day21:内核链表与队列学习

前言:学习完双向普通链表,今天继续进阶学习 Linux 内核链表,还有非常常用的队列结构。这两块在嵌入式 Linux 驱动、任务缓存里面大量使用,是实际项目开发高频用到的数据结构。内核链表:数据结构体里面包含链表结点。据存放在链表结点内部;,分.h 头文件、.c 实现文件、main 测试文件,完整工程实践。内核链表是 Linux 内核内部大量使用的链表形式,链式队列依靠链表节点动态分配内存,没有固定容量限制。顺序队列基于数组实现,是循环队列,存在最大容量。,同一套链表接口,能够管理多种不同类型的据。

kkkkkkkkkk_Z的博客 366

从中序与后序遍历序列构造二叉树

这道题目要求根据二叉树的中序遍历和后序遍历序列重建原始二叉树。解题关键在于利用后序遍历确定根节点,然后在中序遍历中分割左右子树。具体步骤是:1) 后序数组末尾元素是根节点;2) 在中序数组中找到根节点位置,左边是左子树,右边是右子树;3) 递归处理左右子树。

知识备忘录 283

数据结构入门:从零手搓顺序表

摘要: 顺序表是数组的工程化升级版,封装了增删改查和自动扩容功能,作为基础线性结构支撑栈、队列等高级数据结构。其核心实现包括:1)动态顺序表通过指针管理堆空间,支持按需扩容;2)关键操作如尾插(O(1))、头插/删(O(n))需考虑元素搬移;3) 使用realloc实现2倍扩容策略,平衡效率与空间。虽然顺序表随机访问高效且缓存友好,但中间插入/删除性能较差且扩容存在开销。通过标准化接口(如SLInsert、SLErase)可统一操作逻辑,提升代码复用性。

Edbse的博客 460

ScriptableObject作为深嵌套数据结构编辑器的应用

我们知道对于结构复杂,嵌套很深的数据结构,比如一个任务的多种任务效果、发布事件、写入NPC对话,用Excel做配表很难。ScriptableObject SO由于能快速根据脚本生成检查器面板,成为了编辑深嵌套NoSQL型据的理想编辑器。这里我们并不用SO长期储存据,我的原因是修改SO的字段名或结构后原来的据会丢失,此时我们需要json这种可读文件恢复丢失的据。

Jesui的博客 90

排序-希尔排序(Shell Sort)

希尔排序是插入排序的优化版本,通过设置间隔gap进行分组预排序,解决插入排序中尾部小元素需要多次移动的问题。其核心思想是:先远距离分组粗排,再逐步缩小gap至1进行最终微调。算法时间复杂度约为O(n^1.3),优于普通插入排序的O(n²),且空间复杂度为O(1)。需要注意gap必须递减至1才能保证完全有序,且希尔排序是不稳定的排序方法。该算法适合处理大规模据,通过预排序有效减少元素移动次,显著提升排序效率。

2401_87882972的博客 485
上一篇: 代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07.链表相交、142.环形链表II、总结|Sundri
下一篇: Day4: GG高频面经 | LC2013 Detect Squares|Sundri |哈希表,判断正方形
Sunlovecoding
博客等级 码龄5年 2粉丝 19原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值