牛客网_算法初级班_打印两个有序链表的公共部分_判断链表是否是回文结构_复制含有随机指针节点的链表_python语言描述

一、打印两个有序链表的公共部分

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).也就是不借助辅助空间。
三种思路:

  1. (借助辅助空间)借助栈
  2. 不借助额外空间,借助快指针、慢指针

3、python实现

方法一、借助栈

  1. 遍历链表,每取出一个值都放入栈中。
  2. 栈先进后出,因此从栈中取出元素实际为逆序。
    则与链表的节点一一比对,如果一样则为回文结构。
'''
问题二:判断一个链表是否是回文结构
三种方法:
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

你可能感兴趣的:(算法刷题,牛客网算法初级班,数据结构)