ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

蓝桥杯python组备赛笔记(赛前必看)

2022-01-29 21:58:45  阅读:428  来源: 互联网

标签:必看 nums python 蓝桥 range board print dp math


CONTENT

写在前面

一年一度的蓝桥杯再过几个月就要开始了,博主去年参加了这个比赛拿了省二,多多少少也有一些心得。虽然是很菜的一个奖项,但作为一个数学专业且几乎处处在学数学的蒟蒻,能拿到这个奖已经满意了。

本文实际上来源于我去年备赛的笔记,今天分享出来也是希望能够帮助到准备参加比赛的各位uu们。个人觉得如果你能掌握本文的全部内容并拥有一定的刷题量,拿个奖还是不难的。

废话不多说,直接上干货!

一、输入框架

1. 单个整数输入

n = int(input())

2. 输入一行由空格隔开的整数

a, b = map(int, input().split())  # 用变量存储
arr = list(map(int, input().split()))  # 用列表存储

3. 输入大小为 m × n m\times n m×n 的二维整型数组

arr = []
for i in range(m):
    arr.append(list(map(int, input().split())))

二、输出框架

1. 输出一行由空格隔开的 n n n 个数

# 方法一
for i in range(n - 1):
    print(arr[i], end=' ')
print(arr[n - 1])

# 方法二(推荐)
print(*arr)

2. 输出大小为 m × n m\times n m×n 的二维数组

for i in range(m):
    for j in range(n - 1):
        print(arr[i][j], end=' ')
    print(arr[i][n - 1])

3. print()的用法

print(*objects, sep=' ', end='\n')
# sep是分隔符,默认是空格. end是结尾,默认是换行符.
n = 123
print('|%5d|' % n)   # |  123|
print('|%-5d|' % n)  # |123  |
print('|%05d|' % n)  # |00123|

4. 格式化输出

方法一:

print('%c,%s,%f,%d' % (97, 'abc', 1.0, 2))  # a,abc,1.000000,2

方法二:

print('{} {} {}'.format('a', 'b', 'c'))  # a b c

5. 格式化输出浮点数

pi = 3.1415926
print('%.3f' % pi)  # 3.142

三、IDLE的用法

ctrl + n 新建 python 文件, F5 运行。

四、常用标准库

4.1 math

1. 数论

math.ceil(3.5)  # 4
math.floor(3.5)  # 3
math.fabs(-3)  # 3.0
math.factorial(4)  # 24
math.comb(4, 2)  # 6(组合数)
math.isqrt(5)  # 2(根号5向下取整)
math.gcd(4, 6)  # 2(最大公约数)
math.lcm(4, 6)  # 12(最小公倍数)
math.prod([2, 3, 5])  # 30
math.isfinite(x)  # x既不是无穷大也不是NaN返回True
math.isinf(x)  # x是正或负无穷大,返回True
math.isnan(x)  # x是NaN,返回True

2. 数学函数(结果均为浮点数)

math.exp(x)
math.log(x[, base])  # base默认为e
math.pow(x, y)  # x的y次方
math.sqrt(x)
math.sin(x)
math.cos(x)
math.tan(x)

3. 常数

math.pi
math.e
math.inf  # 浮点正无穷大,对于负无穷大,使用-math.inf,相当于float('inf')
math.nan  # 浮点非数字(NaN)值,相当于float('nan')

4.2 random

random.random()  # 返回[0.0, 1.0)范围内的一个随机浮点数
random.randint(a, b)  # 返回随机整数N满足a<=N<=b
random.choice(seq)  # 从非空序列seq中随机选取一个元素

4.3 collections

4.3.1 Counter

Counter 是 dict 的子类,是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。

collections.Counter('aabbccc')  # Counter({'c': 3, 'a': 2, 'b': 2})
collections.Counter(['a', 'a', 'b'])  # Counter({'a': 2, 'b': 1})
collections.Counter(cats=4, dogs=8)  # Counter({'dogs': 8, 'cats': 4})

