Mosbyllc


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

数据挖掘概念与技术笔记(3):挖掘频繁模式、关联和相关性

发表于 2018-11-02 | 分类于 数据挖掘
字数统计: 3.7k | 阅读时长 ≈ 13

基本概念

关联规则

关联规则可以用以下表示:

规则的支持度和置信度是两个规则兴趣度度量,它们分别反映发现规则的有用性和确定性。上诉关联规则
的支持度(表示同时包含A和B的事务占所有事物的比例)为2%,意味所分析的事务的2%显示 计算机和杀毒软件被同时购买。置信度(表示包含A的事务同时也包含B的比例) 60%意味购买计算机的顾客 60%的几率也购买财务管理软件。一般如果关联规则满足最小支持度阈值和最小置信度阈值,则认为关联规则是有趣的,是值得关注的现象。

频繁项集、闭项集、极大项集

设任务相关的数据 D 是数据库事务的集合,每一个事务有一个标识符,称作 TID。设A、B是两个项集,有:

同时满足最小支持度阈值(min_sup)和最小置信度阈值(min_conf)的规则称作强规则

项的集合称为项集。包含 k 个项的项集称为 k-项集。项集的出现频率是包含项集的事务数,简称为项集的频率、支持计数或计数。如果项集满足最小支持度,则称它为频繁项集。频繁 k -项集的集合通常记作 LkLk。一般而言,关联规则的挖掘是一个两步的过程:

1、找出所有频繁项集:根据定义,这些项集出现的频繁性至少和预定义的最小支持计数一样。

2、由频繁项集产生强关联规则:根据定义,这些规则必须满足最小支持度和最小置信度。

从大型数据集中挖掘项集的主要挑战是,这种挖掘常常产生大量满足最小支持度(min_sup)阈值的项集,当min_sup设置的很低的时候尤其如此,这是因为如果一个项集的频繁的(项集每个项计数都满足最小支持度),则它的每个子集都是频繁的。因此,得到的频繁项集的总个数太大了,为了更好的计算和存储,引入了闭频繁项集和极大频繁项集的概念。

  • 闭频繁项集:指这个项集X既是频繁项集又是闭项集,闭项集是指不存在真超项集Y和此项集X具有相同的支持度计数
  • 极大频繁项集:指这个项集X既是频繁项集又是极大项集,极大项集是指不存在频繁的真超项集Y,它已经是最大规模频繁项集了。

一个举例:(AB项集为非闭是因为和ABC项集具有相同的支持度计数,ABC为非极大是存在频繁项集ABCD)

挖掘频繁项集的方法

Apriori 算法

算法是一种最有影响的挖掘布尔关联规则频繁项集的算法。Apriori 使用一种称作逐层搜索的迭代方法,k项集用于探索(k+1)项集。首先,找出频繁 1-项集的集合。该集合记作 L1。L1用于找频繁 2-项集的集合 L2,而L2用于找L3,如此下去,直到不能找到频繁 k-项集.

所以关键在于须看看如何用频繁项集$L_k$找到频繁项集$L_{k+1}$.具体是由以下两步组成:

1、连接步:为找 $L_k$,通过$L_{k - 1}$与自己连接产生候选 k-项集的集合。假定事务或项集中的项按字典次序排序,如果$L_{k - 1}$它们前(k-2)个项相同的,则可以执行连接操作。

2、剪枝步:连接操作得到$C_k$,$C_k$是 $L_k$的超集,可以通过扫描数据库计算支持度从而在$C_k$里确定$L_k$。然而,$C_k$可能很大,这样所涉及的计算量就很大。为压缩 $C_k$,可以用以下办法使用 Apriori 先验性质:任何非频繁的(k-1)项集都不是可能是频繁 k项集的子集(频繁项集的子集一定是频繁的)。因此,如果一个候选 k项集的(k-1)子集不在 $L_{k - 1}$中 ($L_{k - 1}$包含所有频繁的k-1项集,若某个k-1项集不在里面则是不频繁的),则该候选也不可能是频繁的,从而可以由$C_k$中删除。

一个例子:

aproori算法过程:假设最小支持度计数为2,即min_sup=2

其中,$L_2$连接步寻找$L_3$,要将$L_2$的前(3-2)个相同的项连接起来,得到{I1,I2,I3}, {I1,I2,I5}, {I1,I3,I5}, {I2,I3,I4}, {I2,I3,I5}, {I2,I4,I5},然后执行剪枝步,扫描整个数据库,可以得到剩下的{1,I2,I3}, {I1,I2,I5}。或者使用Apriori 先验性质:举\{I2,I4,I5\}项集为例,\{I2,I4,I5\}的2项子集为\{I2,I4\}, \{I2,I5\} 和 \{I4,I5\}。但\{I4, I5\}不是$L_2$的元素,因此不是频繁的。同理L3L3连接步得到\{I1, I2, I3, I5\}的其中一个3项集\{I2,I3,I5\}不是$L_3$的元素,因此\{I1, I2, I3, I5\}也不是频繁的。

由频繁项集产生关联规则

一旦由数据库 D 中的事务找出频繁项集,由它们产生强关联规则是直接了当的(强关联规则满足最小支持度和最小置信度)。对于置信度,可以用下式,其中条件概率用项集支持度计数表示

FP-growth算法

正如我们已经看到的,在许多情况下,Apriori 的候选产生-检查方法大幅度压缩了候选项集的大小,并导致很好的性能。然而,它可能受两种超高开销的影响:

  • 它可能需要产生大量候选项集。例如,如果有 10^4个频繁 1-项集,则 Apriori 算法需要产生多达 10^7个候选 2-项集
  • 它可能需要重复地扫描数据库

“ 可以设计一种方法,挖掘全部频繁项集,而不产生候选吗?”一种有趣的方法称作频繁模式增长,或简单地,FP-增长,它采取如下分治策略:将提供频繁项集的数据库压缩到一棵频繁模式树(或 FP-树),但仍保留项集关联信息;然后,将这种压缩后的数据库分成一组条件数据库(一种特殊类型的投影数据库),每个关联一个频繁项,并分别挖掘每个数据库。

FP-growth算法的优点是采用了高级的数据结构。那么这种高级的数据结构是什么呢?实际上就是FP树。 FP树是一种输入数据的压缩表示。他通过把事务映射到FP树上来构造一条路径。这样如果不同事务之间的重叠路径越多,那么就有理由认为他们是频繁项集。由于不同的事务可能会有若干个相同的项,因此它们的路径相互重叠越多,则使用FP树结构获得的压缩效果越好。

FP-growth算法的基本过程1)构建FP数。 2)从FP树中挖掘频繁项集

依然是用之前那个例子:

数据库的第一次扫描与 Apriori 相同,它导出频繁项(1-项集)的集合,并得到它们的支持度计数(频繁性)。设最小支持度计数为 2。频繁项的集合按支持度计数的递减序排序。结果集或表记作 L。这样,我们有 L = [I2:7, I1:6, I3:6, I4:2, I5:2]。

然后,FP-树构造如下:首先,创建树的根结点,用“null”标记。二次扫描数据库 D。每个事务中的项按 L 中的次序处理(即,根据递减支持度计数排序)并对每个事务创建一个分枝。例如,第一个事务“T100: I1, I2, I5”按 L 的次序包含三个项\{ I2, I1, I5\},导致构造树的第一个分枝[(I2:1), (I1:1), (I5:1)]。该分枝具有三个结点,其中,I2 作为根的子女链接,I1 链接到 I2,I5 链接到 I1。第二个事务 T200 按 L 的次序包含项 I2 和 I4,它导致一个分枝,其中,I2 链接到根,I4 链接到 I2。然而,该分枝应当与 T100 已存在的路径共享前缀 I2。这样,我们将结点 I2 的计数增加 1,并创建一个新结点(I4:1),它作为(I2:2)的子女链接。一般地,当为一个事务考虑增加分枝时,沿共同前缀上的每个结点的计数增加 1,为随在前缀之后的项创建结点并链接。

按TID顺序T100到T900,不断创建节点和连接,并更新节点的支持度计数,知道完成FP树的构建

构建好FP树后,开始利用FP树挖掘频繁项集。FP-树挖掘处理如下。由长度为 1 的频繁模式(初始后缀模式)开始,构造它的条件模式基(一个“子数据库”, 由 FP-树中与后缀模式一起出现的前缀路径集组成)。然后,构造它的(条件)FP-树,并递归地在该树上进行挖掘。模式增长通过后缀模式与由条件 FP-树产生的频繁模式连接实现。

FP-树的挖掘总结在表 6.1 中,细节如下。让我们首先考虑 I5,它是 L 中的最后一个项,而不是第一个。其原因随着我们解释 FP-树挖掘过程就会清楚。I5 出现在上图 的 FP-树的两个分枝。(I5 的出现容易通过沿它的结点链到。)它的两个对应前缀路径是[(I2, I1:1)>和<(I2, I1, I3:1)],它们形成I5 的条件模式基。然后以及条件模式基和最小支持度计数构建条件FP树,它的条件 FP-树只包含单个路径[(I2:2, I1:2)] (括号里面为路径形式,给出构建的FP树种每个节点的支持度计数);不包含 I3,因为它的支持度计数为 1,小于最小支持度计数。最后,I5与该路径产生频繁模式的所有组合(I5与路径的所有组合一定是要包含I5的)。组合的支持度计数是根据与结合的节点的支持数决定的。

类似的,对于 I4,它的两个前缀形成条件模式基\{(I2 I1:1), (I2:1)\},产生一个单结点的条件 FP-树< I2:2>,并导出一个频繁模式 I2 I4:2。与以上分析类似,I3 的条件模式基是\{(I2 I1:2), (I2:2), (I1:2)\}。它的条件 FP-树有两个分枝< I2:4, I1:2>和< I1:2>,如图 6.9 所示,它产生模式集:\{I2 I3:4, I1 I3:2, I2 I1 I3:2\}.

FP-增长方法将发现长频繁模式的问题转换成递归地发现一些短模式,然后与后缀连接。它使用最不
频繁的项作后缀,提供了好的选择性。该方法大大降低了搜索开销。

使用垂直数据格式挖掘频繁项集

Apriori算法和FP-growth算法都是从TID-项集格式(即\{TID : itemset \})的事务集中挖掘频繁模式,其中TID是事务标识符, 而itemset是事务TID中购买的商品。这种数据格式称为水平数据格式。或者,数据也可以用项-TID集格式(即\{item : TID_set\})表示,这种格式称为垂直数据格式。

通过取每对频繁项的TID集的交,可以在该数据集上进行挖掘。项集的支持度计数为TID-集的元素个数。

强规则不一定是有趣的

大部分关联规则挖掘算法使用支持度-置信度框架。尽管使用最小支持度和置信度阈值排除了一些无兴趣的规则的探
查,仍然会产生一些对用户来说不感兴趣的规则。当A与B是负相关时,规则 A ⇒ B 的置信度有一定的欺骗性。因此,支持度和置信度度量不足以过滤掉无趣的关联规则,为了处理这个问题,可以使用相关性度量来扩充关联规则的支持度-置信度框架。这导致如下形式的相关规则

提升度(lift)

提升度(lift)是一种简单的相关性度量,A和B出现之间的提升度可以通过计算下式得到

如果lift(A,B)值小于1,则A的出现和B的出现是负相关的,意味一个出现可能导致另一个不出现。如果值大于1,则A和B是正相关的,意味着每一个的出现都蕴含另一个的出现。如果结果值等于1,则A和B是独立的,它们之间没有相关性。

使用提升度的相关分析

如果我们要分析如下的关联规则:

且有下面的事务相依表:

使用卡方检测的相关分析

模式评估度量比较

最近,另外一些模式评估度量引起了关注,本书介绍了四种这样的度量:全置信度、最大置信度、Kulczynsji和余弦。然后,比较它们的有效性,并且与提升度和卡方检测$X^2$进行比较。

全置信度:

最大置信度:

Kulczynski:

余弦:

对于评估所发现的模式联系,哪一个度量最好呢?对于零事务提升度和卡方检测效果都不好,零事务是指不包含任何考察项集的事务。典型地,零事务的个数可能远远大于个体的购买的个数,因为许多人都即不买牛奶也不买咖啡。另一方面,上面的新的四种度量都能解决零事务,因为他们的定义都消除了零事务的影响。一般的,推荐Kluc优先。

数据挖掘概念与技术笔记(2):数据预处理

发表于 2018-11-02 | 分类于 数据挖掘
字数统计: 821 | 阅读时长 ≈ 3

冗余和相关分析

分类属性的$X^2$卡方检测

对于分类属性,两个属性A和B的相关联系可以通过X2X2(卡方)检测。

以下为一个典型的四格卡方检验,我们想知道喝牛奶对感冒发病率有没有影响:

通过简单的统计我们得出喝牛奶组和不喝牛奶组的感冒率为30.94%和25.00%,两者的差别可能是抽样误差导致,也有可能是牛奶对感冒率真的有影响。

得到的感冒率可能是抽样误差导致,也有可能是牛奶对感冒率真的有影响。

为了确定真实原因,我们先假设喝牛奶对感冒发病率是没有影响的,即喝牛奶喝感冒时独立无关的,所以我们可以得出感冒的实际发病率是(43 + 28)/(43 + 28 + 96 + 84)= 28.29%

所以,理论的四格表应该如下表所示:

即下表:

如果喝牛奶喝感冒真的是独立无关的,那么四格表里的理论值和实际值差别应该会很小。

$X^2$卡方检测值可以用下式计算:

其中,A为实际值,T为理论值。$x^2$值用于衡量实际值与理论值的差异程度和相对大小,值越小属性越独立无关,值越大,属性是统计相关的。

根据上面的卡方检验公式,有

卡方分布的临界值:

上一步我们得到了卡方的值,但是如何通过卡方的值来判断喝牛奶和感冒是否真的是独立无关的?也就是说,怎么知道无关性假设是否可靠?

答案是,通过查询卡方分布的临界值表。这里需要用到一个自由度的概念,自由度等于V =(行数- 1)*(列数- 1),对四格表,自由V = 1度。对V = 1,喝牛奶和感概冒95%率不相关的卡方分布的临界概率是:3.84。即如果卡方大于3.84,则认为喝牛奶和感冒有95%的概率相关,有统计联系。

显然1.077 < 3.84,没有达到卡方分布的临界值,所以喝牛奶和感冒独立不相关的假设成立,说明两者之间没说明联系。

数值类型的皮尔逊相关系数

其中,$\bar A$和$\bar B$为均值,和$\sigma_A$ 和$\sigma_B$为标准差。

数值类型的协方差

在概率学和统计学中,协方差和方差是两个类型的度量,评估两个属性如何一起变化。

其中,$E(A\cdot B)$表示期望 ,用均值表示。

协方差值为0表示具有独立性,协方差越大代表两个属性会一起变化。

小波变换

知乎这里讲的很清楚了,主要用于选出有效的特征属性。

戳我

数据挖掘概念与技术笔记(1):数据认识

