【刷题日记】链表,栈和队列

说明:代码没毛病。建议手敲。直接复制可能有对齐之类的问题。

链表

  • 单向链表
  • 双向链表
  • 结点定义
class ListNode:
	def __init__(self,x):
		self.val = x
		self.next = None

栈:

  • 后进先出

队列:

  • 先进先出

这三种数据结构是手撕代码的最高频出现的基础数据结构了

链表相关

题目1:从尾到头打印一个单链表(返回一个list)

def Reverselist(ListNode):
	if not ListNode:
		return []
	lis = []
	while ListNode:
		lis.append(ListNode)
		ListNode = ListNode.next
	return lis[::-1]

题目2:翻转一个单链表,输出反转后的头结点

非递归解法

def ReverseList(pHead):
	if not pHead or not pHead.next:
		return pHead
	# 三个指针:
	# last :前一个结点
	# pHead:当前节点
	# next_one:下一个结点
	last = None 
	while pHead: 
	# 遍历链表直到pHead == None,那么last最终移到反转后的头结点
		next_one = pHead.next # 记录下一个结点位置
		pHead.next = last # 指针反向
		last = pHead # 指针后移
		pHead = next_one  # 指针后移
	return last #返回头结点

递归解法

def ReverseList(head):
	# 递归终止条件
	if not head or not head.next:
		return head
	# 缩小范围递归剩余的链表
	newHead = ReverseList(head.next)
	# 处理没有调整的第一个和第二个结点
	tail = head.next # 找到当前尾结点:旧的头结点head此时仍然指向尾结点
	tail.next = head # 尾结点指向旧头结点
	head.next = None # 旧头结点指向None
	return newHead # 返回新的头结点

题目3:判断一个链表是不是回文结构

思路1:空间 O ( n ) O(n) O(n)
链表入栈。
逐个弹出与原链表比较

def huiwenList(head):
	if not head or not head.next:
		return True
	stack = []
	pHead = head
	while pHead:
		stack.append(pHead)
		pHead = pHead.next
	while stack:
		pHead = stack.pop()
		if pHead.val != head.val:
			return False
		else:
			head = head.next
	return True

思路2:空间 O ( n / 2 ) O(n/2) O(n/2)
链表的后一半入栈(偶数个则后一半。奇数个则跳过中点)
如何找到中点呢?
一个慢指针每次走一步,一个快指针每次走两步。
因为快指针每次走两步,所以判断是否走到最后的条件有两种:
fast.next == None or fast.next.next == None分别对应奇数个结点和偶数个结点
快指针不能继续前进时,慢指针指向表长/2的位置

def huiwenList(phead):
	if not phead or phead.next:
		return True
	slow, fast = phead,phead
	while fast.next or fast.next.next:
		slow = slow.next
		fast = fast.next.next
	stack =[]
	slow = slow.next
	while slow:
		stack.append(slow)
		slow = slow.next
	while stack:
		node = stack.pop()
		if node.val != phead.val:
			return False
		phead = phead.next
	return True

思路3
翻转链表的后半段,与前半段进行比较。空间 O ( 1 ) O(1) O(1),时间 O ( n ) O(n) O(n)

def Reverselist(head):
	if not head or not head.next:
		return headt
	newHead = Reverselist(head.next)
	tail = head.next
	tail.next = head
	head.next =None
	return newHead
def huiwenList(phead):
	if not head or not head.next:
		return True
	slow, fast = phead,phead
	while fast.next or fast.next.next:
		slow = slow.next
		fast = fast.next.next
	stack =[]
	slow = slow.next
	revHead = Reverselist(slow)
	while reveHead: # 如果是奇数个结点,a+1+a个,反转部分是a个,所以revHead先于phead 变成空结点
		if phead.val != revHead.val:
			return False
		revHead = revHead.next
		phead = phead.next
	return True

题目4:如何判断一个链表有环。找到环的入口结点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路1
可以接著一个辅助list,如果list有结点值,则说明有环,没有当前节点值则加入list
如果list没有环,指针轮空时停止。
如果list有环,list中出现当前元素时停止

def EntryNodeOfLoop(pHead):
	if not pHead or not pHead.next:
		return None
	lis = []
	while pHead:
		if pHead in lis:
			return pHead
		else:
			lis.append(pHead)
		pHead = pHead.next

思路2
链表有环:
设置一个快指针,一个慢指针,如果链表有环,则一定会相遇。
入口结点位置确定:
可以证明。两节点相遇后。一个结点回到起点。同速度前进。下次相遇点为入口节点
边界检查:
空结点,单结点,双节点不成环

