leetcode 初级链表学习笔记(python3)

  链表是一种单向搜索的数据结构,形式上像链子一样由许多节点串联形成,但是其搜索方式并不灵活,只能从头节点出发,依次前进,直到达到目标节点。
  如果有一个链表,我们想要得到该链表中的第四个节点,python代码应为:

	point1 = head  # 指针初始化指向头指针
	for i in range(1,4):  # 执行3次循环
		point1 = point1.next  # 遍历链表

  经过以上代码,point1指向了第四个节点。控制次数地遍历链表就可以实现具体节点的定位。
  这里的题目较为经典,本质是定点删除链表中的节点。
  
在这里插入图片描述  
  在了解链表的节点定位法后,我们以此作为出发点去解决这个问题。
  需要注意到,题目要求删除倒数第n个节点,在不知道链表长度的情况下,这个倒数第n是没法直接用的,因此需要先整体遍历链表一遍,计算其长度。
  以下代码实现了计算链表长度的功能。

		point1 = head
		length = 1  # 因为遍历链表是从第一个节点往后开始的,所以需要先算上头结点
        while point1.next != None:
            point1 = point1.next  # 遍历整个链表
            length += 1  #计算出链表的长度

  需要注意的是由于遍历是从头结点的下一个开始的,因此头结点在遍历前就应当计算在内,所以初始值是1不是0。
  按照这个思路可以写出以下代码:

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if head.next == None:
            return None

        point1 = head
        point2 = head
        length = 1  # 因为遍历链表是从第一个节点往后开始的,所以需要先算上头结点
        while point1.next != None:
            point1 = point1.next  # 遍历整个链表
            length += 1  #计算出链表的长度
        
        for i in range(1,length-n):
            point2 = point2.next  # 遍历链表,移动到需要删除的节点的上一个节点
        if point2.next.next != None: 
            point2.next = point2.next.next # 令需要删除的节点的上一个节点指向需要删除的节点的下一个节点
        else:
            point2.next = None
        return head  #返回链表

  经测试,以上代码存在一个bug:无法删除掉头结点。
  因为在遍历链表时,第一次操作就将头结点给跳过去,这样我们只能删除头结点以外的节点。
  按照这个改进思路,也应当将头结点纳入遍历范围内。
  因此我们可以在头指针前面插入一个节点,称其为“哑节点”:

		dummy = ListNode(0, head)

  该节点指向了头结点,这样每次遍历就会从头结点开始了。
  此外,该题目规定,节点内的值在0到100之间,该哑结点并没有帮助检索外的其他作用,因此赋值个0(当然赋值其他也可以)。
  修改后的代码如下:

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if head.next == None:
            return None
        
        length = 1
        point1 = head
        while point1.next != None:  # 先把长度算出来
            point1 = point1.next
            length += 1
        
        dummy = ListNode(0, head)  # 哑节点,指向头结点
        cur = dummy

        for i in range(1, length-n+1):  # 可以直接遍历了
            cur = cur.next
        cur.next = cur.next.next  # 删除需要删除的节点
        return dummy.next  # dummy.next指向的是新链表的头结点

  考虑到该题涉及“取倒数第几的xx”,还可以考虑用栈来写:

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if head.next == None:
            return None
        stack = list()  # 初始化栈列表
        dummy = ListNode(0, head)  # 哑节点,指向头结点
        cur = dummy
		
		while(cur != None):
			stack.append(cur)  # 将链表节点正序加入列表中,压栈处理
			cur = cur.next
		
		for i in range(n):
			stack.pop()  # 出栈操作,将包含需要删除的节点在内的倒数节点抛出
		pre = stack[-1]  # 栈里的最后一个元素就是需要删除的节点的前一个节点
		pre.next = pre.next.next
		return dummy.next
        

  在python中使用栈较为简单:1.用一个空列表当栈使用。2.把数据都压进去。3.根据所需元素的倒数次序进行出栈。
  此外,还可以使用快慢指针法来处理这个问题。

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)  # 需要返回的链表指针一定要初始指向哑指针
        point1 = head  # 快指针
        point2 = dummy  # 慢指针
        for i in range(n):  # 让快指针先走,提前n步
            point1 = point1.next

        while point1 != None:  # 然后前后指针再一起走,快指针到末尾,停住,慢指针也同时停下
            point1 = point1.next
            point2 = point2.next
        
        point2.next = point2.next.next
        return dummy.next


  来个使用已有链表构建新链表的。
  
