LeetCode高频算法题Python代码

本文探讨了动态规划和回溯法在解决信息技术领域复杂问题中的核心作用,通过分析多个实例,如股票最大利润、括号生成、二叉树的最近公共祖先等,详细阐述了这两种算法的思路和实现策略,旨在帮助读者深入理解并掌握这两种算法的实际运用。
摘要由CSDN通过智能技术生成

目录

树的子结构

股票的最大利润

把数字翻译成字符串

环形链表的入口

K个一组翻转链表

矩阵中的路径

二叉树的最近公共祖先

剪绳子

和为s的连续正数序列

顺时针打印矩阵

把字符串转换成整数

滑动窗口的最大值

正则表达式匹配

最小编辑代价

括号生成

加起来和为目标值的组合


树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        if not A or not B: return False

        def recur(A, B):
            if not B: return True
            if not A or A.val != B.val: return False
            return recur(A.left, B.left) and recur(A.right, B.right)
        
        return recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)

题解:递归

recur函数:

  • 终止条件
  1. 当节点B为空,说明B已经完成匹配,返回True
  2. 当节点A为空,说明已经越过A的叶节点仍未完成匹配,返回False
  3. 当节点A和B的值不同,说明不匹配,返回False
  • 返回值
  1. 判断A和B的左子节点是否相同
  2. 判断A和B的右子节点是否相同

股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        cost, profit = float('inf'), 0
        for price in prices:
            cost = min(cost, price)
            profit = max(profit, price - cost)
        return profit

题解:动态规划

  • 状态定义:设动态规划列表dpdp[i]表示以prices[i]结尾的子数组的最大利润。
  • 状态转移方程:前i日的最大利润=max(前i-1日的最大利润,第i日价格-前i日最低价格)

把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

class Solution:
    def translateNum(self, num: int) -> int:
        str_num = str(num)
        n = len(str_num)
        dp = [1] * (n + 1)
        for i in range(2, n + 1):
            if str_num[i - 2] == '1' or (str_num[i - 2] == '2' and str_num[i - 1] < '6'):
                dp[i] = dp[i - 2] + dp[i - 1]
            else:
                dp[i] = dp[i - 1]
        return dp[n]

题解:动态规划

对于一个数字num[i],可以有两种选择,一是只翻译自己,二是和前面的数组合翻译,前提是组合数在10-25之间。如果只翻译自己,状态转移方程为:dp(i)=dp(i-1),如果组合翻译,则状态转移方程为:dp(i)=dp(i-2)+dp(i-1)。对于初始化,无数字情况下dp[0]=1,原因是当第1,2位可以组合时,显然有dp[2]=dp[1]+dp[0]=2,而显然dp[1]=1,由此推出dp[0]=1

环形链表的入口

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast, slow = head, head
        while fast:
            if fast.next and fast.next.next:
                fast = fast.next.next
                slow = slow.next
            else:
                return None
            if fast == slow:
                break
        
        slow = head
        while slow != fast:
            slow = slow.next
            fast = fast.next
        return slow

K个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        def reverse(head, tail):
            cur = tail.next
            p = head
            while cur != tail:
                post = p.next
                p.next = cur
                cur = p
                p = post
            return tail, head
        
        hair = ListNode(0)
        hair.next = head
        prev = hair
        while head:
            tail = prev
            for i in range(k):
                tail = tail.next
                if not tail:
                    return hair.next
            nex = tail.next
            head, tail = reverse(head, tail)
            prev.next = head
            tail.next = nex
            prev = tail
            head = tail.next
        return hair.next

矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 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, j + 1, k + 1) or dfs(i-1, j, 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

题解:回溯(深度优先搜索+剪枝)

  • 递归参数:当前元素在矩阵中的行列索引i和j,当前字符在word中的索引k
  • 终止条件:行列索引越界或当前矩阵元素与目标字符不一致则返回False,若k==len(word)-1,则字符串全部匹配完毕,返回True
  • 递推工作:将board[i][j]标记为空,代表此元素已访问过;搜索上下左右相邻的元素;还原当前矩阵元素,即board[i][j]=word[k]。

二叉树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        def dfs(root, p, q):
            if not root or root == p or root == q: return root
            left = dfs(root.left, p, q)
            right = dfs(root.right, p, q)
            if not left: return right
            if not right: return left
            return root
        
        return dfs(root, p, q)

题解:递归

  1. 终止条件:越过叶节点则返回null;当root等于p,q,则返回root
  2. 递推过程:递归左子节点,返回left;递归右子节点,返回right
  3. 返回值:若left和right同时为空,说明root的左右子树都不含p,q,返回null;若left和right同时不为空,说明p,q在root两侧,root为最近公共祖先;当left为空,right不为空,说明p,q都不在root的左子树中,返回right。

剪绳子

给你一根长度为 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。

class Solution:
    def cuttingRope(self, n: int) -> int:
        dp = [0] * (n + 1)
        dp[2] = 1
        for i in range(3, n + 1):
            for j in range(2, i):
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
        return dp[n]

题解:动态规划

对于dp[i],我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对乘积毫无增益,因此从2开始剪。剪了第一段之后,剩下的i-j长度可以剪也可以不剪,如果不剪,长度乘积为j*(i-j);如股票剪的话长度为j*dp[i-j]。因此,取两者最大值。

和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

class Solution:
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        i, j = 1, 1
        res = []
        sum = 0
        while (i <= target // 2):
            if sum < target:
                sum += j
                j += 1
            elif sum > target:
                sum -= i
                i += 1
            else:
                arr = list(range(i, j))
                res.append(arr)
                sum -= i
                i += 1
        return res

题解:滑动窗口法

滑动窗口一般表示成一个左闭右开区间,窗口的左边界和右边界永远只能向右移动。当窗口和小于target时,要扩大窗口,右边界向右移动;当窗口和大于target时,要减小窗口,左边界向右移动。

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix or not matrix[0]: return []

        rows, columns = len(matrix), len(matrix[0])
        visited = [[False] * columns for _ in range(rows)]
        total = rows * columns
        order = [0] * total

        directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
        row, column = 0, 0
        directionIndex = 0
        for i in range(total):
            order[i] = matrix[row][column]
            visited[row][column] = True
            nextRow, nextColum = directions[directionIndex][0] + row, directions[directionIndex][1] + column
            if not(0 <= nextRow < rows and 0 <= nextColum < columns and not visited[nextRow][nextColum]):
                directionIndex = (directionIndex + 1) % 4
            row = row + directions[directionIndex][0]
            column = column + directions[directionIndex][1]
        return order

题解:模拟法

每一时刻更新下一个时刻所要访问的元素时,先试探按照当前状态更新的新元素是否被访问过或越界,然后再更新所要访问的元素。

把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

class Solution:
    def myAtoi(self, s: str) -> int:
        res, i, sign, length = 0, 0, 1, len(s)
        min_int, max_int, boundary = - 2 ** 31, 2 ** 31 - 1, 2 ** 31 // 10
        if not s: return 0
        while s[i] == ' ':
            i += 1
            if i == length: return 0
        if s[i] == '-': sign = -1
        if s[i] in '+-': i += 1
        for c in s[i:]:
            if not '0' <= c <= '9': break
            if res > boundary or (res == boundary and c > '7'):
                return max_int if sign == 1 else min_int
            res = 10 * res + ord(c) - ord('0')
        return sign * res

滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

import collections

class MyQueue:
    def __init__(self):
        self.queue = collections.deque()
    
    def pop(self, value):
        if self.queue and value == self.queue[0]:
            self.queue.remove(self.queue[0])
    
    def push(self, value):
        while self.queue and self.queue[-1] < value:
            self.queue.pop()
        self.queue.append(value)
    
    def front(self):
        return self.queue[0]

class Solution:
    def maxInWindows(self, num, size):
        # write code here
        if size <= 0 or size > len(num):
            return []
        res = []
        queue = MyQueue()
        for i in range(size):
            queue.push(num[i])
        res.append(queue.front())
        for i in range(size, len(num)):
            queue.pop(num[i - size])
            queue.push(num[i])
            res.append(queue.front())
        return res

题解:单调队列

每次滑动窗口时,需要将队列最前面的删除(可能已经删除了),同时在向队列压入新元素时,要始终维持一个单调递减队列。

正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        m, n = len(s), len(p)
        dp = [[False] * (n + 1) for _ in range(m + 1)]
        dp[0][0] = True
        for j in range(2, n + 1):
            dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if p[j - 1] == '*':
                    if dp[i][j - 2]: dp[i][j] = True
                    elif dp[i - 1][j] and s[i - 1] == p[j - 2]: dp[i][j] = True
                    elif dp[i - 1][j] and p[j - 2] == '.': dp[i][j] = True
                else:
                    if dp[i - 1][j - 1] and s[i - 1] == p[j - 1]: dp[i][j] = True
                    elif dp[i - 1][j - 1] and p[j - 1] == '.': dp[i][j] = True
        
        return dp[m][n]

题解:动态规划

最小编辑代价

给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。

class Solution:
    def minEditCost(self , str1 , str2 , ic , dc , rc ):
        # write code here
        m, n = len(str1), len(str2)
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(1, m + 1):
            dp[i][0] = dp[i - 1][0] + dc
        for j in range(1, n + 1):
            dp[0][j] = dp[0][j - 1] + ic
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if str1[i - 1] == str2[j - 1]:
                    dp[i][j] = min(dp[i - 1][j] + dc, dp[i][j - 1] + ic, dp[i - 1][j -1])
                else:
                    dp[i][j] = min(dp[i - 1][j] + dc, dp[i][j - 1] + ic, dp[i - 1][j - 1] + rc)
        return dp[m][n]

题解:动态规划

当str1[i-1]和str2[j-1]不相等时,如果dp[i-1][j]已得,即str1[i-2]和str2[j-1]已经相等,可以由删除str1[i-1]得到目标;如果dp[i][j-1]已得,即str1[i-1]和str2[j-2]已经相等,可以由插入str[i]得到目标。

括号生成

给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。
 

class Solution:
    def generateParenthesis(self , n ):
        # write code here
        res = []
        
        def dfs(string, left, right):
            if left == 0 and right == 0:
                res.append(string)
            if right < left:
                return
            if left > 0:
                dfs(string + '(', left - 1, right)
            if right > 0:
                dfs(string + ')', left, right - 1)
        left, right = n, n
        dfs('', left, right)
        return res

题解:回溯法

加起来和为目标值的组合

给出一组候选数 C 和一个目标数 T ,找出候选数中起来和等于 T 的所有组合。

C 中的每个数字在一个组合中只能使用一次。

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        res, path = [], []
        def backstrack(begin, target):
            if target == 0:
                res.append(list(path))
                return
            
            for i in range(begin, len(candidates)):
                if target < 0: break
                if i > begin and candidates[i] == candidates[i - 1]:
                    continue
                path.append(candidates[i])
                backstrack(i + 1, target - candidates[i])
                path.pop()
        backstrack(0, target)
        return res

题解:回溯法

关键在于两个参数的调整以及剪枝过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值