def EntryNodeOfLoop(pHead):
	if not pHead or not pHead.next or not pHead.next.next:
		return False
	slow = pHead.next
	fast = pHead.next.next
	while slow!=fast:
		if not fast.next or fast.next.next:
			return None
		fast = fast.next.next
		slow = slow.next
	slow = pHead
	while slow!=fast:
		slow = slow.next
		fast = fast.next
	return fast		

题目4.1:判断两个链表有没有公共结点并返回公结点

思路1 O ( n 2 ) O(n^2) O(n2)
遍历第一个链表,检查遍历结点在另一个链表里有没有
这个思路,,代码很短,但是复杂度很高

def FindFirstCommonNode(pHead1, pHead2):
	if not pHead1 or not pHead2:
		return None
	lis = []
	while pHead1:
		lis.append(pHead1)
		pHead1 = pHead1.next
	while pHead2:
		if pHead2 in lis:
			return pHead2
		pHead2 = pHead2.next
	return None

思路2
两个链表相交之后就不会分离
(每个结点只有一个指针,相交之后就呈Y形,而不是X形)
所以两个链表如有不同,出现在Y上的两分叉长度不同

可以先找到一个链表比另一个链表长多少(k)
指针在长链表上先走k步
然后同步遍历到值相同or遍历完毕。

def FindFirstCommonNode(pHead1, pHead2):
	if not pHead1 or not pHead2:
		return None
	p1,p2 = pHead1,pHead2 # 需要两次遍历,不能丢失链表头结点
	# 先走完短链表
	while p1 and p2:
		p1 = p1.next
		p2 = p2.next
	# 然后对长链表计算剩余节点个数
	count = 0
	flag = 1 # 默认p1是长链表
	# 如果p1 是长链表,继续遍历
	if p1:
		while p1:
			count+=1
			p1 = p1.next
	# 如果p2是长链表,修改flag值继续遍历
	elif p2:
		flag = 2
		while p2:
			count+=1
			p2 = p2.next
	# 第二次遍历,先在长链表上走count步
	if flag ==1:
		while count:
			pHead1= pHead1.next
			count -=1
	else:
		while count:
			pHead2 = pHead2.next
			count -=1
	# 然后继续同步遍历
	while pHead1 and pHead2:
		if pHead1.val == pHead2.val:
			return pHead1
		else:
			pHead1 = pHead1.next
			pHead2 = pHead2.next
	# 如果没有找到公共节点
	return None

题目5:找到链表的倒数第k个结点

这个比较简单。设置两个指针,第一个先走k步。
此时两个指针相差k个结点
之后两个指针同步前进。
当前指针走到none时,后指针就是倒数第k个
但是注意:

  • 检查链表合法性
  • 检查k的合法性
def lastK(pHead,k):
	# 检查链表
	if not pHead:
		return None
	p1 = pHead
	p2 = pHead
	for i in range(k):
		# 检查 k
		if p1:
			p1 = p1.next
		else:
			return None
	while p1:
		p1 = p1.next
		p2 = p2.next
	return p2

题目6:删除链表中的重复结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路
题目的重点:

  • 排序链表,说明重复节点连续
  • 不保留重复结点:则不止要记住重复开始的结点,还要记住重复前的一个结点。重复结束的结点。
  • 头结点也可能是重复结点,所以需要先加一个前面的结点。
    代码逻辑:
  1. 检查边界
  2. curNode初始化为头结点,创建一个preNode并指向当前结点curNode
  3. 使用curNode遍历链表:
    如果curNode不是最后一个结点且curNode的值与下一个结点值相同:
    比较下一个结点nextNode和curNode的值:
    + 值相同则继续比较直到找到不同值nextNode 或者nextNode已空:
    preNode指向该nextNode,curNode移到该NextNode
    + 值不同则:preNode和curNode后移
class Solution:
    def deleteDuplication(self, pHead):  
        if not pHead or not pHead.next:
            return pHead
        preHead = ListNode(-1)
        preHead.next = pHead
        preNode = preHead
        curNode = pHead
        while curNode: # 遍历链表
            # 如果当前结点有下一个结点,且值相同
            if curNode.next and curNode.next.val == curNode.val:
                # 记录下一个结点的位置,
                nextNode = curNode.next
                # 从下一个结点开始找到值不同的结点
                while nextNode and nextNode.val == curNode.val:
                    nextNode = nextNode.next
                # 找到值不同的结点后,跳过中间的重复节点curNode到nextNode
                # preNode.next指到这个不同结点,curNode定位到这个结点
                preNode.next = nextNode
                curNode = nextNode
            # 没有下一个结点(curNode.next == None)或者值不相同,两指针顺序后移继续遍历
            else:
                preNode = curNode
                curNode = curNode.next
        return preHead.next

题目7:合并两个排序链表

这个题很简单。和归并排序的归并方法是一样的。
加一个头结点mergeHead。
加一个遍历指针p。
但别忘了每次添加节点后,遍历指针p要后移一下

