算法(11)-leetcode-explore-learn-数据结构-链表的经典问题

leetcode-explore-learn-数据结构-链表3

  • 1.反转一个链表
  • 2.移除链表元素
  • 3.奇偶链表
  • 4.回文链表
  • 5.小结

本系列博文为leetcode-explore-learn子栏目学习笔记,如有不详之处,请参考leetcode官网:https://leetcode-cn.com/explore/learn/card/linked-list/

所有例题的编程语言为python

1.反转一个链表

leetcode 206
思路1: 迭代求解,将当前结点next信息保存下来,然后将前一个节点的信息存入当前结点的next中。更新当前结点。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head==None:
            return head
        pre_node=None
        cur_node=head
        while(cur_node):
            next_node=cur_node.next
            cur_node.next=pre_node
            pre_node=cur_node
            cur_node=next_node
        return pre_node   

思路2:
递归:假设链表的其余部分都已经被翻转,现在该如何翻转它前面的部分。由最后一个开始往前不断翻转
算法(11)-leetcode-explore-learn-数据结构-链表的经典问题_第1张图片

class Solution(object):
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head==None or head.next==None:
            return head
        p=self.reverseList(head.next)   # 记录最后一个结点作为头指针用的。
        head.next.next=head
        head.next=None
        return p

2.移除链表元素

删除链表中等于给定值 val 的所有节点。
思路:遍历链表的每一结点,如果值等于给定值将其删除即可。
注意点:要删除链表节点时,可以使用哑结点技巧,防止删原链表的头结点。最后返回时,返回dummy.next即可。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        dummy=ListNode(0)
        dummy.next=head
        pre_node=dummy
        cur_node=dummy.next
        while(cur_node):
            next_node=cur_node.next
            if cur_node.val==val:
                pre_node.next=next_node  # 删除结点
            else:
                pre_node=cur_node
                
            cur_node=next_node
        return dummy.next
                

3.奇偶链表

给定一个单链表,把所有奇数节点和偶数节点(节点 编号的奇偶性)分别排在一起。
思路1:原来的链表分成奇偶两个子链表,然后将偶链表链接到奇链表后面。
没有使用额外的空间,直接从原来的链表中截取。

# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution(object):
    def oddEvenList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head==None:
            return head
        even_h=ListNode(0)
        even_node=even_h
        cur_node=head
        i=1
        while(cur_node):
            next_node=cur_node.next
            if i %2==0:
                cur_node.next=None   # 将node.next的值给设置为零,能防止成环
                even_node.next=cur_node
                even_node=even_node.next
                pre_node.next=next_node
                
            else:
                pre_node=cur_node
            cur_node=next_node
            i+=1
        cur_node=head.next
        pre_node=head
        while(cur_node):
            print(pre_node.val)
            pre_node=cur_node
            cur_node=cur_node.next
        pre_node.next=even_h.next
        return head        

4.回文链表

判断一个链表是否为回文链表。
o(n)时间复杂度,o(1)空间复杂度

思路1:可以先把链表装进数组中,判断数组中元素是否构成回文。数组的前后遍历比单链表方便。时间复杂度o(n),空间复杂度o(n)

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head or not head.next:
            return True
        lst=[]
        p=head
        while(p):
            lst.append(p.val)
            p=p.next
        return lst==lst[::-1]

思路2:翻转原链表,对照两个链表是否一致,如果回文链表应该是一致的,反之原链表不为回文链表。时间复杂度o(n),空间复杂度o(n)

# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """

        if head==None or head.next==None:
            return True
        # 备份原链表
        head_be=ListNode(0)
        node=head
        node_be=head_be    
        while(node):
            node_be.next=ListNode(node.val)
            node_be=node_be.next
            node=node.next
        # 转置原链表
        pre_node=None
        cur_node=head
        while(cur_node):
            next_node=cur_node.next
            cur_node.next=pre_node
            pre_node=cur_node
            cur_node=next_node
        # 比较两个链表
        node_be=head_be.next
        node_af=pre_node
        while(node_be and node_af):
            if node_be.val!=node_af.val:
                return False
            node_be=node_be.next
            node_af=node_af.next
        return True

思路三:避免使用 O(n)O(n) 额外空间的方法就是改变输入。

我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,因为使用该函数的人不希望链表结构被更改。

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head or not head.next:
            return True
        # 计算链表长度
        p1=head
        n=1
        while(p1.next):
            p1=p1.next
            n+=1
        p1=head
        p2=head
        if n==2:
            if  head.val==head.next.val:
                return True
            else:
                return False
        # 找链表中点
        for i in range(int(round(n/2.0))-1): # 0
            p1=p1.next
        half_end=p1     # 前一半链表的最后一个节点
        # 翻转后一半链表
        p1=p1.next
        pre_node=None
        for i in range(int(n/2.0)): # 0,1
            next_node=p1.next
            p1.next=pre_node
            pre_node=p1
            p1=next_node
        half_end.next=pre_node
        p1=head
        # 比较前一半和翻转后的后一半。
        for i in range(int(round(n/2.0))): # 0,1
            p1=p1.next
        for i in range(int(n/2)):# 0,1
            if p1.val!=p2.val:
                return False
            p1=p1.next
            p2=p2.next
        
        return True

5.小结

1.使用链表时不易调试,自己多尝试几个测试用例总是很有用的,通过输出链表节点的值来观测代码运行情况。

2.多指针时,为指针设定合适的名称,防止自己被搞混

3.单链表操作时,储存前一个节点的信息往往是有效的。

你可能感兴趣的:(算法)