优劣排序:O(1) > O(logn) > O(n) > O(nlogn) > O(n^2) > O(2^n) > O(n!)
python代码数据结构可视化:
leetcode(力扣)刷题网站:https://leetcode-cn.com/
视频教学:https://edu.csdn.net/course/play/29389/416882
算法题多使用内置函数和考虑时间复杂度
内置函数:https://docs.python.org/3/library/functions.html
pytest:测试工具
when-changed:自动监控文件并运行单元测试工具(看门狗)
命令行执行:
when-changed -r -v -1 'filepath' pytest -s 'filepath'
-r(Watch recursively):监听
-v(Verbose output):输入详细的信息
-1:等待代码更新后才继续执行“filepath”文件
-s:是pytest的命令,输入print打印的信息
掌握抽象数据类型的概念(ADT:Abstract Data Type)
学会用Python类实现自己的抽象数据类型
了解魔术方法的概念和使用
练习:用Python类实现一个新的抽象数据类型背包类Bag,Bag方法:构造函数、放入(add)、移除(remove)、长度(len)、遍历(iter)
class Bag():
def __init__(self, maxsize=10):
self.maxsize = maxsize
self._item = list() # []
def add(self, item):
if len(self) > self.maxsize:
raise Exception('Bag is full')
self._item.append(item)
def remove(self, item):
self._item.remove(item)
def __len__(self):
return len(self._item)
def __iter__(self):
for i in self._item:
yield i
# def size(self):
# return len(self._item)
# def iter(self):
# for i in self._item:
# yield i
def test_Bag():
b = Bag()
b.add(1)
b.add(2)
b.add(3)
# assert b.size() == 3
assert len(b) == 3
b.remove(3)
# assert b.size() == 2
assert len(b) == 2
print()
for i in iter(b):
print(i)
时间复杂度:
查找元素,通过下标查找:O(1)
1、支持动态扩容的线性结构,下标访问
2、超出容量之后,会开辟新内存,并复制旧数据
3、Python list可以包含不同的数据类型(元素的类型可以是list、string、tuple…)
4、业务代码和题目当中最常用的一种数据结构
5、L = [] 或者 L= list() 来创建(这两种方式等价)
练习:list数据类型的注意点(坑)
# 1、如何初始化一个元素都相同的一维list
# 第一种方式:直接先创建一个list,然后for循环遍历数值并添加进list
l = []
for _ in range(10):
l.append(5)
print(l) # [5,5,5,5,5,5,5,5,5,5]
# 第二种方式:使用Python list提供的语法糖
l = [5]*10
#2、如何初始化一个元素都相同的二维list(有一个坑在这里)
def init_matrix(val,rows, cols):
return [[val]*cols]*rows # 这种写法会有一个坑,引用了同一个对象
# return [[val]*cols for _ in range(rows)] # 应该这样写
ll = init_matrix(0, 3, 4)
print(ll) # [[0,0,0],[0,0,0],[0,0,0]]
ll[0][0] = 100
print(ll) # [[100,0,0],[100,0,0],[100,0,0]]
# 解释:因为第一种return写法中的list中每一个元素都是指向同一个子list对象(语法糖*的问题),而第二种return写法中的list中每个元素指向的子list都是不同的对象
# 可以使用可视化网站查看http://pythontutor.com/live.html#mode=edit
解法:
1、暴力解法:使用两次for循环遍历,时间复杂度:O(n^2)
2、空间换时间:使用dict存储,只使用一次for循环,O(n)
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
d = {}
for i, num in enumerate(nums):
n = target - num
if n in d:
return [d[n],i]
d[num] = i
return []
解法:
1、在内存空间中开辟新的list空间用来存储奇数以及偶数,空间复杂度会高
2、使用指针方式,不开辟新的内存空间
给定一个非负整数数组 A,返回一个数组,在该数组中, A 的所有偶数元素之后跟着所有奇数元素。
你可以返回满足此条件的任何数组作为答案。
示例:
输入:[3,1,2,4]
输出:[2,4,3,1]
输出 [4,2,3,1],[2,4,1,3] 和 [4,2,1,3] 也会被接受。
提示:
1 <= A.length <= 5000
0 <= A[i] <= 5000
# 按奇偶排序数组
# class Solution(object):
# def odd(self, n):
# return n % 2 == 1
# def even(self, n):
# return n % 2 == 0
# def sortArrayByParity(self, A):
# """
# :type A: List[int]
# :rtype: List[int]
# """
# L = []
# even = list(filter(self.even, A))
# odd = list(filter(self.odd, A))
# L = even + odd
# return L
class Solution(object):
def sortArrayByParity(self, A):
"""
:type A: List[int]
:rtype: List[int]
"""
beg, end = 0, len(A)-1
while True:
while A[beg] % 2 == 0 and beg < end:
beg += 1
while A[end] % 2 == 1 and beg < end:
end -= 1
if beg >= end:
break
A[beg], A[end] = A[end], A[beg]
return A
解法:
1、for循环两次,O(m*n)
2、也是两次for循环,第二次for循环是使用二分查找,O(m*logn)
3、使用指针方式,只使用一次for循环,O(m+n)
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if not matrix:
return False
rows = len(matrix)
cols = len(matrix[0])
i, j = 0, cols-1
while i < rows and j >= 0:
val = matrix[i][j]
if target == val:
return True
elif target < val:
j -= 1
else:
i += 1
return False
def test_searchMatrix(): # 测试
M = [
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
assert Solution().searchMatrix(M, 5) == True
assert Solution().searchMatrix(M, 20) == False
# 测试命令:when-changed -r -v -1 'filepath' pytest -s 'filepath'
解法:
1、反向遍历。如果使用正向遍历的过程,删除list的多个元素的同时会导致list内部数据会变动从而index值会变,会比较麻烦
2、使用指针
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
# 反向遍历
# for i in range(len(nums)-1, -1, -1):
# if nums[i] == val:
# nums.pop(i)
# return len(nums)
# 使用指针
i = 0
for j in range(len(nums)):
if nums[j] != val:
# nums[i] = nums[j]
i += 1
return i
def test_removeElement():
print()
nums = [3,2,2,3]
val = 3
nums2 = [0,1,2,2,3,0,4,2]
val2 = 2
assert Solution().removeElement(nums, val) == 2
assert Solution().removeElement(nums2, val2) == 5
解法:
分析输入与输出的数据,多利用指针解决
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
i = m-1
j = n-1
tmp = i+j+1
while j >= 0:
if i == -1:
nums1[tmp] = nums2[j]
j -= 1
tmp -= 1
continue
if nums1[i] < nums2[j]:
nums1[tmp] = nums2[j]
j -= 1
tmp -= 1
else:
nums1[tmp] = nums1[i]
i -= 1
tmp -= 1
# 测试
def test_merge():
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
Solution().merge(nums1, m, nums2, n)
assert nums1 == [1,2,2,3,5,6]
特点:
class Node:
def __init__(self, value, next=None):
self.value = value
self.next = next
特点:
class Node:
def __init__(self, value, prev=None, next=None):
self.value, self.prev, self,next = value, prev, next
class LinkedListNode():
def __init__(self, value, next=None):
self.value = value
self.next = next
def gen_linked_list(nums):
if not nums:
return None
head = LinkedListNode(nums[0])
cur = head
for i in range(1, len(nums)):
node = LinkedListNode(nums[i])
cur.next = node
cur = node
return head
def print_linked_list(head):
cur = head
while cur:
print('{}->'.format(cur.value), end='')
cur = cur.next
print('nil')
if __name__ == "__main__":
nums = [1,2,3,4]
head = gen_linked_list(nums)
print_linked_list(head)
解法:
第一种方法利用指针迭代,比较好理解
第二种使用递归,找出最后一个节点,然后递归实现最后一个节点需要做的事情
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 迭代 时间复杂度:O(n),空间复杂度:O(1)
# pre = None
# cur = head
# while cur:
# nextnode = cur.next
# cur.next = pre
# pre = cur
# cur = nextnode
# return pre
# 递归 时间复杂度:O(n),空间复杂度:O(1)
if not (head and head.next):
return head
newhead = self.reverseList(head.next)
head.next.next = head
head.next = None
return newhead
# when-changed -r -v -1 'filepath' pytest -s 'filepath'
def test_reverselist():
L = [1,2,3,4,5]
head = gen_linked_list(L)
print()
print_linked_list(head)
pre = Solution().reverseList(head)
print_linked_list(pre)
解法:
纸上做草稿,使用指针完成
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution(object):
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
pre = ListNode(0)
cur = pre
while l1 and l2:
if l1.val <= l2.val:
cur.next = l1
l1 = l1.next
cur = cur.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 or l2
return pre.next
def test_mergeTwoLists():
l1 = gen_linked_list([1,2,4])
l2 = gen_linked_list([1,3,4])
s = Solution().mergeTwoLists(l1, l2)
print_linked_list(s)
def gen_linked_list(nums):
if not nums:
return None
head = ListNode(nums[0])
cur = head
for i in range(1, len(nums)):
node = ListNode(nums[i])
cur.next = node
cur = node
return head
def print_linked_list(head):
cur = head
while cur:
print('{}->'.format(cur.val), end='')
cur = cur.next
print('nil')
解法:
第一种方式,将长链表砍掉一部分使得它的长度与短链表一样长,然后判断
第二种方式,将两个链表相交即长度都相等了,再判断
编写一个程序,找到两个单链表相交的起始节点。
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
# 第一种方式,将长链表砍掉一部分使得它的长度与短链表一样长
# if headA is None or headB is None:
# return None
# curA = headA
# lenA = 0
# while curA:
# lenA += 1
# curA = curA.next
# curB = headB
# lenB = 0
# while curB:
# lenB += 1
# curB = curB.next
# ldiff = abs(lenA-lenB)
# if lenA > lenB:
# for _ in range(ldiff):
# headA = headA.next
# else:
# for _ in range(ldiff):
# headB = headB.next
# while headA and headB:
# if headA == headB:
# # print(headA,headB)
# return headA
# headA = headA.next
# headB = headB.next
# return None
# 第二种方式,将两个链表相交即长度都相等了,再判断
ha, hb = headA, headB
while ha != hb:
ha = ha.next if ha else headB
hb = hb.next if hb else headA
return ha
思路:
1、开辟一个新的内存空间即创建一个set(集合),利用集合元素不重复的特性
2、快慢指针,若闭环,快慢指针一定会相遇
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
# 第一种使用hash表存储(集合,根据元素不重复的特性)
# 时间复杂度O(n),空间复杂度O(n)
# hash = {}
# cur = head
# while cur:
# if hash.get(cur.next) is not None:
# return True
# hash[cur] = 1
# cur = cur.next
# return False
# 利用快慢指针
# 时间复杂度O(n),空间复杂度O(1)
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
思路:
1、竖式计算,定义多几个变量(指针)进行操作,会方便很多,重点在于获取值的方式,%10为获取个位,//10获取进位
2、使用递归的方式
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 迭代的方式
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
# 迭代的方式,创建了新的链表,空间复杂度O(n),时间复杂度O(n)
pre = cur = ListNode(None) # pre变量用于返回,cur用于指向新创建的节点
s = 0 # 中间暂存变量
while l1 or l2 or s:
# if l1 == None:
# l1 = ListNode(0)
# if l2 == None:
# l2 = ListNode(0)
s += (l1.val if l1 else 0) + (l2.val if l2 else 0) # 注意是+=,存储l1和l2节点的值
cur.next = ListNode(s%10) # 新节点赋值 %10取个位
s = s//10 # 取进位的值
cur = cur.next # 使指向新节点的指针向前指
l1 = l1.next if l1 else None # l1向前走
l2 = l2.next if l2 else None # l2向前走
return pre.next # 返回新节点的首节点
# 递归的方式,添加参数carry
# class Solution:
# def addTwoNumbers(self, l1: ListNode, l2: ListNode, carry=0) -> ListNode:
# if l1==None and l2==None and carry==0:
# return None
# if l1==None and l2==None and carry==1 :
# return ListNode(1)
# if l1==None :
# l1 = ListNode(0)
# if l2==None :
# l2 = ListNode(0)
# l1.val, carry = (l1.val+l2.val+carry)%10, (l1.val+l2.val+carry)//10
# l1.next = self.addTwoNumbers(l1.next, l2.next, carry)
# return l1
def test_addTwoNumbers():
l1 = gen_linked_list([2,4,3,2])
l2 = gen_linked_list([5,6,4])
s = Solution().addTwoNumbers(l1,l2)
print_linked_list(s)
def gen_linked_list(nums):
if not nums:
return None
head = ListNode(nums[0])
cur = head
for i in range(1, len(nums)):
node = ListNode(nums[i])
cur.next = node
cur = node
return head
def print_linked_list(head):
cur = head
while cur:
print('{}->'.format(cur.val), end='')
cur = cur.next
print('nil')
FIFO-first in first out,先进先出结构,类似于排队
最先插入的元素最先出来,可以使用数组、链表等结构可以用来实现队列
队列:queue,双端队列:double-ended queue
下面使用deque(python内置模块)来实现队列或双端队列(利用双端队列deque的特性)
from collections import deque
class Queue():
def __init__(self):
self.items = deque()
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.popleft()
def head(self):
return self.items[0]
def isempty(self):
return len(self.items) == 0
if __name__ == "__main__":
q = Queue()
q.push(1)
q.push(2)
q.push(3)
q.pop()
qh = q.head()
print(qh)
LIFO-last in first out,后进先出结构,类似于往桶里面添加东西
可以用数组、双端队列等方式实现栈
下面同样使用deque实现
class Stack():
def __init__(self):
self.items = deque()
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def head(self):
return self.items[0]
def isempty(self):
return len(self.items) == 0
if __name__ == "__main__":
s = Stack()
s.push(4)
s.push(5)
s.push(6)
s.pop()
sh = s.head()
print(sh)
思路:
使用两个栈,push数据时push进第一个栈,然后将数据以栈的方式移进第二个栈,这样pop数据时使用第二个栈pop,取数据相当于先进先出
使用栈实现队列的下列操作:
push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
from collections import deque
class Stack():
def __init__(self):
self.items = deque()
def push(self, val):
return self.items.append(val)
def pop(self):
return self.items.pop()
def __len__(self):
return len(self.items)
def top(self):
return self.items[-1]
class MyQueue(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.s1 = Stack()
self.s2 = Stack()
def push(self, x):
"""
Push element x onto stack.
:type x: int
:rtype: None
"""
self.s1.push(x)
def pop(self):
"""
Removes the element on top of the stack and returns that element.
:rtype: int
"""
print(self.s2)
if self.s2:
return self.s2.pop()
while self.s1:
val = self.s1.pop()
self.s2.push(val)
return self.s2.pop()
def peek(self):
"""
Get the top element.
:rtype: int
"""
if self.s2:
return self.s2.top()
while self.s1:
val = self.s1.pop()
self.s2.push(val)
return self.s2.top()
def empty(self):
"""
Returns whether the stack is empty.
:rtype: bool
"""
return len(self.s1) == 0 and len(self.s2) == 0
def test():
q = MyQueue()
q.push(1)
q.push(2)
# val = q.pop()
# print(val)
assert q.pop() == 1
assert q.pop() == 2
assert q.empty() is True
思路:
使用两个栈来完成
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
from collections import deque
class Stack():
def __init__(self):
self.items = deque()
def push(self, val):
self.items.append(val)
def pop(self):
return self.items.pop()
def top(self):
return self.items[-1]
def __len__(self):
return len(self.items)
def empty(self):
return len(self.items) == 0
class MinStack(object):
def __init__(self):
"""
initialize your data structure here.
"""
self.s = Stack()
self.mins = Stack()
def push(self, x):
"""
:type x: int
:rtype: None
"""
if self.s.empty():
self.s.push(x)
self.mins.push(x)
else:
top = self.mins.top()
if x < top:
self.mins.push(x)
else:
self.mins.push(top)
self.s.push(x)
def pop(self):
"""
:rtype: None
"""
self.mins.pop()
return self.s.pop()
def top(self):
"""
:rtype: int
"""
return self.s.top()
def getMin(self):
"""
:rtype: int
"""
return self.mins.top()
def test():
s = MinStack()
s.push(-2)
s.push(0)
s.push(-3)
assert s.getMin() == -3
s.pop()
print(s.top())
assert s.getMin() == -2
思路:
1、使用栈
2、使用字典外加列表
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
方法一:使用栈
from collections import deque
class Stack():
def __init__(self):
self.items = deque()
def push(self, val):
self.items.append(val)
def pop(self):
return self.items.pop()
def top(self):
return self.items[-1]
def __len__(self):
return len(self.items)
def empty(self):
return len(self.items) == 0
class Solution(object):
def isValid(self, s):
"""
:type s: str
:rtype: bool
"""
if not s:
return True
stack = Stack()
for char in s:
if stack.empty():
stack.push(char)
else:
top = stack.top()
if get_pair(char) == top:
stack.pop()
else:
stack.push(char)
return stack.empty()
def get_pair(char):
if char == ')':
return '('
if char == ']':
return '['
if char == '}':
return '{'
return None
def test_valid_parentheses():
s = Solution()
assert s.isValid('[]') is True
assert s.isValid('{
{[]}}') is True
assert s.isValid('[{]}') is False
方法二:使用字典加列表
class Solution:
def isValid(self, s:str) -> bool:
dic = {'(':')', '[':']', '{':'}'}
stack = ['?']
for c in s:
if c in dic:stack.append(c)
elif: dic[stack.pop()] != c: return False
return len(stack) == 1
思路:利用set的特性,熟悉set里面的方法使用
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
class Solution(object):
def intersection(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
return set(nums1) & set(nums2)
思路:
1、哈希表
2、指针排序(下面未实现)
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
import collections
# 哈希表,时间复杂度O(max(n,m)),空间复杂度O(max(n,m))
class Solution:
def intersect(self, nums1, nums2):
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
m = collections.Counter()
for num in nums1:
m[num] += 1
intersection = list()
for num in nums2:
if (m.get(num, 0)) > 0:
intersection.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return intersection
解决缓存空间不够用的一种常见的高效淘汰策略
思路:
1、题目说O(1)时间复杂度,说明空间复杂度不限,限定了容量。
2、使用字典存储key/value值,查询只需要根据关键字查找所以是O(1)
3、记录key对应的顺序,且要对key进行移位操作,使用**(循环)双向链表**,双向链表移位插入的时间复杂度为O(1)
4、Python内置的collections模块中的OrderedDict类底层是字典+循环双向链表,正好适合此题
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
你是否可以在 O(1) 时间复杂度内完成这两种操作?示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
from collections import OrderedDict
class LRUCache(object):
def __init__(self, capacity:int):
"""
:type capacity: int
"""
self.od = OrderedDict()
self.capacity = capacity
def get(self, key):
"""
:type key: int
:rtype: int
"""
res = self.od.get(key)
if res:
self.od.move_to_end(key)
return res
else:
return -1
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
self.od[key] = value
self.od.move_to_end(key)
if len(self.od) > self.capacity:
self.od.popitem(last=False)
详细讲解:https://www.zhihu.com/question/31412436/answer/683820765
1、理解以下代码
# 这一段代码输出什么?为什么?
def print_num_recursive(n):
if n > 0:
print_num_recursive(n-1)
print(n)
# 1
# 2
# 3
# ...
# n-1
# n
# 如果把print(n) 放在上面则输出什么?
def print_num_recursive(n):
if n > 0:
print(n)
print_num_recursive(n-1)
# n
# n-1
# ...
# 3
# 2
# 1
2、正整数的阶乘
3、汉诺塔问题
思路:
1、纯递归方式,时间复杂度O(2^n)
2、动态规划方式,时间复杂度O(n)
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N,计算 F(N)。
import functools
class Solution:
def fib(self, N: int) -> int:
# 使用递归,时间复杂度O(2**n)
# if N <= 1:
# return N
# return self.fib(N-1) + self.fib(N-2)
# 迭代(动态规划),时间复杂度O(n)
# a, b = 0, 1
# if N <= 1:
# return N
# for _ in range(N-1):
# a, b = b, a+b
# return b
# 递归+动态规划,时间复杂度O(n)
@functools.lru_cache(None)
def fib(self, N: int) -> int:
if N <= 1:
return N
return self.fib(N-1) + self.fib(N-2)
class LinkedListNode():
def __init__(self, value, next=None):
self.value = value
self.next = next
def gen_linked_list(nums):
if not nums:
return None
head = LinkedListNode(nums[0])
prev = head
for i in range(1, len(nums)):
node = LinkedListNode(nums[i])
prev.next = node
prev = node
return head
def print_linked_list(head):
cur = head
while cur:
print('{}->'.format(cur.value), end='')
cur = cur.next
print('nil')
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 迭代 时间复杂度:O(n),空间复杂度:O(1)
# pre = None
# cur = head
# while cur:
# nextnode = cur.next
# cur.next = pre
# pre = cur
# cur = nextnode
# return pre
# 递归,时间复杂度:O(n),空间复杂度:O(1)
if not (head and head.next):
return head
newhead = self.reverseList(head.next)
nextnode = head.next
nextnode.next = head
head.next = None
return newhead
# pytest -s 'filepath'
def test_reverselist():
L = [1,2,3,4,5]
head = gen_linked_list(L)
print()
print_linked_list(head)
pre = Solution().reverseList(head)
print_linked_list(pre)
思路:
1、寻找子问题:当数列有三个元素,可以将第一个数固定,则后面的数继续递归操作,同理数列变成两个元素的话,也是固定第一个数,直到数列只有一个数的时候返回数列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution:
def permute(self, nums):
res = []
def per(nums, beg, end):
if beg == end-1:
res.append(nums[:])
return
for i in range(beg, end):
nums[i], nums[beg] = nums[beg], nums[i]
per(nums, beg+1, end)
nums[i], nums[beg] = nums[beg], nums[i]
per(nums, 0, len(nums))
return res
data表示节点数据,left表示左孩子指针,right表示右孩子指针
class TreeNode:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
先(根)序遍历
中(根)序遍历
后(根)序遍历
深度遍历:递归与非递归方式,使用栈模拟递归过程,实现非递归遍历,因为递归操作本身是因为计算机帮我们完成了入栈和出栈过程
广度遍历(层序遍历):使用list或者队列实现
递归,非递归(使用栈,因为递归操作是),层序遍历(list或队列)三种方式
递归方式
class TreeNode():
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
def preorder(root):
if root is not None:
print(root.data,end=' ')
preorder(root.left)
preorder(root.right)
def inorder(root):
if root is not None:
preorder(root.left)
print(root.data)
preorder(root.right)
def postorder(root):
if root is not None:
preorder(root.left)
preorder(root.right)
print(root.data)
非递归方式(使用栈)(标记法)
from collections import deque
def preorder_use_stack(root):
if not root:
return
s = deque()
s.append((root, False))
while s:
node, visted = s.pop()
if node:
if visted:
print(node.data)
else: # stack是 后进先出。所以先序:根左右出栈 -> 右左根入栈,同理中序和后续
s.append((node.right, False))
s.append((node.left, False))
s.append((node, True))
层序遍历(使用list/队列)
# 使用list完成层序遍历
def layer_order_uselist(root):
# if not root:
# return
# cur_nodes = [root]
# next_nodes = []
# while cur_nodes or next_nodes:
# print(cur_nodes)
# for node in cur_nodes:
# if node.left:
# next_nodes.append(node.left)
# if node.right:
# next_nodes.append(node.right)
# cur_nodes = next_nodes
# next_nodes = []
li = [root]
while li:
n = len(li)
for _ in range(n):
l = li.pop(0)
if l:
print(l.data)
li.append(l.left if l.left else None)
li.append(l.right if l.right else None)
# 使用队列完成层序遍历
def layer_order_usequque(root):
d = deque()
d.append(root)
while d:
node = d.popleft()
if node:
print(node.data)
d.append(node.left if node.left else None)
d.append(node.right if node.right else None)
思路:
1、使用迭代遍历方法实现(栈+标记法)
2、使用递归实现
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
class TreeNode:
def __init__(self, x, left=None, right=None):
self.val = x
self.left = left
self.right = right
# # 递归,后序遍历:左右根
# class Solution:
# def postorder(self, root, res):
# if not root:
# return
# self.postorder(root.left,res)
# self.postorder(root.right,res)
# print(root.val)
# res.append(root.val)
# # 后序遍历:左右根
# def postorderTraversal(self, root: TreeNode):
# res = []
# self.postorder(root, res)
# return res
# 迭代后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode):
stack = deque()
res = []
stack.append((root, False))
while stack:
node, visited = stack.pop()
if node:
if visited:
res.append(node.val)
else: # 由于是用栈存储,所以后序遍历原本时左右根->变成入栈顺序根右左
stack.append((node, True))
stack.append((node.right, False))
stack.append((node.left, False))
return res
思路:
使用list/队列
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
cur_nodes = [root]
next_nodes = []
res = []
vals = [i.val for i in cur_nodes]
res.append(vals)
while cur_nodes or next_nodes:
for node in cur_nodes:
if node.left:
next_nodes.append(node.left)
if node.right:
next_nodes.append(node.right)
if next_nodes:
res.append([i.val for i in next_nodes])
cur_nodes = next_nodes
next_nodes = []
return res
思路
递归:递归出口、子问题、返回值
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
思路:
递归:递归出口、子问题、返回值
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
left = self.maxDepth(root.left) + 1
right = self.maxDepth(root.right) + 1
return max(left, right)
思路:
1、递归出口:序列为空的时候
2、子问题:递归地去构造左右子树
3、返回值:根节点
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder, inorder):
if not preorder:
return None
root_val = preorder[0]
root = TreeNode(root_val) # build root node
root_idx = inorder.index(root_val)
# 计算左右子树的长度
left_len = root_idx
right_len = len(inorder)-root_idx-1
# 递归构造
if left_len:
root.left = self.buildTree(preorder[1:left_len+1], inorder[0:root_idx])
if right_len:
root.right = self.buildTree(preorder[left_len+1:], inorder[root_idx+1:])
return root