在这里插入图片描述  
  由题意可知,需要构建一个新链表返回,因此一共涉及到三个链表。从宏观的流程角度上讲,该程序实现三个链表的共同前进,在程序中需要处理的是三个链表之间前进的关系。
  从示意图中可知,每一步都是从链表1或者链表2中找一个节点给链表3(新链表)装上,每次装上个新节点,也就意味着链表3需要前进一次,给予节点的链表前进一次,也就是说一次操作需要3个链表中的2个同时前进。

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0,None) #新建一个链表和哑指针
        point1 = dummy
        while l1 != None and l2 != None:  # 如果其中一个链表走到尽头就结束
            if l1.val <= l2.val:  # 如果需要从链表1中取节点
                point1.next = l1  # 将节点装给新链表
                point1 = point1.next  # 新链表前进
                l1 = l1.next  # 链表1前进
            else:  # 关于链表2的操作同理
                point1.next = l2
                point1 = point1.next
                l2 = l2.next
        if l1 != None:  #剩余的某个链表直接连在新链表后面即可
            point1.next = l1
        if l2 != None:
            point1.next = l2
        return dummy.next

  涉及多链表的问题要注意每次操作都是哪些链表在前进
  来个单链表的。
  
在这里插入图片描述  
  回文xx在算法题里还是挺常见的,一开始考虑到有这个“倒数”情况(因为回文是倒着和正着比的),所以我拿栈写了一个函数。

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        dummy = ListNode(0,head)  # 先上哑指针
        point1 = dummy
        point2 = dummy
        length = 0
        while head != None:  # 测量链表的长度
            head = head.next
            length += 1

        if length % 2 != 0:  #将链表的后一半放进栈中,由于链表的长度奇数偶数有差异,所以分了情况
            for i in range(length//2 + 1):
                point2 = point2.next
        else:
            for i in range(length//2):
                point2 = point2.next
                
        stack = list()  # 建立栈,将链表的后半部分压入栈
        for i in range(length//2):
            point2 = point2.next
            stack.append(point2.val)
            
        for i in range(length//2):
            point1 = point1.next 
            temp = stack.pop() # 每次从链表前面与链表末尾取相同位次的内容进行比较
            if  point1.val != temp:
                return False
        return True

  拿栈写了之后感觉不是很对味(对胃 ),翻出官方答案,发现官方的答案确实有很简单的办法。
  说本质的问题,链表的出现就是为了一定程度上替代数组,数组可是个方便操作的数据结构,美中不足就是只能连续存储,对内存空间的利用率有点堪忧。今天既然搞算法,将链表转成数组再操作,岂不是美汁汁。

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        vals = [] 
        cur = head
        while current_node is not None:  # 将链表的节点依次加入空列表中
            vals.append(current_node.val)
            cur = cur.next
        return vals == vals[::-1] #列表显然可以通过简单方法翻转,翻了之后直接比

  直呼内行,可见数组算法是相当基础的,数组得学好。
  翻转列表也是基本功:

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while (cur != None):
            temp = cur.next  # 当前节点的下一个,存起来
            cur.next = pre  #将当前节点的下一个设成前一个
            pre = cur  # 前一个向后移
            cur = temp  # 当前的向后移
        return pre #注意返回的不是cur,因为cur移到链表的最后,指向了None。链表最后一个元素是None。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值