2019/9/29:实现 Trie (前缀树)
题目链接:https://leetcode-cn.com/problems/implement-trie-prefix-tree/
看题目意思,是让我构建一个trie前缀树,我找到以下两篇比较好的资料,第一篇介绍的比较全面,从历史到所有的形式变化,第二篇就是针对trie树和hash的区别与应用场景来分析利弊,然后我发现这里要求写的算是hash trie吧。
可变数组取词速度太慢,于是就有人想起用一组键值对(Java中可用HashMap类型,Python 中为 dict 类型,C#为Dictionary类型)代替可变数组:其中每个节点包含一组 Key-Value,每个 Key 对应该节点下的一个子节点字符,value 则指向相应的后一个状态。这种方式可以有效的减少空间浪费,同时由于键值对本质上就是一个哈希实现,因此理论上其查词效率也很高(理想状态下取词复杂度为O(1))。
但是哈希有的缺点,这种实现的 Trie 树也会有:
- 为了尽可能的避免键值冲突,哈希表需要额外的空间避开碰撞,因此仍有一部分的空间会被浪费;
- 哈希表很难做到完美,尤其是数据体量增大之后,其查词复杂度常常难以维持在O(1),同时,对哈希值的计算也需要额外的时间,因此实际查询效率要比经典实现低,其具体复杂度由相应的哈希实现来定。
与数组和可变数组实现相比,这种实现做到了空间和时间上的一种平衡,这个结果并不意外,因为哈希表本身就是平衡数组(查寻迅速、增删悲剧)和可变数组(增删迅速,查询悲剧)相应优点和缺点的一种数据结构。
总体而言,Hash Trie 结构简单,性能堪用,而且由于哈希实现可以为每个节点分配唯一的id,因此可以做到节点的实时动态添加(这点是非常大的优势)因此对于中小规模的词典或者对词典的实时更新有需求的应用,该实现非常适合。
class Trie(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.d = {}
def insert(self, word):
"""
Inserts a word into the trie.
:type word: str
:rtype: None
"""
t = self.d
for c in word:
if c not in t:
t[c] = {}
t = t[c] # 字典迭代
t["end"] = True # 标记单词终点
def search(self, word):
"""
Returns if the word is in the trie.
:type word: str
:rtype: bool
"""
t = self.d
for c in word:
if c not in t:
return False
t = t[c]
return "end" in t
def startsWith(self, prefix):
"""
Returns if there is any word in the trie that starts with the given prefix.
:type prefix: str
:rtype: bool
"""
t = self.d
for c in prefix:
if c not in t:
return False
t = t[c]
return True
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
2019/10/4:Fizz Buzz
题目链接:https://leetcode-cn.com/problems/fizz-buzz/
可以直接写出代码:
class Solution(object):
def fizzBuzz(self, n):
"""
:type n: int
:rtype: List[str]
"""
a = []
for i in range(1,n+1):
# print(i)
if i % 3 == 0 and i % 5 == 0:
a.append("FizzBuzz")
elif i % 3 == 0:
a.append("Fizz")
elif i % 5 == 0:
a.append("Buzz")
else:
a.append(str(i))
return a
class Solution:
def fizzBuzz(self, n):
return ['Fizz' * (not i % 3) + 'Buzz' * (not i % 5) or str(i) for i in range(1, n+1)]
2019/10/5:把二叉搜索树转换为累加树
题目链接:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/
关于二叉搜索树,我之前在第三篇中有刷到过找二叉搜索树的最近节点,记得当时清楚概念后发现代码比较简单,然而现在又忘了啥概念来着,再次引用一下:
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
(4)没有键值相等的节点。
然后就可以发现,只要依据中序遍历的方式,就能得到比自己节点大的总和:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def __init__(self):
self.now = 0
def convertBST(self, root: TreeNode) -> TreeNode:
if root == None:
return None
self.convertBST(root.right)
self.now += root.val
root.val = self.now
self.convertBST(root.left)
return root
这里我发现了一个神奇的事情,如果不在init方法中定义new,而是在下面设置,那么得到的结果只是中序遍历后的子树,而没有相加。然后百度了一下,回顾了一下变量间的区别:
1:成员变量:变量电议部分所定义的变量被称为累的成员变量。也就是说成员变量在整个类中都有效,类中的方法可以直接调用成员变量使用。
然而成员变量又分为实例成员变量(简称实例变量)和类变量(简称静态变量)
1.1:实例变量:就是我们正常定义的变量,比如int a; a就是实例变量
1.2:静态变量:静态变量定义前要加上static 比如static int a;这个a就是静态变量,当在变量定义前加上static的时候就代表着该变量在使用的时候有一处改变则各个用到这个变量的地方,该变量都发生改变,就是所谓的一处改变处处改变,静态变量的生存期为整个源程序,但是只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
2:局部变量:在方法体中定义的变量和方法的参数称局部变量。也就是说局部变量只在定义它的方法内有效,而方法外部的其他方法无法使用局部变量。当局部变量名字与成员变量名字相同,则成员变量被隐藏,即这个成员变量在这个方法内暂时失效,以局部变量定义的为准。
那这里算是局部变量和成员变量间的区别了,然后找到了这篇帖子,大致懂了,明天在实验一下:
2019/10/6:至少有K个重复字符的最长子串
题目链接:https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters/
这题刚开始看完感觉就是按照k的大小,取一个重复最多的数字,然后我就用count去统计了下给定字符串的数值,再将大于等于k的数全部取出来求和,然后通过了13个测试用例,看到14个s = "ababacb"的时候返回0发现想错了。。
正确思路应该是分治法根据k的值对字符串进行分割,然后统计大于k的子串相加得到结果:
class Solution(object):
def longestSubstring(self, s, k):
if not s:
return 0
for c in set(s):
if s.count(c) < k:
return max(self.longestSubstring(t, k) for t in s.split(c))
return len(s)
2019/10/6:每日温度
题目链接:https://leetcode-cn.com/problems/daily-temperatures/
这题其实是需要用栈的概念,刚开始我看题目并没有看懂新生成的列表到底怎么来的,因为正着看没有一点规律可言,而如果倒过来的话发现,这就是模拟栈的取出操作,所以可以写出代码:
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
out_l = [0] * len(T)
for i in range(len(T) - 2, -1, -1):
if T[i] < T[i + 1]:
out_l[i] = 1
elif T[i] == T[i + 1]:
out_l[i] = out_l[i + 1] + 1 if out_l[i + 1] != 0 else 0
else:
n = i + 1
while out_l[n] != 0 and T[i] >= T[n + out_l[n]]:
n += out_l[n]
if out_l[n] == 0:
out_l[i] = out_l[n]
else:
out_l[i] = n + out_l[n] - i
return out_l
2019/10/7:最短无序连续子数组
题目链接:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray
因为要求的是最短无序子数组,我开始的想法,不妨先对这个数组进行排序,然后按照有序数组和无序的比较,从开头和结尾开始遍历,记录索引值,也可以说双指针,发现不同的时候结束,那么那个位置就可能是最长子数组的开始和结束。
代码为:
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
# 双指针
num1 = sorted(nums)
n = len(nums)
if num1 == nums: return 0
for i,num in enumerate(nums):
if num1[i] != num: break
for j in range(n - 1,-1,-1):
if num1[j] != nums[j]: break
return j - i + 1