leetcode刷题总结(五)

2019/9/14:两数相加

题目链接: https://leetcode-cn.com/problems/add-two-numbers/

这题可以从两个方向去考虑,第一个是将链表转换成数字或者是列表甚至是任意形式,因为转换过后比原来在链表的基础上更好做操作,第二就是在原链表上做,虽然比较难理解,但代码更加简洁。所以我们可以先考虑第一种,先转换成数字,然后再转换回来:

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

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        def convert(listNode):
            nums = ""
            if listNode is None:
                return 
            while listNode != None:
                nums = str(listNode.val) + nums
                listNode = listNode.next
            return int(nums)
            
        nums1,nums2 = convert(l1),convert(l2)
        num = nums1 + nums2
        
        root = ListNode(num%10)
        l3 = root
        num = num // 10
        while num != 0:
            l3.next = ListNode(num%10)
            l3 = l3.next
            num = num // 10
        return root

然后还可以直接用链表操作,这个是最高赞的那个代码,然后我加了一点我的注释

class Solution:
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        re = ListNode(0)
        r=re	# 定义一个指针
        carry=0		# 进位标志位
        while(l1 or l2):
            x= l1.val if l1 else 0	# 非空判断
            y= l2.val if l2 else 0
            s=carry+x+y
            carry=s//10		
            r.next=ListNode(s%10)	# 确定下一个节点
            r=r.next	# 更新当前节点
            if(l1!=None):l1=l1.next
            if(l2!=None):l2=l2.next
        if(carry>0):		# 边界情况
            r.next=ListNode(1)
        return re.next		# 返回实际起始节点

2019/9/14:反转链表

题目链接:https://leetcode-cn.com/problems/reverse-linked-list/

这题目隐隐约约在哪里看过,仔细一想好像是我之前有写过剑指offer 66中第三题同样也是链表反转,但那个要求返回一个数组,而这个是原格式,虽然依然可以用链表转数组再转链表,但那样感觉过于麻烦,然后看了下面这篇一个大佬写的特别详细的解答方案,我发现python的特性真是奇妙:

https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-zhi-chang-gui-si-lu-he-die-dai/

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        """
        :type head: ListNode
        :rtype: ListNode
        """
        current_node,prev = head,None		# 定义一个指向当前的指针,一个空指针,prev作用是空指针变为前置指针
        while current_node:
        		"""当前指针的下一个引用域指向前置指针,前置指针优空指针反向,当前指针指向后面保证循环进行"""
                current_node.next, prev, current_node  = prev, current_node, current_node.next		

        return prev

2019/9/15:删除链表中的节点

题目链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list/

这道题刚开始以为是直接考研一下链表的性质,和前面的最小栈一样,然后直接就开始写,发现不对的是没有head,我尝试自己定义了一个,嗯。。就完全走偏了,然后发现这题真实的代码只有两行,或者说一行:

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

2019/9/15:环形链表

题目链接:https://leetcode-cn.com/problems/linked-list-cycle/

判断没有没有环,我们其实可以定义两个指针,一个快,一个慢,假如有环,那么则他们会在某一点相遇,反之不会,就如下图中的跑道,可以跑无数圈,那么快速的人总有一圈会追尾。
Alt
那么代码为:

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        fast, slow = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow: return True
        return False

另外就是还有一种取巧的方式,不管链表里的值是什么,我可以将里面的内容全部覆盖掉,那么之后我只要判断循环过程中存不存在相同内容就行了,这种方法可以去看讨论里最高评论量那个。

2019/9/17:旋转链表

题目链接:https://leetcode-cn.com/problems/rotate-list/

该题需要理解的是当链表在旋转时,我们要找到旋转点的位置进行分割,然后再将分开的两段拼起来,另外如果k = 6其实就是相等于k=1;所以我们为了防止循环次数,得到链表长度n,k = n - k % n - 1.

然后第一种思路可以是找到断开的点,将其分成两段链表,将后一段保存,然后依次加入前一段中,如下图,那么代码为:
在这里插入图片描述

class Solution:
    def rotateRight(self, head: ListNode, k: int) -> ListNode:
        if not head or not head.next: return head
        # 链表个数
        num = 0
        p = head
        while p:
            num += 1
            p = p.next
        k = num - k % num - 1
        p = head
        # 找前一段链表
        while k:
            p = p.next
            k -= 1
        head1 = p.next	# 通过head1保存后一段
        if not head1: return head
        #前一段链表最后至空
        p.next = None
        p = head1	# 此时P为4 —— > 5 ——> None
        # 后一段链表和前一段链表连接起来
        while p.next:
            p = p.next
        p.next = head
        return head1

第二种方案就是先将链表闭合成环,找到相应的位置断开这个环,确定新的链表头和链表尾。