发表于 2018-11-01 | 分类于 数据挖掘
字数统计: 1.7k | 阅读时长 ≈ 6

数据的基本统计描述

中心趋势度量:均值、中位数和众数

尽管均值是描述数据集的最有用的单个量,但是它并非总是度量数据中心的最佳方法。主要问题是,均值对极端值(例如、离群点)很敏感。为了解决这个问题,我们可以采用结尾均值。结尾均值是丢弃高低极端值后的均值。

对于倾斜(非对称)数据,数据中心的更好的度量是中位数。

度量数据散布:极差、四分位数、方差、标准差和四分位数极差

极差:集合的最大值减去最小值

四分位数第1个四分位数记作$Q_1$,是第25个百分位数,第2个为50%,第3个四分位数记作$Q_3$,第75个百分位数。其中,第1个和第3个百分位数之间的距离是散布的一种简单度量,它给出被数据的中间一半所覆盖的范围。该距离称为四分位数极差(IQR),定义为

识别离群点的通常规则是,挑选落在第3个四分位数之上或第1个四分位数之下至少1.5×IQR处的值。

盒图是一种流行的分布的直观表示,盒图表示了五数概括:

  • 盒的端点一般在Q1和Q3四分位数上,使得盒的长度是四分位数极差
  • 中位数用盒内的线标记
  • 盒外的两条线(称为胡须)延伸到集合的最大和最小值

方差和标准差:标准差是方差的平方根,低的标准差表示数据观测趋向于非常靠近均值。

分位数图

分位数图是一种观察数据分布的简单有效的方法。首先,它显示所有的数据(允许用户评估总的情况和不寻常的出现),并将数据由小到大排序,每个观测值$x_(i)$ 与一个百分数 $f_i$ 配对。下图表示了单价数据的分位数图。

分位数-分位数图,或 q-q 图对着另一个的对应分位数,绘制一个单变量分布的分位数。它是一
种强有力的直观表示工具,使得用户可以观察从一个分布到另一个是否有移位。

假定对于变量单价,我们有两个观测集,取自两个不同的分店。每组数据都已按递增序排序。下图给出两个部门的QQ图(分位数-分位数图)

度量数据的相似性和相异性

一般的,如果两个对象i和j不相似,则他们的相似性度量将返回0。反之两个对象相似则返回1。

数据矩阵和相异性矩阵

数据矩阵称对象-属性结构,形式为n×p(n个对象p个属性)矩阵存放n个数据对象,每行对应于一个对象;相异性矩阵存放n个对象两两之间的相似度量,是个n×n对称矩阵(类似皮尔逊相关系数)

分类属性的邻近性度量

如何计算分类属性所刻画对象之间的相异性?两个对象i和j之间的相异性可以根据不匹配率来计算:

其中,m是匹配的数目(即i和j取值相同状态的属性数),而p是刻画对象的属性总数。假设我们有表2.2中的4个对象的数据样本,每个对象3个属性,其中只有一个分类属性test-1,在上面的式子中,当对象i和j属性匹配时, $d(i,j)=0$ 当对象不匹配时, $d(i,j)=1$.

布尔属性的邻近性度量

上面表示两个对象的取0或1的属性数目(q,s,r,t)

对于对称的二元属性(布尔属性),是指每个属性都同样重要。基于对称二元属性的相异性称作对称的二元相异性。如果对象i和j都用对称的二元属性刻画,则i和j的相异性为:

互补的,相似性可用下式计算:

一个例子:下面gender为对称属性,其余为非对称属性(共6个),这里我们只考虑患者(对象)非对称属性,值Y(yes)和P(positive)都设置为1,N(no,negative)为0. 采用上面非对称的二元相异性计算公式。

数值属性的相异性:闵可夫斯基距离

偏序属性的邻近性度量

偏序属性的值之间具有意义的序或排位,例如size属性的序列值[small, medium, large]. 计算这种偏序属性首先计算状态在序数属性上的排名,并映射到[0, 1]数值上。然后把转换后的数值用闵可夫斯基距离来计算相似性。排名转换公式如下:

其中,属性$f$有$M_f$个有序的状态,表示排位$1,2…M_f$。排位$r_{if}$表示当前属性状态排名。

一个例子:

test-2偏序属性,有三个状态,即$M_f$=3,四个对象转换为排位分别为3、1、2、3。然后分别映射为1.0、0.0、0.5、1.0数值,最后可以使用欧几里得距离来计算如下的相异性矩阵。

混合类型属性的相异性

解决这种情况的方法是讲所有类型一起处理,把所有有意义的属性转换到共同区间[0.0 , 1.0]上

1、如果是数值,用归一化。2、如果是类别属性,匹配为1不匹配为0。3、偏序将排位先转为数值,再按数值的归一化处理。

一个例子:(混合了分类、偏序、和数值)

之前,处理test-1(分类属性)和test-2(偏序属性)的过程已经给出,可以使用它们之前的相异性矩阵。所以这里首先计算test-3(数值属性)的相异性矩阵。有max=64,min=22,比较对象用归一化处理后,得到test-3的相异性矩阵:

其中,d(3,1)是对象1和对象3每个不同属性的相似性矩阵计算得到的值,总的处理方式还是归一化。

余弦相似性

给出了四个文档的词频向量,用于比较这些文档之间的相似性。

动态规划

发表于 2018-09-24 | 分类于 数据结构与算法
字数统计: 4.6k | 阅读时长 ≈ 21

五问动态规划

问:动态规划是什么?

答:动态规划是一种通过“大而化小”的思路解决问题的算法。区别于一些固定形式的算法,如二分法,宽度优先搜索法,动态规划没有实际的步骤来规定第一步做什么第二步做什么。所以更加确切的说,动态规划是一种解决问题的思想。这种思想的本质是,一个规模比较大的问题(假如用2-3个参数可以表示),是通过规模比较小的若干问题的结果来得到的(通过取最大,取最小,或者加起来之类的运算)所以我们经常看到的动态规划的核心——状态转移方程都长成这样:

问:动态规划什么时候可以用?

答:动态规划解决的一定是最优化问题。一个问题必须有重叠的子问题和最优子结构,才能使用动态规划取解决问题。

问:动态规划的常见类型有哪些?

  • 矩阵型
  • 序列型
  • 双序列型
  • 划分型
  • 区间型
  • 背包型

问:什么样的问题适合使用动态规划?

答:可以使用动态规划的问题一般都有一些特点可以遵循。如题目的问法一般是三种方式:

  1. 求最大值/最小值
  2. 求可不可行
  3. 求方案总数

如果你碰到一个问题,是问你这三个问题之一的,那么有90%的概率是使用动态规划来求解。要重点说明的是,如果一个问题让你求出“所有的”方案和结果,则肯定不是使用动态规划。

解决一个动态规划问题的步骤是什么?

答:首先判断是否是动态规划的问题,如果是,则尝试将其进行分类常见类型,找到对应的类别和相似的问题。接着从下面的4个要素去逐步剖析解决这道题:

  1. 状态是什么
  2. 状态转移方程是什么
  3. 状态的初始值是什么
  4. 问题要求的最后答案是什么

每个步骤分析完成之后,就基本上解决了整道动态规划的问题。

动态规划相关题

交叉字符串

题目:给出三个字符串:s1、s2、s3,判断s3是否由s1和s2交叉构成。

样例

比如 s1 = “aabcc” s2 = “dbbca”

  • 当 s3 = “aadbbcbcac”,返回 true.
  • 当 s3 = “aadbbbaccc”, 返回 false.

思路:

1.这题我们利用动态规划加记忆化搜索。如果能够进行交叉组成,利用动态规划,建立 $boolean dp[i][j]$, 意思是s1的第i为 和s2的第j为是否能够够成s3的i + j 长度的交叉字符串。不一定要每个字符交替插入,s1 = aa, s2 = d 也可以组成s3 = aad。记忆矩阵这里要清楚定义,一个维度是s1的长度,一个维度是s2的长度。

2.因此状态转移方程就可以写成:

3.初始条件要注意,我们这里是把记忆矩阵建立为$test$,因此第一行就是没有s1的情况下看看s2能不能与s3配对,第一列就是在没有s2的情况下能不能与s3配对。
4.最后就是看最右下角的dp。时间复杂度是o(n*m)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution:
"""
@params s1, s2, s3: Three strings as description.
@return: return True if s3 is formed by the interleaving of
s1 and s2 or False if not.
@hint: you can use [[True] * m for i in range (n)] to allocate a n*m matrix.
"""
def isInterleave(self, s1, s2, s3):
# write your code here
if s1 is None or s2 is None or s3 is None:
return False
if len(s1) + len(s2) != len(s3):
return False
# 初始边界s1行s2列的false
interleave = [[False] * (len(s2) + 1) for i in range(len(s1) + 1)]
interleave[0][0] = True
for i in range(len(s1)):
interleave[i + 1][0] = s1[:i + 1] == s3[:i + 1]
for i in range(len(s2)):
interleave[0][i + 1] = s2[:i + 1] == s3[:i + 1]

for i in range(len(s1)):
for j in range(len(s2)):
interleave[i + 1][j + 1] = False
if s1[i] == s3[i + j + 1]:
interleave[i + 1][j + 1] = interleave[i][j + 1]
if s2[j] == s3[i + j + 1]:
interleave[i + 1][j + 1] |= interleave[i + 1][j]
return interleave[len(s1)][len(s2)]

最长上升子序列

描述:给定一个整数序列,找到最长上升子序列(LIS,不要求一定连续),返回LIS的长度。例如现在有序列A=\{1,2,3,-1,-2,7,9\},它的最长上升子序列为\{1,2,3,7,9\},长度为5.

思路:dp[i] 表示走到第i个元素时的当前最大连续子序列的长度 ,这样对A[i]有两种可能:

1、如果A[i]之前的元素A[j],其中$jdp[i]$,那么可以把A[i]拼接到A[j]的后面

2、如果之前的元素都比A[i]大,则A[i]自己成为最大的上升自序,长度为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
"""
@param nums: The integer array
@return: The length of LIS (longest increasing subsequence)
"""
def longestIncreasingSubsequence(self, nums):
# write your code here
if nums is None or not nums:
return 0
dp = [1] * len(nums) # 边界初始条件
for curr, val in enumerate(nums):
for prev in xrange(curr):
if nums[prev] < val: # 如果之前的元素大于等于curr,则dp[curr]为初始的1
dp[curr] = max(dp[curr], dp[prev] + 1) #状态转移方程
return max(dp)

单词拆分 ++

描述:给定字符串 s 和单词字典 dict,确定 s 是否可以分成一个或多个以空格分隔的子串,并且这些子串都在字典中存在。

样例:给出s = “lintcode”,dict = [“lint”,”code”]返回 true 因为“lintcode”可以被空格切分成“lint code”

思路:如果最大字典长度为k,f[i]的状态由前面i-k到i-1之间决定,这中间任何一段属于dict则f[I]为True

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
# @param s: A string s
# @param dict: A dictionary of words dict
def wordBreak(self, s, dict):
if len(dict) == 0:
return len(s) == 0

n = len(s)
f = [False] * (n + 1)
f[0] = True # 初始边界

maxLength = max([len(w) for w in dict]) #先计算字典中最大长度,减少复杂度
for i in xrange(1, n + 1):
for j in range(1, min(i, maxLength) + 1): #不必遍历i之前的所有j
if not f[i - j]:
continue
if s[i - j:i] in dict:
f[i] = True
break # 有一个满足即可判断下一个f[i+1]
return f[n]

数字三角形

描述:给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上

样例:比如,给出下列数字三角形:

1
2
3
4
5
6
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]

从顶到底部的最小路径和为11 ( 2 + 3 + 5 + 1 = 11)。

思路:自底向上的动态规划, 当前位置由左下或者右下最小值决定,时间复杂度O(n), python3 实现 , triangle数组代表第i行第j个数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
"""
@param triangle: a list of lists of integers
@return: An integer, minimum path sum
"""
def minimumTotal(self, triangle):
rows = len(triangle)
dp = [[0] * len(triangle[row]) for row in range(rows)]
for i in range(len(triangle[rows - 1])):
dp[rows - 1][i] = triangle[rows - 1][i] #初始边界
for i in range(rows - 2, -1, -1):
for j in range(i + 1):
dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j] #状态转移方程
return dp[0][0]

最小路径和

描述:给定一个只含非负整数的m×n网格,找到一条从左上角到右下角的可以使数字和最小的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
"""
@param grid: a list of lists of integers.
@return: An integer, minimizes the sum of all numbers along its path
"""
def minPathSum(self, grid):
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0 and j > 0:
grid[i][j] += grid[i][j-1] # 第一行,只在左边
elif j == 0 and i > 0:
grid[i][j] += grid[i-1][j] # 第一列,只在右边
elif i > 0 and j > 0:
grid[i][j] += min(grid[i-1][j], grid[i][j-1]) # 由上面和左面的最小路径决定
return grid[len(grid) - 1][len(grid[0]) - 1]

爬楼梯 ++

描述:假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
"""
@param n: An integer
@return: An integer
"""
def climbStairs(self, n):
# write your code here
if n == 0:
return 1
if n <= 2:
return n
result=[1,2]
for i in range(n-2):
result.append(result[-2]+result[-1])
return result[-1]

不同的路径

描述:有一个机器人的位于一个 m × n 个网格左上角。机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。问有多少条不同的路径?

思路:有左边一格的路径数和上面一格的路径数决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:

def uniquePaths(self, m, n):
paths = [[0] * n for i in range(m)] #初始边界
# initial rows
paths[0][0] = 1
for x in range(1, m):
paths[x][0] = paths[x - 1][0] #边界
# initail columns
for y in range(1, n):
paths[0][y] = paths[0][y - 1] #边界

for x in range(1, m):
for y in range(1, n):
paths[x][y] = paths[x -1][y] + paths[x][y - 1]
return paths[m - 1][n - 1]

编辑距离 +

题目:给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。你总共三种操作方法:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

样例:给出 work1=”mart” 和 work2=”karma”,返回 3。(先进行2个替换,后面进行1个插入)

思路:f[i][j代表第一个字符串以i结尾匹配上(编辑成)第二个字符串以j结尾的字符串,最少需要多少次编辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
"""
@param word1: A string
@param word2: A string
@return: The minimum number of steps.
"""
def minDistance(self, word1, word2):
n, m = len(word1), len(word2)
f = [[0] * (m + 1) for _ in range(n + 1)]

for i in range(n + 1):
f[i][0] = i # 边界
for j in range(m + 1):
f[0][j] = j # 边界

for i in range(1, n + 1):
for j in range(1, m + 1):
if word1[i - 1] == word2[j - 1]:
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j] + 1, f[i][j - 1] + 1)
# equivalent to f[i][j] = f[i - 1][j - 1]
else: #分别代表替换,插入,删除
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j], f[i][j - 1]) + 1

return f[n][m]

正则表达式匹配 ++

描述:实现支持‘.’和‘*‘的正则表达式匹配。’.’匹配任意一个字母。’*’匹配零个或者多个前面的元素。匹配应该覆盖整个输入字符串,而不仅仅是一部分。返回true 和 false

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
isMatch("aa","a") → false

