Leetcode刷题之剑指offfer
这个博客记录leetcode-剑指offer中遇到问题的题目
剑指 Offer 04. 二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
限制:
0 <= n <= 1000
0 <= m <= 1000
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if matrix == []:
return False
row = 0
col = len(matrix[0]) - 1
n = len(matrix)
m = len(matrix[0])
while -1 < row < n and -1 < col < m:
if matrix[row][col] == target:
return True
elif matrix[row][col] > target:
col -= 1
else:
row += 1
return False
从右上角开始,左边的比它小,下边的比它大。
剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/
9 20
/
15 7
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if inorder == []:
return None
elif len(inorder) == 1:
return TreeNode(preorder[0])
elif len(inorder) == 2:
root = TreeNode(preorder[0])
if preorder == inorder:
root.right = TreeNode(preorder[-1])
else:
root.left = TreeNode(preorder[-1])
return root
else:
root = TreeNode(preorder[0])
ind = inorder.index(preorder[0])
left_in = inorder[:ind]
right_in = inorder[ind+1:]
left_pre = preorder[1:1+len(left_in)]
right_pre = preorder[-len(right_in):]
root.left = self.buildTree(left_pre,left_in) if left_in != [] else None
root.right = self.buildTree(right_pre,right_in) if right_in != [] else None
return root
在前序遍历中寻找左右子树对应节点时,可以利用之前左右子树中序遍历节点长度生成。
剑指 Offer 13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
class Solution:
def getnum(self,x):
tmp = 0
while x > 0:
tmp += (x%10)
x = x // 10
return tmp
def movingCount(self, m: int, n: int, k: int) -> int:
list_res = set([(0,0)])
for i in range(m):
for j in range(n):
if ((i - 1, j) in list_res or (i, j - 1) in list_res) and self.getnum(i) + self.getnum(j) <= k:
list_res.add((i, j))
return len(list_res)
不要把简单的问题复杂化,由[0,0]开始,依次计算就可以,不要过度追求递归之类的。
剑指 Offer 16. 数值的整数次方
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
elif x == 0:
return 0
res = 1
n_abs = abs(n)
while n_abs > 0:
if n_abs % 2 == 1:
res = res*x
x = x*x
n_abs = n_abs//2
if n > 0 :
return res
else:
return 1/res
算法分为两个知识点:
1.十进制和二进制之间的转换:
n = 1b1 + 2b2 + 4*b3 + … +2^(m-1)*b_m
2.x的n次方展开
xn = x1b1+2b2+4b3+…+2**(m-1)bm
= x1*b1*x2*b2x4*b3…*x2**(m-1)*bm
剑指 Offer 12. 矩阵中的路径(重点题目)
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
示例 2:
输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]:
return False
if k == len(word) - 1:
return True
board[i][j] = ''
res = dfs(i + 1, j, k + 1) or dfs(i - 1, j, k + 1) or dfs(i, j + 1, k + 1) or dfs(i, j - 1, k + 1)
board[i][j] = word[k]
return res
for i in range(len(board)):
for j in range(len(board[0])):
# 起点
if dfs(i, j, 0):
return True
return False
递归
今天看的两个题都是这种里面嵌套了一个函数的实现方法
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/
2 6
/
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
class Solution:
def verifyPostorder(self, postorder: [int]) -> bool:
# 二叉搜索树
# 后序遍历
if postorder == []:
return True
root = postorder[-1]
left = postorder[:-1]
right = []
for i in range(0,len(postorder)-1):
if postorder[i] > root:
left = postorder[:i]
right = postorder[i:-1]
break
# 左边肯定是符合要求的,只检查右子树即可
for i in right:
if i < root:
return False
return self.verifyPostorder(left) and self.verifyPostorder(right)
这道题目里面有两个知识点,1.后序遍历;2.二叉搜索树左节点<根节点<右节点
剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
if root == None:
return []
if root.left == None and root.right == None:
res = [[root.val]] if root.val == sum else []
return res
else:
left_res = self.pathSum(root.left,sum-root.val)
right_res = self.pathSum(root.right,sum-root.val)
res = []
for r in left_res+right_res:
r = [root.val] + r
res.append(r)
return res
递归最原始,最朴素的写法和思想
剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
# 压入 弹出 先进后出
storehouse = []
while pushed != [] and popped != []:
# 入栈
storehouse.append(pushed[0])
pushed = pushed[1:]
while storehouse!= [] and storehouse[-1] == popped[0]:
# 出栈
storehouse = storehouse[:-1]
popped = popped[1:]
if storehouse == [] and pushed == [] and popped == []:
return True
else:
return False
剑指 Offer 45. 把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: “102”
示例 2:
输入: [3,30,34,5,9]
输出: “3033459”
提示:
0 < nums.length <= 100
class Solution:
def minNumber(self, nums: List[int]) -> str:
def join_list(list_nums):
str_nums = [str(n) for n in list_nums]
return int(''.join(str_nums))
# min
for x in range(int(0.5*len(nums))):
i = x
j = len(nums)-1-x
while i < len(nums)-1-x:
tmp = nums[:]
tmp[i],tmp[i+1] = tmp[i+1],tmp[i]
if join_list(tmp) < join_list(nums):
nums[i],nums[i+1] = nums[i+1],nums[i]
tmp = nums[:]
tmp[j-1],tmp[j] = tmp[j],tmp[j-1]
if join_list(tmp) < join_list(nums):
nums[j-1],nums[j] = nums[j],nums[j-1]
"""
if nums[i] > nums[i+1]:
nums[i],nums[i+1] = nums[i+1],nums[i]
if nums[j-1] > nums[j]:
nums[j-1],nums[j] = nums[j],nums[j-1]
"""
i += 1
j -= 1
nums_str = [str(n) for n in nums]
return ''.join(nums_str)
其实就是一个排序算法,只不过目标不一样!!!
剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
# ^异或符号, &与符号, << 左移一位
ret = functools.reduce(lambda x, y: x ^ y, nums)
# 所有数据的异或结果,相同数据的异或结果是0
div = 1
# 一开始应该是只有一位
while div & ret == 0:
div <<= 1
# 左移一位
# 找到不为0的第一位
a, b = 0, 0
for n in nums:
# 分组,只要是异或为1,必定是不一样的值
if n & div:
# 不为0的第一位对于数值是1
a ^= n
else:
# 不为0的第一位对于数值是0
b ^= n
return [a, b]
剑指 Offer 64. 求1+2+…+n
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
限制:
1 <= n <= 10000
class Solution:
def __init__(self):
self.res = 0
def sumNums(self, n: int) -> int:
(n>1) and self.sumNums(n-1)
self.res += n
return self.res
常见的逻辑运算符有三种,即 “与 &&&& ”,“或 ||∣∣ ”,“非 !! ” ;而其有重要的短路效应,如下所示:
if(A && B)
// 若 A 为 false ,则 B 的判断不会执行(即短路),直接判定 A && B 为 false
if(A || B)
// 若 A 为 true ,则 B 的判断不会执行(即短路),直接判定 A || B 为 true
本题需要实现 “当 n = 1时终止递归” 的需求,可通过短路效应实现。
n > 1 && sumNums(n - 1)
// 当 n = 1 时 n > 1 不成立 ,此时 “短路” ,终止后续递归
剑指 Offer 60. n个骰子的点数
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
class Solution:
def dicesProbability(self, n: int) -> List[float]:
# n个骰子的话,最大的数是n,最小的数是6*nn
dp = [[0 for i in range(6*n+1)]for j in range(1+n)]
# dp这里,行数就是骰子的个数,列数就是n个骰子时,最大数,这里这样写的好处是和原始数据对上了
dp[1][1:7] = [1]*6
for i in range(2,n+1):
for j in range(i,6*i+1):
# n个骰子时的数字变化范围
for k in range(1,7):
# 骰子有六个面
dp[i][j] += dp[i-1][j-k]
x = dp[n][n:6*n+1]
y = sum(x)
x = [xx/y for xx in x]
return x
剑指 Offer 66. 构建乘积数组
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
提示:
所有元素乘积之和不会溢出 32 位整数
a.length <= 100000
class Solution:
def constructArr(self, a: List[int]) -> List[int]:
"""
if len(a) <= 1:
return a
elif len(a) == 2:
return [a[1],a[0]]
else:
tmp = self.constructArr(a[:-1])
last = 1
for t in range(len(tmp)):
tmp[t] = tmp[t]*a[-1]
last *= a[t]
return tmp + [last]
"""
res = [1 for i in range(len(a))]
for i in range(1,len(a)):
res[i] = a[i-1]*res[i-1]
res1 = [1 for i in range(len(a))]
for i in range(len(a)-2,-1,-1):
res1[i] = a[i+1]*res1[i+1]
res[i] = res1[i]*res[i]
return res
第一种方法会超时,第二种方法是,从上到下计算一次,从右到左再计算一次。