# 本节讲的内容是链表和双链表结构,以及和链表相似度很高的二叉树结构
import sys
class LinkNode(object):
def __init__(self, value=None):
self.value = value
self.next = None
def __repr__(self):
return str(self.value)
class DoubleLinkNode(object):
def __init__(self, value=None):
self.value = value
self.next = None
self.last = None
def __repr__(self):
return str(self.value)
class TreeNode(object):
def __init__(self, value=None):
self.value = value
self.left = None
self.right = None
def __repr__(self):
return str(self.value)
# 以上便是三个数据结构的基本节点结构,不难发现三者的相似度之高
# 逆序链表
# 单链表的逆序:双指针,一个指向当前节点,一个指向下一个节点。记录p2的下一个节点,让p2的next指向p1。当p2为None意味着p1是最后一个节点,跳出循环。
# 双链表的逆序:双指针,一个指向上一个节点,一个指向当前节点。记录head的下一个节点,让head的next指向p1,last指向下一个节点。当前节点是None意味着p1是最后一个节点。
def inverted_list(head):
if head.next == None:
return head
p1 = head
p2 = head.next
while p2 != None:
t = p2.next
p2.next = p1
p1 = p2
p2 = t
head.next = None
return p1
def inverted_Double_list(head):
if head.next == None:
return head
p1 = None
while head != None:
p2 = head.next
head.next = p1
head.last = p2
p1 = head
head = p2
return p1
# 打印两个有序链表的公共部分
# 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
# 解题思路:双指针,一个指向head1,一个指向head2.哪个value小就移动谁。
def find_common_part(head1, head2):
ans = []
while head1 != None and head2 != None:
if head1.value < head2.value:
head1 = head1.next
elif head1.value > head2.value:
head2 = head2.next
else:
ans.append(head1.value)
head1 = head1.next
head2 = head2.next
return ans
# 判断一个链表是否为回文结构
# 给定一个单链表的头节点head,请判断该链表是否为回文结构。
# 解题思路:设置快慢指针,快指针走两个,慢指针走一个。
# 然后慢指针的next指向None,后面的全部逆序。再从head和fast一起往中间走,直到head指向None,如果中间用一个相等就返回False
def isPalindrome(head):
fast = head
slow = head
while fast.next != None and fast.next.next != None:
fast = fast.next.next
slow = slow.next
if fast.next != None:
# 偶数的话,慢指针停在中点的前一个
fast = fast.next
t = slow.next
slow.next = None
while t != None:
p = t.next
t.next = slow
slow = t
t = p
while True:
if head == None:
return True
if head.value != fast.value:
return False
head = head.next
fast = fast.next
# 将单链表按照num划分成左边小、中间相等、右边大的形式
# 定义六个指针每两个代表小于、等于、大于的三条链表,遍历一边链表再把三条链表串起来
def list_partition(head, num):
lessH = LinkNode()
lessT = LinkNode()
equalH = LinkNode()
equalT = LinkNode()
moreH = LinkNode()
moreT = LinkNode()
while True:
if head == None:
break
if head.value < num:
if lessH.value == None:
# 如果链表为空,就把头尾都定为这个值,因为是单链表所以不用担心值重复,因为这里是自己指向自己
lessH = head
lessT = head
else:
# 如果已经有值了,就把尾连向head
lessT.next = head
lessT = head
elif head.value == num:
if equalH.value == None:
equalH = head
equalT = head
else:
equalT.next = head
equalT = head
elif head.value > num:
if moreH.value == None:
moreH = head
moreT = head
else:
moreT.next = head
moreT = head
if lessH.value != None:
lessT.next = equalH
equalT.next = moreH
return lessH
elif equalH.value != None:
equalT.next = moreH
return equalH
else:
return moreH
# 复制含有随机指针节点的链表
# rand指针是单链表结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向None。请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点
# 解题思路:在原链表的两个节点之间,都放上复制节点,例如123,变成112233.
# 从头遍历节点,每次跳两个保证遍历的是原链表,让next节点(即复制节点)的rand指针指向原链表节点的rand指针的next,保证了复制节点指向的也是复制节点
# 再从头遍历,断开原节点和新节点,让新节点之间的next指针相连,返回复制节点的头
class Rand_list(object):
def __init__(self, value=None):
self.value = value
self.next = None
self.rand = None
def __repr__(self):
return str(self.value)
def copy_randlist(head):
if head.next == None:
cp1 = head
cp1.next = head.next
cp1.rand = head.rand
return cp1
head2 = head
while head2 != None:
cur = head2.next
p = LinkNode(head2.value)
p.next = head2.next
head2.next = p
head2 = cur
head2 = head
while head2 != None:
if head2.rand != None:
head2.next.rand = head2.rand.next
else:
head2.next.rand = None
head2 = head2.next.next
cp1 = head.next
while head.next.next != None:
head2 = head.next.next
head.next.next = head.next.next.next
head = head2
return cp1
# 两个单链表相交的一系列问题
# 给定两个可能有环可能无环的单链表,头节点head1和head2.实现一个函数,如果两个链表相交,返回相交的第一个节点。如果不相交返回None
# 解题思路:
# 1.首先要判断是否成环:用快慢执政遍历链表,如果快指针到了None则是无环链表。如果快慢指针重合了,将快指针指到head,两个指针一次遍历一个,再重合的位置就是入环点
# 2.如果两个都是无环:如果相交,那么肯定是呈现为"Y"字形式,否则交点就有两个next指针.把两个链表长度相减,长链表先走n步,再两个链表一起走,都走到同一个位置就是交点
# 3.如果一个是有环一个无环:不相交,因为如果相交肯定会有两个next指针
# 4.如果两个都是有环链表:如果相交肯定是公用一个环,否则交点会有两个next指针.
# 如果两个入环点相同,意味着在入环之前结构就像情况2的"Y"字结构一样.按照情况二解决
# 如果入环点不同,从一个入环点往下走,如果在回到这个入环点之前遇到了另外一个入环点,则这就是相交点,否则不相交
def isloop(head):
p1 = head
p2 = head
count = 0
while True:
if p2.next == None or p2.next.next == None:
if p2.next == None:
count += 1
else:
count += 2
return False, None, count
p1 = p1.next
p2 = p2.next.next
count += 2
if p1 == p2:
break
p2 = head
while True:
p1 = p1.next
p2 = p2.next
if p1 == p2:
return True, p1, 0
def isintersect_list(head1, head2):
isloop1, inloop1, lenlink1 = isloop(head1)
isloop2, inloop2, lenlink2 = isloop(head2)
if isloop1 != isloop2:
return None
if isloop1 == False:
n = lenlink2 - lenlink1
if n > 0:
cur2 = head2
cur1 = head1
for i in range(n):
cur2 = cur2.next
else:
cur1 = head1
cur2 = head2
for i in range(-n):
cur1 = cur1.next
while cur1 != cur2:
if cur1 == None or cur2 == None:
return None
cur1 = cur1.next
cur2 = cur2.next
return cur1
else:
if inloop1 == inloop2:
cur1 = head1
cur2 = head2
lenlink1 = 1
lenlink2 = 1
while cur1 != inloop1:
cur1 = cur1.next
lenlink1 += 1
while cur2 != inloop2:
cur2 = cur2.next
lenlink2 += 1
n = lenlink2 - lenlink1
if n > 0:
cur2 = head2
cur1 = head1
for i in range(n):
cur2 = cur2.next
else:
cur1 = head1
cur2 = head2
for i in range(-n):
cur1 = cur1.next
while cur1 != cur2:
if cur1 == inloop1 or cur2 == inloop2:
break
cur1 = cur1.next
cur2 = cur2.next
return cur1
else:
cur1 = inloop1
while True:
cur1 = cur1.next
if cur1 == inloop2:
return inloop2
if cur1 == inloop1:
return None
'''
最后的链表相交问题是链表问题的精髓,链表问题实际上就是需要考虑不同情况下的取舍问题.
下面加入二叉树内容
'''
# 用递归方法是先二叉树的先序\中序\后序遍历
# 先序\中序\后序的意思就是,先\中\后次序遍历树的头节点
# 所以先序:中-->左-->右
# 中序:左-->中-->右
# 后序:左-->右-->中
def recursion_preorder(root, res):
if root == None:
return
res.append(root)
recursion_preorder(root.left, res)
recursion_preorder(root.right, res)
return res
def recursion_inorder(root, res):
if root == None:
return
recursion_inorder(root.left, res)
res.append(root)
recursion_inorder(root.right, res)
return res
def recursion_postorder(root, res):
if root == None:
return
recursion_postorder(root.left, res)
recursion_postorder(root.right, res)
res.append(root)
return res
# 用非递归方法是先二叉树的先序\中序\后序遍历
# 先序遍历:因为是中左右的打印方式,所以按照右左的顺序压入栈中,每次弹出头的时候都执行,就能保证先打印头节点再打印左树再打印右树
# 中序遍历:因为是左中右的打印方式,所以一次性把左边界压入栈中,每次弹出的时候重复以上
# 后序遍历:因为是左右中的打印方式,所以按照左右(因为后面要放到另外一个栈,所以按照预取顺序放入)的顺序压入栈中,然后把弹出的头节点压倒另一个栈里
def preorder(root, res):
stack = []
stack.append(root)
res = []
while stack != []:
root = stack.pop()
res.append(root)
if root.right != None:
stack.append(root.right)
if root.left != None:
stack.append(root.left)
return res
def inorder(root, res):
stack = []
while root != None:
stack.append(root)
root = root.left
while stack != []:
root = stack.pop()
res.append(root)
if root.right != None:
root = root.right
while root != None:
stack.append(root)
root = root.left
return res
def postorder(root, res):
stack1 = []
stack2 = []
stack1.append(root)
while stack1 != []:
root = stack1.pop()
stack2.append(root)
if root.left != None:
stack1.append(root.left)
if root.right != None:
stack1.append(root.right)
while stack2 != []:
res.append(stack2.pop())
return res
# 总结一下:把中看作为第一轮压栈弹出的东西就很好理解了
# 先序:想得到的顺序是中左右,所以弹出节点的时候先压右在压左就好了.
# 中序:想得到的顺序是左中右,意味着在弹出之前就头节点之前要先得到左孩子,所以就要一次性把所有的左孩子压入栈,这样弹回的顺序就是左中.
# 那么剩下右孩子怎么取到呢,在得到弹出的头节点的时候,把右孩子压入栈.同样的,自然也需要把右孩子的所有的左孩子都要压入栈.
# 后序:因为我们想得到左右中,怎么样才能让最先弹出的反而最后得到呢,那就是栈.
# 所以我们把第一个栈弹出的放到另一个栈里,剩下的预期顺序为先左再右.所以第二个栈里的关系应该是先压右再压左;所以压入第一个栈的顺序就是先压左再压右
# 层次遍历(宽度优先遍历),顺便求宽度
# 因为是一层一层从左往右遍历,所以使用队列
def level(root, res):
queue = []
queue.append(root)
width = []
expectcount = 1
nowcount = 1
nextcount = 0
while queue != []:
root = queue.pop(0)
res.append(root)
if root.left != None:
queue.append(root.left)
nextcount += 1
if root.right != None:
queue.append(root.right)
nextcount += 1
if nowcount >= expectcount:
width.append(nowcount)
expectcount = nextcount
nowcount = 1
nextcount = 0
else:
nowcount += 1
return res, width
# 搜索二叉树(Binary Search Tree)
# 搜索二叉树的 left<root<right.注意:右树所有的节点都要大于头节点,左树所有的节点都要小于头节点
# 解题思路: 观察搜索二叉树的大小关系,很容易发现正好是中序遍历的顺序.
def isBST(root):
stack = []
while root != None:
stack.append(root)
root = root.left
root = stack.pop()
criterion = root.value
while stack != []:
root = stack.pop()
if criterion >= root.value:
return False
criterion = root.value
if root.right != None:
root = root.right
while root != None:
stack.append(root)
root = root.left
return True
def recursion_isBST(root, themin_by_isBST):
if root == None:
return True
left = recursion_isBST(root.left, themin_by_isBST)
if left == False:
return False
if root.value <= themin_by_isBST:
return False
else:
themin_by_isBST = root.value
return recursion_isBST(root.right, themin_by_isBST)
# 完全二叉树(complete binary tree)
# 完全二叉树:最后一层是从左到右一次变满的,其他层都是满的.(任意节点有右无左则Flase,如果是叶子节点,则后续所有节点都不能有左右孩子)
def isCBT(root):
queue = []
isNotLeaf = True
queue.append(root)
while queue != []:
root = queue.pop(0)
if isNotLeaf:
if root.left != None:
queue.append(root.left)
if root.right != None:
if root.left != None:
return False
if root.left == None:
queue.append(root.left)
if root.right == None:
isNotLeaf = False
else:
if root.left != None or root.right != None:
return False
return True
'''
二叉树套路:左右子树分别获取一个值,对这个值做处理,再判断(树形dp的雏形)
'''
# 满二叉树
# 满二叉树:每一层的节点个数都是满的
def isFBT(root):
if root == None:
return True, 0
left = isFBT(root.left)
right = isFBT(root.right)
if left[0] == False or right[0] == False:
return False, -1
if left[1] != right[1]:
return False, -1
return True, left[1] + right[1] + 1
# 平衡二叉树
# 平衡二叉树左右的高度差不超过1
def isBBT(root):
if root == None:
return True, 0
left = isBBT(root.left)
right = isBBT(root.right)
if left[0] and right[0] and abs(left[1] - right[1]) < 2:
return True, max(left[1], right[1]) + 1
else:
return False, -1
# 搜索二叉树
# 左侧是搜索二叉树,右侧也是搜索二叉树,左侧的最大值小于头节点,右侧的最小值大于头节点
def isBST2(root):
if root == None:
return None
left = isBST2(root.left)
right = isBST2(root.right)
TORF = True
themin = root.value
themax = root.value
if left != None:
themin = min(left[1], themin)
themax = max(left[2], themax)
if left[0] == False or left[2] >= root.value:
TORF = False
if right != None:
themin = min(right[1], themin)
themax = max(right[2], themax)
if right[0] == False or right[1] <= root.value:
TORF = False
return [TORF, themin, themax]
# 查找O1、O2的最低公共点
# 解题思路:有两种情况,一个是O1在O2的子树上,一个是不在O2的子树上。从底层出发,如果遇到了的话就带着信息往上走,直到两个都找到
def common_point(root, O1, O2):
if root == None or root == O1 or root == O2:
return root
left = common_point(root.left)
right = common_point(root.right)
if left != None and right != None:
return root
if left != None:
return left
else:
return right
# 查找一个节点的后继节点(中序遍历的后一个节点),节点拥有父节点信息
# 左中(左中(左中))
# 实际上这道题考的就是为中序遍历的理解,查找一个节点的后续节点,只有两种可能,一个是自己是中,一个是自己是左
# 如果自己有右树,意味着自己是中,返回右树的最左下节点
# 如果自己没有右树且自己是父节点的左节点,意味着是自己是左,返回自己的父节点
# 如果自己没有右树且自己是父节点的右节点,意味着自己是在一棵树的右下位置,这时候这棵树中的其他节点都已经被遍历过了。需要一直往上找,找到一个符合情况二的节点
class father_treenode(object):
def __init__(self, value, father):
self.value = value
self.left = None
self.right = None
self.father = father
def find_nextnode(root):
if root.right != None:
root = root.right
while root.left != None:
root = root.left
return root
else:
while root.father.left != root and root.father == root:
root = root.father
if root.father == root:
return None
else:
return root.father
# 二叉树的序列化和反序列化
# 本体所有的可以套用所有的遍历方法,只要序列号和反序列化按照选择同一种遍历方法
def tree_encode(root, res):
if root == None:
res.append("#")
return
res.append(root.value)
tree_encode(root.left, res)
tree_encode(root.right, res)
return res
def decode(res):
node = res.pop(0)
if node == "#":
return
node = TreeNode(node)
left = decode(res)
right = decode(res)
node.left = left
node.right = right
return node
# 折纸问题
# 把一段纸条竖着放在桌子上,然后从纸条的下边向上对折一次,压出折痕后展开。把这种折痕叫做凹折痕。
# 请回答出从下向上折n次后的折痕
# 解题思路:画图可知,每一次对折实际上就是叶节点的一个分支。每一次分支都是上凹下凸.而且按照从上往下的顺序读取,可以发现正好是中序遍历的顺序
def paper_crease (i,n,isconvex,res):
if i>n:
return
paper_crease(i+1,n,False,res)
if isconvex==False:
res.append("凹")
else:
res.append("凸")
paper_crease(i+1,n,True,res)
return res