isMatch("aa","aa") → true

isMatch("aaa","aa") → false

isMatch("aa", "a*") → true

isMatch("aa", ".*") → true

isMatch("ab", ".*") → true

isMatch("aab", "c*a*b") → true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution(object):
# DP
def isMatch(self, s, p):
dp = [[False for i in range(0,len(p) + 1)] for j in range(0, len(s) + 1)]
dp[0][0] = True
for i in range(1, len(p) + 1):
if (p[i - 1] == '*'):
dp[0][i] = dp[0][i - 2]
for i in range(1, len(s) + 1):
for j in range(1, len(p) + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 2]
if s[i - 1] == p[j - 2] or p[j - 2] == '.':
dp[i][j] |= dp[i-1][j]
else:
if s[i - 1] == p[j - 1] or p[j - 1] == '.':
dp[i][j] = dp[i - 1][j - 1]

return dp[len(s)][len(p)]

# 懒癌版
def isMatch(self, s, p):
# '$'字符规则代表匹配字符串的末尾,匹配返回一个Match 对象,否则返回None
return re.match(p + '$', s) != None

不同的二叉查找树 II

描述:给出n,生成所有由1…n为节点组成的不同的二叉查找树

样例:给出n = 3,生成所有5种不同形态的二叉查找树:

