python_ACM模式《剑指offer刷题》链表3

文章讨论了在不同场景下删除链表中指定节点的时间复杂度差异,指出剑指offer中的问题可以使用O(1)复杂度解决,而LeetCode上的问题由于需要遍历查找节点可能达到O(N)。作者提供了针对节点指针的高效删除算法和代码实现。

题目:

注意:

剑指offer上对这道题目的描述是给定的删除节点是节点指针。这表明这道题可以用时间复杂度为O(1)的方式解决。

而leetcode上对类似本题的描述是:

给定删除节点是节点值,这决定了本题时间复杂度必然至少为O(N)。因为必定要从头遍历链表。

          

面试tips:

1. 注意以上两种问法的区别。若是第一种,最优的方式时复为O(1)。

2. 这道题默认了所给的删除节点就在链表上,可以跟面试官提一下,显示对此考虑的更全面。如果不在还要进行判断。

思路:

这里实现剑指offer上的问法,leetcode上的采用这里的思路一很容易解决。

1. 遍历一遍链表,找到要删除的节点的前一个节点,进行删除操作即可。

2. 如果给定的删除节点是一个节点指针(该指针指向待删除链表的某一个节点),则可不需从头遍历就已经知道了要删除的节点的位置,只是不知道其前一个位置。事实上,由下面步骤我们可知,只要给的是要删除节点的指针,则可以不需要知道其前一个位置也可进行删除。

①设置虚拟节点,使得若删除的是头节点,下面的操作都相同。

②若要删除的不是尾节点,则将要删除的节点深拷贝其下一个节点,将下一个节点进行删除(因为知道当前节点就是下一个节点的前一个节点,因此很容易删除),虽然不是真正删除所删除的节点,但因为深拷贝,也就等价于是删除掉所删除的节点的结果了。见下图的c。

③若删除的是尾节点,因为一个正常节点是不能够深拷贝为空节点的,因此②走不通,这时只能从头遍历了,找到尾节点的前一个节点进行删除。

总的平均时间复杂度:((n-1)* O(1) + 1 * O(n) )  /  n  -> O(1)

代码实现:

1和2的代码实现上更改的部分只有class solution部分,其余部分是根据数组构建单链表及要删除的节点指针。

1.

class ListNode:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next

def arr2List(arr, val):
    # 一维数组转单链表, 同时对应值要找到对应节点
    # 设置虚拟头节点,使得对头节点的操作同
    prev = dummy_head = ListNode()
    for i in range(len(arr)):
        tmp = ListNode(val = arr[i])
        prev.next = tmp
        prev = prev.next
        if arr[i] == val:
            tod_Node = tmp
    return dummy_head.next, tod_Node

def List2arr(head):
    # 单链表转一维数组
    curr = head
    arr = []
    while curr:
        arr.append(curr.val)
        curr = curr.next
    return arr

class Solution:
    def deleteNode(self, head, tod_Node):
        # 从头遍历找到对应的节点
        prev = dummy_head = ListNode(next = head)
        while prev.next:
            if prev.next == tod_Node:
                prev.next = prev.next.next
                return head
            else:
                prev = prev.next

if __name__ == '__main__':
    # 若给定一维数组,及要删除的节点值
    arr = [4, 5, 1, 9]
    val = 9  # 这里表明要删除的节点为5所对应的节点
    # 先要将其转为单链表以及相应节点
    head, tod_Node= arr2List(arr, val)
    a = Solution()
    new_head = a.deleteNode(head, tod_Node)
    new_arr = List2arr(new_head)
    print(new_arr)

2.

class ListNode:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next

def arr2List(arr, val):
    # 一维数组转单链表, 同时对应值要找到对应节点
    # 设置虚拟头节点,使得对头节点的操作同
    prev = dummy_head = ListNode()
    for i in range(len(arr)):
        tmp = ListNode(val = arr[i])
        prev.next = tmp
        prev = prev.next
        if arr[i] == val:
            tod_Node = tmp
    return dummy_head.next, tod_Node

def List2arr(head):
    # 单链表转一维数组
    curr = head
    arr = []
    while curr:
        arr.append(curr.val)
        curr = curr.next
    return arr

class Solution:
    def deleteNode(self, head, tod_Node):
        # 1. 为确保头节点操作与其余节点同,设置一个虚拟头节点
        # 2. 若删除的节点不是最后一个节点,则将其下一个节点复制过来,删除下一个节点,这等价于删除当前节点
        # 3. 若删除的节点是最后一个节点,则下一个节点为空是无法复制的,因此这是需从头节点遍历找到最后一个的前一个节点进行删除操作
        dummy_head = ListNode(0,next = head)
        if tod_Node.next != None:
            # 则说明要删除的节点不是最后一个节点
            # 深拷贝下一个节点至当前节点 并同时也将下一个节点删除了
            tod_Node.val = tod_Node.next.val
            tod_Node.next = tod_Node.next.next
            return head
        else:
            # 此时要删除的节点是最后一个节点,同时其也有可能是第一个节点(但是因为有虚拟头节点在 因此不可能为真正意义上的第一个节点)
            # 因为该节点不可能从不空变为空(深拷贝意义上的空) 因此只能从头节点开始遍历
            prev = dummy_head
            while prev.next:
                if prev.next == tod_Node:
                    prev.next = prev.next.next
                    return head
                else:
                    prev = prev.next

if __name__ == '__main__':
    # 若给定一维数组,及要删除的节点值
    arr = [4, 5, 1, 9]
    val = 1  # 这里表明要删除的节点为5所对应的节点
    # 先要将其转为单链表以及相应节点
    head, tod_Node= arr2List(arr, val)
    a = Solution()
    new_head = a.deleteNode(head, tod_Node)
    new_arr = List2arr(new_head)
    print(new_arr)

参考:

1.《剑指offer》

2. 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值