首页 > 技术文章 > 【剑指offer】leetcode刷题 -- Python3实现 -- 共75题(更新中)

yanqiang 2020-10-26 23:39 原文

目录:

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

返回目录

题目链接:

题目描述

解题思路

代码


返回目录

推荐阅读