1
2
3
4
5
6
1         3     3       2    1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
this.val = val
this.left, this.right = None, None
"""
class Solution:
# @paramn n: An integer
# @return: A list of root
def generateTrees(self, n):
# write your code here
return self.dfs(1, n)

def dfs(self, start, end):
if start > end: return [None]
res = []
for rootval in range(start, end+1):
LeftTree = self.dfs(start, rootval-1)
RightTree = self.dfs(rootval+1, end)
for i in LeftTree:
for j in RightTree:
root = TreeNode(rootval)
root.left = i
root.right = j
res.append(root)
return res

乘积最大子序列 +

描述:找出一个序列中乘积最大的连续子序列(至少包含一个数)

样例:比如, 序列 [2,3,-2,4] 中乘积最大的子序列为 [2,3] ,其乘积为6。

思路:这道题和maximal subarray思路一样,不同的是对于加法加上负数会变小,加上正数会变大;而对于乘法,乘以正数有可能变大也有可能变小(原数是负数的情况下),乘以负数也有可能变大或者变小

所以需要两个变量:
min_p表示行进到当前subarray能得到的最小的积
max_p表示行进到当前subarray能得到的最大的积

对于某一个subarray来说,它最大的积,有可能来自之前的最大积乘以一个正数,或者之前的最小积乘以一个负数,或者nums[i]就是最大的
因此 $max_p = max(nums[i], max_p × nums[i], min_p × nums[i])$
最小积同理

最后用res变量跟踪一下全局最大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
"""
@param nums: An array of integers
@return: An integer
"""
def maxProduct(self, nums):
if not nums:
return None

global_max = prev_max = prev_min = nums[0]
for num in nums[1:]:
if num > 0:
curt_max = max(num, prev_max * num)
curt_min = min(num, prev_min * num)
else:
curt_max = max(num, prev_min * num)
curt_min = min(num, prev_max * num)

global_max = max(global_max, curt_max)
prev_max, prev_min = curt_max, curt_min

return global_max

通配符匹配

描述:判断两个可能包含通配符“?”和“*”的字符串是否匹配。匹配规则如下:

1
2
3
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个串完全匹配才算匹配成功。

样例:

1
2
3
4
5
6
7
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
"""
@param s: A string
@param p: A string includes "?" and "*"
@return: A boolean
"""
def isMatch(self, s, p):
# write your code here
n = len(s)
m = len(p)
f = [[False] * (m + 1) for i in range(n + 1)]
f[0][0] = True

if n == 0 and p.count('*') == m:
return True

for i in range(0, n + 1):
for j in range(0, m + 1):
if i > 0 and j > 0:
f[i][j] |= f[i-1][j-1] and (s[i-1] == p[j-1] or p[j - 1] in ['?', '*'])

if i > 0 and j > 0:
f[i][j] |= f[i - 1][j] and p[j - 1] == '*'

if j > 0:
f[i][j] |= f[i][j - 1] and p[j - 1] == '*'
return f[n][m]

打劫房屋 ++

描述:假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,你最多可以得到多少钱 在不触动报警装置的情况下。

样例:给定 [3, 8, 4], 返回 8.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution:
"""
@param A: An array of non-negative integers
@return: The maximum amount of money you can rob tonight
"""
def houseRobber(self, A):
if not A:
return 0
if len(A) <= 2:
return max(A)

f = [0] * len(A)
f[0], f[1] = A[0], max(A[0], A[1])

for i in range(2, len(A)):
f[i] = max(f[i - 1], f[i - 2] + A[i])
return f[len(A) - 1]

# 使用滚动数组版本
class Solution:
"""
@param A: An array of non-negative integers
@return: The maximum amount of money you can rob tonight
"""
def houseRobber(self, A):
if not A:
return 0
if len(A) <= 2:
return max(A)

f = [0] * 3
f[0], f[1] = A[0], max(A[0], A[1])

for i in range(2, len(A)):
f[i % 3] = max(f[(i - 1) % 3], f[(i - 2) % 3] + A[i])

return f[(len(A) - 1) % 3]

买卖股票的最佳时机

描述:假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格。设计一个算法来找到最大的利润。你最多可以完成 k 笔交易。你不可以同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

样例:给定价格 = [4,4,6,1,1,4,2,5], 且 k = 2, 返回 6.(1买4卖,2买5卖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
"""
@param k: an integer
@param prices: an integer array
@return: an integer which is maximum profit
"""
def maxProfit(self, k, prices):
# write your code here
size = len(prices)
if k >= size / 2:
return self.quickSolve(size, prices)
dp = [None] * (2 * k + 1)
dp[0] = 0
for i in range(size):
for j in range(min(2 * k, i + 1) , 0 , -1):
dp[j] = max(dp[j], dp[j - 1] + prices[i] * [1, -1][j % 2])
return max(dp)

def quickSolve(self, size, prices):
sum = 0
for x in range(size - 1):
if prices[x + 1] > prices[x]:
sum += prices[x + 1] - prices[x]
return sum

解码方法+

描述:有一个消息包含A-Z通过以下规则编码,现在给你一个加密过后的消息,问有几种解码的方式

1
2
3
4
'A' -> 1
'B' -> 2
...
'Z' -> 26

样例:给你的消息为12,有两种方式解码 AB(12) 或者 L(12). 所以返回 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
# @param {string} s a string, encoded message
# @return {int} an integer, the number of ways decoding
def numDecodings(self, s):
# Write your code here
if s == "" or s[0] == '0':
return 0

dp=[1,1]
for i in range(2,len(s) + 1):
if 10 <= int(s[i - 2 : i]) <=26 and s[i - 1] != '0':
dp.append(dp[i - 1] + dp[i - 2])
elif int(s[i-2 : i]) == 10 or int(s[i - 2 : i]) == 20:
dp.append(dp[i - 2])
elif s[i-1] != '0':
dp.append(dp[i-1])
else:
return 0

return dp[len(s)]

完美平方

描述:给一个正整数 n, 找到若干个完全平方数(比如1, 4, 9, … )使得他们的和等于 n。你需要让平方数的个数最少。

样例:

给出 n = 12, 返回 3 因为 12 = 4 + 4 + 4。
给出 n = 13, 返回 2 因为 13 = 4 + 9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def numSquares(self, n):
# write your code here
dp = []
import sys
for i in range(n+1):
dp.append(sys.maxint)
i = 0
while i * i <= n:
dp[i*i] = 1
i += 1


for i in range(n+1):
j = 1
while j * j <= i:
dp[i] = min(dp[i], dp[i-j*j] + 1)
j += 1
return dp[n]

重点掌握

发表于 2018-09-21 | 分类于 数据结构与算法
字数统计: 1.4k | 阅读时长 ≈ 6

二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class Node():
#节点类
def __init__(self,data = -1):
self.data = data
self.left = None
self.right = None
class Tree():
#树类
def __init__(self):
self.root = Node()

def add(self,data):
# 为树加入节点
node = Node(data)
if self.root.data == -1: #如果树为空,就对根节点赋值
self.root = node
else:
myQueue = []
treeNode = self.root
myQueue.append(treeNode)
while myQueue: #对已有的节点进行层次遍历
treeNode = myQueue.pop(0)
if not treeNode.left:
treeNode.left = node
return
elif not treeNode.right:
treeNode.right = node
return
else:
myQueue.append(treeNode.left)
myQueue.append(treeNode.right)

def pre_order_recursion(self,root): #递归实现前序遍历
if not root:
return
print root.data,
self.pre_order_recursion(root.left)
self.pre_order_recursion(root.right)

def pre_order_stack(self,root): #堆栈实现前序遍历(非递归)
if not root:
return
myStack = []
node = root
while myStack or node:
while node: #从根节点开始,一直寻找他的左子树
print node.data, # 先序,进栈前就要读取了
myStack.append(node) # 先存进栈,以后还需要它的右节点
node = node.left
node = myStack.pop() #while结束表示当前节点node为空,即前一个节点没有左子树了
node = node.right #开始查看它的右子树

def in_order_recursion(self,root): #递归实现中序遍历
if not root:
return
self.in_order_recursion(root.left)
print root.data,
self.in_order_recursion(root.right)

def in_order_stack(self,root): #堆栈实现中序遍历(非递归)
if not root:
return
myStack = []
node = root
while myStack or node: #从根节点开始,一直寻找它的左子树
while node:
myStack.append(node)
node = node.left
node = myStack.pop() # 中序,弹出来后才读取
print node.data,
node = node.right


def post_order_recursion(self,root): #递归实现后序遍历
if not root:
return
self.post_order_recursion(root.left)
self.post_order_recursion(root.right)
print root.data,

def post_order_stack(self, root): # 堆栈实现后序遍历(非递归)
# 先遍历根节点,再遍历右子树,最后是左子树,这样就可以转化为和先序遍历一个类型了,最后只把遍历结果逆序输出就OK了。
if not root:
return
myStack1 = []
myStack2 = []
node = root
while myStack1 or node:
while node:
myStack2.append(node)
myStack1.append(node)
node = node.right
node = myStack1.pop()
node = node.left
while myStack2:
print myStack2.pop().data,

def level_order_queue(self,root): #队列实现层次遍历(非递归)
if not root :
return
myQueue = []
node = root
myQueue.append(node)
while myQueue:
node = myQueue.pop(0)
print node.data,
if node.left:
myQueue.append(node.left)
if node.right:
myQueue.append(node.right)

if __name__ == '__main__':
#主函数
datas = [2,3,4,5,6,7,8,9]
tree = Tree() #新建一个树对象
for data in datas:
tree.add(data) #逐个加入树的节点

print '递归实现前序遍历:'
tree.pre_order_recursion(tree.root)

print '\n堆栈实现前序遍历'
tree.pre_order_stack(tree.root)

print "\n\n递归实现中序遍历:"
tree.in_order_recursion(tree.root)

print "\n堆栈实现中序遍历:"
tree.in_order_stack(tree.root)

print '\n\n递归实现后序遍历:'
tree.post_order_recursion(tree.root)

print '\n堆栈实现后序遍历:'
tree.post_order_stack(tree.root)

print '\n\n队列实现层次遍历:'
tree.level_order_queue(tree.root)!

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#  二分查找
def BinarySearch(array,t):
low = 0
height = len(array)-1
while low < height:
mid = (low+height)/2
if array[mid] < t:
low = mid + 1

elif array[mid] > t:
height = mid - 1

else:
return array[mid]
return -1


if __name__ == "__main__":
print BinarySearch([1,2,3,34,56,57,78,87],57)

广度优先与深度优先

下面的代码强调一下: dfs和bfs区别(重点)

  1. pop()和pop(0)
  2. order加入w的时机
  3. 判断w的条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
'''
深度优先遍历: 是一种用于遍历树或者图的算法。沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所在边都被搜索过了。搜索将回溯到节点v的那条边的起始节点。直到已发现从源节点可达的所有节点为止。如果还存在未发现的节点,则选择其中一个作为源节点并重复上述过程,整个进程反复进行直到所有节点都被访问为止.

广度优先遍历:从根节点开始,沿着树的宽度遍历树的节点,如果所有节点都被访问,则算法终止
'''
class Graph(object):
def __init__(self, nodes, sides):
# nodes表示用户输入的点,int型,sides表示用户输入的边,是一个二元组(u, v)
# self.sequence是字典,key是点,value是与key相连的边
self.sequence = {}
# self.side是临时变量,主要用于保存与 指定点v 相连接的点
self.side = []
for node in nodes:
for side in sides:
u, v = side
if node == u:
self.side.append(v)
elif node == v:
self.side.append(u)
# 第二层主要是遍历属于这个点的所有边,然后将点和边组成字典
self.sequence[node] = self.side
self.side = []
# print self.sequence
# {1: [2, 3], 2: [1, 4, 5], 3: [1, 6, 7], 4: [2, 8], 5: [2, 8], 6: [3, 7], 7: [3, 6], 8: [4, 5]}

def dfs(self, node0):
# order里面存放的是具体的访问路径,已经遍历的了
queue, order = [], []
queue.append(node0)
while queue:
v = queue.pop() # 取出最后一个,为上一个刚加入节点的连接节点
order.append(v) # 深度优先先加入,注意这个order的加入顺序
for w in self.sequence[v]: # 两边
# 不在order表示没被遍历,
if w not in order and w not in queue:
queue.append(w)
return order

# bfs同理
def bfs(self, node0):
queue, order = [], []
queue.append(node0)
order.append(node0)
while queue:
v = queue.pop(0) # 层次遍历按迅速取出
for w in self.sequence[v]:
# if w not in order and w not in queue:
if w not in order
order.append(w) # 没被遍历就直接将两边加入order
queue.append(w)
return order


def main():
nodes = [i+1 for i in xrange(8)]
sides = [(1, 2),
(1, 3),
(2, 4),
(2, 5),
(4, 8),
(5, 8),
(3, 6),
(3, 7),
(6, 7)]

G = Graph(nodes, sides)
print G.dfs(1)
print G.bfs(1)


if __name__ == '__main__':
main()

字符串解题

发表于 2018-09-19 | 分类于 数据结构与算法
字数统计: 1.3k | 阅读时长 ≈ 5

字符串相关题_python版

最长无重复字符子串长度

题目:给定一个字符串,请找出其中无重复字符的最长子字符串。例如,在”abcabcbb”中,其无重复字符的最长子字符串是”abc”,其长度为 3。

思路:遍历字符串中的每一个元素。借助一个辅助键值对来存储某个元素最后一次出现的下标。用一个整形变量存储当前无重复字符的子串开始的下标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 分析 a b c d e f g d 此时从最近重复的前一个字符d的后一位开始记,即e标记为start。此时继续取下一个数, 例如a,它的前一个字符下标为d[s[i]]=0,若d[s[i]]<start,则不需要更新start,否则更新start。新的无重复子串变为e f g d a
class Solution:
"""
@param: s: a string
@return: an integer
"""
def lengthOfLongestSubstring(self, s):
# write your code here
res = 0
if s is None or len(s) == 0:
return res
d = {} # 存储某个元素最后一次出现的下标
tmp = 0 # 存储每次循环中最长的子串长度
start = 0 # 记录最近重复字符所在的位置+1
for i in range(len(s)): # 下标
# 判断当前字符是否在字典中和当前字符的下标是否大于等于最近重复字符的所在位置
if s[i] in d and d[s[i]] >= start: # 这里的d[s[i]]为前一个重复的下标
start = d[s[i]] + 1
tmp = i - start + 1
d[s[i]] = i
res = max(res, tmp)
return res

最长回文字符串

思路一:中心扩展法。根据回文的特性,显然所有的回文串都是对称的。长度为奇数回文串以最中间字符的位置为对称轴左右对称,而长度为偶数的回文串的对称轴在中间两个字符之间的空隙。可否利用这种对称性来提高算法效率呢?答案是肯定的。我们知道整个字符串中的所有字符,以及字符间的空隙,都可能是某个回文子串的对称轴位置。可以遍历这些位置,在每个位置上同时向左和向右扩展,直到左右两边的字符不同,或者达到边界。对于一个长度为n的字符串,这样的位置一共有n+n-1=2n-1个 ,时间复杂度O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution(object):
def longestPalindrome(self, s):
str_length = len(s)
max_length = 0 # 记录最大字符串长度,不是对称长度
start = 0 # 记录位置
for i in range(str_length): # 当前下标位置
# 对称位置在对称轴间隙,偶数
if i - max_length >= 1 and s[i-max_length-1: i+1] == s[i-max_length-1: i+1][::-1]:
# 记录当前开始位置
start = i - max_length - 1
max_length += 2
continue
# 对称位置在对称字符,奇数
if i - max_length >= 0 and s[i-max_length: i+1] == s[i-max_length: i+1][::-1]:
start = i - max_length
max_length += 1
# 返回最长回文子串
return s[start: start + max_length]


if __name__ == '__main__':
s = "babad"
# s = "cbbd"
sl = Solution()
print(sl.longestPalindrome(s))

思路二:马拉车算法

看这篇

KMP算法

过程描述看这篇

其实就是,对模式串p进行预处理,得到前后缀的部分匹配表,使得我们可以借助已知信息,算出可以右移多少位.即 kmp = 朴素匹配 + 移动多位.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#KMP
def kmp_match(self, s, p):
m = len(s)
n = len(p)
cur = 0 # 起始指针cur,累积移动数
table = self.partial_table(p)
while cur <= m-n: # 长度不够就终止
for i in range(n): # 一次p从头开始匹配的长度
if s[i+cur] != p[i]:
# 移动位数 = 已匹配的字符数 - 对应的部分匹配值
# 有了部分匹配表,我们不只是单纯的1位1位往右移,可以一次移动多位
cur += max(i - table[i-1], 1)
break
else: # 执行了break就不会执行这句,相当于for循环里所有都满足 s[i+cur] == p[i]
return cur # 返回匹配开始的位置
return -1 # 匹配失败

#部分匹配表
def partial_table(self, p):
'''''partial_table("ABCDABD") -> [0, 0, 0, 0, 1, 2, 0]'''
prefix = set()
table = [0]
for i in range(1, len(p)): # 从1开始进行前后缀比较
prefix.add(p[:i]) # 前缀每次累加就行
postfix = set()
for j in range(1, i+1): # i+1 因为i需要包括
postfix.add(p[j:i+1])
# print(prefix, postfix)
# print(prefix&postfix, len(prefix&postfix))
# table.append(len((sorted((prefix&postfix),key = len)or {''}).pop()))
if prefix&postfix:
table.append(max(map(len,prefix&postfix)))
else:
table.append(0)
return table

字符串去重

1
string = 'abc123456ab2s'r = ''.join(x for i, x in enumerate(string) if string.index(x) == i)

统计一个字符串中英文字母、空格、数字的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import string
s = raw_input('请输入一个字符串:\n')
letters = 0
space = 0
digit = 0
others = 0
for c in s:
if c.isalpha():
letters += 1
elif c.isspace():
space += 1
elif c.isdigit():
digit += 1
else:
others += 1
print 'char = %d,space = %d,digit = %d,others = %d' % (letters,space,digit,others)

数组解题

发表于 2018-09-16 | 分类于 数据结构与算法
字数统计: 2k | 阅读时长 ≈ 8

数组相关题_python版

寻找某个值的区间(leetcode 34 Search for a Range)

题目:这题要求在一个排好序可能有重复元素的数组里面找到包含某个值的区间范围。要求使用O(log n)的时间,所以我们采用两次二分查找。

For Example:
Given [5, 7, 7, 8, 8, 10] and target value 8,
return [3, 4].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

class Solution:
# @param A, a list of integers
# @param target, an integer to be searched
# @return a list of length 2, [index1, index2]
def searchRange(self, A, target):
left = 0
right = len(A) - 1
result = [-1, -1]
while left <= right:
mid = (left + right) / 2
if A[mid] > target:
right = mid - 1
elif A[mid] < target:
left = mid + 1
else: # 找到时
result[0] = mid
result[1] = mid

i = mid - 1 # 向前找
while i >= 0 and A[i] == target:
result[0] = i
i -= 1

i = mid + 1 # 向后找
while i < len(A) and A[i] == target:
result[1] = i
i += 1
break
return result

第K个数的问题

题目:这题是一道很好的面试题目,首先题目短小,很快就能说清题意而且有很多种解法。从简单到复杂的解法都有,梯度均匀。解决它不需要预先知道特殊领域知识。

这题有很多思路:

  1. 按从大到小全排序,然后取第k个元素,时间复杂度O(nlogn),空间复杂度O(1)
  2. 利用堆进行部分排序。维护一个大根堆,将数组元素全部压入堆,然后弹出k次,第k个就是答案。时间复杂度O(klogn)O(klogn),空间复杂度O(n)O(n)
  3. 选择排序,第k次选择后即可得到第k大的数,时间复杂度O(nk),空间复杂度O(1)

以上三种方法时间复杂度太高。下面介绍两种更好的方法:

维持K大小的堆排序(优先队列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
用容量为K的最大堆来存储最小的K个数。最大堆的堆顶元素就是最小K个数中的最大的一个。每次扫描一个数据X,如果X比堆顶元素Y大,则不需要改变原来的堆。如果X比堆顶元素小,那么用X替换堆顶元素Y,在替换之后,X可能破坏了最大堆的结构,需要调整堆来维持堆的性质。用优先队列思想也一样,只不过k大小的队列每次移动的元素量较大,堆会好一些。
'''
# 使用了heapq的内置数据结构,用了一个trick 因为默认是创建小顶堆,所以在添加元素的时候加个 负号就变成大顶堆了。


import heapq
class Solution:
def GetLeastNumbers_Solution(self, tinput, k):
if k > len(tinput) or k == 0: return []
heap = []
for num in tinput:
if len(heap) < k:
heapq.heappush(heap, -num)
else:
if -num > heap[0]:
heapq.heapreplace(heap, -num)
return sorted(list(map(lambda x: x*-1, heap)))

快速排序,时间复杂度近似O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
利用快速排序的思想,从数组S中随机找出一个元素X,把数组分为两部分Sa和Sb。Sa中的元素大于等于X,Sb中元素小于X。这时有两种情况:
1. Sa中元素的个数小于k,则Sb中的第k-|Sa|个元素即为第k大数;
2. Sa中元素的个数大于等于k,则返回Sa中的第k大数。时间复杂度近似为O(n)
'''
def qselect(A,k):
if len(A)<k:return A
pivot = A[-1]
right = [pivot] + [x for x in A[:-1] if x>=pivot]
rlen = len(right)
if rlen==k:
return right
if rlen>k:
return qselect(right, k)
else:
left = [x for x in A[:-1] if x<pivot]
return qselect(left, k-rlen) + right

for i in range(1, 10):
print qselect([11,8,4,1,5,2,7,9], i)

求根算法( LeetCode 69)

题目:计算并返回 x 的平方根,其中 x 是非负整数。 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

思路一:直接从1到x/2之间遍历,判断是否是平方根的条件是,i*i小于等于x并且小于等于x并且(i+1)*(i+1)大于x,则返回i。超时 。

思路二:二分查找法。初始化i=0,j=x,mid=0。进入循环,找到中间值mid = (i + j) / 2,如果mid>x / mid,表示mid不是平方根,且数值过大,则j=mid。如果mid小于等于x / mid,则判断(mid + 1) > x / (mid + 1),表示mid*mid小于x,并且mid再加1后的平方就会比x大,这表示mid就是那个平方根,返回mid。否则表示mid过小,i=mid。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution(object):
def mySqrt(self, x):
"""
:type x: int
:rtype: int
"""
if x==0 or x==1:
return x
i=0
j=x
mid=0
while True:
mid=(i+j)/2
if mid>x/mid:
j=mid
else:
if (mid+1)>x/(mid+1):
return mid
i=mid

数组中后面的数减前面的数的差的最大值

题目:如何求数组中数对差最大。数对差是指一个数组中某两个元素a和b(并且a排在b的前面),a减去b所得到的差值。

思路一:遍历存储最大值

思路二:首先求出数组中任意一对相邻的数据之间的差值,得到一个新的数组。如果某两个数据之间的数对差最大,也就是说这两个数据之间的差值最大。假设这两个数据的位置是i和j,那么这两个位置之间的数据是a[i],a[i+1],a[i+2]……,a[j-1],a[j]。那么a[i]-a[j]=(a[i]-a[i+1])+(a[i+1]-a[i+2])+……(a[j-1]-a[j]),括号中的数据是相邻数据的差值,都已经在前面求出来了。然后这个问题就转化为了求数组中连续的子数组和最大的问题,这个问题可以通过动态规划问题求出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import random
def Max(firstNum,secondNum):
if firstNum>=secondNum:
return firstNum
else:
return secondNum

def Count(array):
gapArray=[]
length=len(array)
#遍历一遍记录相邻两个之间的gap
for i in xrange(length-1):
gap=array[i]-array[i+1]
gapArray.append(gap)
#转化为子集合最大和问题
max=-((1<<32)-1)
sum=0
for i in xrange(len(gapArray)):
sum+=gapArray[i]
max=Max(max,sum)
if sum<0:
sum=0
return max

array=[]
for i in xrange(20):
array.append(random.randint(0,50))
print array
print "max gap:"+str(Count(array))

合并多个有序数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#采用归并排序算法
#拆解到最后,实际变成两个数组进行排序
def MergeSort(nums):
#请牢记传入的参数是多维数组
#此处是递归出口
if len(nums) <= 1:
return nums
mid = len(nums) // 2

#记住此处得到的也是多维数组
Left = MergeSort(nums[:mid])
Right = MergeSort(nums[mid:])

#print(Left[0], Right[0])
#要传入的参数是数组中第一个索引处的值
return Sort_list(Left[0], Right[0])

def Sort_list(Left, Right):
#存储排序后的值
res = []
a = 0
b = 0

while a < len(Left) and b < len(Right):

if Left[a] < Right[b]:
res.append(Left[a])
a += 1
else:
res.append(Right[b])
b += 1
res = res + Left[a:] + Right[b:]
# 转为二维数组
res = [res]
return res

两个有序数组求差集

思路一:依次取出较小数组的元素,然后再另外一个数组上进行二分查找

思路二:用齐头并进的两个下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def intersect(self, nums1, nums2):
nums3 = []
nums1.sort()
nums2.sort()
m = len(nums1)
n = len(nums2)
i = 0
j = 0
while i<m and j<n:
if nums1[i] == nums2[j]:
nums3.append(nums1[i])
i += 1
j += 1
elif nums1[i] > nums2[j]:
j += 1
else:
i += 1
return nums3

两个集合如何求并集,交集;

1
2
3
4
5
6
7
python集合支持一系列标准操作,包括并集、交集、差集和对称差集,例如:  

a = t | s # t 和 s的并集

b = t & s # t 和 s的交集

c = t – s # 求差集(项在t中,但不在s中)

给定一个数组求中位数

(中位数,就是数组排序后处于数组最中间的那个元素)

思路:和TOP k问题一样,这里就不写了。(先排序再取中位数不优)

链表解题

发表于 2018-09-11 | 分类于 数据结构与算法
字数统计: 3.2k | 阅读时长 ≈ 13

链表相关题_python版

在O(1)时间删除链表结点

题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。

思路:我们要删除结点i,先把i的下一个结点i.next的内容复制到i,然后在把i的指针指向i.next结点的下一个结点即i.next.next,它的效果刚好是把结点i给删除了。需要考虑如果这个节点是链表的尾节点那么就需要从头遍历这个链表了。(通常,在单向链表中,删除一个链表的结点,都会先从表头开始遍历整个链表,找到需要删除的结点的前一个结点,然后将这个结点的(指向下一个结点的)指针元素指向需要删除结点的下一个结点,最后把需要删除的结点删除.但此过程的平均时间复杂度为 O(n). )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ListNode:
def __init__(self, value):
self.value = value
self.next_ = None

def deleteNode(self,pHead,Node):
if Node == None or pHead == None:
return
if Node.next != None: # else情况1:只有一个Node节点;情况2:Node节点在尾巴
Node.val = Node.next.val
Node.next = Node.next.next
elif Node == pHead:# 如果链表只有一个节点,那么就把头节点删掉就好了
pHead.val = None
else:
pNode = pHead # 把Node节点删除,然后接上一个None
while pNode.next != Node:
pNode = pNode.next
pNode.next = None
return pHead

合并两个排序的链表(要求不新建链表)

题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。

思路:非递归情况:找到两个链表中头节点值相对更小的链表,将其作为主链表,第二个链表中的元素则不断加入到主链表中。具体策略是:主链表定义两个指针,指向两个相邻的元素。当第二个链表中的元素值小于主链表中第二个指针时,将第二个链表的当前元素插入到主链表两个指针指向的元素中间,并调整指针指向。 不要让链表断开,考虑链表为空的几种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#  ============非递归版本===============
def Merge(self, pHead1, pHead2):
if not pHead1:
return pHead2
if not pHead2:
return pHead1
mainHead = pHead1 if pHead1.val <= pHead2.val else pHead2 # 主链
secHead = pHead2 if mainHead == pHead1 else pHead1 # 副链
mergeHead = mainHead
mainNext = mainHead.next # 主链第二个指针
while mainNext and secHead:
if secHead.val <= mainNext.val: # 副链节点插入到两个指针之间
mainHead.next = secHead # 第一个指针连接到副链头指针
secHead = secHead.next # 副链头指针后移
mainHead.next.next = mainNext # 副链头指针连接到第二个指针
mainHead = mainHead.next # 第一个指针后移(变成原副链的头指针),插入操作第二个指针不需要后移
else:
mainHead = mainNext
mainNext = mainNext.next
if not mainNext: # 副链元素都比第二个指针要大,不能插入,要拼接
mainHead.next = secHead
return mergeHead
# ==================递归版本=================
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
if pHead1 is None: # 处理末尾状态,pHead1为空,要拼接的就是pHead2了
return pHead2
if pHead2 is None:
return pHead1
if pHead1.val < pHead2.val:
pHead1.next = self.Merge(pHead1.next,pHead2)
return pHead1 # 返回整段拼接后的链表
else:
pHead2.next = self.Merge(pHead1,pHead2.next)
return pHead2

二叉搜索树与双向链表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向 ,例如

思路:

  1. 核心算法依旧是中序遍历
  2. 不是从根节点开始,而是从中序遍历得到的第一个节点开始
  3. 定义两个辅助节点listHead(链表头节点)、listTail(链表尾节点)。事实上,二叉树只是换了种形式的链表;listHead用于记录链表的头节点,用于最后算法的返回;listTail用于定位当前需要更改指向的节点。了解了listHead和listTail的作用,代码理解起来至少顺畅80%。

过程图示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'''
稍微多说一句,其实这段代码也就5行,2行是中序遍历的代码;3行是更改节点指向的代码,为if、else行。if语句段只有在中序遍历到第一个节点时调用,自此之后listHead不变,listTail跟随算法的进度。对比中序遍历可以看出来,实际上只是中序遍历中的第八行代码被上述的if-else语句替代了,仅此而已。
'''
class TreeNode:
def __init__(self, x):
self.left = None
self.right = None
self.val = x

class Solution:
def __init__(self):
self.listHead = None
self.listTail = None
def Convert(self, pRootOfTree):
if pRootOfTree==None:
return
self.Convert(pRootOfTree.left)
if self.listHead==None: # if/else替换中序遍历存储值
self.listHead = pRootOfTree # if这一段只有中序遍历的第一个节点出现,即最左子树
self.listTail = pRootOfTree # 此时,链表头尾指针都指向中序第一个节点
else:
self.listTail.right = pRootOfTree # 尾指针与中序下一个节点互连,有right属性是因为上一步self.listTail和self.listHead已经指向pRootOfTree了
pRootOfTree.left = self.listTail
self.listTail = pRootOfTree # 尾指针指向中序下一个节点
self.Convert(pRootOfTree.right)
return self.listHead

翻转部分链表

题目:给定一个单链表的头指针 head, 以及两个整数 a 和 b下标,在单链表中反转 linked_list[a-b] 的结点,然后返回整个链表的头指针 。

思路:采用翻转单链表的思路,回顾一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
翻转单链表
思路很简单:1->2->3->4->5,遍历链表,把1的next置为None,2的next置为1,以此类推,5的next置为4。得到反转链表。
'''
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
if pHead==None or pHead.next==None:
return pHead
pre = None # 前指针
cur = pHead # 当前指针
while cur!=None:
tmp = cur.next # 记录下一个指针,为下一步当前指针后移准备
cur.next = pre
pre = cur
cur = tmp
return pre
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 翻转部分链表
class Solution:
def reverseBetween(self, head, m, n):
# 计算需要逆至的节点数
reverse_length = n - m + 1
pre_head = None # 初始化要记录的前驱节点
result = head # 最终转换后要返回的链表头结点

# 将head向后移动m-1个位置
while head and m > 1:
pre_head = head
head = head.next
m -= 1

# 记录翻转后的链表尾部,翻转后的尾巴即为当前head
modify_list_tail = head
pre = None # 前指针

# 逆置n - m + 1个节点
while head and reverse_length: #和翻转单链表一样,翻转后和第一、第三段是断开的
tmp = head.next
head.next = pre
pre = head
head = tmp
reverse_length -= 1

# 此时,尾巴为空, modify_list_tail指向最后一个非空元素
# 连接逆置后的链表尾与第三段的头结点结合,此时head已经指向第三段正序的头结点
modify_list_tail.next = head

# 如果pre_head不为空,说明不是从第一个节点开始逆至,即m>1
if pre_head is not None:
# pre_head指向第一段最后一个元素,连接逆序后的头结点
pre_head.next = pre #pre指向逆序后头结点,head为第三段的头结点
else:
# 此时m=1,则逆置后的头结点就是链表的头结点,即翻转整个单链表
result = pre
return result

链表插入排序(leetcode 147 Insertion Sort List)

题目:利用插入排序对链表进行排序

思路:1->3->2->4->null,将头结点和后面的部分断开,变成1->null和3->2->4->null,1->null看做是排好序的部分,添加的时候依次取后面的那部分的节点,比如在这里,先取3,然后对前面排好序的链表从前往后遍历,找到应该插入的位置即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""
Definition of ListNode
class ListNode(object):

def __init__(self, val, next=None):
self.val = val
self.next = next
"""