def rotateRight(self, head, k):
        """
        :type head: ListNode
        :type k: int
        :rtype: ListNode
        """
        # 思路:转化为循环链表,然后在合适的位置断开
        if not head:
            return None
        tail = head
        count = 1
        while tail.next != None:        # 遍历找到尾节点位置
            tail = tail.next
            count += 1

        tail.next = head                # 尾节点指向头,构建循环链表
        ptr = head
        loop = count - (k%count)        # 移动步数
        for i in range(loop):           # 同时移动头节点和尾节点
            tail = tail.next
            ptr = ptr.next
        tail.next = None                # 断开环形链表
        return ptr

2019/9/19:交替打印foobar

题目链接:https://leetcode-cn.com/problems/print-foobar-alternately

首先做这个题目之前我们需要知道两个python的内置模块,一个是queue队列,还有一个就是Semaphore或者BoundedSemaphore信号量,题目意思需要我们用两个线程去轮流调用其中两个方法,第一种方式可以用队列来实现,讲一个任务添加到队列里,执行结束后再阻塞既能完成交替操作:

import queue

class FooBar:
    def __init__(self, n):
        self.n = n
        self.foo_queue = queue.Queue()
        self.bar_queue = queue.Queue()
        self.foo_queue.put('')


    def foo(self, printFoo: 'Callable[[], None]') -> None:

        for i in range(self.n):
            # printFoo() outputs "foo". Do not change or remove this line.
            self.foo_queue.get()
            printFoo()
            self.bar_queue.put('')


    def bar(self, printBar: 'Callable[[], None]') -> None:

        for i in range(self.n):
            # printBar() outputs "bar". Do not change or remove this line.
            self.bar_queue.get()
            printBar()
            self.foo_queue.put('')

第二种即是用信号量:Semaphore类和BoundedSemaphore类都是信号量类,每次有线程获得信号量的时候(即acquire())计数器-1,释放信号量时候(release())计数器+1,计数器为0的时候其它线程就被阻塞无法获得信号量。当计数器为设定好的上限的时候BoundedSemaphore就无法进行release()操作了,Semaphore没有这个限制检查。

import threading
class FooBar:
    def __init__(self, n):
        self.n = n
        self.foo_lock = threading.Semaphore()
        self.foo_lock.acquire()
        self.bar_lock = threading.Semaphore()
        self.bar_lock.acquire()

    def foo(self, printFoo: 'Callable[[], None]') -> None:
        for i in range(self.n):
            # printFoo() outputs "foo". Do not change or remove this line.
            printFoo()
            self.bar_lock.release()
            self.foo_lock.acquire()


    def bar(self, printBar: 'Callable[[], None]') -> None:
        for i in range(self.n):
            # printBar() outputs "bar". Do not change or remove this line.
            self.bar_lock.acquire()
            printBar()
            self.foo_lock.release()

2019/9/20:环形链表 II

题目链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/

这题基本上和之前的环形链表一样,只是中间返回值稍微改了而已,同样能和上面方法一致,这里可以考虑快慢指针和哈希表:

class Solution(object):
    def detectCycle(self, head):
        fast, slow = head, head
        while True:
            if not (fast and fast.next): return
            fast, slow = fast.next.next, slow.next
            if fast == slow: break
        fast = head
        while fast != slow:
            fast, slow = fast.next, slow.next
        return fast

另一种就是哈希表了:

class Solution(object):
    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        visited = set()
        node = head
        while node is not None:
            if node in visited:
                return node
            else:
                visited.add(node)
                node = node.next

        return None

2019/9/21:排序链表

题目链接:https://leetcode-cn.com/problems/sort-list/

一般针对数组来讲,时间复杂度为O(nlogn)的排序算法有快排、堆排、归并、快排,而堆排空间复杂度为O(1),归并空间复杂度为O(n),但此题因为是链表,依据排序的原理,堆排序是通过不断 交换元素 实现排序的:在数组中交换两个索引的元素时间复杂度为 O(1),而链表 交换元素 本身是 O(N) 时间复杂度,所以也算是两种相反的方向吧。

另外此题代码加了点自己注释整理为:

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next: 	
			return head # termination.
        # cut the LinkedList at the mid index.
        slow, fast = head, head.next	# 使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后慢的所在位置就是中间位置,这样就分成了两段。

        while fast and fast.next:
            fast, slow = fast.next.next, slow.next	# 快慢指针,快的为2,慢的为1
        mid, slow.next = slow.next, None # save and cut.从中间分割,中点为奇数个中间节点或者偶数个的左边一位,然后中点的下一个引用域置空
        # recursive for cutting.	
        left, right = self.sortList(head), self.sortList(mid)	# 递归将其分成更小份
        # merge `left` and `right` linked list and return it.
        h = res = ListNode(0)	# 做一个头指针
        while left and right:	# 归并排序
            if left.val < right.val: 
				h.next, left = left, left.next
            else: 
				h.next, right = right, right.next
            h = h.next
        h.next = left or right	# 左右子链表可能不相同,如果还有没有走完的,比如说7 = 3 + 4,那么还有值就全部添加到尾部
        return res.next	# 头指针指向的下一个引用域即是我们将排好序的结果

猜你喜欢

转载自blog.csdn.net/submarineas/article/details/100831750