【LeetCode】力扣刷题之路
题目(20240325~20240329)
141. 环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true。 否则,返回 false。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是 [0, 104]
- -105 <= Node.val <= 105
- pos 为 -1 或者链表中的一个有效索引。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
知识点回顾
环形链表:一种链表的尾结点指向头结点或者其他结点的特殊结构。
哈希表(Hash Table):也称为散列表,是一种根据关键码值直接进行访问的数据结构。通过散列函数将关键码值映射到表中一个特定的位置,以便快速访问记录。散列函数是用于映射的函数,而存放记录的数组称为散列表或哈希表。在哈希表中,记录的存储位置是由散列函数计算得出的,即存储位置=f(关键字),其中f是散列函数,也称为哈希函数。
代码
方法1:哈希表(散列表)
from typing import Optional
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 创建链表
def create_linked(self, values):
d = ListNode(0)
cur = d
for val in values:
cur.next = ListNode(val)
cur = cur.next
return d.next
# 创建环形链表
def create_linked_cycle(self, head, pos):
if not head or pos + 1 <= 0:
return head
len = 1
cur = head
while cur.next:
cur = cur.next
len += 1
if pos + 1 < len:
cur.next = head
return head
# 哈希表
def hasCycle(self, head: Optional[ListNode]) -> bool:
map = {}
while head:
if map.get(head):
return True
map[head] = 1
head = head.next
return False
if __name__ == '__main__':
# 141.环形链表 - 示例1
head = [3, 2, 0, -4]
pos = 1
s = Solution()
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例2
head = [1, 2]
pos = 0
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例3
head = [1]
pos = -1
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:false
方法2:快慢指针
from typing import Optional
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 创建链表
def create_linked(self, values):
d = ListNode(0)
cur = d
for val in values:
cur.next = ListNode(val)
cur = cur.next
return d.next
# 创建环形链表
def create_linked_cycle(self, head, pos):
if not head or pos + 1 <= 0:
return head
len = 1
cur = head
while cur.next:
cur = cur.next
len += 1
if pos + 1 < len:
cur.next = head
return head
# 快慢指针
def hasCycle(self, head: Optional[ListNode]) -> bool:
if not head or not head.next:
return False
s = head
f = head.next
while s != f:
if not f or not f.next:
return False
s = s.next
f = f.next.next
return True
if __name__ == '__main__':
# 141.环形链表 - 示例1
head = [3, 2, 0, -4]
pos = 1
s = Solution()
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例2
head = [1, 2]
pos = 0
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例3
head = [1]
pos = -1
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:false
方法3:链表计数
from typing import Optional
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 创建链表
def create_linked(self, values):
d = ListNode(0)
cur = d
for val in values:
cur.next = ListNode(val)
cur = cur.next
return d.next
# 创建环形链表
def create_linked_cycle(self, head, pos):
if not head or pos + 1 <= 0:
return head
len = 1
cur = head
while cur.next:
cur = cur.next
len += 1
if pos + 1 < len:
cur.next = head
return head
# 链表计数
def hasCycle(self, head: Optional[ListNode]) -> bool:
cnt = 0
while head and cnt <= 10**4:
cnt += 1
head = head.next
return cnt > 10**4
if __name__ == '__main__':
# 141.环形链表 - 示例1
head = [3, 2, 0, -4]
pos = 1
s = Solution()
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例2
head = [1, 2]
pos = 0
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例3
head = [1]
pos = -1
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:false
方法4:链表反转
from typing import Optional
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 创建链表
def create_linked(self, values):
d = ListNode(0)
cur = d
for val in values:
cur.next = ListNode(val)
cur = cur.next
return d.next
# 创建环形链表
def create_linked_cycle(self, head, pos):
if not head or pos + 1 <= 0:
return head
len = 1
cur = head
while cur.next:
cur = cur.next
len += 1
if pos + 1 < len:
cur.next = head
return head
# 链表反转
def hasCycle(self, head: Optional[ListNode]) -> bool:
if not head or not head.next:
return False
p, q = None, head
while q:
u, p, q = p, q, q.next
p.next = u
return head == p
if __name__ == '__main__':
# 141.环形链表 - 示例1
head = [3, 2, 0, -4]
pos = 1
s = Solution()
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例2
head = [1, 2]
pos = 0
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例3
head = [1]
pos = -1
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:false
方法5:标记val值
from typing import Optional
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 创建链表
def create_linked(self, values):
d = ListNode(0)
cur = d
for val in values:
cur.next = ListNode(val)
cur = cur.next
return d.next
# 创建环形链表
def create_linked_cycle(self, head, pos):
if not head or pos + 1 <= 0:
return head
len = 1
cur = head
while cur.next:
cur = cur.next
len += 1
if pos + 1 < len:
cur.next = head
return head
# 标记val值
def hasCycle(self, head: Optional[ListNode]) -> bool:
while head:
if head.val == '1':
return True
head.val = '1'
head = head.next
return False
if __name__ == '__main__':
# 141.环形链表 - 示例1
head = [3, 2, 0, -4]
pos = 1
s = Solution()
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例2
head = [1, 2]
pos = 0
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:true
# 示例3
head = [1]
pos = -1
head = s.create_linked(head)
head = s.create_linked_cycle(head, pos)
res = s.hasCycle(head)
print(res) # 输出:false
28. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = “leetcode”, needle = “leeto”
输出:-1
解释:“leeto” 没有在 “leetcode” 中出现,所以返回 -1 。
提示:
- 1 <= haystack.length, needle.length <= 104
- haystack 和 needle 仅由小写英文字符组成
知识点回顾
find()函数:是一种字符串方法,用于确定一个字符串是否包含另一个字符串,如果包含则返回该子字符串首次出现的位置,否则返回-1。
Sunday算法:一种高效的字符串模式匹配算法,由Daniel M.Sunday于1990年提出。核心思想是在匹配过程中,如果发现不匹配,可以跳过尽可能多的字符以进行下一步的匹配,从而提高了匹配效率。这种算法在处理随机字符串时的效率比其他匹配算法更高。在匹配失败时关注的是文本串中参加匹配的最末位字符的下一位字符。如果该字符没有在匹配串中出现则直接跳过,即移动步长= 匹配串长度+1。
BM(Boyer-Moore)算法:一种非常高效的字符串搜索算法。它由Bob Boyer和J Strother Moore设计于1977年。此算法仅对搜索目标字符串(关键字)进行预处理,而非被搜索的字符串。虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分:它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。即移动步长=匹配串中最右端的该字符到末尾的距离+1。
KMP(The Knuth-Morris-Pratt Algorithm)算法:一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
Horspool算法:是一种在字符串中查找子串的字符串匹配算法。由 Nigel Horspool 教授于1980年出版的,是首个对Boyer Moore算法进行简化的算法。改进了BM算法的坏字符规则。
BF(Brute Force)算法:又称暴力算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。
RK(Rabin-Karp)算法:通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式 串的哈希值比较大小。如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配。
代码
普通方法
方法1:find()函数
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
return haystack.find(needle)
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法2:index()函数
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
try:
return haystack.index(needle)
except:
return -1
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
算法
方法1:Sunday算法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
# 特殊情况处理
if not needle: # 匹配串为空
return 0
if not haystack or len(needle) > len(haystack): # 字符串为空或匹配串多于字符串
return -1
if needle == haystack[0:len(needle)]: # 匹配串碰巧就是字符串头
return 0
if len(needle) == len(haystack): # 匹配串和字符串长度相等
if needle == haystack: # 匹配串就是字符串
return 0
else:
return -1
# 存储匹配串中出现的字符距末尾字符后一位位移
def Shift(s):
dict = {}
for i in range(len(s) - 1, -1, -1):
if not dict.get(s[i]):
dict[s[i]] = len(s) - i
dict["end"] = len(s) + 1
return dict
dic = Shift(needle)
idx = 0
while idx + len(needle) <= len(haystack):
cur = haystack[idx + len(needle)] # 当前字符=匹配串长度+1
if dic.get(cur): # 若当前字符在匹配串中出现
idx += dic[cur] # 索引=当前字符匹配串中存储的位移
else:
idx += dic["end"] # 索引=匹配串长度+1
if needle == haystack[idx:idx + len(needle)]: # 移动中出现匹配串则跳出当前循环
break
if idx + len(needle) >= len(haystack): # 移出字符串长度则返回-1
return -1
return idx
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法2:BM算法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def boyerMoore(haystack: str, needle: str) -> int:
bc = getBadChar(needle) # 生成坏字符位置表
gs = getGoodSuffix(needle) # 生成好后缀规则后移位数表
i = 0
while i <= len(haystack) - len(needle):
j = len(needle) - 1
while j > -1 and haystack[i + j] == needle[j]:
j -= 1
if j < 0:
return i
bm = j - bc.get(haystack[i + j], -1)
gm = gs[j]
i += max(bm, gm)
return -1
# 生成坏字符位置表
def getBadChar(needle: str):
bc = dict()
for i in range(len(needle)):
bc[needle[i]] = i # 坏字符在模式串中最后一次出现的位置
return bc
# 生成好后缀规则后移位数表
def getGoodSuffix(needle: str):
m = len(needle)
gs = [m for _ in range(m)]
s = getSuffix(needle)
j = 0
for i in range(m - 1, -1, -1):
if s[i] == i + 1:
while j < m - 1 - i:
if gs[j] == m:
gs[j] = m - 1 - i
j += 1
for i in range(m - 1):
gs[m - 1 - s[i]] = m - 1 - i
return gs
def getSuffix(needle: str):
m = len(needle)
s = [m for _ in range(m)]
for i in range(m - 2, -1, -1):
start = i
while start >= 0 and needle[start] == needle[m - 1 - i + start]:
start -= 1
s[i] = i - start
return s
return boyerMoore(haystack, needle)
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法3:KMP算法
class Solution:
# 构造前缀表next数组
def getnext(self, needle):
next = ['' for _ in range(len(needle))]
o = -1
next[0] = -1
for i in range(1, len(needle)):
while o > -1 and needle[o + 1] != needle[i]:
o = next[o]
if needle[o + 1] == needle[i]:
o += 1
next[i] = o
return next
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
next = self.getnext(needle)
og = -1
for i in range(len(haystack)):
while og >= 0 and needle[og + 1] != haystack[i]:
og = next[og]
if needle[og + 1] == haystack[i]:
og += 1
if og == len(needle) - 1:
return i - len(needle) + 1
return -1
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法4:Horspool 算法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def horspool(haystack: str, needle: str) -> int:
lh, ln = len(haystack), len(needle)
bc = getBadChar(needle)
i = 0
while i <= lh - ln:
j = ln - 1
while j > -1 and haystack[i + j] == needle[j]:
j -= 1
if j < 0:
return i
i += bc.get(haystack[i + ln - 1], ln)
return -1
# 生成坏字符后移位置表
def getBadChar(needle: str):
ln = len(needle)
bc = dict()
for i in range(ln - 1):
bc[needle[i]] = ln - i - 1
return bc
return horspool(haystack, needle)
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法5:BF(Brute-Force)算法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
i = 0
j = 0
lh = len(haystack)
ln = len(needle)
while i < lh and j < ln:
if haystack[i] == needle[j]:
i += 1
j += 1
else:
i = i - (j - 1)
j = 0
if j == ln:
return i - j
else:
return -1
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1
方法6:RK(Robin-Karp)算法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def rabinKarp(haystack: str, needle: str) -> int:
lh, ln = len(haystack), len(needle)
hn = hash(needle)
for i in range(lh - ln + 1):
hh = hash(haystack[i: i + ln])
if hn != hh:
continue
k = 0
for j in range(ln):
if haystack[i + j] != needle[j]:
break
k += 1
if k == ln:
return i
return -1
return rabinKarp(haystack, needle)
if __name__ == '__main__':
# 28.找出字符串中第一个匹配项的下标 - 示例1
haystack = "sadbutsad"
needle = "sad"
s = Solution()
res = s.strStr(haystack, needle)
print(res) # 输出:0
# 示例2
haystack = "leetcode"
needle = "leeto"
res = s.strStr(haystack, needle)
print(res) # 输出:-1