class Solution:
"""
@param: head: The first node of linked list.
@return: The head of linked list.
"""
def insertionSortList(self, head):
# write your code here
if head is None:
return None
if head.next is None:
return head
l=ListNode(-1)
while head:
node=l # 每次从头开始
fol=head.next # 保持下一个,防止断开
while node.next and node.next.val < head.val: # 插入到node和node.next之间
node = node.next
head.next = node.next # 先连接后面head-<node.next
node.next = head # 再连接前面
head = fol
return l.next

链表归并排序(leetcode 148 Sort List)

题目:要求我们用O(nlogn)算法对链表进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class ListNode(object):
def __init__(self, val, next=None):
self.val = val
self.next = next

class Solution:
# 归并法
def sortList(self, head):
# write your code here
if head is None or head.next is None:
return head
pre = head
slow = head # 使用快慢指针来确定中点
fast = head
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next

left = head
right = pre.next # 第二段头结点
pre.next = None # 从中间打断链表
left = self.sortList(left)
right = self.sortList(right)
return self.merge(left,right)

def merge(self, left, right): #合并两个有序链表
pre = ListNode(-1) # 新链表
first = pre # 新链表头结点
while left and right:
if left.val < right.val:
pre.next = left
pre = left
left = left.next
else:
pre.next = right
pre = right
right = right.next
if left:
pre.next = left
else:
pre.next = right
return first.next

两两交换链表中相邻的两个元素

题目:交换链表中相邻的两个元素。 注意第一个节点与第二个节点要交换位置,而第二个节点不用与第三个节点交换位置。 如要交换链表中A->B->C->D中的B和C需要做如下操作(交换B和C):

  • 将A指向C
  • 将B指向D
  • 将C指向B

思路:在头节点之前加一个假节点就可以使所有的交换都符合上面的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution(object):
def swapPairs(self, head):
dummy = ListNode(-1)
dummy.next = head # 假结点连接原链表
temp = dummy # 头结点
while temp.next and temp.next.next:
node1 = temp.next # node1是B
node2 = temp.next.next # node2是C
temp.next = node2 # A指向C
node1.next = node2.next # B指向D
node2.next = node1 # C指向B
temp = temp.next.next # 跳过两个
return dummy.next

判断两个链表相交和相交的第一个节点

思路1:链表两个链表的长度差diff,然后快指针先走diff步,然后快慢指针一起走。直到两个指针相同,否则无相交节点。(需要先遍历得到两个链表长度)

思路2:两个指针一起走,当一个指针p1走到终点时,说明p1所在的链表比较短,让p1指向另一个链表的头结点开始走,直到p2走到终点,让p2指向短的链表的头结点,那么,接下来两个指针要走的长度就一样了 ,然后就可以一起走,直到两个指针相同。(若无交点,可以一开始在链表尾巴的设置一个标志点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 思路二
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
if not pHead1 or not pHead2:
return None
p1, p2 = pHead1, pHead2
while p1 != p2:
p1 = pHead2 if not p1 else p1.next
p2 = pHead1 if not p2 else p2.next
return p1

分隔链表

题目:给定一个链表以及一个目标值,把小于该目标值的所有节点都移至链表的前端,大于或等于目标值的节点移至链表的尾端,同时要保持这两部分在原先链表中的相对位置。

思路:两个链表指针,一个负责收集比目标小的,一个收集大于等于目标的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution(object):
def partition(self, head, x):
dummy = ListNode(-1)
dummy.next = head
small_dummy = ListNode(-1)
large_dummy = ListNode(-1)

# small_prev和large_prev往后遍历增加,small_dummy和large_dummy则负责最后作为返回头结点
small_prev = small_dummy
large_prev = large_dummy
while dummy.next: # head第一个节点
curr = dummy.next
if curr.val < x:
small_prev.next = curr
small_prev = small_prev.next
else:
large_prev.next = curr
large_prev = large_prev.next
dummy = dummy.next

large_prev.next = None # 最后指针置为none
small_prev.next = large_dummy.next # large_dummy对应的是大链表的第一个数
return small_dummy.next # 返回的是small_dummy

重排链表

将单向链表L0→L1→…→Ln-1→Ln转化为L0→Ln→L1→Ln-1→L2→Ln-2→…的形式,也就是从头部取一个节点,从尾部取一个节点,直到将原链表转化成新的链表。

思路:

  1. 去中间节点,将链表分为两段.
  2. 翻转后一段
  3. 拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
def reorderList(self, head):
if not head:
return
# split {1,2,3,4,5} to {1,2,3}{4,5}
fast = slow = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
head1 = head
head2 = slow.next
slow.next = None
# reverse the second {4,5} to {5,4}
cur, pre = head2, None
while cur:
tmp = cur.next # 标记下一个
cur.next = pre
pre = cur
cur = tmp
# merge
cur1, cur2 = head1, pre
while cur2:
nex1, nex2 = cur1.next, cur2.next
cur1.next = cur2
cur2.next = nex1
cur1, cur2 = nex1, nex2

经典排序总结

发表于 2018-09-11 | 分类于 数据结构与算法
字数统计: 4.4k | 阅读时长 ≈ 17

先看一个排序算法可视化大概了解一下经典的排序算法。

排序算法可视化

冒泡排序 BubbleSort

介绍

冒泡排序(bubble sort):每个回合都从第一个元素开始和它后面的元素比较,如果比它后面的元素更大的话就交换,一直重复,直到这个元素到了它能到达的位置。每次遍历都将剩下的元素中最大的那个放到了序列的“最后”(除去了前面已经排好的那些元素)。注意检测是否已经完成了排序,如果已完成就可以退出了。

步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对第0个到第n-1个数据做同样的工作。这时,最大的数就“浮”到了数组最后的位置上。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

源代码

Python源代码(错误版本):

1
2
3
4
5
6
7
8
def bubble_sort(arry):
n = len(arry) #获得数组的长度
for i in range(n):
for j in range(i+1, n):
if arry[i] > arry[j] : #如果前者比后者大
arry[i],arry[j] = arry[j],arry[i] #则交换两者
return arry

注:上述代码是没有问题的,但是实现却不是冒泡排序,而是选择排序(原理见选择排序),注意冒泡排序的本质是“相邻元素”的顺序交换,而非每次完成一个最小数字的选定。

Python源代码(正确版本):

1
2
3
4
5
6
7
8
def bubble_sort(arry):
n = len(arry) #获得数组的长度
for i in range(n):
# 这里n-i有可能是最后的下标,如果用j和j+1会超过数组限制,所以应该用j-1和j,把 range改为(1,n-i)
for j in range(1, n-i): # 每轮找到最大数值,n-i因为前面已经确定i个最大值,只需比较剩下n-i个
if arry[j-1] > arry[j] : #如果前者比后者大
arry[j-1],arry[j] = arry[j],arry[j-1] #则交换两者
return arry

优化1:

某一趟遍历如果没有数据交换,则说明已经排好序了,因此不用再进行迭代了。用一个标记记录这个状态即可。

Python源代码:

1
2
3
4
5
6
7
8
9
10
11
12
def bubble_sort2(ary):
n = len(ary)
for i in range(n):
flag = False # 标记
for j in range(1, n - i):
if ary[j] < ary[j-1]:
ary[j], ary[j-1] = ary[j-1], ary[j]
flag = True
# 某一趟遍历如果没有数据交换,则说明已经排好序了,因此不用再进行迭代了
if not flag:
break
return ary

选择排序 SelectionSort

介绍

每个回合都选择出剩下的元素中最大的那个,选择的方法是首先默认第一元素是最大的,如果后面的元素比它大的话,那就更新剩下的最大的元素值,找到剩下元素中最大的之后将它放入到合适的位置就行了。和冒泡排序类似,只是找剩下的元素中最大的方式不同而已。

步骤

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 以此类推,直到所有元素均排序完毕。

源代码

1
2
3
4
5
6
7
8
9
def select_sort(ary):
n = len(ary)
for i in range(0,n):
min = i # 最小元素下标标记,这句是最重要的
for j in range(i+1,n):
if ary[j] < ary[min] :
min = j # 找到最小值的下标
ary[min],ary[i] = ary[i],ary[min] # 交换两者
return ary

插入排序 Insertion Sort

介绍

插入排序的工作原理是,对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 对具有nn个数据元素的序列进行排序时,插入排序需要进行n−1n−1趟插入。进行第j(1≥j≥n−1)j(1≥j≥n−1)趟插入时,前面已经有jj个元素排好序了,第jj趟将aj+1aj+1插入到已经排好序的序列中,这样既可使前面的j+1j+1个数据排好序。

步骤

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

源代码

1
2
3
4
5
6
7
8
9
10
11
# 插入排序
def insert_sort(ary):
n = len(ary)
for i in range(1, n):
pre_key = i - 1 # 往前的下标
mark = ary[i] # 记录当前元素 这两句很重要
while pre_key >= 0 and ary[pre_key] > mark: # 找到第一个比mark的小的元素或到头时结束
ary[pre_key+1] = ary[pre_key] # 往后移一位
pre_key -= 1
ary[pre_key+1] = mark # 找到并插入(想象一个简单的例子)
return ary

希尔排序 ShellSort

介绍

希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。希尔排序是非稳定排序算法。

希尔排序的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。

n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例

第一次 gap = 10/2 = 5

49 38 65 97 26 13 27 49 55 4

1A 1B 2A 2B 3A 3B 4A 4B 5A 5B

1A, 1B, 2A, 2B等为分组标记,数字相同的表示在同一组,同组进行直接插入排序

第二次 gap = 5 / 2 = 2,排序后

13 27 49 55 4 49 38 65 97 26

1A 1B 1C 1D 1E 2A 2B 2C 2D 2E

第三次 gap = 2 / 2 = 1

4 26 13 27 38 49 49 55 97 65

1A 1B 1C 1D 1E 1F 1G 1H 1I 1J

第四次 gap = 1 / 2 = 0 排序完成得到数组:

4 13 26 27 38 49 49 55 65 97

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def shell_sort(ary):
count = len(ary)
gap = round(count/2) # round精度不够可以考虑用math.floor()
# 双杠用于整除(向下取整),在python直接用 “/” 得到的永远是浮点数,用round()得到四舍五入值
while gap >= 1: # 不要忘了这句
for i in range(gap, count): # 前面的gap间隔数组视为已排好序,每次插入到排好序数组中
cur = ary[i]
preindex = i -gap # 往前的下标
while preindex >= 0 and ary[preindex] > cur: # 到这里与插入排序一样了
ary[preindex+gap] = ary[preindex] # 往后移
preindex -= gap
ary[preindex+gap] = cur # 插入
gap = round(gap/2)
return ary

归并排序 Merge Sort

介绍

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 归并排序
def merge_sort(self, ary):

if len(ary) <= 1:
return ary

median = int(len(ary)/2) # 二分分解
left = self.merge_sort(ary[:median]) # 先自调用,最里一层只有一个单元素
right = self.merge_sort(ary[median:])
return self.merge(left, right) # 合并成有序数组

def merge(self, left, right):
'''合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组'''
res = []
i = j = k = 0
while(i < len(left) and j < len(right)):
if left[i] < right[j]:
res.append(left[i])
i += 1
else:
res.append(right[j])
j += 1

res = res + left[i:] + right[j:]
return res

快速排序 QuickSort

介绍

快速排序是图灵奖得主C.R.A Hoare于1960年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法。分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题组合为原问题的解。

以一个数组作为示例,取区间第一个数为基准数。

0 1 2 3 4 5 6 7 8 9

72 6 57 88 60 42 83 73 48 85

初始时,i = 0; j = 9; X = a[i] = 72

由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j–;

数组变为:

0 1 2 3 4 5 6 7 8 9

48 6 57 88 60 42 83 73 88 85

i = 3; j = 7; X=72

再重复上面的步骤,先从后向前找,再从前向后找。

步骤

利用分治法可将快速排序分为三步:

  1. 从数列中挑出一个元素作为“基准”(pivot)。
  2. 分区过程,将比基准数大的放到右边,小于或等于它的数都放到左边。这个操作称为“分区操作”,分区操作结束后,基准元素所处的位置就是最终排序后它的位置
  3. 再对“基准”左右两边的子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

源代码

版本一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def quick_sort(self, ary):
return qsort(ary, 0, len(ary)-1)

def qsort(self, ary, start, end): # ary为原数组,其他为下标
if start < end: # 这句不能忘!!!
left = start
right = end
key = ary[start]
while left < right: # 这里都没有等号,left=right是最后key赋值
while left < right and ary[right] >= key: # 核心
right -= 1
if left < right: #说明打破while循环的原因是ary[right] <= key
ary[left] = ary[right] # 填坑
left += 1 # 换位继续
while left < right and ary[left] < key:
left += 1
if left < right: #说明打破while循环的原因是ary[left] >= key
ary[right] = ary[left]
right -= 1
ary[left] = key #此时,left=right,用key来填坑
self.qsort(ary, start, left-1) # 注意这里的下标顺序
self.qsort(ary, left+1, end)
return ary

版本二

1
2
3
4
5
6
7
8
9
10
11
12
13
def quickSort(array):
if len(array)<2:
return array
else:
base = array[0] # 元素,return时要变为列表
#小于等于基准值的元素组成的数组
less = [i for i in array[1:] if i<=base]
#大于基准值的元素组成的数组
greater = [i for i in array[1:] if i> base]
#将数组串起来
return quickSort(less)+[base]+quickSort(greater)

print(quickSort([45, 26, 34, 2, 888, 54, 23, 45, 76, 2]))

堆排序 HeapSort

介绍

堆排序在 top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是一维数组。堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

如下图,是一个堆和数组的相互关系,可看做堆的初始化

对于给定的某个结点的下标 i,可以很容易的计算出这个结点的父结点、孩子结点的下标:

  • Parent(i) = floor(i/2),i 的父节点下标
  • Left(i) = 2i,i 的左子节点下标
  • Right(i) = 2i + 1,i 的右子节点下标

二叉堆具有以下性质:

  1. 父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
  2. 每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)。

