欢迎关注微信公众号:简说Python
关注后回复:1024,可以领取精选编程学习电子书籍。
这两天和几个朋友组了个互相督促学习群,想着督促一下自己学习,也督促自己的原创输出,其实很多时候都是懒,真不是没有东西可以写了,这不,我在我的免费知识星球简说编程
里开了个新的标签日常编程问题
,后面我会把自己学习工作中遇到的一些问题和解决方法记录到里面,有些可以扩展的点,我会写到微信公众号里。
我定的目标是:
我简单写了个规则,大家说可以,然后,我们就开始吧,我习惯把该做的事情提前一天做(如果有时间的话)。
今天给大家分享的书籍《Python程序员面试算法宝典》第一章第十小节:删除给定结点,而且只给该结点和第一章第十一小节:判断两个单链表(无环)是否交叉,并找出交叉结点。
如果你是第一次看,也许,你可以看看本系列下面的文章:
Smaller And Smarter Python数据结构:链表逆转
Smaller And Smarter Python数据结构:删除无序链表重复结点
Smaller And Smarter Python数据结构:链表相加
Smaller And Smarter Python数据结构:链表进行重新排序
Smaller And Smarter Python数据结构:链表倒数第K个元素+检测单链表环
Smaller And Smarter Python数据结构:翻转链表相邻结点
Smaller And Smarter Python数据结构:翻转链表k个相邻结点
Smaller And Smarter Python数据结构:合并两个有序链表
"""
目标:写一段程序,删除给定结点,而且只给该结点
例如:
初始化链表-> 1->2->3->4->5->6
输入 结点 3
删除后链表-> 1->2->4->5->6
输出 给定结点3已删除
Goal: write a program, delete the given node, and only give the node
For example:
Initialize the list - > 1 - > 2 - > 3 - > 4 - > 5 - > 6
Input node 3
Delete the linked list - > 1 - > 2 - > 4 - > 5 - > 6
Output given node 3 deleted
"""
首先我们写好链表的基本操作,在a_0_base.py
文件中,目前包含对链表的定义类,初始化函数,遍历函数。(带头结点)
# -*- coding: utf-8 -*-
"""
@author = 老表
@date = 2019-10-19
@个人微信公众号 : 简说Python
"""
# 链表数据结构定义
class ListNode:
def __init__(self, x):
self.data = x
self.next = None
class ListOperation:
# 根据链表数据初始化链表
@staticmethod
def init_list(n_list):
# 初始化一个头指针
head = ListNode("head")
cur = head
for i in n_list:
node = ListNode(i)
cur.next = node
cur = node # 相当于 cur = cur.next,后移
return head
# 遍历链表,带头结点
@staticmethod
def ergodic_list(head):
cur = head.next
while cur:
print(cur.data)
cur = cur.next
# 获取链表长度
@staticmethod
def get_len_list(head):
cur = head.next
len_list = 0
while cur:
len_list = len_list + 1
cur = cur.next
return len_list
开始程序前需提前导入前面写好的链表基本操作包和结点数据结构,在Linked_list的a_0_base.py
中。
from Linked_list.a_0_base import ListOperation
"""
Method One : 复制删除法
简单分析:因为题目只给了一个结点,然后要求是把这个结点从链表中删除,
一般我们要删除链表的一个结点,肯定是遍历找到这个结点的前驱结点,让
前驱结点指向该结点的后继结点即可,但,我们现在只有一个待删除的结点。
核心思路:比如给定链表p,如果p为尾结点,无法删除,因为找不到前驱结点,
否则,让p的数据域等于p.next的数据域,然后删除p.next结点。
时间复杂度:O(1)
空间复杂度:O(1)
"""
def delete_given_node_one(p):
if not p.next:
# p为链表尾结点
return False # 无法删除
copy_node = p.next # 记录p结点下个结点
p.data = copy_node.data # 复制结点数据域
p.next = copy_node.next # 删除p结点下个结点
return True # 返回结果
# 当然,也许还有别的方法,比如建一个辅助的链表
# 欢迎你说出你的想法
# 程序入口,测试函数功能
if __name__ == "__main__":
list_data = [1, 2, 3, 4, 5, 6, 7] # 链表初始化数据
head = ListOperation.init_list(list_data) # 初始化链表,带头结点
ListOperation.ergodic_list(head) # 未操作前,遍历打印链表
p = ListOperation.get_random_node(head) # 获取一个随机结点
print("待删除结点数据域为:", p.data)
result = delete_given_node_one(p) # 测试方法一
if result:
print("结点已删除")
else:
print("尾结点无法删除")
print("---------------------")
ListOperation.ergodic_list(head) # 操作后,遍历打印链表
"""
目标:写一段程序,判断两个单链表(无环)是否交叉,并找出交叉结点
例如:
输入-> A->B->C->D->5->6->7
输入-> 1->4->5->6->7
输出 5
Goal: Write a program to judge whether two single chain tables (acyclic) cross, and find out the cross node
For example:
Input - > A - > B - > C - > D - > 5 - > 6 - > 7
Input - > 1 - > 4 - > 5 - > 6 - > 7
Output 5
"""
首先我们写好链表的基本操作,在a_0_base.py
文件中,目前包含对链表的定义类,初始化函数,遍历函数。(带头结点)
# -*- coding: utf-8 -*-
"""
@author = 老表
@date = 2019-10-19
@个人微信公众号 : 简说Python
"""
# 链表数据结构定义
class ListNode:
def __init__(self, x):
self.data = x
self.next = None
class ListOperation:
a = 1
# 根据链表数据初始化链表
@staticmethod
def init_list(n_list):
# 初始化一个头指针
head = ListNode("head")
cur = head
for i in n_list:
node = ListNode(i)
cur.next = node
cur = node # 相当于 cur = cur.next,后移
return head
# 根据链表数据初始化一个有环的链表
@staticmethod
def init_ring_list(n_list):
# 初始化一个头指针
head = ListNode("head")
cur = head
for i in n_list:
node = ListNode(i)
cur.next = node
cur = node # 相当于 cur = cur.next,后移
cur.next = head.next.next.next.next # 随意的,让最后一个结点指向第四个结点
return head
# 遍历链表
@staticmethod
def ergodic_list(head):
# print(head.data)
cur = head.next
while cur:
print(cur.data)
cur = cur.next
# 获取链表长度
@staticmethod
def get_len_list(head):
cur = head.next
len_list = 0
while cur:
len_list = len_list + 1
cur = cur.next
return len_list
开始程序前需提前导入前面写好的链表基本操作包和结点数据结构,在Linked_list的a_0_base.py
中。
from Linked_list.a_0_base import ListOperation
"""
Method One : 借助辅助空间
核心思想:首先遍历链表1,将链表1的每个结点都存储到字典中,作为key,
然后遍历链表2,判断链表2中的结点是否在字典中,如果不存在,则两个链表
不相交,否则两个链表相交,相交结点即为链表2遍历到的结点。
不修改原始链表
时间复杂度:O(N) N = n1+n2
空间复杂度:O(n1)
"""
def links_cross_one(head1, head2):
# 有链表为空,则肯定不相交
if not head1.next:
return None # 直接返回None
if not head2.next:
return None # 直接返回None
cur_node1 = head1.next # 记录第一个结点,后面做前驱结点
dict_all = {cur_node1: "node"} # 直接把第一个结点加入字典
while cur_node1.next: # 遍历链表1,将结点加入字典
cur_node1 = cur_node1.next # 后移遍历
dict_all[cur_node1] = "node" # 将该结点加入字典
cur_node2 = head2.next
while cur_node2: # 遍历链表2,判断是否相交
if cur_node2 in dict_all: # 判断遍历结点是否在字典中
return cur_node2 # 在字典中,则两链表相交,相交结点即为当前遍历到的结点
cur_node2 = cur_node2.next # 后移,继续遍历
return None # 遍历完成,没有触发相交判断,则说明不相交,返回None
"""
Method Two :首尾连接法
核心思想:将两个链表连接,然后判断连接后的链表是否存在环,
如果有环的话,说明两个链表是相交。
会修改原始链表
时间复杂度:O(N) N = n1 + n2
空间复杂度:O(1)
"""
# 辅助函数:判断链表是否有环
def check_ring_two(head):
if not head.next: # 空链表肯定不存在环
return None # 直接返回None
fast_node = head.next # 快指针,初始值为第一个结点
slow_node = head.next # 慢指针,初始值为第一个结点
while slow_node: # 遍历查找
slow_node = slow_node.next # 慢指针,每次位移一
fast_node = fast_node.next.next # 快指针,每次位移二
if slow_node == fast_node: # 判断快慢指针是否相同
return slow_node # 相同,则有环,返回相遇结点
return None # 否则无环,返回None
# 辅助函数:找出有环链表的环入口结点
def get_ring_node(head, meet_node):
first_node = head.next # 链表第一个结点
while first_node != meet_node: # 遍历链表,查找环入口
first_node = first_node.next
meet_node = meet_node.next
'''
为什么这样做可以找到环入口?
1.在我们判断链表是否有环时,有环情况会返回快慢指针相遇结点,
注意快指针比慢指针快一倍,快指针遍历圈数对于1,才有可能快慢
指针相遇,相遇时在环内
2.相遇结点到环入口的距离和第一个结点到环入口的距离(顺序遍历)
是相等的
3.如果不明白,建议在纸上把整个过程画出来
'''
return first_node # 返回环入口,即两链表相交结点
def links_cross_two(head1, head2):
# 有链表为空,则肯定不相交
if not head1.next:
return None # 直接返回None
if not head2.next:
return None # 直接返回None
cur_node = head1.next # 获取链表1的第一个结点
while cur_node.next: # 遍历链表1,找到链表1的尾结点
cur_node = cur_node.next # 后移遍历
cur_node.next = head2.next # 让链表1的尾结点指向链表2的第一个结点(连接链表1和链表2)
# 第一步:检查是否有环
ring_is = check_ring_two(head1)
if ring_is: # 有环说明原来链表相交
# 第二步:找出环的入口(即原两链表的相交结点)
cross_node = get_ring_node(head1, ring_is)
return cross_node # 返回找到的相交结点
return None # 不相交
"""
Method Three :尾结点法
核心思想:如果两个链表相交,则尾结点必然相同(一画便知),
所以可以先遍历两个链表,记录链表长度,比较尾结点是否相同,
不相同,则不相交,否则,两链表相交,再次遍历两链表,长链
表先出发(n1-n2)步,然后短链表也开始遍历,两链表遍历到结
点相同时,即为相交结点。
时间复杂度:O(N)
空间复杂度:O(1)
"""
def links_cross_three(head1, head2):
# 有链表为空,则肯定不相交
if not head1.next:
return None # 直接返回None
if not head2.next:
return None # 直接返回None
cur_node1 = head1.next # 获取链表1第一个结点
cur_node2 = head2.next # 获取链表2第一个结点
n1 = 1 # 记录链表1长度
n2 = 1 # 记录链表2长度
while cur_node1.next: # 遍历链表1
cur_node1 = cur_node1.next
n1 = n1 + 1
while cur_node2.next: # 遍历链表2
cur_node2 = cur_node2.next
n2 = n2 + 1
# print(n1,n2)
if cur_node1 != cur_node2: # 链表1与链表2尾结点不一样
return None # 说明两个链表不相交
# 否则,两个链表相交
if n1 > n2: # 链表1长
while n1 - n2 > 0: # 链表长的先遍历
head1 = head1.next
n1 = n1 - 1
else: # 链表2长
while n2 - n1 > 0:
head2 = head2.next
n2 = n2 - 1
# print(head1.data)
# print(head2.data)
while head1 != head2: # 一起遍历,查找相交结点
head1 = head1.next # 后移遍历
head2 = head2.next # 后移遍历
return head1 # 返回找到的相交结点
# 当然,也许还有别的方法,比如建一个辅助的链表
# 欢迎你说出你的想法
# 程序入口,测试函数功能
if __name__ == "__main__":
list_data1 = [1, 2, 3, 4, 5, 6, 7, 8] # 链表1初始化数据
list_data2 = ["A", "B", "C", "D"] # 链表2初始化数据
head1 = ListOperation.init_list(list_data1) # 初始化链表1,带头结点
head2 = ListOperation.get_random_link(list_data2, head1) # 初始化链表2,带头结点,与链表1相交
ListOperation.ergodic_list(head1) # 未操作前,遍历打印链表1
print("___________________________")
ListOperation.ergodic_list(head2) # 未操作前,遍历打印链表2
# result = links_cross_one(head1, head2) # 测试方法一
# result = links_cross_two(head1, head2) # 测试方法二
result = links_cross_three(head1, head2) # 测试方法一
print("---------------------")
if result:
print("链表1与链表2相交,相交结点为:", result.data)
else:
print("链表1与链表2不相交")
本文代码思路部分来自书籍《Python程序员面试宝典》,书中部分代码有问题或未提供代码,文中已经修改过了,并添加上了丰厚的注释,方便大家学习,后面我会把所有内容开源放到Github上,包括代码,思路,算法图解(手绘或者电脑画),时间充裕的话,会录制视频。
希望大家多多支持。
大家好,我是老表
觉得本文不错的话,转发、留言、点赞,是对我最大的支持。
欢迎关注微信公众号:简说Python
关注后回复:1024,可以领取精选编程学习电子书籍。