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