步骤

  1. 构造最大堆(Build_Max_Heap)自底向上:若数组下标范围为0~n,考虑到单独一个元素是大根堆,则从下标n/2开始的元素均为大根堆。于是只要从n/2-1开始(最后一个非叶子节点开始),分别与左孩子和右孩子比较大小,如果最大,则不用调整,否则和孩子中的值最大的一个交换位置,若交换之后还比此节点的孩子要小,继续向下交换(这里是自顶向下) 。并向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。
  2. 堆排序(HeapSort):由于堆是用数组模拟的。得到一个最大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是总是移除根节点(用最后一个元素来填补空缺),并做最大堆调整的递归运算。第一次将heap[0]与heap[n-1]交换,再对heap[0…n-2]做最大堆调整。第二次将heap[0]与heap[n-2]交换,再对heap[0…n-3]做最大堆调整。重复该操作直至heap[0]和heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。
  3. 最大堆调整(Max_Heapify):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整,使得子节点永远小于父节点 。

构造最大堆:先自底向上,再自顶向下

调整最大堆:交换之后,被交换的节点从顶向下调整,调完继续交换,依次递归。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def heap_sort(ary):
n = len(ary)
first = int(n/2-1) #最后一个非叶子节点
for start in range(first,-1,-1): #构建最大堆
max_heapify(ary,start,n-1)
# range(n-1,0,-1)因为0时不用和顶点自己交换
for end in range(n-1,0,-1): #堆排,将最大跟堆转换成有序数组,
ary[end],ary[0] = ary[0], ary[end] #将根节点元素与最后叶子节点进行互换,取出最大根节点元素,对剩余节点重新构建最大堆
max_heapify(ary,0,end-1) #因为end上面取的是n-1,故而这里直接放end-1,相当于忽略了最后最大根节点元素ary[n-1]
return ary


#最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary,start,end):
root = start
while True: # 记住这一句
child = root * 2 + 1 #调整节点的子节点,这里要注意数值下标从0开始,左节点为root * 2 + 1,这里都是下标表示
if child > end: # 左子树超过边界,右子树肯定也超了
break
if child + 1 <= end and ary[child] < ary[child+1]: #两个都没超,选数值大的下标
child = child + 1 #取较大的子节点
# 满足父节点比子节点小才叫交换
if ary[root] < ary[child]: # 子节点成为父节点;child为左子树或上一步较大的子节点
ary[root], ary[child] = ary[child], ary[root] #交换
root = child # 调整时候要自顶向下继续调整,不要忘了这一句
else:
break

时空复杂度总结

时间复杂度

"快些以O(nlog2n)O(nlog2n)的速度归队"

即快,希,归,堆都是O(nlog2n)O(nlog2n),其他都是O(n2)O(n2),基数排序例外,是O(d(n+rd))O(d(n+rd))

空间复杂度

  • 快排O(log2n)O(log2n)
  • 归并O(n)O(n)
  • 基数O(rd)O(rd)
  • 其他O(1)O(1)

稳定性

"心情不稳定,快些找一堆朋友聊天吧"

即不稳定的有:快,希,堆

其他性质

  • 直接插入排序,初始基本有序情况下,是O(n)O(n)
  • 冒泡排序,初始基本有序情况下,是O(n)O(n)
  • 快排在初始状态越差的情况下算法效果越好.
  • 堆排序适合记录数量比较大的时候,从n个记录中选择k个记录.
  • 经过一趟排序,元素可以在它最终的位置的有:交换类的(冒泡,快排),选择类的(简单选择,堆)
  • 比较次数与初始序列无关的是:简单选择与折半插入
  • 排序趟数与原始序列有关的是:交换类的(冒泡和快排)

剑指offer解题_Python版

发表于 2018-09-07 | 分类于 数据结构与算法
字数统计: 13.5k | 阅读时长 ≈ 60

1.二维数组中的查找

题目: 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路:遍历每一行,查找该元素是否在该行之中。

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
for i in array:
if target in i:
return True
return False

2.替换空格

题目: 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:利用字符串中的replace直接替换即可。

1
2
3
4
5
# -*- coding:utf-8 -*-
class Solution:
# s 源字符串
def replaceSpace(self, s):
return s.replace(' ','%20')

3.从尾到头打印链表

题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路:将链表中的值记录到list之中,然后进行翻转list。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None

class Solution:
# 返回从尾部到头部的列表值序列,例如[1,2,3]
def printListFromTailToHead(self, listNode):
l = list()
while listNode:
l.append(listNode.val)
listNode=listNode.next
return l[::-1]

4.重建二叉树(flag)

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

题解:首先前序遍历的第一个元素为二叉树的根结点,那么便能够在中序遍历之中找到根节点,那么在根结点左侧则是左子树;在根结点右侧,便是右子树。然后在递归遍历左子树和右子树。这里要注意一点,pre的左右子树分割长度与中序的左右子树分割长度一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回构造的TreeNode根节点
def reConstructBinaryTree(self, pre, tin):
if len(pre)==0:
return None
if len(pre)==1:
return TreeNode(pre[0])
else:
flag = TreeNode(pre[0])
#pre的左右子树分割长度与中序的左右子树分割长度一致。
flag.left = self.reConstructBinaryTree(pre[1:tin.index(pre[0])+1],tin[:tin.index(pre[0])])
flag.right = self.reConstructBinaryTree(pre[tin.index(pre[0])+1:],tin[tin.index(pre[0])+1:])
return flag

5.用两个栈实现队列(flag)

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

题解:申请两个栈Stack1和Stack2,Stack1当作输入,Stack2当作pop。当Stack2空的时候,将Stack1进行反转,并且输入到Stack2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
class Solution:
def __init__(self):
self.Stack1=[]
self.Stack2=[]
def push(self, node):
# write code here
self.Stack1.append(node)
def pop(self):
# return xx
if self.Stack2==[]:
while self.Stack1:
self.Stack2.append(self.Stack1.pop())
return self.Stack2.pop()
return self.Stack2.pop()

6.旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

1
2
3
4
5
6
7
# -*- coding:utf-8 -*-
class Solution:
def minNumberInRotateArray(self, rotateArray):
if not rotateArray:
return 0
else:
return min(rotateArray)

7.斐波那契数列(flag)

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。n<=39。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
if n==0:
return 0
if n==1 or n==2:
return 1
a,b=1,1
for i in range(n-2):
a,b=b,a+b
return b

8.跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

题解:ans[n]=ans[n-1]+ans[n-2]

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
if not number:
return 0
if number==1:
return 1
if number==2:
return 2
a,b=1,2
for i in range(number-2):
a,b=b,a+b
return b

9.变态跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

题解:ans[n]=ans[n-1]+ans[n-2]+ans[n-3]+…+ans[n-n],ans[n-1]=ans[n-2]+ans[n-3]+…+ans[n-n],ans[n]=2*ans[n-1]。

1
2
3
4
5
6
7
8
9
class Solution:
def jumpFloorII(self, number):
if not number:
return 0
if number==1:
return 1
if number==2:
return 2
return 2**(number-1)

10.矩形覆盖

题目:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

题解:新增加的小矩阵竖着放,则方法与n-1时相同,新增加的小矩阵横着放,则方法与n-2时相同,于是f(n)=f(n-1)+f(n-2)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
class Solution:
def rectCover(self, number):
# write code here
if not number:
return 0
if number==1:
return 1
if number==2:
return 2
a,b=1,2
for _ in range(number-2):
a,b=b,a+b
return b

11.二进制中1的个数(flag)

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

题解:每次进行右移一位,然后与1进行相与,如果是1则进行加1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 向右移1位可以看成除以2,向左移一位可以看成乘以2。移动n位可以看成乘以或者除以2的n次方。
# 负数原码(int整型用32位表示)所有位取反码然后+1得到补码;正数的补码为其自身
class Solution: #转为字符串
def NumberOf1(self, n):
# write code here
if n==0:
return 0
if n>0:
a=bin(n).count('1')
return a
else:
a=bin(2**32+n).count('1')
return a

class Solution: #每次移一位,看此为是否为1,负数的表示内部已经是补码了
def NumberOf1(self, n):
# write code here
count = 0
for i in range(32):
count += (n >> i) & 1
return count

12.数值的整次方

题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
class Solution:
def Power(self, base, exponent):
# write code here
ans=1
for i in range(0,abs(exponent)):
ans=ans*base
if exponent>0:
return ans
else:
return 1/ans

13.调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

题解:申请奇数数组和偶数数组,分别存放奇数值和偶数值,数组相加便为结果。

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding:utf-8 -*-
class Solution:
def reOrderArray(self, array):
array=list(array)
a=[]
b=[]
for i in array:
if i%2==1:
a.append(i)
else:
b.append(i)
return a+b

14.链表中倒数第K个节点

题目:输入一个链表,输出该链表中倒数第k个结点。

题解:快慢指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None

class Solution:
def FindKthToTail(self, head, k):
l=[]
count=0
if not head:
return None
a1=head
for i in range(k):
if not a1:
return None
else:
a1=a1.next
a2=head
while a1:
a1=a1.next
a2=a2.next
return a2

15.反转链表(flag)

题目:输入一个链表,反转链表后,输出新链表的表头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
if not pHead:
return None
if not pHead.next:
return pHead
Pre=None
Next=None
while pHead:
Next=pHead.next # 暂存当前节点的下一个节点信息
pHead.next=Pre # 断开链表, 反转节点, 这两句都是为了保护链表断开不丢失next的指向
Pre=pHead
pHead=Next
return Pre

16.合并两个排序的列表

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
if not pHead1 and not pHead2:
return None
if not pHead1 or not pHead2:
if not pHead1:
return pHead2
else:
return pHead1
merge=ListNode(0)# 新一个头结点数值为x的链表
p=merge #返回时的指向头结点的指针
while pHead1 and pHead2:
if pHead1.val<=pHead2.val:
merge.next=pHead1
pHead1=pHead1.next
else:
merge.next=pHead2
pHead2=pHead2.next
merge=merge.next
while pHead1:
merge.next=pHead1
pHead1=pHead1.next
merge=merge.next
while pHead2:
merge.next=pHead2
pHead2=pHead2.next
merge=merge.next
return p.next

17.树的子结构(flag)

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。

题解:递归;或者将树转变为中序序列,然后转变为str类型,最后判断是否包含。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def HasSubtree(self, pRoot1, pRoot2):
# write code here
if not pRoot1 or not pRoot2:
return False
root1 = pRoot1
root2 = pRoot2
result = False
if root1.val==root2.val: # HasSubtree条件出口,满足根节点相同才继续判断子树结构
result = self.hastree(root1,root2)
if not result:
result = self.HasSubtree(root1.left,root2)
if not result:
result = self.HasSubtree(root1.right,root2)
return result
def hastree(self,root1,root2):
if root2==None:
return True
if root1==None:
return False
if root1.val==root2.val:
return self.hastree(root1.left,root2.left) and self.hastree(root1.right,root2.right)
if root1.val!=root2.val:
return False

18.二叉树的镜像

题目: 操作给定的二叉树,将其变换为源二叉树的镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回镜像树的根节点
def Mirror(self, root):
if root:
root.left,root.right=root.right,root.left
self.Mirror(root.left)
self.Mirror(root.right)

19.顺时针打印矩阵(flag)

