剑指17.打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
1.作为一个傻瓜,这道题可以说是过于简单了,一行代码搞定。
class Solution:
def printNumbers(self, n: int) -> List[int]:
return list(range(1,10**n))
2.果然傻瓜终究是傻瓜,这道题还有一个隐藏考点,那就是大数问题。考虑大数的话,就只能用字符串来做,n位字符,每隔字符取值0-9,通过dfs得到可能的取值。如何删除高位0是关键,其实就是设置一个起始位start,一开始是n-1,也就是只有最后一位,当低位全是9(n - self.start == self.nine)的时候,start前移,以此来解决高位0的问题。
class Solution:
def printNumbers(self, n: int) -> [int]:
result = []
temp = []
self.start = n-1
self.nine = 0
def recursion(nth):
if nth == n:
may = "".join(temp[self.start:])
if may != '0':result.append(int(may))
if n - self.start == self.nine: self.start -= 1
return
for num in range(10):
if num == 9: self.nine += 1
temp.append(str(num))
nth += 1
recursion(nth)
nth -= 1
temp.pop()
self.nine -= 1
recursion(0)
return result
自我反思: 大数问题感觉真的很关键,在笔试和面试中,都必须考虑大数的情况。
剑指18.删除链表中的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
1.思路灰常简单,就是遍历这个链表,找到该节点,把前一个节点连到下一个节点就好了。
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
start = head
if head.val == val:
return head.next
while head.next:
if head.next.val == val:
head.next = head.next.next
head = head.next
if not head:
break
return start
2.(十分重要)但是,个人建议还是得养成使用双指针pre,cur的习惯,这样链表相关的题思路会更清晰。
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if head.val == val: return head.next
pre, cur = head, head.next
while cur and cur.val != val:
pre, cur = cur, cur.next
pre.next = cur.next
return head
**自我反思:**好的代码习惯和思路习惯,真的非常重要。
剑指19.正则表达式匹配
请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
1.动态规划
状态定义: 设动态规划矩阵 dp , dp[i][j] 代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。
转移方程: 需要注意,由于 dp[0][0] 代表的是空字符的状态, 因此 dp[i][j] 对应的添加字符是 s[i - 1] 和 p[j - 1] 。
当 p[j - 1] = ‘*’ 时, dp[i][j] 在当以下任一情况为 true 时等于 true :
dp[i][j - 2]: 即将字符组合 p[j - 2] * 看作出现 0 次时,能否匹配;
dp[i - 1][j] 且 s[i - 1] = p[j - 2]: 即让字符 p[j - 2] 多出现 1 次时,能否匹配;
dp[i - 1][j] 且 p[j - 2] = ‘.’: 即让字符 ‘.’ 多出现 1 次时,能否匹配;
当 p[j - 1] != ‘*’ 时, dp[i][j] 在当以下任一情况为 true 时等于 true :
dp[i - 1][j - 1] 且 s[i - 1] = p[j - 1]: 即让字符 p[j - 1] 多出现一次时,能否匹配;
dp[i - 1][j - 1] 且 p[j - 1] = ‘.’: 即将字符 . 看作字符 s[i - 1] 时,能否匹配;
初始化: 需要先初始化 dp 矩阵首行,以避免状态转移时索引越界。
dp[0][0] = true: 代表两个空字符串能够匹配。
dp[0][j] = dp[0][j - 2] 且 p[j - 1] = ‘*’: 首行 s 为空字符串,因此当 p 的偶数位为 * 时才能够匹配(即让 p 的奇数位出现 0 次,保持 p 是空字符串)。因此,循环遍历字符串 p ,步长为 2(即只看偶数位)。
返回值: dp 矩阵右下角字符,代表字符串 s 和 p 能否匹配。
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m = len(s)+1
n = len(p)+1
matrix = [[False] * n for _ in range(m)]
matrix[0][0] = True
for c in range(2,n,2):
if p[c-1] == '*':
matrix[0][c] = matrix[0][c-2]
for r in range(1,m):
for c in range(1,n):
if p[c-1] == '*':
matrix[r][c] = matrix[r][c-2] or (matrix[r-1][c] and (s[r-1] == p[c-2] or p[c-2] == '.'))
else:
matrix[r][c] = matrix[r-1][c-1] and (s[r-1]==p[c-1] or p[c-1]=='.')
return matrix[-1][-1]
**自我反思:**二刷这道题,依然感觉十分吃力,要么多想,要么少想。K神的解法确实值得反复琢磨和回顾。
剑指20.表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
1.这道题的关键就是画出有限状态机,先写出一个可能的最复杂的数字-12.3e-16,然后根据这个数字来考虑状态转移。
class Solution:
def isNumber(self, s: str) -> bool:
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e'
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot'
{ 'd': 3 }, # 4. 'digit' after 'dot' (‘blank’ before 'dot')
{ 's': 6, 'd': 7 }, # 5. 'e'
{ 'd': 7 }, # 6. 'sign' after 'e'
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e'
{ ' ': 8 } # 8. end with 'blank'
]
p = 0 # start with state 0
for c in s:
if '0' <= c <= '9': t = 'd' # digit
elif c in "+-": t = 's' # sign
elif c in "eE": t = 'e' # e or E
elif c in ". ": t = c # dot, blank
else: t = '?' # unknown
if t not in states[p]: return False
p = states[p][t]
return p in (2, 3, 7, 8)
剑指21.调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
1.傻瓜办法,定义了两个空数组,然后遍历原数组,奇数和偶数分别放在两个数组里,然后返回两个数组的和
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
odd = []
even = []
for number in nums:
if number%2==1:
odd.append(number)
else:
even.append(number)
return odd+even
2.双指针双指针,总是忘了双指针!!!
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
i, j = 0, len(nums)-1
while i < j:
while i < j and nums[i] & 1 == 1: i+=1
while i < j and nums[j] & 1 == 0: j-=1
nums[i], nums[j] = nums[j], nums[i]
return nums
**自我反思:**感觉还是思维习惯不好,要尝试用机器的思维去解题。
剑指22.链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
1.快慢指针,快指针提前k位出发,那么快指针到达链表尾的时候,慢指针的位置就是答案。
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
temp1 = head
temp2 = head
while temp1:
if k > 0:
temp1 = temp1.next
k = k - 1
else:
temp1 = temp1.next
temp2 = temp2.next
return temp2
**自我反思:**对于数据结构,养成良好的思维习惯和代码规范是很重要的。
剑指24.反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
1.依然是双指针,pre和cur的故事
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre, cur = None, head
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
**自我反思:**这类简单的基础题已经要熟练掌握,做到又快又好
剑指25.合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
1.捞中之捞的算法,建了一个新的链表,然后遍历两个原链表,把小的值往里放。
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
head = ListNode(0)
start = head
while l1 or l2:
if l1 == None:
head.next = l2
break
if l2 == None:
head.next = l1
break
if l1.val <= l2.val:
head.next = ListNode(l1.val)
l1 = l1.next
head = head.next
else:
head.next = ListNode(l2.val)
l2 = l2.next
head = head.next
return start.next
2.代码简化
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
head = ListNode(0)
start = head
while l1 and l2:
if l1.val <= l2.val:
start.next = l1
l1 = l1.next
else:
start.next = l2
l2 = l2.next
start = start.next
start.next = l1 if l1 else l2
return head.next
**自我反思:**注意优化代码细节,这道题递归应该也是可以的,但是运行效率可能低一点。
剑指26.数的字结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
1.十分冗长的菜鸡解法,定义了两个递归函数,一个函数去递归A,找到入口。另一个函数递归A和B,B是不是A的子结构。
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
self.result = False
if B == None:
return self.result
def ispartof(node1,node2):
if node2 == None:
return True
if node1 == None:
return False
if node1.val == node2.val:
return ispartof(node1.left,node2.left) and ispartof(node1.right,node2.right)
else:
return False
def recursion(node1):
if node1 == None:
return
if node1.val == B.val:
if ispartof(node1,B):
self.result = True
recursion(node1.left)
recursion(node1.right)
recursion(A)
return self.result
2.K神的简化版本,说实话,这代码我真写不出来,还是老老实实按上面的写吧,其实思想是一样的,都是双递归。
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def recur(A, B):
if not B: return True
if not A or A.val != B.val: return False
return recur(A.left, B.left) and recur(A.right, B.right)
return bool(A and B) and (recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B))
**自我反思:**大道至简,学会代码的优化很重要,但是现在还做不来。
剑指27.二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
1.就是递归,然后把左右子树互换就可以了
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
def recursion(root):
if root == None:
return
temp = root.left
root.left = root.right
root.right = temp
recursion(root.left)
recursion(root.right)
recursion(root)
return root
2.用栈(但是入栈出栈,好像效率不高)
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root: return
stack = [root]
while stack:
node = stack.pop()
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
node.left, node.right = node.right, node.left
return root
**自我反思:**其实递归和栈也就对应了dfs和bfs
剑指28.对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
1.对root进行镜面递归,判断是否相等
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def recursion(root1,root2):
if root1 == None and root2 == None:
return True
if root1 == None or root2 == None:
return False
if root1.val == root2.val:
return recursion(root1.left,root2.right) and recursion(root1.right,root2.left)
else:
return False
return recursion(root,root)
**自我反思:**其实很简单,但我记得刚开始做的时候,没有想着用镜面递归,而是想把值取出来判断数组是否回文,十分傻叉。
剑指29.顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
1.再一次被我的长度震惊了,大致思路就是顺时针打印矩阵边缘,然后不断缩小边界。就是代码有点过于冗长了。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
result = []
if matrix == []:
return result
start_c,start_r = 0, 0
end_c,end_r = len(matrix[0])-1, len(matrix)-1
num = 0
sumof = (end_c+1)*(end_r+1)
while start_c<=end_c and start_r<=end_r:
for c in range(start_c,end_c):
result.append(matrix[start_r][c])
num += 1
for r in range(start_r,end_r):
result.append(matrix[r][end_c])
num += 1
for c in range(end_c,start_c,-1):
result.append(matrix[end_r][c])
num += 1
for r in range(end_r,start_r,-1):
result.append(matrix[r][start_c])
num += 1
start_c += 1
start_r += 1
end_c -= 1
end_r -= 1
if num == sumof - 1:
result.append(matrix[start_r-1][start_c-1])
return result[:sumof]
**自我反思:**代码还是不够清晰和精简,感觉面试或者笔试很容易卡bug