[剑指Offer]49~52

[剑指Offer]49~52

学习使用工具

剑指Offer http://itmyhome.com/sword-means-offer/sword-means-offer.pdf

LeetCode的剑指Offer题库 https://leetcode.cn/problemset/all/

剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

**说明: **

  1. 1 是丑数。
  2. n 不超过1690。

解法:

1是丑数,剩下的丑数都可以由已知丑数乘2、3、5得到。

建立三个一模一样的列表,记录丑数序列。同时设置3个指针,分别遍历三个列表中的元素,被遍历过的数已经被用于做乘子来得到更大的丑数。而三个列表分别记录该数是否已经被乘过2、3或5。

设置计数变量i,每次循环寻找下一个最小的丑数,并令对应列表的指针后移一位。同时判定该数是否已经被加入列表,如果已经加入,则继续寻找;若没有,则将该数加入列表,并使i+1。当i已经计数到n时,结束循环。返回列表中的第n个数即可。

def nthUglyNumber(self, n: int) -> int:
        ans2 = [1]
        ans3 = [1]
        ans5 = [1]
        poi2 = 0
        poi3 = 0
        poi5 = 0
        i = 0

        while i < n:
            temp = min(ans2[poi2]*2, ans3[poi3]*3, ans5[poi5]*5)
            if temp == ans2[poi2]*2:
                poi2 += 1
            elif temp == ans3[poi3]*3:
                poi3 += 1
            else: 
                poi5 += 1
            if not temp in [ans2[-1], ans3[-1], ans5[-1]]:
                ans2.append(temp)
                ans3.append(temp)
                ans5.append(temp)
                i += 1

        return ans2[n - 1]

剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例 1:

输入:s = "abaccdeff"
输出:'b'

示例 2:

输入:s = "" 
输出:' '

限制:

0 <= s 的长度 <= 50000

解法:

开辟flag数组进行记录。flag[i]记录26个小写字母是否出现过。遍历原始字符串,检查每个字符,如果该字符未出现过,则将对应flag置为1;如果已出现过,从字符串中删去所有该字符。遍历结束后,返回结果字符串中的第一个字符。

def firstUniqChar(self, s: str) -> str:
        flag = [0] * 26
        ans = s

        for c in s:
            if flag[ord(c) - ord('a')] == 0:
                flag[ord(c) - ord('a')] = 1
            else:
                ans = ans.replace(c, '')

        if ans:
            return ans[0]
        else:
            return ' '

剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

解法:

进行一次归并排序。在合并阶段,记录逆序对总数即可。

def reversePairs(self, nums: List[int]) -> int:
        temp = [0] * len(nums) # 辅助数组,用于归并阶段
        def merge(l: int, r: int): # 归并排序,返回值为此数组所含的逆序对总数
            if l >= r: # 如果l>=r,说明此时子数组的长度已经<=1,返回0
                return 0
            
            m = (l + r) // 2 # 划分
            res = merge(l, m) + merge(m + 1, r) # res等于左右两个数组各自划分后得到的逆序对总数

            i = l # 左指针遍历左数组
            j = m + 1 # 右指针遍历右数组
            temp[l: r + 1] = nums[l: r + 1] # temp保存原始数组的值便于归并
            for k in range(l, r + 1): # 归并阶段,k表示当前的排序位置,每次将对应的元素置于nums[k]
                if i == m + 1: # 如果左指针已经遍历完毕,则将右指针数字加入排序位置,右指针后移
                    nums[k] = temp[j]
                    j += 1
                elif j == r + 1: # 如果右指针已经遍历完毕,则将左指针数字加入排序位置,左指针后移
                    nums[k] = temp[i]
                    i += 1
                elif temp[i] <= temp[j]:# 如果左指针小于右指针,则将左指针数字加入排序位置,左指针后移
                    nums[k] = temp[i]
                    i += 1
                else: 
                # 如果左指针大于右指针,则将右指针数字加入排序位置,右指针后移。
                # 此时产生数量为(m-i+1)的逆序对(即左数组剩下的元素数量),加入res
                    nums[k] = temp[j]
                    j += 1
                    res += m - i + 1
            return res

        return merge(0, len(nums) - 1)

剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表

img

在节点 c1 开始相交。

示例 1:

img

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

img

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

img

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

解法:

自己写的稍微有点笨蛋的解法,先获取两个链表的长度,然后截去长链表的开头部分让两个链表一样长,之后再遍历就行了。

def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        if not headA or not headB:
            return None
        
        def getlen(head: ListNode):
            count = 0
            while head:
                count += 1
                head = head.next
            return count
        
        lenA = getlen(headA)
        lenB = getlen(headB)

        if lenA >= lenB:
            A = headA
            for i in range(lenA - lenB):
                A = A.next
            B = headB
        else:
            A = headB
            for i in range(lenB - lenA):
                A = A.next
            B = headA

        while A:
            if A == B:
                return A
            A = A.next
            B = B.next

        return A

翻答案发现有更好的双指针相遇解法,截取一下K神的解说:

​考虑构建两个节点指针 A , B 分别指向两链表头节点 headA , headB ,做如下操作:

指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:a+(b−c)

指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:b+(a−c)

如下式所示,此时指针 A , B 重合,并有两种情况:

a+(b−c)=b+(a−c)

若两链表 有 公共尾部 (即c>0) :指针 A , B 同时指向「第一个公共节点」node 。

若两链表 无 公共尾部 (即c=0) :指针 A , B 同时指向 null。

因此返回 A 即可。

def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        A, B = headA, headB
        while A != B:
            A = A.next if A else headB
            B = B.next if B else headA
        return A
posted @ 2023-03-06 14:43  无机呱子  阅读(15)  评论(0编辑  收藏  举报