题目: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- coding:utf-8 -*-
class Solution:
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
rows=len(matrix)
cols=len(matrix[0])
l=[]
if rows==1 and cols==1:
return [matrix[0][0]]
left,right, up, down = 0,cols-1,0,rows-1 #这个是数组下标
while left<=right and up<=down:
for i in range(left,right+1): #range函数接收参数从小到大,大的数值不计入
l.append(matrix[up][i])
for j in range(up+1,down+1):
l.append(matrix[j][right])
if down-up>=1: #相等时为剩余单行
for k in range(left,right)[::-1]: #这里注意逆序的数组下标
l.append(matrix[down][k])
if right-left>=1: #相等时为剩余单列
for p in range(up+1,down)[::-1]:
l.append(matrix[p][left])
up=up+1
down=down-1
left=left+1
right=right-1
return l

20.包含Min函数的栈(flag)

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
class Solution:
def __init__(self):
self.stack=[]
self.minstack=[] #pop()删除列表的最后一个元素,[-1]获取最后一个元素
def push(self, node): #最小栈存储 整个原栈的最小元素,若最小元素在原栈删除,则也要删除最小栈
if not self.minstack or node<self.minstack[-1]:
self.minstack.append(node)
self.stack.append(node)
def pop(self):
if not self.stack:
return None
elif self.stack[-1]==self.minstack[-1]:
self.minstack.pop()
else:
self.stack.pop()
# write code here
def top(self):
return self.stack[-1]
# write code here
def min(self):
return self.minstack[-1]
# write code here

21.栈的压入弹出序列(flag)

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)。

题解:构建压入和活动栈,只有处于压入栈栈顶或者活动栈内才可弹出;或者新构建一个中间栈,来模拟栈的输入和栈的输出,比对输入结果和输出结果是否相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
class Solution:
def IsPopOrder(self, pushV, popV):
if not pushV:
return None
fixed=[] #压下去的辅助栈,处于栈顶可以出栈
left=pushV[:] #剩余的可活动栈,p元素位置之前的都要压入辅助栈
flag=True
for p in popV:
if p not in pushV: #还要判断pushV和popV元素不同的情况
return False
if p in left:
k=left.index(p)
fixed=fixed+left[:k+1]
left=left[k+1:]
fixed.pop()
elif fixed: #避免fixed[-1]越界
if p!=fixed[-1]:
return False
else:fixed.pop()
return flag

22.从上往下打印二叉树

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:层次遍历,用队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回从上到下每个节点值列表,例:[1,2,3]
def PrintFromTopToBottom(self, root):
# write code here
res=[]
value=[]
if not root:
return [] #返回空列表,而不是None
else:
res.append(root)
value.append(root.val)
while res:
p=res.pop(0) # pop(0)才是第一个元素
if p.left:
res.append(p.left)
value.append(p.left.val)
if p.right:
res.append(p.right)
value.append(p.right.val)
return value

23.二叉树的后续遍历序列(flag)

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:二叉搜索树的特性是所有左子树值都小于中节点,所有右子树的值都大于中节点,递归遍历左子树和右子树的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding:utf-8 -*-
# 后续遍历要满足 去除序列最后一个元素(根)后,将小于和大于这个元素直接分成两段,递归
class Solution:
def VerifySquenceOfBST(self, sequence):
# write code here
if not sequence:
return False
if len(sequence)==1:
return True
front=[]
back=[]
flag=0 # 辅助与分段的标记,第一个大于p元素之后的都放在back断
p=sequence.pop()
for i in sequence:
if i<=p and flag==0:
front.append(i)
else:
flag=1
back.append(i)
if front and max(front)>p: #front要满足所有元素都小于p
return False
if back and min(back)<p: #back要满足所有元素都大于p
return False
LEFT=True #递归出口
RIGHT=True
if front:
LEFT=self.VerifySquenceOfBST(front)
if back:
RIGHT=self.VerifySquenceOfBST(back)
return LEFT and RIGHT

24.二叉树中和为某一值的路径(flag)

题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
# write code here
# 这题不太会,记一下
if not root:#即是一开始条件语句,也是递归出口,若叶子节点不满足条件,left或者right返回空
return []
if root and not root.left and not root.right and root.val == expectNumber:
return [[root.val]] #递归出口,即叶子节点满足条件,不会执行以下任何语句;

res = [] #每次清空
# 先会一直先递归left,相当于深度优先搜索
left = self.FindPath(root.left, expectNumber-root.val) #left_left1_left11_right11_...right1
right = self.FindPath(root.right, expectNumber-root.val)
# 遍历后的操作
for i in left+right: #是指里面的元素,[[root.val]]得到的元素是[root.val]
res.append([root.val]+i)
return res

# 6 左图所示,left返回[[3]],right返回的res为[].append([2]+[1])=[[2,1]]
# 3 2 最后一层返回[[6,3],[6,2,1]]
# 1

25.复杂链表的复制(flag)

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。

思路:

1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面(暂不处理随机节点);

2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;

3、拆分链表,将链表拆分为原链表和复制后的链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# -*- coding:utf-8 -*-
class RandomListNode:
def __init__(self, x):
self.label = x
self.next = None
self.random = None

class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
# write code here
if not pHead:
return
pCur = pHead
while pCur:
node = RandomListNode(pCur.label)
node.next = pCur.next
pCur.next = node
pCur = node.next
pCur = pHead
while pCur:
if pCur.random:
pCur.next.random = pCur.random
pCur = pCur.next.next
pCur = pHead
cur = pHead.next
h = cur
while cur: #拆分
pCur.next = cur.next
if pCur.next:
cur.next = pCur.next.next
pCur = pCur.next
cur = cur.next
return h

26.二叉搜索树与双向列表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

思路:中序遍历,然后添加一个pre指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def __init__(self):
self.a=[]
def Convert(self, pRootOfTree):
# write code here

if pRootOfTree==None:
return
self.Convert(pRootOfTree.left)
self.a.append(pRootOfTree)
self.Convert(pRootOfTree.right)
for i in range(len(self.a)-1):
self.a[i].right=self.a[i+1]
self.a[i+1].left=self.a[i]
return self.a[0]

27.字符串的排列(flag)

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

思路:用itertools.permutations;或者通过将固定每一位的字符,然后进行和后面的每个字符进行交换,得到所有结果集。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
import itertools
class Solution:
def Permutation(self, ss):
if not ss:
return []
return sorted(list(set(map(''.join, itertools.permutations(ss)))))

# abc
# itertools.permutations(ss)输出('a','b','c') ('a','c','b')...等
# map函数后为abc acb..

28.数组中出现次数超过一般的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0

题解:判断是否有超过一半的元素,如果有则在数组中间的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
class Solution:
def MoreThanHalfNum_Solution(self, numbers):
# write code here
if not numbers:
return 0
if len(numbers)==1:
return numbers[0]
l=len(numbers)//2
k=sorted(list(numbers)) #排序,满足条件处于中间位置的为最多元素
count=0 #不满足条件则遍历判断 此元素maxcount是否超过一半
maxcount=0
for i in range(len(k)-1):
if k[i]==k[i+1]:
count=count+1
if count>maxcount:
maxcount=count
else:
count=0
if maxcount>=l:
return k[l]
else:
return 0

29.最小的K个数

题目:输入n个整数,找出其中最小的K个数,例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

1
2
3
4
5
6
7
8
# -*- coding:utf-8 -*-
class Solution:
def GetLeastNumbers_Solution(self, tinput, k):
# write code here
if len(tinput)<k:
return []
res=sorted(tinput)
return res[:k]

30.连续子数组的最大和(flag)

题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding:utf-8 -*-
class Solution:
def FindGreatestSumOfSubArray(self, array):
# write code here 只需要返回最大数,下标不管
# 思路:创建一个列表储存要加入的元素,当累积和小于0则清空前面一段
# 另外一个更简单的思路PART2
if len(array)==1:
return array[0]
if max(array)<=0: #全是负数
return max(array)
if min(array)>=0:
return sum(array)
cum_max=0
maxnum=0
res=[]
for k, i in enumerate(array):
if cum_max+i>=0 and max(array[k:])>0: # 注意一点,当前数组后面全是负数,不能再加了
res.append(i)
cum_max=cum_max+i
else:
#curmax=0 #此时可以记下标,这里不要求
cum_max=0
if sum(res)>maxnum:
maxnum=sum(res)
res=[]
return max(maxnum,sum(res)) #sum(res)是最后数组res一直加入元素而没有更新maxnum

# ======================part2:==========================
# class Solution:
# def FindGreatestSumOfSubArray(self, array):
# maxnum= float(-inf)
# cum_max= 0
# for i in array:
# if cum_max+i<0:
# cum_max=i #这一句是精华,什么都不作处理,下一个值当做cum_max
# else:
# cum_max=cum_max+i
# if cum_max>maxnum:
# maxnum=cum_max
# return maxnum

31.整数中1出现的次数

题目:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)

1
2
3
4
5
6
7
8
# -*- coding:utf-8 -*-
class Solution:
def NumberOf1Between1AndN_Solution(self, n):
# write code here
count=0
for i in range(1+n):
count=count+str(i).count('1')
return count

32.把数组排成最小的数(flag)

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路:将数组转换成字符串之后,进行两两比较字符串的大小,比如3,32的大小由332和323确定,即3+32和32+3确定。

1
2
3
4
5
6
7
8
9
10
# -*- coding:utf-8 -*-
class Solution:
def PrintMinNumber(self, numbers):
# write code here
if not numbers:
return ''
cmp_def = lambda x1,x2: int(str(x1)+str(x2))-int(str(x2)+str(x1))
a=sorted(numbers,cmp=cmp_def) #sortef创建副本,sort原地
b=list(map(lambda x:str(x),a)) #列表数字转字符串
return ''.join(b) #jion反馈一个字符串

33.丑数(flag)

题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路:每一个丑数必然是由之前的某个丑数与2,3或5的乘积得到的,这样下一个丑数就用之前的丑数分别乘以2,3,5,找出这三这种最小的并且大于当前最大丑数的值,即为下一个要求的丑数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding:utf-8 -*-
class Solution:
def GetUglyNumber_Solution(self, index):
# write code herei
# 思路,创建一个数组存储丑数;创建三个丑数数组独立下标对应乘以235,最小为当前丑数
if index==0:
return 0
if index==1:
return 1
uglyarr=[1]
a2,a3,a5=0,0,0
for i in range(index-1):
n1,n2,n3=uglyarr[a2]*2,uglyarr[a3]*3,uglyarr[a5]*5
min_num=min(n1,n2,n3)
uglyarr.append(min_num)
if min_num==n1:
a2=a2+1
if min_num==n2:
a3=a3+1
if min_num==n3:
a5=a5+1
return uglyarr[-1]

34.第一个只出现一次的字符

题目:在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1。

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
class Solution:
def FirstNotRepeatingChar(self, s):
# write code here
ss=list(s)
for i,e in enumerate(ss):
if s.count(e)==1:
return i
return -1

35.数组中的逆序对(Flag)

题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。

输入描述:题目保证输入的数组中没有的相同的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
'''
/*
归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面数组array[i]~array[mid]都是大于array[j]的,
count += mid+1 - i参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
*/
'''
# -*- coding:utf-8 -*-
class Solution:
def InversePairs(self, data):
# write code here
return self.inverseCount(data[:], 0, len(data)-1, data[:])%1000000007

def inverseCount(self, tmp, start, end, data):
if end-start <1:
return 0
if end - start == 1:
if data[start]<=data[end]:
return 0
mid = (start+end)//2
cnt = self.inverseCount(data, start, mid, tmp) + self.inverseCount(data, mid+1, end, tmp)
# print(start, mid, end, cnt, data)
i = start
j = mid + 1
ind = start # 用于tmp的下标

while(i <= mid and j <= end): # tmp排序
if data[i] <= data[j]:
tmp[ind] = data[i]
i += 1
else:
tmp[ind] = data[j]
cnt += mid - i + 1
j += 1
ind += 1
while(i<=mid):
tmp[ind] = data[i]
i += 1
ind += 1
while(j<=end):
tmp[ind] = data[j]
j += 1
ind += 1
return cnt

36.两个链表的第一个公共节点

题目:输入两个链表,找出它们的第一个公共结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

#有些题的输入指针,没有val属性,只有next

class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
if not pHead1 or not pHead2:
return None
while pHead1:
p2=pHead2
while p2:
if pHead1==p2:
return pHead1
p2=p2.next
pHead1=pHead1.next
return None

37.数字在排序数组中出现的次数

题目:统计一个数字在排序数组中出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
class Solution:
def GetNumberOfK(self, data, k):
# write code here
count=0
flag=1
if not data:
return 0
for i in data:
if i==k:
count=count+1
flag=1 #设置处在相同阶段的标志
if count>0 and flag!=1: #不在相同阶段就break
break
return count

38.二叉树的深度(flag)

题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def TreeDepth(self, pRoot):
# write code here
if not pRoot:
return 0
left=self.TreeDepth(pRoot.left) #操作放在后面,想象后序遍历
right=self.TreeDepth(pRoot.right)
return max(left,right)+1

39.平衡二叉树(flag)

题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。

题解:平衡二叉树是左右子数的距离不能大于1,因此递归左右子树,判断子树距离是否大于1。用Maxdeep递归求深度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def IsBalanced_Solution(self, pRoot):
# write code here
# 递归且有一个实现求深度的递归函数
if not pRoot:
return True
if abs(self.Maxdeep(pRoot.left)-self.Maxdeep(pRoot.right))>1:
return False
return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)
def Maxdeep(self,pRoot):
if not pRoot:
return 0
left = self.Maxdeep(pRoot.left)
right = self.Maxdeep(pRoot.right)
return max(left,right)+1

40.数组中只出现一次的数字

题目:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

题解:转为字符串;或者将数组中数转到set之中,然后利用dict存储每个数字出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- coding:utf-8 -*-
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
if not array:
return None
if len(array)==1:
return array[0]
l=[]
arr_str_list=list(map(lambda x:str(x),array))
arr_str=''.join(arr_str_list) #输入要为字符列表
for i,e in enumerate(arr_str_list):
if arr_str.count(e)==1:
l.append(i)
ll=[]
for i in l:
ll.append(array[i])
return ll
# ====================================
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
arrayset=set(array)
dict={}
for num in arrayset:
dict[num]=0
for i in range(0,len(array)):
dict[array[i]]=dict[array[i]]+1
ans=[]
for num in arrayset:
if dict[num]==1:
ans.append(num)
return ans

41.和为S的连续正整数序列

题目:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding:utf-8 -*-
class Solution:
def FindContinuousSequence(self, tsum):
# write code here
#若是100,只需要从1:50找就可以了,+2是考虑到下标0开始,让搜索范围大一些
bitsum=tsum//2+2
ori=list(range(1,bitsum))
res=[]
for i in range(1,bitsum):
l=[]
summ=i #累积总和
for j in range(i+1,bitsum):
summ=summ+j
if summ==tsum:
l=ori[i-1:j] #i,j看做下标返回满足条件的数组
if summ>tsum:
break
if l:
res.append(l)
return res