如果引用的键没有任何记录,并不会报错,而是返回 0:

from collections import Counter
c = Counter(['eggs', 'ham'])
print(c['bacon'])  # 0

如果需要删除元素,需使用 del:

del c['eggs']

计数器对象除了字典方法以外,还提供了以下三个方法。

4.3.2 elements()

返回一个迭代器,其中每个元素将重复出现计数值所指定次。 元素会按首次出现的顺序返回。 如果一个元素的计数值小于一,elements() 将会忽略它。

c = Counter(a=4, b=2, c=0, d=-2)
print(list(c.elements()))  # ['a', 'a', 'a', 'a', 'b', 'b']

4.3.3 most_common()

返回一个列表,其中包含 n 个最常见的元素及出现次数,按常见程度由高到低排序。 如果 n 被省略或为 Nonemost_common() 将返回计数器中的 所有 元素。 计数值相等的元素按首次出现的顺序排序:

c = Counter('abracadabra')
print(c.most_common(3))  # [('a', 5), ('b', 2), ('r', 2)]

4.3.4 subtract()

迭代对象映射对象 减去元素。输入和输出都可以是0或者负数。

c = Counter(a=4, b=2, c=0, d=-2)
d = Counter(a=1, b=2, c=3, d=4)
print(c.subtract(d))  # Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})

五、常用小技巧

1. 创建全 0 0 0 的 m × n m\times n m×n 数组

A = [[0] * n for i in range(m)]  # 较快
A = [[0 for j in range(n)] for i in range(m)]  # 较慢

2. split()

str.split(str="", num=string.count(str)).
  • str – 分隔符,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等。
  • num – 分割次数。默认为 -1, 即分隔所有。

3. 进制转换

有前缀:

x = 123
print(bin(x))  # 0b1111011(二进制)
print(oct(x))  # 0o173(八进制)
print(hex(x))  # 0x7b(十六进制)

没有前缀:

x = 123
print(format(x, 'b'))  # 1111011
print(format(x, 'o'))  # 173
print(format(x, 'x'))  # 7b

任意进制转换为10进制:

int(x, a)

x 为字符串, 且是 a 进制数。

4. 字符与ASCII码相互转换

print(ord('A'))  # 65
print(chr(65))  # A

5. 程序计时

import time
start = time.time()
# 程序
end = time.time()
print(end - start)

6. join()

str.join(seq)

字符串与列表的互相转化:

string = 'abc'
seq = list(string)
print(''.join(seq))

7. lambda函数

语法:

lambda argument_list:expression

argument_list是参数列表,它的结构与Python中函数的参数列表是一样的。expression是一个关于参数的表达式,表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的。

下面两个等价:

f = lambda argument_list:expression

def f(argument_list):
	return expression

8. filter()

filter(function, iterable)  # 返回的是filter类,需要用list转化

例如:

print(list(filter(lambda x: x % 2 == 1, range(1, 10))))
# [1, 3, 5, 7, 9]

9. sorted()

sorted(iterable, key=None, reverse=False)  
  • iterable – 可迭代对象。
  • key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
  • reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
dic = [('a', 3), ('c', 1), ('b', 2)]
print(sorted(dic, key=lambda x: x[0])) 
print(sorted(dic, key=lambda x: x[1]))
# [('a', 3), ('b', 2), ('c', 1)]
# [('c', 1), ('b', 2), ('a', 3)]

10. 三元表达式

语法:

为真时的结果 if 判断条件 else 为假时的结果

下面两个等价:

c = max(a, b)
c = a if a >= b else b  # if 左边是 a 而不是 c = a

11. exit()

exit()常用于多层循环的退出,它可直接将python程序终止,之后的所有代码都不会继续执行。

12. 遍历列表

假设我们事先不知道数组 nums 的大小,那么有以下两种方法遍历该列表:

# 方法一
n = len(nums)
for i in range(n):
    ...
    
# 方法二
for i in range(len(nums)):
    ...

事实上,方法二比方法一稍微快一点。

