1、问题描述
给定两个有序链表的头指针head1和head2,打印两个 链表的公共部分。
**注意:**这里是有序链表,很关键。因此其实就是前面所学的数组部分的merge思想
2、python描述
'''
问题一:打印两个有序链表的公共部分
1。生成两个有序链表
2.实现有序链表的公共部分寻找(同mergesort)
'''
class LNode:
'''
产生结点类
为形成链表做准备
'''
def __init__(self, elem, next_=None):
self.elem = elem #这个是用来形成结点的,结点里有什么?元素,和 next指针。因此写的时候考虑这两个属性即可
self.next = next_
class Llist:
'''
基于节点类LNode,
定义单链表对象类
实现时 需要一个指针变量p,用来标记是否到达链尾
'''
def __init__(self):
self._head = None #这里LList对象的_head域作为对象的内部表示,不希望外部使用。
#python属于这种情况的域按照习惯用单个下划线开头的名字命名
def isEmpty(self):
'''
判断该单链表是否为空
'''
return self._head is None
def prepend(self, elem):
'''
在单链表的表头添加元素 函数
'''
self._head = LNode(elem, self._head)
def pop(self):
'''
单链表的弹出操作
删除表头结点 并 返回这个结点里的数据
'''
if self._head is None:
print('该链表已经为空') #其实这里应该是引发异常
e = self._head.elem
self._head = self._head.next
return e
def append(self, elem):
'''
后端操作,在链表的最后插入元素。
必须先找到链表的最后一个结点
其实现首先是一个扫描循环,找到相应结点后把包含新元素的结点插入在其后
'''
if self._head is None:
self._head = LNode(elem) #此时表示没有头结点,因此则可以把新的结点直接作为头结点
return
p = self._head
while p.next is not None:
p = p.next #这里第一次写的是self._head.next 这是不对的,因为p是指针变量,可以一直变,但是写成self._head.next就出不去循环了!!!
p.next = LNode(elem)
def pop_last(self):
'''
删除表中最后元素的操作
两个特殊情况:
1.如果表空没有可返回的元素时应引发异常
2.表中只有一个元素的情况需要特殊处理,因为这是应该修改表头指针,并且不存在p.next.next变量
'''
if self._head is None:
print('空表')
p = self._head
if p.next is None: #只有一个头结点
e = p.elem
self._head = None
return e
while p.next.next is not None:
p = p.next
e = p.next.elem
p.next = None
return e
def find(self, pred):
'''
找到满足给定条件的表元素
该方法返回第一个满足谓词(pred)的表元素
'''
p = self._head #这里可以明白为什么要引入一个p变量,其实也就是一个扫描指针,用来遍历链表。随时改变值
while p is not None:
if pred(p.elem):
return pred(p.elem)
p = p.next
def printAll(self):
'''
打印链表操作,随时随地可以查看链表内部情况
'''
p = self._head
while p is not None:
print(p.elem, end='')
if p.next is not None:
print(', ', end='') #end参数表示以什么结尾,默认为\n,即换行符
p = p.next
print('')
def filter(self, pred):
'''
迭代器,
注意yield的用法,相当于return
但是和return又有区别(主要就是有next函数和send函数)
yield可以暂停,然后返回值,下次继续从暂停的地方开始
迭代器的好处是:
find函数只能返回遇到的第一个值,但是通过利用迭代器就可以把所有满足条件的全都返回出来!
调用方法:
for x in list1.filer(pred)
print(x)
'''
p = self._head
while p is not None:
if pred(p.elem):
yield p.elem
p = p.next
#=============================================================================
'''
实例化
'''
mlist1 = Llist()
#for i in range(11, 20):
# mlist1.append(i)
mlist1.append(2)
mlist1.append(3)
mlist1.append(4)
mlist1.append(5)
mlist1.printAll()
mlist2 = Llist()
mlist2.append(3)
mlist2.append(5)
#mlist2.append(12)
#mlist2.append(14)
#mlist2.append(15)
#mlist2.append(17)
#mlist2.append(18)
mlist2.printAll() #生成两个有序链表,用来线下测试
#----------------------------------------------------------------------------
a = mlist1._head
b = mlist2._head
c = Llist()
while a is not None and b is not None:
if a.elem < b.elem:
a = a.next
elif a.elem > b.elem:
b = b.next
else:
c.append(b.elem)
a = a.next
b = b.next
c.printAll()
1、问题描述
给定一个链表的头节点head,请判断该链表是否为回 文结构。
例如: 1->2->1,返回true。 1->2->2->1,返回true。 15->6->15,返回true。 1->2->3,返回false。
进阶: 如果链表长度为N,时间复杂度达到O(N),额外空间复杂 度达到O(1)。
2、思路
这道题的困难在于要求额外空间复杂度O(1).也就是不借助辅助空间。
三种思路:
3、python实现
方法一、借助栈
'''
问题二:判断一个链表是否是回文结构
三种方法:
1.一种是全部压栈,然后依次比较
2.一半压栈,然后比较一半
3.不借助辅助空间,实现在原来链表上判断回文结构
'''
class LNode:
'''
产生结点类
为形成链表做准备
'''
def __init__(self, elem, next_=None):
self.elem = elem #这个是用来形成结点的,结点里有什么?元素,和 next指针。因此写的时候考虑这两个属性即可
self.next = next_
class Llist:
'''
基于节点类LNode,
定义单链表对象类
实现时 需要一个指针变量p,用来标记是否到达链尾
'''
def __init__(self):
self._head = None #这里LList对象的_head域作为对象的内部表示,不希望外部使用。
#python属于这种情况的域按照习惯用单个下划线开头的名字命名
def isEmpty(self):
'''
判断该单链表是否为空
'''
return self._head is None
def prepend(self, elem):
'''
在单链表的表头添加元素 函数
'''
self._head = LNode(elem, self._head)
def pop(self):
'''
单链表的弹出操作
删除表头结点 并 返回这个结点里的数据
'''
if self._head is None:
print('该链表已经为空') #其实这里应该是引发异常
e = self._head.elem
self._head = self._head.next
return e
def append(self, elem):
'''
后端操作,在链表的最后插入元素。
必须先找到链表的最后一个结点
其实现首先是一个扫描循环,找到相应结点后把包含新元素的结点插入在其后
'''
if self._head is None:
self._head = LNode(elem) #此时表示没有头结点,因此则可以把新的结点直接作为头结点
return
p = self._head
while p.next is not None:
p = p.next #这里第一次写的是self._head.next 这是不对的,因为p是指针变量,可以一直变,但是写成self._head.next就出不去循环了!!!
p.next = LNode(elem)
def pop_last(self):
'''
删除表中最后元素的操作
两个特殊情况:
1.如果表空没有可返回的元素时应引发异常
2.表中只有一个元素的情况需要特殊处理,因为这是应该修改表头指针,并且不存在p.next.next变量
'''
if self._head is None:
print('空表')
p = self._head
if p.next is None: #只有一个头结点
e = p.elem
self._head = None
return e
while p.next.next is not None:
p = p.next
e = p.next.elem
p.next = None
return e
def find(self, pred):
'''
找到满足给定条件的表元素
该方法返回第一个满足谓词(pred)的表元素
'''
p = self._head #这里可以明白为什么要引入一个p变量,其实也就是一个扫描指针,用来遍历链表。随时改变值
while p is not None:
if pred(p.elem):
return pred(p.elem)
p = p.next
def printAll(self):
'''
打印链表操作,随时随地可以查看链表内部情况
'''
p = self._head
while p is not None:
print(p.elem, end='')
if p.next is not None:
print(', ', end='') #end参数表示以什么结尾,默认为\n,即换行符
p = p.next
print('')
def filter(self, pred):
'''
迭代器,
注意yield的用法,相当于return
但是和return又有区别(主要就是有next函数和send函数)
yield可以暂停,然后返回值,下次继续从暂停的地方开始
迭代器的好处是:
find函数只能返回遇到的第一个值,但是通过利用迭代器就可以把所有满足条件的全都返回出来!
调用方法:
for x in list1.filer(pred)
print(x)
'''
p = self._head
while p is not None:
if pred(p.elem):
yield p.elem
p = p.next
#======================================================================================
class ArrayStack:
'''
数组结构实现栈
'''
def __init__(self, initsize):
self.initsize = initsize
'''
定义栈的结构
'''
if self.initsize < 0:
print('The init size is less than 0')
self.arr = [0 for i in range(self.initsize)]
self.index = 0
#def ArrayStack(self):
def isEmpty(self):
'''
判断栈是否为空
'''
return not self.index
def peek(self):
'''
返回栈顶元素函数
'''
if self.index == 0:
return 0
return self.arr[self.index - 1]
def push(self, num):
'''
添加元素函数:
可以让用户往栈中不断添加元素
'''
if self.index == self.initsize:
print("It's wrong!")
self.arr[self.index] = num
self.index += 1
def pop(self):
'''
弹出元素函数:
允许用户将栈中的元素弹出
'''
if self.index == 0:
print("It's wrong!")
#self.arr[index] = 0 #这个可以不加,因为弹出操作其实只需要让指针变量改变位置就行,因为再添加push的时候会覆盖原先的值的
self.index -= 1
return self.arr[self.index]
#=============================================================================
'''
实例化
'''
mlist1 = Llist()
mlist1.append(1)
mlist1.append(2)
mlist1.append(2)
mlist1.append(1)
mlist1.printAll()
mlist2 = Llist()
mlist2.append(1)
mlist2.append(2)
mlist2.append(3)
mlist2.append(2)
mlist2.append(1)
mlist2.printAll()
mlist3 = Llist()
mlist3.append(1)
mlist3.append(2)
mlist3.append(3)
mlist3.append(4)
mlist3.append(5)
mlist3.printAll() #生成三个有序链表,用来线下测试是否是回文结构
#----------------------------------------------------------------------------
def isPalindrome1(tmpList=Llist()):
'''
方法一:
判断是否是回文结构,
遍历链表,每取出一个值都放在栈中,然后再从栈中弹出依次比较
'''
storeStack = ArrayStack(10)
p = tmpList._head
while p is not None:
storeStack.push(p.elem)
p = p.next
p2 = tmpList._head
while p2 is not None:
if p2.elem != storeStack.pop():
return 0
else:
p2 = p2.next
return 1
print (isPalindrome1(mlist1))
print (isPalindrome1(mlist2))
print (isPalindrome1(mlist3))
方法二、压栈的技巧方法
找一个慢指针、找一个快指针;慢指针走一步,快指针走两步。当快指针走到头的时候,慢指针大概在中点的位置;然后将从慢指针到表尾的部分进行压栈。最后再引入一个指针从头开始遍历,与栈中元素一个个比较,如果直到栈为空都相等,则回文(相当于用了后一半元素做逆序再与前一半比较)
def isPalindrome2(tmpList=Llist()):
'''
方法二:
判断是否是回文结构
遍历链表,用一个慢指针,一个快指针,
然后将链表的一半放进栈中
'''
storeStack = ArrayStack(10)
slowPoint = tmpList._head.next #慢指针 这里和方法三有区别,方法三的慢指针刚开始是从head结点开始的
#因为方法二是用了额外空间,我们要存的只是后半段,如果是奇数个,不要中间,如果是偶数个我们只要后半边
#而方法三没有用额外空间,如果是奇数个,我们要做的是最中间的数的next域改变,如果是偶数个,我们要做的是
#前半部分的最后一个数的next域改变;因此方法二和方法三正好错开一位
fatterPoint = tmpList._head #快指针
while (fatterPoint.next is not None) and (fatterPoint.next.next is not None):
#这里必须将fatterPoint.next写在fatterPoint.next.next前面!!!
#因为写在前面如果前面这个fatterPoint.next不满足就不会看后面的这个判断了。否则会报错,Nonetype对象没有next属性
#这样的好处就是不会在往后判断,也就不会碰到next判断了
slowPoint = slowPoint.next
fatterPoint = fatterPoint.next.next #循环结束以后可以保证慢指针在链表的中间位置
while slowPoint is not None: #压栈
storeStack.push(slowPoint.elem)
slowPoint = slowPoint.next
p = tmpList._head
while not storeStack.isEmpty():
if p.elem != storeStack.pop():
return 0
else:
p = p.next
return 1
print(isPalindrome2(mlist1))
print(isPalindrome2(mlist2))
print(isPalindrome2(mlist3))
方法三、不用额外空间,定义快慢指针
借助慢指针、快指针。其中慢指针一次走一步,快指针一次走两步;当快指针走到表尾,慢指针走到中点,然后将慢指针到表尾的部分逆序。然后表头指针和尾指针一起往中间走并比较大小,直到中点 结束。若均相等则为回文结构,否则返回false。但是判断完以后需要再将链表改回原来的,因为不可以改变用户的值!
def isPalindrome3(tmpList=Llist()):
'''
方法三:
判断链表是否是回文结构,
这里不用额外空间,同样我们定义快指针,慢指针,
只不过这里我们不用栈结构存储,而是改变后半段的next域,然后判断比较是否是回文结构
最后判断完以后我们还需要把结构改回来,因为用户给我们一个链表,我们不能改变了用户给我们的东西
(这里是从工程角度考虑的)
'''
slowPoint = tmpList._head #慢指针
fatterPoint = tmpList._head #快指针
while fatterPoint.next is not None and fatterPoint.next.next is not None:
slowPoint = slowPoint.next
fatterPoint = fatterPoint.next.next #让指针移动到指定位置
prev = slowPoint #p指针的作用是用来标记next域应该更改指向哪里,也就是前向指针
slowPoint.next = None
while fatterPoint.next is not None:
p1 = fatterPoint.next #后向指针,因为next域下面要改变,因此需要先存着后向指针的位置
fatterPoint.next = prev #更改next域
prev = fatterPoint #前向指针后移
fatterPoint = p1 #fatterPoint后移,因为已经保存了最初的next域的指向
#完成后半部分的next域更改工作
tmprRear = fatterPoint #尾指针,右边
tmpHead = tmpList._head #头指针,左边
res = 1
while tmpHead is not None and tmprRear is not None: #有一方到达了None终点都停止
if tmpHead.elem != tmprRear.elem:
res = 0
break
else:
tmpHead = tmpHead.next
tmprRear = tmprRear.next
# print(fatterPoint.elem)
# while fatterPoint != slowPoint: #要把更改后的next域还原
# p_last = fatterPoint.next
# fatterPoint.next = None
# #p_last.next = fatterPoint
# fatterPoint = p_last
return res
print(isPalindrome3(mlist1))
print(isPalindrome3(mlist2))
print(isPalindrome3(mlist3))
1、问题描述
一种特殊的链表节点类描述如下:
public class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } }
Node类中的value是节点值,next指针和正常单链表中next指针的意义 一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指 针可 能指向链表中的任意一个节点,也可能指向null。 给定一个由 Node节点类型组成的无环单链表的头节点head,请实现一个 函数完成 这个链表中所有结构的复制,并返回复制的新链表的头节点。
进阶: 不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N) 内完成原问题要实现的函数。
2、思路
两种方法:
3、python实现
方法一、哈希表(python对应着字典)
'''
问题三:复制含有随机指针节点的链表
两种方法实现:
1.借助hash表,也就是使用额外空间
2.自己建立映射关系,不借助额外空间
'''
'''
方法一:hash表方法
'''
class Node(object):
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
自己的python实现,超出了时间限制
原因可能是因为:
1.while循环太多,当链表太长时会出现时间限制
2.当链表里面random很随机时,可能多个都指向了同一个时,我这种写法重复创建了节点;
而解析的python实现判断了是否之前已经创建了该节点,如果创建了则只赋值
而不是重复创建,Java中这么写不会出现这个问题是因为hash函数的get方法正好是利用了这个思想,
这个函数它自己会判断是否重复创建了,如果已经在其中,就不重复创建了,直接相应赋值;
这种也对应了为None时就直接把None作为value 了,code更加鲁棒,而不会去把None作为键去找value
"""
if head is None:
return
dict_hash = {}
p = head
while p is not None:
dict_hash[p] = Node(p.val, None, None)
#不断的生成新的节点,这里是在构造映射关系,就是生成1',2’...;将其作为值
p = p.next
p = head
while p.next is not None and p.random is not None:
dict_hash[p].next = dict_hash[p.next]
dict_hash[p].random = dict_hash[p.random]
p = p.next
while p is not None:
if p.random is None and p.next is not None:
dict_hash[p].random = None
dict_hash[p].next = dict_hash[p.next]
p = p.next
elif p.next is None and p.random is not None:
dict_hash[p].next = None
dict_hash[p].random = dict_hash[p.random]
p = p.next
elif p.next is None and p.random is None:
dict_hash[p].next = None
dict_hash[p].random = None
p = p.next
'''
左神理论 实现的代码逻辑,与Java一致,但是python跑不了,报错
原因:第二个while循环中,p.next会报错,因为如果p为最后一个,则p.next会指向None;
但是在字典中,None是不能做键的;因此在python中会报错;
而Java中可以做,是因为hash库的完善;get函数本质是判断是否存在的,若不存在会返回None,而不是去访问None键对应的值
因此不会报错;
那么如果在python中想要实现hash表,就要做一个判断,如果节点不是None,再进去访问;否则,就应该直接返回None
而这正是解析的做法,见getClonedNode函数
'''
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if head is None:
return
dict_hash = {}
p = head
while p is not None:
dict_hash[p] = Node(p.val, None, None)
#不断的生成新的节点,这里是在构造映射关系,就是生成1',2’...;将其作为值
p = p.next
p = head
while p is not None:
dict_hash[p].next = dict_hash[p.next]
dict_hash[p].random = dict_hash[p.random]
p = p.next
return dict_hash[head]
'''
解析--python实现
'''
"""
# Definition for a Node.
class Node(object):
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
"""
class Solution(object):
def __init__(self):
# Creating a visited dictionary to hold old node reference as "key" and new node reference as the "value"
self.visited = {}
def getClonedNode(self, node): #和自己写的唯一不同的地方,解析加上了如果已经生成了节点则不会重复生成;
#但是我自己写的没有这个逻辑,这个应该会导致超出时间限制
# If node exists then
if node:
# Check if its in the visited dictionary
if node in self.visited:
# If its in the visited dictionary then return the new node reference from the dictionary
return self.visited[node]
else:
# Otherwise create a new node, save the reference in the visited dictionary and return it.
self.visited[node] = Node(node.val, None, None)
return self.visited[node]
return None #如果key为None时,直接返回None
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head:
return head
old_node = head
# Creating the new head node.
new_node = Node(old_node.val, None, None)
self.visited[old_node] = new_node
# Iterate on the linked list until all nodes are cloned.
while old_node != None:
# Get the clones of the nodes referenced by random and next pointers.
new_node.random = self.getClonedNode(old_node.random)
new_node.next = self.getClonedNode(old_node.next)
# Move one step ahead in the linked list.
old_node = old_node.next
new_node = new_node.next
return self.visited[head]
方法二、自己建立映射关系
'''
方法二:不借助额外空间,自己建立映射关系
这个方法是改变原始链表的next域,把新节点都加进去;
然后以两个为一对儿,一次往后走两步(但是走之前必须把两个的域都搞定)
'''
"""
# Definition for a Node.
class Node(object):
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
"""
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head:
return head
p = head
while p is not None: #更改原链表的next域,将新节点加入老节点
next_ = p.next #先把最右侧的存储起来,一个是因为最后一步再赋值,另一个是因为下面要改变next域
p.next = Node(p.val, None, None)
p.next.next = next_ #中间的两步是操作步
p = next_
p = head #更改原链表的random域,使得新节点根据老节点的random域连接
while p is not None:
next_ = p.next.next #每次都是先把最右侧的先存储起来,因为后面就要改变域;
#并且可以赋值给最后一步,也就是移动两个(或一个)步长
pCopy = p.next
pCopy.random = p.random.next if (p.random != None) else None #中间的两步是操作步
p = next_ #这个方法是以两个为一对儿,往后走
res = head.next
#还原原始链表
p = head
while p is not None:
next_ = p.next.next
pCopy = p.next
p.next = next_
pCopy.next = next_.next if (next_ != None) else None
p = next_
return res