【剑指offer】leetcode刷题 -- Python3实现 -- 共75题(更新中)
目录:
1. 剑指 Offer 03. 数组中重复的数字 -- 简单
2. 剑指 Offer 04. 二维数组中的查找 -- 简单
3. 剑指 Offer 05. 替换空格 -- 简单
4. 剑指 Offer 06. 从尾到头打印链表 -- 简单
5. 剑指 Offer 07. 重建二叉树 -- 中等
6. 剑指 Offer 09. 用两个栈实现队列 -- 简单
7. 剑指 Offer 10- I. 斐波那契数列 -- 简单
8. 剑指 Offer 10- II. 青蛙跳台阶问题 -- 简单
9. 剑指 Offer 11. 旋转数组的最小数字 -- 简单
10. 剑指 Offer 12. 矩阵中的路径 -- 中等
11. 剑指 Offer 13. 机器人的运动范围 -- 中等
12. 剑指 Offer 14- I. 剪绳子 -- 中等
13. 剑指 Offer 14- II. 剪绳子 II -- 中等
14. 剑指 Offer 15. 二进制中1的个数 -- 简单
剑指 Offer 03. 数组中重复的数字 -- 2020-9-8
题目链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
题目描述
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
解题思路
方法一:先排序,然后再遍历一遍。时间复杂度O(nlogn),空间复杂度O(1)。
方法二:使用哈希表。时间复杂度O(n),空间复杂度O(n)。
方法三:时间复杂度O(n),空间复杂度O(1),如下:
遍历,判断当前数m和下标i是否相同,如果不同,则与下标m的数比较,如果相同则找到重复,如果不同则交换。
缺点:这个方法会改变输入的数组。
题目稍微变一下:
长度为n+1的数组中包含有1~n的数字,找出任意一个重复元素。并且不能改变输入的数组。
思路1:将数组复制一份,然后采用上面的方法。
思路2:时间换空间。因为元素总数n+1大于n,所以可以采取二分查找的方法。
将数组一分为二,如果其中一个子数组个数大于n/2,则这个子数组里有重复元素,继续递归查找。
这个方法的时间复杂度O(nlogn),空间复杂度O(1)。
代码
class Solution:
def findRepeatNumber(self, nums):
# 数组为空检查
if nums is None or len(nums) == 0:
return -1
# 非法值检查
for i in range(len(nums)):
if not (nums[i] >= 0 and nums[i] <= len(nums)-1):
return -1
# 查找重复值
for i in range(len(nums)):
while nums[i] != i:
m = nums[i]
if nums[i] == nums[m]: # 发现重复值
return nums[i]
else:
# swap
temp = nums[i]
nums[i] = nums[m]
nums[m] = temp
return -1
剑指 Offer 04. 二维数组中的查找 -- 2020-9-8
题目链接:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/
题目描述
在一个 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 is None or len(matrix) == 0 or len(matrix[0]) == 0:
return False
rows, cols = len(matrix), len(matrix[0])
i, j = 0, cols-1
while(i < rows and j >= 0):
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
j -= 1
else:
i += 1
return False
剑指 Offer 05. 替换空格 -- 2020-9-8
题目链接:https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/
题目描述
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
解题思路
从前往后替换,需要多次移动字符,时间复杂度是O(\(n^2\))。
从后往前替换,则时间复杂度只有O(n)。
不过python的话,更加简单,直接replace或者先split再join即可。
代码
class Solution:
def replaceSpace(self, s: str) -> str:
if s is None or len(s) == 0:
return s
return s.replace(' ', '%20')
剑指 Offer 06. 从尾到头打印链表 -- 2020-9-8
题目链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/
题目描述
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
解题思路
后进先出,用栈实现既可。
代码
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
if head is None:
return []
temp = head
lst = []
while temp != None:
lst.append(temp.val)
temp = temp.next
lst.reverse()
return lst
剑指 Offer 07. 重建二叉树 -- 2020-9-9
题目链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
限制:
0 <= 节点个数 <= 5000
解题思路
前序遍历的第一个元素就是根节点3,在中序遍历中,3左边的所有元素属于左子树,3右边的所有元素属于右子数。
对应于前序遍历,除了根节点外,先输出左子树,后输出右子树。所以找到左右子树对应的前序中序序列,由此可以通过递归的方法解决。
但是递归效率比较低,所以可以改成迭代。
代码
递归版本:
# 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 preorder is None or inorder is None or len(preorder) == 0 or len(preorder) != len(inorder):
return None
root = preorder[0]
left_pre, right_pre, left_in, right_in = [], [], [], []
# 在中序序列中找根节点
found = False
for i in range(len(inorder)):
if inorder[i] != root and not found:
left_in.append(inorder[i])
elif inorder[i] != root and found:
right_in.append(inorder[i])
elif inorder[i] == root:
found = True
if not found:
return None
left_pre = preorder[1:len(left_in)+1]
right_pre = preorder[len(left_in)+1:]
tree_node = TreeNode(root)
tree_node.left = self.buildTree(left_pre, left_in)
tree_node.right = self.buildTree(right_pre, right_in)
return tree_node
迭代版本:
# 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 preorder is None or inorder is None or len(preorder) == 0 or len(preorder) != len(inorder):
return None
stack = []
root = TreeNode(preorder[0])
stack.append(root)
length = len(preorder)
index = 0
for i in range(1, length):
next_pre = preorder[i]
temp = TreeNode(next_pre)
if stack[-1].val != inorder[index]:
stack[-1].left = temp
stack.append(temp)
else:
while stack and stack[-1].val == inorder[index]:
node = stack[-1]
stack.pop()
index += 1
node.right = temp
stack.append(temp)
return root
剑指 Offer 09. 用两个栈实现队列 -- 2020-9-10
题目链接:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
题目描述
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,
分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
解题思路
栈1专门用来推入,栈2用来推出。
推出时,如果栈2为空,则把栈1的依次取出放入栈2,再推出。
如果栈2为空,且栈1也为空,则返回-1。
代码
class CQueue:
def __init__(self):
self.stack1, self.stack2 = [], []
def appendTail(self, value: int) -> None:
self.stack1.append(value)
def deleteHead(self) -> int:
if self.stack2:
return self.stack2.pop()
if not self.stack1:
return -1
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
剑指 Offer 10- I. 斐波那契数列 -- 2020-9-11
题目链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/
题目描述
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 100
解题思路
用迭代的方法计算。
还有一种log(n)时间复杂度的公式法。
代码
class Solution:
def fib(self, n: int) -> int:
a, b = 0, 1
for i in range(n):
a, b = b, a+b
return a % 1000000007
剑指 Offer 10- II. 青蛙跳台阶问题 -- 2020-9-11
题目链接:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
解题思路
如果总共n级台阶,若n大于2,则跳第一步有两种,跳一级和跳两级,f(n) = f(n-1) + f(n-2),还是在计算裴波那切数列。
代码
class Solution:
def numWays(self, n: int) -> int:
a, b = 1, 1
for i in range(n):
a, b = b, a+b
return a % 1000000007
剑指 Offer 11. 旋转数组的最小数字 -- 2020-9-11
题目链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
解题思路
使用两个指针,指向最左边和最右边,值为left和right。
1- 如果left小于right,说明整个数组都排好序了,直接返回left。
2- 如果left大于right,取中间位置的值mid判断:
如果mid和left、right都相等,无法判断属于哪边,则退回到顺序遍历处理。
如果mid大于或者等于left,则该值属于左有序序列,将左边指针指向mid,
如果mid小于或者等于right,则该值属于右有序序列,将右边指针指向mid。
如果left和right位置相邻,则返回right。
代码
class Solution:
def minArray(self, numbers: List[int]) -> int:
left = 0
right = len(numbers)-1
mid = left
while(numbers[left] >= numbers[right]):
if left+1 == right:
return numbers[right]
mid = (left + right)//2
if numbers[mid] == numbers[left] and numbers[mid] == numbers[right]:
temp_min = numbers[left]
for i in range(len(numbers)):
if numbers[i] < temp_min:
temp_min = numbers[i]
return temp_min
elif numbers[mid] >= numbers[left]:
left = mid
elif numbers[mid] <= numbers[right]:
right = mid
return numbers[mid]
剑指 Offer 12. 矩阵中的路径 -- 2020-10-26
题目链接:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的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
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
解题思路
思路很简单,就是所谓的回溯法,或者叫DFS。
不过代码要多写几次,容易出错。
代码
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
if board is None or word is None or len(board) <= 0 or len(board[0]) <= 0 or len(word) <= 0:
return False
rows = len(board)
cols = len(board[0])
for i in range(rows):
for j in range(cols):
visited = []
for k in range(rows):
visited.append([False]*cols)
if board[i][j] == word[0] \
and self.exist_from_here(board, visited, word, 0, rows, cols, i, j):
return True
return False
def exist_from_here(self, board, visited, word, index, rows, cols, x, y):
if index == len(word)-1:
return True
visited[x][y] = True
next_node = self.get_next_node(word[index+1], board, visited, rows, cols, x, y)
for node in next_node:
if self.exist_from_here(board, visited, word, index+1, rows, cols, node[0], node[1]):
return True
visited[x][y] = False
return False
def get_next_node(self, char, board, visited, rows, cols, x, y):
order = [[-1, 0], [0, 1], [1, 0], [0, -1]]
rst = []
for i in range(len(order)):
new_x = order[i][0] + x
new_y = order[i][1] + y
if 0 <= new_x < rows and 0 <= new_y < cols \
and not visited[new_x][new_y] and board[new_x][new_y] == char:
rst.append([new_x, new_y])
return rst
剑指 Offer 13. 机器人的运动范围 -- 2020-10-28
题目链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/
题目描述
地上有一个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
解题思路
和上题相同,也是回溯法,用递归实现DFS即可。
代码
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
if k < 0 or m <= 0 or n <= 0:
return 0
visited = [False]*(m*n)
count = self.moveNext(k, m, n, 0, 0, visited)
return count
def moveNext(self, k, rows, cols, row, col, visited):
count = 0
if(self.check(k, rows, cols, row, col, visited)):
visited[row*cols+col] = True
count = 1 + self.moveNext(k, rows, cols, row+1, col, visited) \
+ self.moveNext(k, rows, cols, row, col+1, visited) \
+ self.moveNext(k, rows, cols, row-1, col, visited) \
+ self.moveNext(k, rows, cols, row, col-1, visited)
return count
def check(self, k, rows, cols, row, col, visited):
if 0 <= row < rows and 0 <= col < cols \
and self.getSum(row)+self.getSum(col)<=k and not visited[row*cols+col]:
return True
return False
def getSum(self, num):
rst = 0
while num != 0:
rst += num % 10
num = num // 10
return rst
剑指 Offer 14- I. 剪绳子 -- 2020-10-28
题目链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]k[1]...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
解题思路
可以采用动态规划法(时间复杂度O(n^2), 空间复杂度O(n)),
贪婪算法(时间复杂度O(1), 空间复杂度O(1))。
动态规划法求解子问题的思路比较常规,而贪婪算法只要数学基础好就行。
代码
class Solution:
def cuttingRope(self, n: int) -> int:
if n < 2:
return 0
if n == 2:
return 1
if n == 3:
return 2
times_3 = n // 3
if n % 3 == 1:
times_3 -= 1
times_2 = (n - times_3*3)//2
return 3**times_3 * 2**times_2
剑指 Offer 14- II. 剪绳子 II -- 2020-10-28
题目链接:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/
题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]k[1]...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
解题思路
题目和上题几乎一样,最后取个模就可以。取模的话,在中间过程中取也是一样的。
代码
class Solution:
def cuttingRope(self, n: int) -> int:
if n < 2:
return 0
if n == 2:
return 1
if n == 3:
return 2
times_3 = n // 3
if n % 3 == 1:
times_3 -= 1
times_2 = (n - times_3*3)//2
rst = 1
for i in range(times_3):
rst *= 3
rst %= 1000000007
for i in range(times_2):
rst *= 2
rst %= 1000000007
return rst
剑指 Offer 15. 二进制中1的个数 -- 2020-10-29
题目链接:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/
题目描述
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
解题思路
两种情况,第一种,最右边是1,如1011,则减去1为1010,与原数与位运算得1010。
第二种情况,最右边是0,如1100,则减去1为1011,与原数与位运算得1000。
用这种技巧可以快速求得结果,也不用担心因为对负数移位导致的死循环问题。
代码
class Solution:
def hammingWeight(self, n: int) -> int:
cnt = 0
while(n):
cnt += 1
n = (n-1) & n
return cnt
题目链接:
题目描述
解题思路
代码