[剑指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 原来是一个升序排序的数组,并进行了 1n 次旋转

解法:

有现成的轮子为什么要自己写?(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"(单词中的字母已标出)。

    img

    示例 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
posted @ 2023-03-06 14:10  无机呱子  阅读(11)  评论(0编辑  收藏  举报