[剑指Offer]10~12
[剑指Offer]10~12
学习使用工具
剑指Offer http://itmyhome.com/sword-means-offer/sword-means-offer.pdf
LeetCode的剑指Offer题库 https://leetcode.cn/problemset/all/
剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n
,求斐波那契(Fibonacci)数列的第 n
项(即 F(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
解法:
递归,剑指Offer的作者到底有多喜欢递归?还是说面试真的很喜欢考递归?
def fib(self, n: int) -> int:
if n == 0:
return 0
if n == 1:
return 1
return (self.fib(n - 1) + self.fib(n - 2)) % 1000000007
三秒写了一个递归上去,结果超时了……遂用非递归手段进行解题。
新建两个变量,\(a = F(N - 1)\),\(b = F(N - 2)\)。更新时:
- a更新为\(F(N)\),因此\(a' = a + b\)
- b更新为\(F(N-1)\),因此\(b = a' - b\)
- \(a'\)取模
def fib(self, n: int) -> int:
if n == 0 or n == 1:
return n
a = 1
b = 0
for i in range(n-1):
a = a + b
b = a - b
a %= 1000000007
return a
剑指 Offer 10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n
级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
解法:
斐波那契数列的变式。
青蛙跳上n级台阶的所有方法可以分为两种:一种是最后跳了1级,另一种是最后跳了2级。而这两种方式的数量分别为跳上n-1级台阶的方法数、跳上n-2级台阶的方法数。
简而言之,青蛙跳上n
级台阶有F(n)
种跳法,而F(n)
具有以下性质:
F(0) = 1, F(1) = 1, F(n) = F(n - 1) + F(n - 2)
因此视为初始值不同的斐波那契问题即可。
def numWays(self, n: int) -> int:
if n == 0 or n == 1:
return 1
a = 1
b = 1
for i in range(n-1):
a = a + b
b = a - b
a %= 1000000007
return a
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers
,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2]
为 [1,2,3,4,5]
的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
示例 1:
输入:numbers = [3,4,5,1,2]
输出:1
示例 2:
输入:numbers = [2,2,2,0,1]
输出:0
提示:
n == numbers.length
1 <= n <= 5000
-5000 <= numbers[i] <= 5000
numbers
原来是一个升序排序的数组,并进行了1
至n
次旋转
解法:
有现成的轮子为什么要自己写?(Offer消失术)
def minArray(self, numbers: List[int]) -> int:
return min(numbers)
当然遍历一次需要O(n)
的时间开销。按照题意应该是希望拿出时间复杂度小于O(N)
的解法,所以用二分,期望时间复杂度为O(logN)
。
旋转后的数组理论上会分为两段升序序列,这两段序列有以下性质:
-
第一段升序序列所有元素均不小于第二段升序序列;
-
数组最小值是第二段升序序列的首位元素。
设当前待查找区间左端点为left
,右端点为right
。对每一次二分查找过程,有以下判定步骤:
- 获取中点位置
temp
- 若
numbers[temp] < numbers[right]
,说明此时中点正处于第二段升序序列中,temp
位置右侧的元素必然都大于temp
位置的元素。因此,将temp
作为新的右端点,即right = temp
- 若
numbers[temp] > numbers[right]
,说明此时中点正处于第一段升序序列中,temp
位置的元素必然都大于temp
位置左侧的元素。因此,将temp+1
作为新的左端点,即left = temp+1
- 若
numbers[temp] = numbers[right]
,无法判断当前位置,右侧端点向左移动一格
def minArray(self, numbers: List[int]) -> int:
left = 0
right = len(numbers) - 1
while left < right:
temp = (left + right) // 2
if numbers[temp] < numbers[right]:
right = temp
elif numbers[temp] > numbers[right]:
left = temp + 1
else:
right -= 1
return numbers[left]
剑指 Offer 12. 矩阵中的路径
-
给定一个
m x n
二维字符网格board
和一个字符串单词word
。如果word
存在于网格中,返回true
;否则,返回false
。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。
示例 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
提示:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board
和word
仅由大小写英文字母组成
解法:
又递归,天天递归。
最初想法是把矩阵看作是图,构建一个邻接链表,但是感觉构建过程很复杂。后来想着用深搜应该可行。差不多就是暴力搜索了,不过代码不是很好写,慢慢来!
虽然思路是对的有时候稍微不注意很容易就超时了。
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i: int, j: int, k: int): # 深搜
if i >= len(board) or j >= len(board[0]): # 先判断越没越界
return False
if i < 0 or j < 0:
return False
if board[i][j] != word[k]: # 再判断匹不匹配
return False
if k == len(word) - 1:
# word已经遍历完了,说明整个字符串已经匹配上了,返回True
return True
board[i][j] = '/' # 被遍历过的节点做标记,不能再被遍历
# 上下左右四个方向继续搜索
a = dfs(i-1,j,k+1)
b = dfs(i+1,j,k+1)
c = dfs(i,j-1,k+1)
d = dfs(i,j+1,k+1)
board[i][j] = word[k] # 搜索完了,去掉标记,恢复节点值
return a or b or c or d # 返回搜索结果
for i in range(len(board)):
for j in range(len(board[0])):
# 两层循环遍历board,对每个起始位置进行深搜,一旦搜索成功返回True
if dfs(i,j,0):
return True
return False # 没搜索到,返回False