六、贪心算法

6.1 基本概念

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

所以,对所采用的贪心策略一定要仔细分析其是否满足无后效性。

6.2 硬币问题

问题描述

有1元、5元、10元、50元、100元、500元的硬币各 C 1 , C 5 , C 10 , C 50 , C 100 , C 500 C_1,C_5,C_{10},C_{50},C_{100},C_{500} C1​,C5​,C10​,C50​,C100​,C500​ 枚,现在要用这些硬币来支付 A A A 元,最少需要多少枚硬币?


解决方案

贪心算法:优先使用面值大的硬币。

V = [1, 5, 10, 50, 100, 500]
C = list(map(int, input().split()))  # 硬币个数列表
A = int(input())
ans = 0

for i in range(5, -1, -1):
    t = min(C[i], A // V[i])  # 使用硬币i的枚数
    A -= t * V[i]
    ans += t

print(ans)

6.3 字典序最小单词

问题描述

给定一个单词,请问在单词中删除 t 个字母后,能得到的字典序最小的单词是什么?

输入格式

输入的第一行包含一个单词,由大写英文字母组成。
第二行包含一个正整数 t。

输出格式

输出一个单词,表示答案

样例输入

LANQIAO
3

样例输出

AIAO


解决方案

贪心算法:从头遍历单词,一旦发现前一个字符的字典序大于后一个字符,则立刻删掉前一个字符,然后再次从头遍历单词。

string = list(input())
t = int(input())

while t:
    for i in range(len(string) - 1):
        if string[i] > string[i + 1]:
            string.pop(i)
            t -= 1
            break

print(''.join(string))

6.4 翻硬币

问题描述

小明正在玩一个“翻硬币”的游戏。
桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:**oo***oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:

输入格式

两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000。

输出格式

一个整数,表示最小操作步数。


解决方案

贪心算法:遍历所有硬币,只要当前硬币与目标状态不同就翻。

init = list(input())
goal = list(input())

# 为了实现翻硬币操作,将正面设为True,反面设为False
for i in range(len(init)):
    init[i] = True if init[i] == '*' else False
    goal[i] = True if goal[i] == '*' else False

# 遍历所有硬币
ans = 0
for i in range(len(init) - 1):
    if init[i] != goal[i]:
        init[i], init[i + 1] = not init[i], not init[i + 1]
        ans += 1

print(ans)

七、二分搜索

二分搜索只适用于单调上升序列。以下假设数组nums不为空。

7.1 寻找一个数

def binarySearch(nums, target):
    left = 0
    right = len(nums) - 1

    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1

    return False

7.2 寻找左侧边界的二分搜索

什么是左侧边界?对于列表 [1,3,3,3,5] 而言,如果我们采用7.1的方法搜索数字 3 3 3,结果返回2。倘若我们需要的是最左侧的那个 3 3 3呢?于是就衍生出了寻找左侧边界的二分搜索算法:

def left_bound(nums, target):
    left = 0
    right = len(nums)

    while left < right:
        mid = (left + right) // 2
        if nums[mid] == target:
            right = mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid

    return left

7.3 寻找右侧边界的二分搜索

def right_bound(nums, target):
    left = 0
    right = len(nums)

    while left < right:
        mid = (left + right) // 2
        if nums[mid] == target:
            left = mid + 1
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid

    return left - 1

八、回溯算法

回溯算法的框架如下:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

8.1 全排列

def backtrack(nums, track):
    if len(track) == len(nums):
        res.append(track[:])  # 浅拷贝
        return

    for i in range(len(nums)):
        if nums[i] in track:
            continue
        track.append(nums[i])
        backtrack(nums, track)
        track.pop()


# 全局变量
res = []
track = []

nums = [1, 2, 3]
backtrack(nums, track)
print(res)
# [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

8.2 N皇后问题

棋盘中皇后可以攻击同一行、同一列,或者左上、左下、右上、右下四个方向的任意单位。现在有一个 N × N N\times N N×N 的棋盘,放置 N N N 个皇后,使得它们不能互相攻击,返回所有可能的结果。

棋盘中的某一格未放置皇后时用 '.' 表示,放置皇后用 'Q' 表示,一个 4 × 4 4\times 4 4×4 的空棋盘的表示方式如下:

['....', '....', '....', '....']

我们首先需要生成棋盘,该函数为

def generateBoard(n):
    board = []
    for i in range(n):
        board.append(''.join(['.' for j in range(n)]))
    return board

接下来便是回溯算法的主体框架:

# board中小于row的那些行都已经成功的放置了皇后
# 第row行的所有列都是放置皇后的选择
# 当row=n时,说明棋盘已经放满了(棋盘最后一行的索引是n-1)
def backtrack(board, row):
    if row == len(board):
        res.append(board[:])  # 浅拷贝

    n = len(board)
    for col in range(n):
        if not isValid(board, row, col):
            # 当这样放置皇后不合法时跳过,isValid的具体内容后面会提到
            continue
		
        # 将board[row][col]这一格放置皇后
        board[row] = board[row][:col] + 'Q' + board[row][col+1:]

        backtrack(board, row + 1)
		
        # 撤销选择
        board[row] = board[row][:col] + '.' + board[row][col+1:]

最后是isValid函数的具体内容:

def isValid(board, row, col):
    """
    判断是否可以在board[row][col]位置处放置皇后
    """
    n = len(board)
	
    # 检查列中是否有皇后相互冲突
    for i in range(row):
        if board[i][col] == 'Q':
            return False

    # 检查右上方是否有皇后相互冲突
    for (i, j) in zip(range(row - 1, -1, -1), range(col + 1, n)):
        if board[i][j] == 'Q':
            return False
	
    # 检查左上方是否有皇后相互冲突
    for (i, j) in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
        if board[i][j] == 'Q':
            return False
	
    # 之所以不需要考虑左下方和右下方,是因为皇后是逐行从上往下放置的

    return True

定义完所有函数之后,我们就可以写出solveNQueens()的主体部分了:

def solveNQueens(self, n: int) -> List[List[str]]:
    def generateBoard(n):
        # 略
    
    def backtrack(board, row):
        # 略
        
   	def isValid(board, row, col):
        # 略
    
    res = []  # 定义全局变量
    board = generateBoard(n)
    backtrack(board, 0)
    return res

8.3 子集

def backtrack(nums, start, track):
    res.append(track[:])

    for i in range(start, len(nums)):
        track.append(nums[i])
        backtrack(nums, i + 1, track)
        track.pop()


res = []
track = []
nums = [1, 2, 3]
backtrack(nums, 0, track)
print(res)
# [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

start 参数实现了“以某个数开头的子集”;而对 res 的更新处在前序遍历的位置,所以 res 记录了树上的所有节点,也就是所有子集。

8.4 组合

输入两个数字 n , k n,k n,k,输出 [ 1 , ⋯   , n ] [1,\cdots,n] [1,⋯,n] 中 k k k 个数字的所有组合。

def backtrack(n, k, start, track):
    if len(track) == k:
        res.append(track[:])
        return
    
    for i in range(start, n + 1):
        track.append(i)
        backtrack(n, k, i + 1, track)
        track.pop()
    

res = []
track = []
n, k = map(int, input().split())

if n <= 0 or k <= 0:
    print(res)
else:
    backtrack(n, k, 1, track)
    print(res)

例如,当 n = 4 , k = 2 n=4,k=2 n=4,k=2时,算法输出

[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

九、广度优先搜索

BFS的核心思想不难理解,就是把一些问题抽象成图,从一个点开始,向四周扩散。一般来说,我们写BFS算法都是用"队列"这种数据结构,每次将一个节点周围的所有节点加入队列。

BFS相对于DFS的最主要区别是:BFS找到的路径一定是最短的,但代价是空间复杂度比DFS大很多。

BFS算法主要用于在一张图中找到从起点start到目标target的最近距离。


BFS框架:

from collections import deque


def bfs(start, target):
    """
    start 和 target 都是 Node 类
    """

    q = deque([start])  # 核心数据结构——队列
    visited = set([start])  # 避免走回头路
    step = 0  # 记录扩散的步数

    while q:
        for i in range(len(q)):
            cur = q.popleft()

            # 判断是否达到终点
            if cur == target:
                return step

            # 将cur的相邻节点加入队列
            for x in cur.adj():  # python没有这种用法,cur.adj()代表cur的相邻节点组成的集合
                if x not in visited:
                    q.append(x)
                    visited.add(x)
        step += 1

    return step

9.1 二叉树的最小高度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: return 0
        
        q = deque([root])
        depth = 1

        while q:
            for i in range(len(q)):
                cur = q.popleft()
                if cur.left is None and cur.right is None:
                    return depth
                if cur.left:
                    q.append(cur.left)
                if cur.right:
                    q.append(cur.right)
            depth += 1

        return depth

十、动态规划

10.1 最长递增子序列(Longest Increasing Subsequence)

输入一个无序数组,找到其中最长递增子序列的长度。

例如输入 nums = [10, 9, 2, 5, 3, 7, 101, 18] ,其中最长的递增子序列是 [2, 3, 7, 10] ,所以算法输出的应该是 4 4 4。

用 dp[i] 表示以 nums[i] 这个数为结尾的最长递增子序列的长度。

显然 dp[i] 的初始值为 1 1 1,因为以 nums[i] 为结尾的最长递增子序列至少要包含它自己。

下面寻找状态转移方程。已经知道了 dp[0…i-1],如何求出dp[i]呢?显然,如果 j < i,且nums[j] < nums[i],那么nums[i]可以接在nums[j]后面形成一个新的递增子序列,但是我们只选最长的那一个,于是有

for j in range(i):
    if nums[i] > nums[j]:
        dp[i] = max(dp[i], dp[j] + 1)

接下来就可以写出完整的代码了:

nums = list(map(int, input().split()))
n = len(nums)
dp = [1 for i in range(n)]

for i in range(n):
    for j in range(i):
        if nums[i] > nums[j]:
            dp[i] = max(dp[i], dp[j] + 1)

print(max(dp))

10.2 最长公共子序列(Longest Common Subsequence)

最长公共子序列问题就是让我们求两个字符串的LCS长度。

例如,输入 str1=“abcde”,str2=“aceb”,算法应该输出 3 3 3,因为它们的最长公共子序列是 “ace”,它的长度就是 3 3 3。

对于两个字符串的动态规划问题,一般需要一个二维 dp 数组。对于上述问题,在用动态规划求解时,我们把 str1 视为 " abcde",即前面多了一个空字符,对 str2 的操作同理。

d p [ i ] [ j ] dp[i][j] dp[i][j] 的含义是,对于 s t r 1 [ 0 , ⋯   , i − 1 ] str1[0,\cdots,i-1] str1[0,⋯,i−1] 和 s t r 2 [ 0 , ⋯   , j − 1 ] str2[0,\cdots,j-1] str2[0,⋯,j−1] ,它们的LCS长度是 d p [ i ] [ j ] dp[i][j] dp[i][j]。对于 d p [ 0 ] [ 3 ] dp[0][3] dp[0][3] ,其含义是空串和"ace"的LCS长度,显然为0。 d p [ 2 ] [ 4 ] dp[2][4] dp[2][4] 的含义是, "ab"和"aceb"的LCS长度。很显然, d p [ 0 ] [ . . . ] dp[0][...] dp[0][...] 与 d p [ . . . ] [ 0 ] dp[...][0] dp[...][0] 都应该初始化为0,这就是base case。

如果 s t r 1 [ i ] = = s t r 2 [ j ] str1[i]==str2[j] str1[i]==str2[j],说明这个公共字符就在 LCS 中,于是
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i - 1][j - 1] +1 dp[i][j]=dp[i−1][j−1]+1
如果 s t r 1 [ i ] ! = s t r 2 [ j ] str1[i] != str2[j] str1[i]!=str2[j],说明 s t r 1 [ i ] str1[i] str1[i]和 s t r 2 [ j ] str2[j] str2[j]这两个字符至少有一个不在LCS中,那到底哪个字符不在LCS中呢?我们都试一下:
d p [ i ] [ j ] = max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = \max(dp[i - 1][j],dp[i][j-1]) dp[i][j]=max(dp[i−1][j],dp[i][j−1])

def longestCommonSubsequence(str1, str2):
    m = len(str1)
    n = len(str2)

    dp = [[0] * (n + 1) for i in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if str1[i - 1] == str2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
    
    return dp[m][n]

10.3 最长回文子序列

输入一个字符串 s,找出 s 中的最长回文子序列长度。

比如输入 s = “aecda”,算法返回 3,因为最长回文子序列是 “aca”,长度为 3。

这个问题对 dp 数组的定义是:在子串 s [ i ⋯ j ] s[i\cdots j] s[i⋯j] 中,最长回文子序列的长度为 d p [ i ] [ j ] dp[i][j] dp[i][j]。

如果相求 d p [ i ] [ j ] dp[i][j] dp[i][j],假设知道了子问题 d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j−1]的结果(即 s [ i + 1 , ⋯   , j − 1 ] s[i+1,\cdots ,j-1] s[i+1,⋯,j−1]中最长回文子序列的长度),能否想办法算出 d p [ i ] [ j ] dp[i][j] dp[i][j] 的值呢?

当然可以,这取决于 s [ i ] s[i] s[i] 和 s [ j ] s[j] s[j] 的字符。

如果它俩相等,那么它俩加上 s [ i + 1 , ⋯   , j − 1 ] s[i+1,\cdots,j-1] s[i+1,⋯,j−1]中的最长回文子序列就是 s [ i ⋯ j ] s[i\cdots j] s[i⋯j]的最长回文子序列。

如果它俩不等,说明它俩不可能同时出现在 s [ i ⋯ j ] s[i\cdots j] s[i⋯j]的最长回文子序列中,那么把它俩分别加入 s [ i + 1 , ⋯   , j − 1 ] s[i+1,\cdots,j-1] s[i+1,⋯,j−1]中,看看哪个子串产生的回文子序列更长即可。

以上两种情况写成代码就是这样:

if s[i] == s[j]:
    dp[i][j] = dp[i + 1][j - 1] + 2
else:
    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

下面明确 base case,如果只有一个字符,显然最长回文子序列长度是1,也就是 d p [ i ] [ j ] = 1 ( i = = j ) dp[i][j] = 1(i==j) dp[i][j]=1(i==j)。

因为 i 肯定小于等于 j,所以对于那些 i > j 的位置,根本不存在什么子序列,应该初始化为0。

根据之前的状态转移方程,想求 d p [ i ] [ j ] dp[i][j] dp[i][j] 需要知道 d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j−1], d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j], d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]这三个位置,它们位于 d p [ i ] [ j ] dp[i][j] dp[i][j]的左下方,为了保证每次计算 d p [ i ] [ j ] dp[i][j] dp[i][j],左下方的位置已经被计算出来,只能斜着遍历或反着遍历。

def longestPalindromeSubseq(s):
    n = len(s)
    dp = [[0] * n for i in range(n)]

    for i in range(n):
        dp[i][i] = 1
    
    for i in range(n - 2, -1, -1):
        for j in range(i + 1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i + 1][j - 1] + 2
            else:
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
    
    return dp[0][n - 1]

写在最后

  • 创作不易,如果这篇文章有帮助到你,还想请你点个赞支持一下博主~
  • 可能会有部分地方有笔误,如有发现可以在评论区留言
  • 文章版权归博主所有,如需转载请注明出处

标签:必看,nums,python,蓝桥,range,board,print,dp,math
来源: https://blog.csdn.net/weixin_44022472/article/details/122747878

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有