LeetCode剑指offer题目记录6

leetcode刷题开始啦, 每天记录几道题.

剑指 Offer 17. 打印从1到最大的n位数

题目描述

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。n 为正整数.

示例

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

思路

只要找到上界就好了. 上界显然是 n 位 9.

python

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        Max = 0
        while n:
            Max = Max * 10 + 9
            n -= 1
        
        return [i for i in range(1, Max+1)]

剑指 Offer 18. 删除链表的节点

题目描述

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。题目保证链表中节点的值互不相同

示例

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

思路

为了处理第一个节点就要删除的情形, 可以用一个空节点指向头节点, 最后返回这个空节点的下一个节点即可. 用一个节点像带着线的一根针一样从这个空节点开始检查,

  • 如果它的下一个节点要被删除, 那当前节点的下一个节点就应该是它原来的下下个节点, 把针缝到下下个节点上, 完成任务, 终止循环.
  • 如果不用删除, 那当前节点的下一个节点就要被穿到线上, 游标向后移一位, 继续检查.

python

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        if not head.next:
            return None
        dummy = ListNode(0, head)
        pointer = dummy
        while pointer and pointer.next:
            if pointer.next.val == val:
                pointer.next = pointer.next.next
                break
            else:
                pointer = pointer.next
        
        return dummy.next

剑指 Offer 19. 正则表达式匹配

题目描述

请实现一个函数用来匹配包含’. ‘和’*‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。

思路

这题我没想出来, 看的答案. 动态规划真难想啊.

总结一下思路, 用动态规划, f[i][j] 表示 s 的前 i 个字符和 p 的前 j 个字符能否匹配. f[0][0] = True, 表示两个空字符串能匹配. 依次检查 p 中的元素,

  • p[j] 是 ‘*’, 就看 s[i] 和 p[j-1] 是否匹配
    • 如果不匹配, 可能的情况是 ‘*’ 前面的元素一次都没出现, 那 s 的前 i 个和 p 的前 j 个能否匹配就看 s 的前 i 个和 p 的前 j-2 个能否匹配, 即 f[i][j] = f[i][j-2]
    • 如果匹配, 那就是说 ‘*’ 前面的元素即 p[j-1] 可能出现过, 因为 ‘*’ 前面的元素 p[j-1] 可能出现多次, 所以检查 s 的前 i-1 个能否和 p 的前 j 个匹配, 如果 s 的前 i-1 和 p 的前 j 个不能匹配, 有可能是 s 的前 i 个和 p 的前 j-2 个能匹配, 而 p[j-1] = p[j-2], 实际上是 p[j-1] 一次都没出现的匹配模式. 所以要把这种情况加上 f[i][j] = f[i-1][j] or f[i][j-2]
  • p[j] 是一个字母或者一个 ‘.’ , 那 f[i][j] 是啥就看 s[i] 和 p[j] 是否匹配
    • 如果匹配, f[i][j] 就看 s 的前 i-1 个和 p 的前 j-1 个能否匹配, 即看 f[i-1][j-1] 是啥. f[i][j] = f[i-1][j-1];
    • 如果不匹配, 那 s 的前 i 个字符肯定和 p 的前 j 个字符不匹配, f[i][j] = False

注意, 数组里第 i 个元素的下标为 i-1, 处理的时候要减一

python

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        m, n = len(s), len(p)

        def match(i, j):
            if i == 0:
                return False
            if p[j-1] == '.' or s[i-1] == p[j-1]:
                return True
            return False

        f = [[False for j in range(n+1)] for i in range(m+1)]
        f[0][0] = True
        for i in range(m+1):
            for j in range(1, n+1):
                if p[j-1] == '*':
                    if match(i, j-1):
                        f[i][j] = f[i-1][j] or f[i][j-2]
                    else:
                        f[i][j] = f[i][j-2]
                else:
                    if match(i, j):
                        f[i][j] = f[i-1][j-1]
                    else:
                        f[i][j] = False
        return f[m][n]

剑指 Offer 20. 表示数值的字符串

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