class Solution:
	def Merge(self, pHead1, pHead2):
        mergeHead = ListNode(90)
        p = mergeHead
        while pHead1 and pHead2:
            if pHead1.val <= pHead2.val:
                p.next =pHead1
                pHead1 = pHead1.next
            else:
                p.next = pHead2
                pHead2 = pHead2.next
            p = p.next #别忘了这一句啊!!!
        if pHead1:
            p.next = pHead1
        if pHead2:
            p.next = pHead2
        return mergeHead.next

题目8:复制一个复杂链表

题目:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
结点定义如下:

class RandomListNode:
    def __init__(self, x):
        self.label = x
        self.next = None
        self.random = None

思路
一个是复制,一个是拆分。

class RandomListNode:
	def __init__(self,x):
		self.val = x
		self.next = None
		self.random = None
class Solution:
	def Clone(self,pHead):
		# 合法性检查
		if not pHead:
			return None
		# 复制链表主链
		p = pHead
		while p:
			# 复制当前结点
			copy = RandomListNode(p.val)
			org_next = p.next
			p.next = copy
			copy.next = org_next
			# 访问下一个结点
			p = org_next
		# 处理random指针:不做复制,主链random指针指向的结点,random链指向相应的复制的结点。
		p = pHead
		# 重新遍历链表
		while p:
			if p.random:
				# p.next是复制的结点,给复制的结点的random指针赋值
				# p.random是主链random指向的结点,p.random.next是相应的复制的结点
				p.next.random = p.random.next
			p = p.next.next
		# 把复制的链表剥离:
		pFirst = pHead
        pSecond = pHead.next
        while (pFirst.next):
            temp = pFirst.next
            pFirst.next = temp.next
            pFirst = temp
        return pSecond
        '''这样写会错,p.next.next可能会空!!!
		p = pHead.next
		newHead = p
		while p:
			nextNode = p.next.next
			p.next = nextNode
			p = nextNode
		return newHead
		'''

栈和队列相关

题目1:两个栈实现一个队列

:先入后出
队列:先入先出
所以:
栈和队列元素进入的方式都是一样的
出队不同:
只需要将一个栈的出栈顺序反过来–>
将元素弹出并压入另一个–>
那么另一个栈的栈顶元素就是队首元素了
所以,需要出队时:
如果另一个栈不是空栈,直接弹出栈顶元素。
如果是空栈。就先将第一个栈里的元素出栈,压入第二个栈。
然后再弹出栈顶元素。
如果第一个栈也是空的。则队列已空。

pop()逻辑:
两个栈,一个入队栈,一个中转栈

  1. 检查中转栈空不空:
    • 不空:中转站出栈一个元素
    • 空: 检查入队栈空不空
      • 空: 返回None
      • 不空:入队栈全弹出到中转栈,然后中转栈出栈一个元素
class Solution:
	def __init__(self):
		self.stack1 = []
		self.stack2 = []
	def push(self,node):
		self.stack1.append(node)
	def pop(self):
		if not self.stack2:
			if not self.stack1:
				return None
			else:
				while self.stack1:
					self.stack2.append(self.stack1.pop())
		return self.stack2.pop()

题目2:两个队列实现一个栈

题目3:实现包含min函数的栈

class Solution:
    def __init__(self):
    	# 一个堆栈
        self.stack =[]
        # 一个负责保存最小值的栈
        self.min_stack =[]
    def push(self, node):
    	# 基本压栈
        self.stack.append(node)
        # 如果最小值栈为空或者压栈元素小于等于最小值栈顶:压栈元素入栈
        if not self.min_stack or self.min_stack[-1]>=node:
            self.min_stack.append(node)
    def pop(self):
    	# 如果栈空
        if not self.stack:
            return None
        else:
        	# 否则正常出栈
            node = self.stack.pop()
            # 如果最小值栈顶正好是出栈元素,最小值栈顶也出栈
            if node == self.min_stack[-1]:
                self.min_stack.pop()
            # 返回出栈元素
            return node
    def top(self):
    	# 如果栈空
        if not self.stack:
            return None
        else:
        # 否则返回栈顶
            return self.stack[-1]

题目4:给一个栈的压入序列1,判断序列2是不是弹出序列

def IsPopOrder(pushV, popV):
	if not pushV and not popV:
		return True
	if len(pushV)!=len(popV):
		return False
	stack =[]
	for item in pushV:
		stack.append(item)
		# 栈顶元素等于出栈序列第一个元素,就栈出栈,序列出一个
		while len(stack) and stack[-1] == popV[0]:
			stack.pop()
			popV.pop(0)
	if len(stack):
		return False
	else:
		return True
		
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值