文章目录
前言
之前的好像因为写太多了所以编辑起来很卡…所以就新写一个
第12天
146.LRU缓存机制
网址:https://leetcode-cn.com/problems/lru-cache/
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
~~~ LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
~~~ int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
~~~ void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间
第一个想法就是用链表来储存,但是遇到的困难就是如何得知链表中存在数据,最后看的官方题解,是用双向链表和hash表来写的。hash表中value值来储存地址。
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
执行用时:200 ms, 在所有 Python3 提交中击败了46.41%的用户
内存消耗:23.7 MB, 在所有 Python3 提交中击败了5.05%的用户
148.排序链表
网址:https://leetcode-cn.com/problems/sort-list/
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
想了半天没想出来…最后看的官方题解
使用归并算法,先设置长度为1,对各个链表进行排序,之后设置长度为2。。。直到设置长度大于总长度
用到了之前做过的有序链表合并的函数merge
class Solution:
def sortList(self, head: ListNode) -> ListNode:
def merge(head1: ListNode, head2: ListNode) -> ListNode: #合并两个有序链表
dummyHead = ListNode(0)
temp, temp1, temp2 = dummyHead, head1, head2
while temp1 and temp2:
if temp1.val <= temp2.val:
temp.next = temp1
temp1 = temp1.next
else:
temp.next = temp2
temp2 = temp2.next
temp = temp.next
if temp1:
temp.next = temp1
elif temp2:
temp.next = temp2
return dummyHead.next
if not head:
return head
#计算链表长度
length = 0
node = head
while node:
length += 1
node = node.next
dummyHead = ListNode(0, head)
subLength = 1 #先设置长度为1进行链表排序
while subLength < length:
prev, curr = dummyHead, dummyHead.next
while curr:
head1 = curr #指向第一部分的链表头
for i in range(1, subLength):
if curr.next:
curr = curr.next
else:
break
head2 = curr.next #指向第二部分的链表头
curr.next = None #切断第一部分
curr = head2
for i in range(1, subLength):
if curr and curr.next:
curr = curr.next
else:
break
succ = None #用来储存第二部分后面的链表
if curr:
succ = curr.next
curr.next = None
merged = merge(head1, head2) #进行排序
prev.next = merged #接头
while prev.next:
prev = prev.next
curr = succ #下一个循环对剩余部分的链表进行
subLength <<= 1 #位操作,乘以二
return dummyHead.next
执行用时:600 ms, 在所有 Python3 提交中击败了6.00%的用户
内存消耗:30 MB, 在所有 Python3 提交中击败了13.51%的用户
155.最小栈
网址:https://leetcode-cn.com/problems/min-stack/
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
~~~ push(x) —— 将元素 x 推入栈中。
~~~ pop() —— 删除栈顶的元素。
~~~ top() —— 获取栈顶元素。
~~~ getMin() —— 检索栈中的最小元素。
做法就是利用一个辅助栈,在进栈的同时储存对应的最小值
我看到评论区里面还有一种做法就是在stack中储存与最小值的差值,然后用min来储存最小值,这样子就可以省下来储存的空间了。等以后再去尝试
class MinStack:
def __init__(self):
self.stack=[]
self.min_stack=[float('inf')]
def push(self, x: int) -> None:
self.stack.append(x)
self.min_stack.append(min(x,self.min_stack[-1]))
def pop(self) -> None:
self.min_stack.pop()
return self.stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
执行用时:68 ms, 在所有 Python3 提交中击败了75.34%的用户
内存消耗:18 MB, 在所有 Python3 提交中击败了12.27%的用户
第十三天
160.相交链表
网址:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
编写一个程序,找到两个单链表相交的起始节点。
注意:如果两个链表没有交点,返回 null. 在返回结果后,两个链表仍须保持原有的结构。 可假定整个链表结构中没有循环。 程序尽量满足 O(n)
时间复杂度,且仅用 O(1) 内存。
第一个想法是先遍历两个数组计算出各自的长度,然后将两个链表的尾部对齐,用两个假头一起往下遍历
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB: return None
def getlen(head):
n=0
while head:
head=head.next
n+=1
return n
a=getlen(headA)
b=getlen(headB)
curA=curB=None
#总使curA为较长的一个
if a<b: #headB长
curB=headA
curA=headB
else:
curA=headA
curB=headB
#让两个链表尾部对齐
for i in range(abs(a-b)):
curA=curA.next
for i in range(b):
if curA==curB:
return curA
curA=curA.next
curB=curB.next
return None
执行用时:168 ms, 在所有 Python3 提交中击败了61.16%的用户
内存消耗:29.5 MB, 在所有 Python3 提交中击败了34.46%的用户
不过还有可以改进的地方,就是可以看成两个新链表,一个是A+B,一个是B+A,即假头A在链表A尾部结束后到B链表的头部
在这里两个链表相交的地方加入一个none,这样子可以减少情况的判断,同时照样符合上述的描述
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB: return None
a=headA
b=headB
while a!=b:
if not a:
a=headB
else:
a=a.next
if not b:
b=headA
else:
b=b.next
return a
执行用时:156 ms, 在所有 Python3 提交中击败了90.47%的用户
内存消耗:29.6 MB, 在所有 Python3 提交中击败了12.95%的用户
169.多数元素
网址:https://leetcode-cn.com/problems/majority-element/
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
摩尔投票法,因为我们要找到的多数元素是众数,因此在投票时总是占据优势的
时间复杂度为 O(n)、空间复杂度为 O(1)
class Solution:
def majorityElement(self, nums: List[int]) -> int:
a,b=0,0
for i in range(len(nums)):
if b==0:
a=nums[i]
b=b+1 if a==nums[i] else b-1
return a
执行用时:56 ms, 在所有 Python3 提交中击败了40.81%的用户
内存消耗:15.9 MB, 在所有 Python3 提交中击败了32.80%的用户
还有一种办法,就是先对数列进行排序,这样子时间复杂度会稍微高一点
因为是众数,且数组经过排序,那么中间数就是我们所需要查找的
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums)//2]
执行用时:44 ms, 在所有 Python3 提交中击败了85.36%的用户
内存消耗:16.2 MB, 在所有 Python3 提交中击败了5.26%的用户
206.反转链表
网址:https://leetcode-cn.com/problems/reverse-linked-list/
反转一个单链表。
递归法
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def d(root,head):
if not head: return root
p=head.next
head.next=root
return d(head,p)
return d(None,head)
执行用时:56 ms, 在所有 Python3 提交中击败了7.91%的用户
内存消耗:20.9 MB, 在所有 Python3 提交中击败了5.04%的用户
递归法二:设定函数返回一个已经反转了的链表,
1->2->3->4->5->NULL
变成
1->2<-3<-4<-5
然后将head指向None即可
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
root=self.reverseList(head.next)
head.next.next=head
head.next=None
return root
执行用时:36 ms, 在所有 Python3 提交中击败了93.36%的用户
内存消耗:19.6 MB, 在所有 Python3 提交中击败了5.04%的用户
迭代法,用三个指针来确保可以向下进行
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
a=head
if not a:return None
b=a.next
if not b:
return a
c=b.next
a.next=None
while True:
if not c:
b.next=a
return b
b.next=a
a,b,c=b,c,c.next
执行用时:36 ms, 在所有 Python3 提交中击败了93.36%的用户
内存消耗:15.6 MB, 在所有 Python3 提交中击败了27.44%的用户
第14天
215.数组中的第K个最大元素
网址:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
这道题目考察排序算法的运用,我就没有仔细写了
那种用自带的就是sort之后再return第-k个元素即可
217.存在重复元素
网址:https://leetcode-cn.com/problems/contains-duplicate/
给定一个整数数组,判断是否存在重复元素。
如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
方法一就是将其作为一个集合,去除重复元素,然后计算长度
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
a=set(nums)
return len(nums)!=len(a)
执行用时:56 ms, 在所有 Python3 提交中击败了15.66%的用户
内存消耗:20.1 MB, 在所有 Python3 提交中击败了31.32%的用户
或者还可以用哈希表或排序来求解
230.二叉搜索树中第K小的元素
网址:https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/
给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。
考虑到二叉搜索树的特殊性,可以用中序遍历的方法来得出从小到大的排序,然后找到第K小的元素。
以下是递归方法,迭代方法也是同样的道理
class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:
def inorder(r):
return inorder(r.left) + [r.val] + inorder(r.right) if r else []
return inorder(root)[k - 1]
第15天
231.2的幂
网址:https://leetcode-cn.com/problems/power-of-two/
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
对于一个数字,判断是否为2的幂,先查看是否为1,再看看是不是2的倍数,用递归进行进一步的判断
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
if n==0: return False
if n==1: return True
if n//2!=n/2: return False
return self.isPowerOfTwo(n/2)
执行用时:36 ms, 在所有 Python3 提交中击败了88.33%的用户
内存消耗:14.8 MB,在所有 Python3 提交中击败了15.92%的用户
这种是看题解的,如果是2的幂,那么只有最高位才是1,当n-1时除了最高位全是1,因此与操作得出结果是0
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
return n>0 and n&(n-1)==0
执行用时:32 ms, 在所有 Python3 提交中击败了96.50%的用户
内存消耗:14.8 MB, 在所有 Python3 提交中击败了16.63%的用户
235.二叉搜索树的最近公共祖先
网址:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
要明白一点就是,如果两个结点在当前节点的两边,那么当前结点就是最近的公共祖先节点。
同时对于当前结点要优先判断是否为其中一个节点,如果是的话那么它就是最终结果了
以下是递归
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root ==p or root==q: return root
if (root.val-p.val)*(root.val-q.val)<0:return root #两个结点分隔在两侧
elif root.val-p.val>0: return self.lowestCommonAncestor(root.left,p,q) #两个结点都在左边
else: return self.lowestCommonAncestor(root.right,p,q)
执行用时:80 ms, 在所有 Python3 提交中击败了90.28%的用户
内存消耗:18.8 MB, 在所有 Python3 提交中击败了15.33%的用户
以下是迭代法
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while True:
if root ==p or root==q: return root
if (root.val-p.val)*(root.val-q.val)<0:return root #两个结点分隔在两侧
elif root.val-p.val>0: root=root.left #两个结点都在左边
else: root=root.right
执行用时:84 ms, 在所有 Python3 提交中击败了78.64%的用户
内存消耗:18.8 MB, 在所有 Python3 提交中击败了8.02%的用户
236.二叉树的最近公共祖先
网址:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
相较于之前的二叉搜索树,这里不能剪枝操作。
所以这里就是将所有的结点进行深度优先搜索,对于q和p,将其带上来,也就是相当于把p和q的上面结点进行记录,直到遇到两个的共同祖先
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or root == p or root == q: return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left: return right
if not right: return left
return root #这种就是left and right的情况了
执行用时:76 ms, 在所有 Python3 提交中击败了79.79%的用户
内存消耗:25.6 MB, 在所有 Python3 提交中击败了34.97%的用户
第16天
237.删除链表中的节点
网址:https://leetcode-cn.com/problems/delete-node-in-a-linked-list/
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
提示:
链表至少包含两个节点。
链表中所有节点的值都是唯一的。
给定的节点为非末尾节点并且一定是链表中的一个有效节点。
不要从你的函数中返回任何结果。
这个题目刚开始都没看懂,后来才明白,这是把下一个节点的值拿过来然后舍弃下一个节点,这样子就变相等于删除了当前的节点。
class Solution:
def deleteNode(self, node):
node.val=node.next.val
node.next=node.next.next
238.除自身以外数组的乘积
网址:https://leetcode-cn.com/problems/product-of-array-except-self/
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
如果采取暴力算法,那么就会产生很多重复的步骤,因此对于暴力算法的优化就能得到题目要求的结果
对于最终数组的每一个值,都可分为两部分:左边相乘和右边相乘
因此,可以对数组进行遍历两次,一次得出各个值左边的乘,一次从右边开始遍历得出右边的乘
两次相乘合并就是最终的output
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
output=[]
left = 1
for i in range(len(nums)):
if i > 0 :
left *= nums[i-1]
output.append(left)
right = 1
for i in range(len(nums)-1,-1,-1):
if i < len(nums)-1:
right *= nums[i+1]
output[i]*=right
return output
292.Nim游戏
网址:https://leetcode-cn.com/problems/nim-game/
你和你的朋友,两个人一起玩 Nim 游戏:
桌子上有一堆石头。
你们轮流进行自己的回合,你作为先手。
每一回合,轮到的人拿掉 1 - 3 块石头。
拿掉最后一块石头的人就是获胜者。
假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。
- 当n为1-3时,无论如何都能拿完
- 当n为4时,无论如何都会被对方拿完
以此类推,如果要输的话,那么对方应该留下4个给我,因为没人只能拿1-3个棋子,所以当n为4的倍数时,对方总有办法能够使一个来回内取走的棋子为4,从而必输
当n为1-3时,无论如何都会赢
class Solution:
def canWinNim(self, n: int) -> bool:
return n%4!=0
第17天
344.反转字符串
网址:https://leetcode-cn.com/problems/reverse-string/
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
第一个无脑操作,直接用库函数
class Solution:
def reverseString(self, s: List[str]) -> None:
s.reverse()
第二种方法是双指针,然后交换两个的值
class Solution:
def reverseString(self, s: List[str]) -> None:
l,r=0,len(s)-1
while l<r:
s[l],s[r]=s[r],s[l]
l+=1
r-=1
557.反转字符串中的单词|||
网址:https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
将每个单词取出然后反转再合并
class Solution:
def reverseWords(self, s: str) -> str:
return " ".join([i[::-1] for i in s.split(' ')])
执行用时:40 ms, 在所有 Python3 提交中击败了81.66%的用户
内存消耗:15.4 MB, 在所有 Python3 提交中击败了32.89%的用户