42.和为S的两个数字(flag)

题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述:对应每个测试案例,输出两个数,小的先输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding:utf-8 -*-
class Solution:
def FindNumbersWithSum(self, array, tsum):
# write code here
# 返回列表类型的,若不满足条件则[]
if len(array)<=1 or min(array)>tsum:
return []
res=[]
for i in range(len(array)): # 都是索引下标循环
for j in range(i+1,len(array)):
if array[i]+array[j]==tsum:
# 从头开始搜索这时候得到的应该是乘积最小的,可以直接退出外层循环
res.append(array[i])
res.append(array[j])
break
if array[j]>tsum:
break
else: # 上面循环正常结束才会执行,若上面循环执行break则这条语句不执行
continue
break

if res:
return res
else:
return []

43.左旋字符子串

题目:汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
class Solution:
def LeftRotateString(self, s, n):
# write code here
if len(s)<n or not s:
return ''
if n==0:
return s
return s[n:]+s[:n]

44.反转单词顺序(flag)

题目:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

1
2
3
4
5
6
# -*- coding:utf-8 -*-
class Solution:
def ReverseSentence(self, s):
# write code here
ss=s.split(' ')
return ' '.join(ss[::-1])

45.扑克牌顺序

题目:LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding:utf-8 -*-
class Solution:
def IsContinuous(self, numbers):
# write code here
# 只需要判断没有0的数组里数值是唯一且max-min<5即可
if len(numbers)<5 and not numbers:
return False
grost_num=0
without_gro=[]
for i in numbers:
if i==0:
grost_num+=1
elif i in without_gro and i!=0: #唯一
return False
else:
without_gro.append(i)
if max(without_gro)-min(without_gro)<5:
return True
else:
return False

46.孩子们的圈圈(圈圈中最后剩下的数)(flag)

题目:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
class Solution:
def LastRemaining_Solution(self, n, m):
# write code here
# flag=0
# 把index想象为连续拼接数组
if not n or not m:
return -1
num = list(range(n))
index = 0
while len(num)>1: # 剩余两个都要继续执行
index=(index+m-1)%len(num) #index为上一次停的地方,加上m-1为重新第m-1个出列
num.pop(index)
return num[0]

47.求1+2+3+…+n

题目:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
class Solution:
def Sum_Solution(self, n):
# write code here
if not n:
return None
if n==1:
return 1
return n+self.Sum_Solution(n-1)

48.不用加减乘除做加法

题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

思路:二进制异或进位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding:utf-8 -*-
class Solution:
def Add(self, num1, num2):
# write code here
# 位操作不懂
return sum([num1,num2])

# ============================
'''
首先看十进制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算进位,得到2。
第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。

第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。

同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。

第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。

第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。
'''
class Solution:
def Add(self, num1, num2):
# write code here
while num2!=0:
sum=num1^num2
carry=(num1&num2)<<1
num1=sum
num2=carry
return num1

49.把字符串转换成整数

题目:将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述:输入一个字符串,包括数字字母符号,可以为空输出描述:如果是合法的数值表达则返回该数字,否则返回0。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
class Solution:
def StrToInt(self, s):
# write code here
l=[s]
try:
num=list(map(eval,l))
except Exception as e:
return 0
else:
return num[0]

50.数组中重复的数字

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

思路:利用dict计算重复数字。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
for i in numbers:
if numbers.count(i)>1:
duplication[0]=i
return True
return False

51.构建乘积数组

题目:给定数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1];其中B中的元素B[i]=A[0]A[1]…A[i-1]A[i+1]…A[n-1]。不能使用除法。

注意:没有A[i]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- coding:utf-8 -*-
class Solution:
def multiply(self, A):
# write code here
if not A:
return None
if len(A)==1:
return None
B=[None]*len(A)
for i,e in enumerate(A):
cumpower=1
for ii,ee in enumerate(A):
if ii!=i:
cumpower=cumpower*ee
B[i]=cumpower
return B

52.正则表达式匹配(flag)

题目:请实现一个函数用来匹配包括’ , ‘和’ * ‘的正则表达式。模式中的字符’ , ‘表示任意一个字符,而’ * ‘表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”abaca”匹配,但是与”aa.a”和”ab*a”均不匹配。

思路:

当模式中的第二个字符不是 *时:

  • 如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。
  • 如果字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。

当模式中的第二个字符是 *时:

  • 如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。
  • 如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式。
    • 模式后移2字符,相当于 x被忽略。即模式串中与他前面的字符和字符串匹配0次。
    • 字符串后移1字符,模式后移2字符。即模式串中*与他前面的字符和字符串匹配1次。
    • 字符串后移1字符,模式不变,即继续匹配字符下一位,因为 可以匹配多位。即模式串中与他前面的字符和字符串匹配多次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding:utf-8 -*-
class Solution:
# s, pattern都是字符串
# flag=0
def match(self, s, pattern):
# write code here
if (len(s) == 0 and len(pattern) == 0):
return True
if (len(s) > 0 and len(pattern) == 0):
return False
if (len(pattern) > 1 and pattern[1] == '*'):
if (len(s) > 0 and (s[0] == pattern[0] or pattern[0] == '.')):
return (self.match(s, pattern[2:]) or self.match(s[1:], pattern[2:]) or self.match(s[1:], pattern))
else:
return self.match(s, pattern[2:])
if (len(s) > 0 and (pattern[0] == '.' or pattern[0] == s[0])):
return self.match(s[1:], pattern[1:])
return False

53.表示数值的字符串

题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串”+100”,”5e2”,”-123”,”3.1416”和”-1E-16”都表示数值。 但是”12e”,”1a3.14”,”1.2.3”,”+-5”和”12e+4.3”都不是。

1
2
3
4
5
6
7
8
9
10
11
# -*- coding:utf-8 -*-
class Solution:
# s字符串
def isNumeric(self, s):
# write code here
l=[s]
try:
b=float(s)
return True
except Exception as e:
return False

54.字符流中第一个不重复的字符(flag)

题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个字符“google”时,第一个只出现一次的字符是”l”。

输出描述:如果当前字符流没有存在出现一次的字符,返回#字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding:utf-8 -*-
# flag=0
class Solution:
# 返回对应char
def __init__(self):
self.s=''
self.dict={}
def FirstAppearingOnce(self):
for i in self.s:
if self.dict[i]==1:
return i
return '#'
# write code here
def Insert(self, char):
# write code here
self.s+=char
if char in self.dict:
self.dict[char]+=1
else:
self.dict[char]=1

55.链表中环的入口节点(flag)

题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路:第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1==p2找到在环中的相汇点。第二步,找环的入口。接上步,当p1==p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点,p2比p1多走一圈有2x=n+x;n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口:直线+小段环=整环,故p1再走整环-小段环到达起点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
fast,slow=pHead,pHead
if fast and fast.next:
fast=fast.next.next
slow=slow.next
while fast and fast.next and fast!=slow:
fast=fast.next.next
slow=slow.next
if not fast.next:
return None
fast=pHead
while fast!=slow:
fast=fast.next
slow=slow.next
return fast

56.删除链表中重复的节点(flag)

题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留(全部删除),返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def deleteDuplication(self, pHead):
# write code here
result = ListNode(0)
res = result
tmp = pHead
while tmp and tmp.next:
if tmp.val == tmp.next.val:
while tmp.next and tmp.val == tmp.next.val:
tmp = tmp.next
else:
res.next = tmp #把整个tmp之后的链表都接上去了
res = res.next
tmp = tmp.next
res.next = tmp
return result.next

# 一开始res为{-1},1和2不同,res.next = tmp得到{-1,1,2,3,3,4,4,5},res和tmp指针往下
# 第二次2和3不同,res.next = tmp得到{-1,1,+,2,3,3,4,4,5},+号前为res指针位置
# 第三次3和3相同,tmp指针到4的位置,下一次res.next=tmp得到{-1,1,2,+,4,4,5}

57. 二叉树中的下一个节点

题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:分析二叉树的下一个节点,一共有以下情况:1.二叉树为空,则返回空;2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;3.节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding:utf-8 -*-
class TreeLinkNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
self.next = None
class Solution:
def GetNext(self, pNode):
# write code here
if not pNode:
return None
if pNode.right:
pNode=pNode.right
while pNode.left:
pNode=pNode.left
return pNode

else: #存在该节点在父节点右边,且一直递归,这时要找爷爷节点 型如"\"
while pNode.next:
if pNode.next.left==pNode:
return pNode.next
else:
pNode=pNode.next
return None

58.对称的二叉树(flag)

题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路:采用递归的方法来判断两数是否相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
# 设计一个递归求issame的函数,issame(root1.left,root2.right)
class Solution:
def isSymmetrical(self, pRoot):
if not pRoot:
return True
if not pRoot.left and not pRoot.right:
return True
if pRoot.left and pRoot.right:
return self.issame(pRoot.left,pRoot.right)
else:
return False
# write code here
def issame(self, root1, root2):
if not root1 and not root2:
return True
if root1 and root2:
if root1.val==root2.val:
return self.issame(root1.left,root2.right) and self.issame(root2.left,root1.right)
else:
return False

59.按之字形顺序打印二叉树(flag)

题目:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None

class Solution:
def Print(self, pRoot):
# write code here
res=[]
if not pRoot:
return []
level=[pRoot]
reverseflag=False
while level:
levelvalue=[]
nextlevel=[]
for i in level:
levelvalue.append(i.val)
if i.left:
nextlevel.append(i.left)
if i.right:
nextlevel.append(i.right)
if reverseflag:
levelvalue.reverse()
if levelvalue:
res.append(levelvalue)
# for j in levelvalue:
# res.append(j)
reverseflag = not reverseflag
level=nextlevel
return res

60.把二叉树打印成多行

题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
from collections import deque
class Solution:
# 返回二维列表[[1,2],[4,5]]
def Print(self, pRoot):
# write code here
if not pRoot:
return []
res=[]
level = [pRoot]
while level:
levelvalue=[]
nextlevel=[]
for i in level:
levelvalue.append(i.val)
if i.left:
nextlevel.append(i.left)
if i.right:
nextlevel.append(i.right)
if levelvalue:
res.append(levelvalue)
level = nextlevel
return res

61.序列化二叉树(flag)

题目:请实现两个函数,分别用来序列化和反序列化二叉树。

思路:转变成前序遍历,空元素利用”#”代替,然后进行解序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
#序列化指的是遍历二叉树为字符串;所谓反序列化指的是依据字符串重新构造成二叉树。
#当在遍历二叉树时碰到Null指针时,这些Null指针被序列化为一个特殊的字符“#”,结点之间的数值用逗号隔开。
class Solution:
def Serialize(self, root):
def Pre_Order(root):
if root:
result.append(str(root.val))
Pre_Order(root.left)
Pre_Order(root.right)
else:
result.append('#')
result = []
Pre_Order(root)
return ','.join(result)

# write code here
def Deserialize(self, s):
s = s.split(',')

def Change(num):
num[0] += 1
if num[0] < len(s):
if s[num[0]] == '#':
return None
root = TreeNode(int(s[num[0]]))
root.left = Change(num)
root.right = Change(num)
return root
else:
return None
num = [-1]
return Change(num)

62.二叉搜索树中的第K个节点

题目:给定一棵二叉搜索树,请找出其中的第k小的结点。例如(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。

思路:中序遍历后,返回第K个节点值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回对应节点TreeNode
def __init__(self):
self.res = []
def KthNode(self, pRoot, k):
# write code here
if not pRoot:
return None
#pnode=pRoot # 为啥要加这一句呢?
self.sorttree(pRoot)
if len(self.res)<k or k<=0:
return None
else:
return self.res[k-1]

def sorttree(self,pRoot): # 中序排序
if not pRoot:
return []
left = self.sorttree(pRoot.left)
self.res.append(pRoot)
right = self.sorttree(pRoot.right)

63.数据流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
class Solution:
def __init__(self):
self.arr=[]
def Insert(self, num):
# write code here
self.arr.append(num)
def GetMedian(self,arr): #为啥这里要加 arr 作为输入,换为其他参数也行,必须要占位?
# write code here
res=sorted(self.arr)
if len(res)%2==0:
return (res[len(res)//2-1]+res[len(res)//2])/2.0
else:
return res[len(res)//2]

64.滑动窗口的最大值

题目:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1},{2,3,4,2,6,[2,5,1]}。

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding:utf-8 -*-
class Solution:
def maxInWindows(self, num, size):
# write code here
if size<=0:
return []
if size==1:
return num
res=[]
for i in range(len(num)-size+1):
res.append(max(num[i:i+size]))
return res

65.矩阵中的路径(flag)

题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

思路:当起点第一个字符相同时,开始进行递归搜索,设计搜索函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding:utf-8 -*-
class Solution:
def hasPath(self, matrix, rows, cols, path):
# write code here
for i in range(rows):
for j in range(cols):
if matrix[i*cols+j]==path[0]:
if self.findpath(list(matrix),rows,cols,path[1:],i,j):
return True
return False
def findpath(self, matrix, rows, cols, path ,i ,j):
if not path:
return True
matrix[i*cols+j]='0'
if j+1<cols and matrix[i*cols+j+1]==path[0]: #j+1<cols 这里是下标,不能等于
return self.findpath(matrix, rows, cols, path[1:],i,j+1)
elif j-1>=0 and matrix[i*cols+j-1]==path[0]:
return self.findpath(matrix, rows, cols, path[1:],i,j-1)
elif i+1<rows and matrix[(i+1)*cols+j]==path[0]:
return self.findpath(matrix, rows, cols, path[1:],i+1,j)
elif i-1>=0 and matrix[(i-1)*cols+j]==path[0]:
return self.findpath(matrix, rows, cols, path[1:],i-1,j)
else:
return False

66.机器人的运动范围

题目:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding:utf-8 -*-
class Solution:
def movingCount(self, threshold, rows, cols):
# write code here
# 回溯的出口 : 所有self.findgrid回溯出口的不满足if条件就停止
matrix = [[0 for i in range(cols)] for j in range(rows)]
count = self.findgrid(threshold,rows,cols,matrix,0,0)
return count

def findgrid(self,threshold,rows,cols,matrix,i,j):
count = 0
if 0<=i<rows and 0<=j<cols and (sum(map(int, str(i) + str(j))) <= threshold) and matrix[i][j]==0:
matrix[i][j] = 1
count = 1 + self.findgrid(threshold,rows,cols,matrix,i,j-1)\
+ self.findgrid(threshold,rows,cols,matrix,i,j+1)\
+ self.findgrid(threshold,rows,cols,matrix,i+1,j)\
+ self.findgrid(threshold,rows,cols,matrix,i-1,j)
return count
<i class="fa fa-angle-left"></i>123…6<i class="fa fa-angle-right"></i>

60 日志
10 分类
73 标签
RSS
GitHub E-Mail
Recommended reading
  • Wepillo
  • Wulc
  • Pinard
  • Donche
  • XFT
  • Seawaylee
© 2018 — 2024 Mosbyllc