数值(按顺序)可以分成以下几个部分:

  • 若干空格
  • 一个 小数 或者 整数
  • (可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
  • 若干空格

小数(按顺序)可以分成以下几个部分:

  • (可选)一个符号字符(’+’ 或 ‘-’)
  • 下述格式之一:
    • 至少一位数字,后面跟着一个点 ‘.’
    • 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
    • 一个点 ‘.’ ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  • (可选)一个符号字符(’+’ 或 ‘-’)
  • 至少一位数字

部分数值列举如下:

  • ["+100", “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]

部分非数值列举如下:

  • [“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]

思路

就一个个条件去判断吧, 就是要细致一点.

python

class Solution:
    def isNumber(self, s: str) -> bool:
        sign = ["+", "-"]

        candidate = s.strip()
        if not candidate:
            return False
      
        def isNum(chars):
            """return if chars only has digit"""
            if not chars:
                return False
            for char in chars:
                if not "0" <= char <= "9":
                    return False
            return True

        def isInt(chars):
            if not chars:
                return False

            if chars[0] in sign:
                return isNum(chars[1:])
            elif "0" <= chars[0] <= "9":
                return isNum(chars)
            else:
                return False

        def isDecimal(chars):
            def check(string):
                """return if string only consist of decimal(without sign)"""
                if not string:
                    return False

                if string[0] == ".":
                    return isNum(string[1:])
                elif string[-1] == ".":
                    return isNum(string[:-1])
                else:
                    dotBox = []
                    for char in string:
                        if "0" <= char <= "9":
                            continue
                        elif char == "." and not dotBox:
                            dotBox.append(char)
                        else:
                            return False
                    return True

            if chars[0] in sign:
                return check(chars[1:])
            elif "0" <= chars[0] <= "9" or chars[0] == ".":
                return check(chars)
            else:
                return False

        if isInt(candidate) or isDecimal(candidate):
            return True
        
        else:
            for i, element in enumerate(candidate):
                if element == "e" or element == "E":
                    break

            if i == len(candidate) - 1 or i == 0:
                return False

            return (isInt(candidate[:i]) or isDecimal(candidate[:i])) and isInt(candidate[i+1:])

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。

思路

第一个想法是用一个奇数数组和一个偶数数组分别存遍历时遇到的奇数偶数, 然后再拼起来. 但这样需要额外的空间.

python

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        odd = []
        even = []
        for num in nums:
            if num & 1:
                odd.append(num)
            else:
                even.append(num)

        return odd + even

改进

用两个指针, 分别指向头和尾, 都往中间走. 中途左指针遇到偶数, 右指针遇到奇数时, 交换两个对象的位置. 这样可以原地修改, 比较省空间. 两个指针相遇, 循环终止.

python

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        left, right = 0, len(nums) - 1
        while left < right:
            if nums[left] & 1:
                left += 1
            else:
                if nums[right] & 1:
                    nums[left], nums[right] = nums[right], nums[left]
                else:
                    right -= 1
        
        return nums

剑指 Offer 22. 链表中倒数第k个节点

题目描述

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

思路

和上一个题一样, 用两个指针, 先都指向头节点, 然后遍历k次, 让二号指针指向第k个节点, 这样两个指针中间差k个节点. 然后继续遍历, 每次让两个指针同步向后移一位, 直到二号指针为尾部空节点, 这时一号指针就是倒数第 k 个节点.

python

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

class Solution:
    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
        pointer1 = ListNode(0, head)
        pointer2 = pointer1
        idx = 0
        while idx < k:
            pointer2 = pointer2.next
            idx += 1
        while pointer2:
            pointer1 = pointer1.next
            pointer2 = pointer2.next
        
        return pointer1

剑指 Offer 24. 反转链表

这是我刚做力扣的时候觉得特别难的一道题.

题目描述

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

思路

用递归.

  • 基线条件: 空节点或者只有一个节点, 这时候直接返回这个节点就可以
  • 递归条件: 先把这个节点之后的链表反序, 这时候只要让它的下一个节点指向它, 它就并入这个反好序的子链里了. 再让它指向空, 因为现在反序子链只到它. 然后返回这个反好序的子链.

python

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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        result = self.reverseList(head.next)
        head.next.next = head
        head.next =None

        return result

剑指 Offer 25. 合并两个排序的链表

题目描述

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例

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

思路

用两个指针分别为这两个链表的头, 我将以一号指针返回递增的合并结果. 还是用递归.

  • 基线条件: 如果一个链表是空的, 那返回另一个链表即为合并后的链表
  • 递归条件: 比较两个节点的值, 小的为一号. 否则交换. 把一号指针之后的链表与二号指针链表合并, 然后让一号指针指向这个合并链表, 返回一号指针.

实际上不需要另外开辟两个指针, 直接用原来的指针的头即可.

python

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        pointer1 = l1
        pointer2 = l2
        if not l1:
            return l2
        if not l2:
            return l1

        if l1.val > l2.val:
            l1, l2 = l2, l1
        
        l1.next = self.mergeTwoLists(l1.next, l2)

        return l1

剑指 Offer 26. 树的子结构

题目描述

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

示例

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

思路

要写一个函数在A中查找B的根节点. 如果找到A的一个节点C是B的根节点, 再判断B的左 (右) 子树是不是从根节点开始包含在C的左 (右) 子树里.

  • 基线条件: 如果B是空的, 意味着一直检查到叶子节点都没返回false, B包含在A里, 返回true; 如果A是空的, 或者A B的值不相等, 返回false.
  • 递归条件: 如果A B不为空, 就检查A B的左子树和右子树是否满足包含.

可以写一个函数, 判断一个二叉树是否从根节点包含另一个二叉树. 然后递归来调用.

  • 基线条件: 如果A或者B是空的, 就返回false.
  • 递归条件: 如果A从根节点包含B或者A的左子树从根节点包含B或者A的右子树从根节点包含B, 就返回true.

python

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:

        def ifContain(rootA, rootB):
            if rootB == None:
                return True
            if rootA == None or rootA.val != rootB.val:
                return False
            return ifContain(rootA.left, rootB.left) and ifContain(rootA.right, rootB.right)

        def findB(rootA, rootB):
            if rootB == None or rootA == None:
                return False

            return ifContain(rootA, rootB) or findB(rootA.left, rootB) or findB(rootA.right, rootB)
        
        return findB(A, B)

剑指 Offer 27. 二叉树的镜像

题目描述

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

思路

还是用递归.

  • 基线条件: 如果节点没有子树, 直接返回即可
  • 递归条件: 如果节点有子树, 先把左右子树各自镜像, 再把左右子树交换, 返回该节点即可.

python

class Solution:
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        if root == None:
            return root
        
        root.left = self.mirrorTree(root.left)
        root.right = self.mirrorTree(root.right)
        root.left, root.right = root.right, root.left

        return root

剑指 Offer 28. 对称的二叉树

题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

示例

输入:root = [1,2,2,3,4,4,3]
输出:true

思路

从根节点出发,对称地往两边走,每走一步检查两个元素是否匹配,如果匹配,就继续对称地往两边走,直到叶结点或者不匹配。

python

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def check(left, right):
            if not left and not right:
                return True
            if (not left and right) or (left and not right):
                return False
            return left.val == right.val and check(left.left, right.right) and check(left.right, right.left)

        return check(root, root)

剑指 Offer 29. 顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

思路

这个题目不复杂. 只要处理好一圈, 整个就处理好了. 首先给出初始左右边界上下边界:

  • l_edge, r_edge = 0, n - 1
  • u_edge, d_edge = 0, m - 1

下面分析第一圈. 给出指标 i 和 j

  • j 从左边界打到右边界, 打过之后修改元素为标记值 “0”. 打完上面一行后, 就要向下转了. 如果下面的元素 matrix[i+1][j] 是标记值 “0”, 就意味着不能再转了, 终止循环. 这时候我们要更新边界. 更新哪个呢? 更新右边界, 因为最后一列马上就被打, 下一次打印横行时就要从 r_edge 的前一个开始向左打印了. 所以r_edge -= 1
  • i 从上到下打印最右边, 打过之后修改为标记值, 检查能否向左转, 更新下边界;
  • j 从右往左打印最下边, 打过之后修改为标记值, 检查能否向上转, 更新左边界;
  • i 从下到上打印最左边, 打过之后修改为标记值, 检查能否向右转, 更新上边界.
    当左边界跑到右边界右边, 或者下边界跑到上边界上边, 意味着我们已经跑完了所有的圈, 就终止循环.

对一些简单的情形可以直接处理掉.

python

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        m = len(matrix)
        if m == 0:
            return []
        if m == 1:
            return matrix[0]
        n = len(matrix[0])
        if n == 0:
            return []
        
        l_edge, r_edge = 0, n - 1
        u_edge, d_edge = 0, m - 1
        i = j = 0
        result = []

        while l_edge <= r_edge or u_edge <= d_edge:
            for j in range(l_edge, r_edge+1):
                result.append(matrix[i][j])
                matrix[i][j] = "0"
            r_edge -= 1
            if matrix[i+1][j] == "0": break

            for i in range(u_edge+1, d_edge+1):
                result.append(matrix[i][j])
                matrix[i][j] = "0"
            d_edge -= 1
            if matrix[i][j-1] == "0": break

            for j in range(r_edge, l_edge-1, -1):
                result.append(matrix[i][j])
                matrix[i][j] = "0"
            l_edge += 1
            if matrix[i-1][j] == "0": break

            for i in range(d_edge, u_edge, -1):
                result.append(matrix[i][j])
                matrix[i][j] = "0"
            u_edge += 1
            if matrix[i][j+1] == "0": break

        return result
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值