Leetcode刷题:初级算法之链表

题目

删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:

输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:

输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

链表至少包含两个节点。
链表中所有节点的值都是唯一的。
定的节点为非末尾节点并且一定是链表中的一个有效节点
不要从你的函数中返回任何结果

方法:与下一个节点交换

  1. 从链表里删除一个节点 node 的最常见方法是修改之前节点的 next 指针,使其指向之后的节点。

  2. 因为我们无法访问我们想要删除的节点之前的节点,我们始终不能修改该节点的 next
    指针

  3. 相反,我们必须将想要删除的节点的值替换为它后面节点中的值,然后删除它之后的节点。

  4. 因为我们知道要删除的节点不是列表的末尾,所以我们可以保证这种方法是可行的

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

class Solution(object):
    def deleteNode(self, node):
        node.val = node.next.val 
        node.next = node.next.next

删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

题目有个提示,这个问题可以用一趟扫描就能解决。
其实初级的思想就是扫描两次,第一次是为了知道这个链表有多长,从而确定要删去的节点在链表里面的位置是哪里
然而更好的方法是用两个指针去实现,左边的指针和右边的指针距离为n,当右边的指针到链表的尾部时,左边指针的对象.next的对象,就是我们要删去的节点。而这个方法之需要扫描一次就可以了
这个问题还有一个需要注意的点,就是头节点和普通的节点删除的方法不同的,要处理好

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

class Solution(object):
    def removeNthFromEnd(self, head, n):
        tmp1 = head
        tmp2 = head
        # 左指针和右指针的距离是n
        for i in range(n):
            tmp2 = tmp2.next
        # 删去的是头节点
        if tmp2 == None:
            head = tmp1.next
        # 删去的是普通节点
        else: 
            while tmp2.next != None:
                tmp1 = tmp1.next
                tmp2 = tmp2.next
			# 删除的过程:释放被删除节点所占用的内存
            del_node = tmp1.next
            tmp1.next = del_node.next
            del del_node
        # 把链表通过数组的形式录入
        a = [] 
        while head != None:
            a.append(head.val)
            head = head.next
        return a

合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

这个就是一个链表实现的归并排序的merge函数
甚至还要更简单,因为当其中一个链表已经遍历完以后,不需再去新增节点了,直接让尾指针指向还没有遍历完的链表的剩下的部分即可~

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

class Solution(object):
    def mergeTwoLists(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        head = ListNode(-1) # 虚头指针
        tmp = head
        while l1  and l2 :
            if l1.val <= l2.val:
                tmp.next = ListNode(l1.val)
                tmp = tmp.next
                l1 = l1.next
            else:
                tmp.next = ListNode(l2.val)
                tmp = tmp.next
                l2 = l2.next
        if l2:
            tmp.next = l2          
        else:
            tmp.next = l1
        return head.next

其实这样做空间复杂度不是最优的哦,因为还要创建对象,更优化的方法是直接改变链表中节点next指针的指向,充分利用了链表灵活性的特点,改动的地方:

while l1  and l2 :
            if l1.val <= l2.val:
                tmp.next = l1
                tmp = tmp.next
                l1 = l1.next
            else:
                tmp.next = l2
                tmp = tmp.next
                l2 = l2.next

回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false
示例 2:

输入: 1->2->2->1
输出: true
进阶:
你能否用O(n) 时间复杂度O(1) 空间复杂度解决此题?

  • 我自己的思路是新建链表,然后双指针判断
  • 但是题解上面用的是数组:先遍历链表,然后把value依次填入数组中,最后和数组中判断回文序列相同的方法。这个方法的好处是只需要记录value而不需要记录next,因为数组是有序的,同时创建对象的过程省去之后,实际上也会使得running time减小~
class Solution(object):
    def isPalindrome(self, head):
        """
        回文链表:
        创建一个新的链表,指针是反向的,比如
        原来的:1 -> 2 -> 3 -> 4
        新建的:1 <- 2 <- 3 <- 4
        然后两个链表分别从自己的头节点开始扫描,进行逻辑判断
        """
        if head == None or head.next == None: return True

        ptr1 = head
        ptr2 = None
        while ptr1:
            tmp = ListNode(ptr1.val)
            tmp.next = ptr2
            ptr2 = tmp
            ptr1 = ptr1.next

		ptr1 = head
        while True:
            if ptr2.val != ptr1.val:
                return False
            ptr2 = ptr2.next
            ptr1 = ptr1.next
            if ptr1 == None or ptr2 == None:
                return True

然鹅一切并没有结束:我们只实现了O(n) 时间复杂度,那O(1) 空间复杂度呢?

  1. 使用快慢指针找中点:原理是利用fast和slow两个指针,每次快指针走两步,慢指针走一步,等快指针走完时,慢指针的位置就是中点
  2. 然后利用原地逆置的方法(空间复杂度0(1)) 将后半段逆置,就可以与前半段相比较了
class Solution(object):
    def isPalindrome(self, head):
        fast = slow = head
        # 快慢指针找中点
        while fast:
            if fast.next:
                fast = fast.next.next
            else:
                fast = fast.next
            slow = slow.next

        tail = self.reverse(slow)
        """
        测试reverse函数:
        while tail:
            print (tail.val)
            tail = tail.next
        """
        # 双指针比较左两边
        while tail and head:
            if tail.val != head.val:
                return False
            tail = tail.next
            head = head.next
        return True

	# 原地逆置,空间复杂度o(n)
    def reverse(self, s):
        p = t = None
        while s:
            t = s.next
            s.next = p
            p = s
            s = t
        return p

环形链表

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

# 方法一: 用时80ms,内存消耗14.2 MB
class Solution(object):
	"""
	使用原地逆置的方法做的,遍历链表,然后【把指针的方向取反】
	1. 如果有环:我们一定可以回到头指针
	2. 如果没有环:我们会碰到空指针
	时间复杂度o(n),空间复杂度o(1)
	"""
    def hasCycle(self, head):
        pre = nex = None 
        cur = head
        while cur:
            if (cur.next == head): return True
            
            nex = cur.next
            cur.next = pre
            pre = cur
            cur = nex
        return False
        
# 方法二:用时56 ms,内存消耗17.1 MB
class Solution(object):
	"""
	用快慢指针的方法
	1. 如果有环:快指针一定会追上慢指针
	2. 如果没有环:快指针的后面为空
	时间复杂度o(n),空间复杂度o(1)
	"""
    def hasCycle(self, head):
        if head == None or head.next == None: # 考虑特殊情况,确保fast、slow不是None
            return False
        fast = head.next
        slow = head
        while slow != fast:
        	# 注意这里判断的顺序不能交换
            if fast == None or fast.next == None:
                return False
            fast = fast.next.next
            slow = slow.next
            
        return True

总结

  • 至此结束。此类题目不算特别难,都是很基础性的问题
  • 学到了两个有用的新方法
    • 快慢指针
    • 原地逆置
  • 在处理链表的时候一定要考虑边界问题(头指针、尾指针)
  • 注意利用其他数据结构:字典、数组等帮助解决链表相关的问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值