字节跳动春招研发部笔试题解

字节跳动春招研发部笔试题

1.万万没想到之聪明的编辑

我叫王大锤,是一家出版社的编辑。我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误。但是,优秀的人总能在平凡的工作中发现真理。我发现一个发现拼写错误的捷径:

  1. 三个同样的字母连在一起,一定是拼写错误,去掉一个的就好啦:比如 helllo -> hello

  2. 两对一样的字母(AABB型)连在一起,一定是拼写错误,去掉第二对的一个字母就好啦:比如 helloo -> hello

  3. 上面的规则优先“从左到右”匹配,即如果是AABBCC,虽然AABB和BBCC都是错误拼写,应该优先考虑修复AABB,结果为AABCC

我特喵是个天才!我在蓝翔学过挖掘机和程序设计,按照这个原理写了一个自动校对器,工作效率从此起飞。用不了多久,我就会出任CEO,当上董事长,迎娶白富美,走上人生巅峰,想想都有点小激动呢!

……

万万没想到,我被开除了,临走时老板对我说: “做人做事要兢兢业业、勤勤恳恳、本本分分,人要是行,干一行行一行。一行行行行行;要是不行,干一行不行一行,一行不行行行不行。” 我现在整个人红红火火恍恍惚惚的……

请听题:请实现大锤的自动校对程序

数据范围: 1≤n≤50 1 \le n \le 50 \ 1≤n≤50 ,每个用例的字符串长度满足 1≤l≤1000 1 \le l \le 1000 \ 1≤l≤1000

输入描述:
第一行包括一个数字N,表示本次用例包括多少个待校验的字符串。

后面跟随N行,每行为一个待校验的字符串。
输出描述:
N行,每行包括一个被修复后的字符串。

示例1

输入

2
helloo
wooooooow

输出

hello
woow

示例2

输入

1
nowcoder

输出

nowcode
"""
1、问题分析:该题主要是对字符串的处理,难点在于如何对字符串进行删除,python里面是通过用list模拟栈来进行快速删除的,其实也可以看成简单模拟,代码很简单都能看懂
"""
def solve(s: str):
    res = []
    for char in s:
        res.append(char)
        if len(res) >= 3 and res[-1] == res[-2] == res[-3]:
            res.pop()
        if len(res) >= 4 and res[-1] == res[-2] and res[-3] == res[-4]:
            res.pop()
    return "".join(res)


n = int(input())
for _ in range(n):
    s = input().strip()
    print(solve(s))

2.万万没想到之抓捕孔连顺

我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议

  1. 我们在字节跳动大街的 N 个建筑中选定 3 个埋伏地点。

  2. 为了相互照应,我们决定相距最远的两名特工间的距离不超过 D 。

我特喵是个天才! 经过精密的计算,我们从X种可行的埋伏方案中选择了一种。这个方案万无一失,颤抖吧,孔连顺!

……

万万没想到,计划还是失败了,孔连顺化妆成小龙女,混在cosplay的队伍中逃出了字节跳动大街。只怪他的伪装太成功了,就是杨过本人来了也发现不了的!

请听题:给定 N(可选作为埋伏点的建筑物数)、 D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。

注意:

  1. 两个特工不能埋伏在同一地点

  2. 三个特工是等价的:即同样的位置组合( A , B , C ) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用

数据范围: 0<n,d≤106 0 < n,d\le 10^6 \ 0<n,d≤106

输入描述:
第一行包含空格分隔的两个数字 N和D(1 ≤ N ≤ 1000000; 1 ≤ D ≤ 1000000)

第二行包含N个建筑物的的位置,每个位置用一个整数(取值区间为[0, 1000000])表示,从小到大排列(将字节跳动大街看做一条数轴)
输出描述:
一个数字,表示不同埋伏方案的数量。结果可能溢出,请对 99997867 取模

示例1

输入

4 3
1 2 3 4

输出

4

说明

可选方案 (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)   

示例2

输入

5 19
1 10 20 30 50

输出

1

说明

可选方案 (1, 10, 20)   

示例3

输入

2 100
1 102

输出

0

说明

无可选方案  
import sys
"""
1、问题分析:要找到一个区间,这个区间内的最大值和最小值的差要在d之间,然后找到这个区间任意三个数组成排列之和
"""

def solve():
    mod = 99997867
    n, d = map(int, sys.stdin.readline().split())
    positions = list(map(int, sys.stdin.readline().split()))
    
    res = 0 #答案
    left = 0 #窗口的左边界
    for right in range(len(positions)):#遍历右边界
        while positions[right] - positions[left] > d: #当窗口的最大值与最小值的差大于d时
            left += 1 #左边界收缩
        #用来计算窗口内满足条件的个数
        count = right - left
        #如果窗口里面刚好三个数或者没有三个数,只有一种方案,这里是2的原因是因为我们把下标0算进去了
        if count <= 2:
            res = 1
        #计算count里面选三个数的组合
        else:
            res += count * (count - 1) // 2
            res %= mod#防止答案越界
    print(res % mod)
solve()

3.雀魂启动

小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。

于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:

  1. ​ 总共有36张牌,每张牌是1~9。每个数字4张牌。
  2. ​ 你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
  • ​ 14张牌中有2张相同数字的牌,称为雀头。
  • ​ 除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)

例如:

1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌

1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌

1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。

现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。

输入描述:
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。
输出描述:
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0

示例1

输入

1 1 1 2 2 2 5 5 5 6 6 6 9

输出

9

说明

可以组成1,2,6,7的4个刻子和9的雀头

示例2

输入

1 1 1 1 2 2 3 3 5 6 7 8 9

输出

4 7

说明

用1做雀头,组123,123,567或456,789的四个顺子

示例3

输入

1 1 1 2 2 2 3 3 3 5 7 7 9

输出

0

说明

来任何牌都无法和牌
备注:
请不要根据自己的常识为题目增加未提及的条件

对于20%的数据,保证和牌时一定是4个刻子+雀头的形式
"""
1、问题分析:该题要我们思考我们要摸到什么样的牌才能拿够胡牌?
那么胡牌的条件是什么呢?题目给出了下面的描述:
4个顺子或者刻子 + AA的形式(A表示1-9)
那么我们就要考虑我选择哪张牌来当AA,同时因为涉及到数目和数字,我们用字典的形式来存储数据
"""
#当前的手牌能够胡牌
def is_win_hand(counter):
    #按照上面的逻辑,我们要选择一个雀头
    for num in range(1,10):
        #雀头的数量必须大于等于2
        if counter[num] >= 2:
            #我们用一个新的字典来存储雀头除去后的剩余刻子
            temp_counter = counter.copy()
            temp_counter[num] -= 2
            #看剩下的牌能不能够形成4个刻子
            if can_form_melds(temp_counter):
                return True
    return False

#看当前除去雀头的牌能不能形成四个刻子
def can_form_melds(counter):
    temp_counter = counter.copy()
    melds = 0#刻子的数量
    for num in range(1,10):#统计每张牌能够形成的刻子数量
        #形成AAA的刻子
        while temp_counter[num] >= 3:
            temp_counter[num] -= 3
            melds += 1
        #形成顺子类型的刻子
        if num <= 7:#只有小于7的牌能当顺子的开头
            while temp_counter[num] >= 1 and temp_counter[num + 1] >= 1 and temp_counter[num + 2] >= 1:
                temp_counter[num] -= 1
                temp_counter[num + 1] -= 1
                temp_counter[num + 2] -= 1
                melds += 1
    return melds == 4
def solve():
    from collections import defaultdict
    hand = list(map(int, input().split()))
    #用来记录我的牌是什么样的
    counter = defaultdict(int)
    for num in hand:
        counter[num] += 1
    #答案数组
    result = []
    #处理逻辑:我从1,10中加入一张牌看能不能胡牌
    for num in range(1,10):
        #同一数字的牌最多只能出现四张
        if counter[num] >= 4:
            continue
        temp_counter = counter.copy()
        #将我摸到的牌加入我的牌里
        temp_counter[num] += 1
        #如果摸到的这张牌能够让我胡牌
        if is_win_hand(temp_counter):
            result.append(num)
    #如果没有可能胡牌了
    if not result:
        print(0)
    else:
        print(' '.join(map(str,sorted(result))))

solve()   

4.特征提取

小明是一名算法工程师,同时也是一名铲屎官。某天,他突发奇想,想从猫咪的视频里挖掘一些猫咪的运动信息。为了提取运动信息,他需要从视频的每一帧提取“猫咪特征”。一个猫咪特征是一个两维的vector<x, y>。如果x_1=x_2 and y_1=y_2,那么这俩是同一个特征。

​ 因此,如果喵咪特征连续一致,可以认为喵咪在运动。也就是说,如果特征<a, b>在持续帧里出现,那么它将构成特征运动。比如,特征<a, b>在第2/3/4/7/8帧出现,那么该特征将形成两个特征运动2-3-4 和7-8。

现在,给定每一帧的特征,特征的数量可能不一样。小明期望能找到最长的特征运动。

输入描述:
第一行包含一个正整数N,代表测试用例的个数。

每个测试用例的第一行包含一个正整数M,代表视频的帧数。

接下来的M行,每行代表一帧。其中,第一个数字是该帧的特征个数,接下来的数字是在特征的取值;比如样例输入第三行里,2代表该帧有两个猫咪特征,<1,1>和<2,2>
所有用例的输入特征总数和<100000

N满足1≤N≤100000,M满足1≤M≤10000,一帧的特征个数满足 ≤ 10000。
特征取值均为非负整数。
输出描述:
对每一个测试用例,输出特征运动的长度作为一行

示例1

输入

1
8
2 1 1 2 2
2 1 1 1 4
2 1 1 2 2
2 2 2 1 4
0
0
1 1 1
1 1 1

输出

3

说明

特征<1,1>在连续的帧中连续出现3次,相比其他特征连续出现的次数大,所以输出3
"""
1、问题分析:本题我们要找到一个出现次数最多的特征,其实就是统计每个特征连续出现的最大数量,那么我们要知道当前特征是不是连续的,就必须知道上一帧里面有没有这个特征
2.数据结构:我们用字典来存储特征以及出现的帧数,这样就可以知道当前特征有没有在上一帧出现过了
"""
def solve():
    import sys
    #读取所有输入的数据并按照空格分隔
    input = sys.stdin.read().split()
    #初始化指针ptr用于遍历输入列表
    ptr = 0
    #读取测试用例的数量N
    N = int(input[ptr])
    ptr += 1
    #读取每个测试用例
    for _ in range(N):
        #读取当前帧的特征个数
        M = int(input[ptr])
        ptr += 1
        #初始化最大特征运动长度max_len
        max_len = 0
        #用来记录上一帧中出现的特征
        last_seen = {}
        #处理每一帧数据
        for _ in range(M):
            #读取当前特征数量K
            K = int(input[ptr])
            ptr += 1
            #初始化列表freatures存储当前帧的所有特征
            features = []
            #读取当前帧下的所有特征
            for _ in range(K):
                x = int(input[ptr])
                y = int(input[ptr + 1])
                features.append((x,y))
                ptr += 2
            
            #处理当前特征
            temp = {}
            for feature in features:#处理特征列表中的每个特征
                if feature in last_seen: #如果特征在上一帧中出现过
                    #连续次数+1
                    temp[feature] = last_seen[feature] + 1
                else:
                    #特征第一次出现
                    temp[feature] = 1
                #处理完后更新最大数据
                if temp[feature] > max_len:
                    max_len = temp[feature]
            #保存当前帧的信息作为下一帧的last_seen
            last_seen = temp
        #输出最大长度
        print(max_len)                
solve()

5.毕业旅行问题

小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。

时间限制:C/C++ 1秒,其他语言2秒

空间限制:C/C++ 256M,其他语言512M

输入描述:

城市个数n(1<n≤20,包括北京)

城市间的车票价钱 n行n列的矩阵 m[n][n]

输出描述:

最小车费花销 s

示例1

输入例子:

4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0

输出例子:

13

例子说明:

共 4 个城市,城市 1 和城市 1 的车费为0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,城市 1 和城市 4 之间的车费为 5,依次类推。假设任意两个城市之间均有单程票可购买,且票价在1000元以内,无需考虑极端情况。
"""
方法思路:
1.问题分析:对于城市的履行方案,是有限个方案里面选最佳,所以我们可以采用动态规划来做
2.动态规划的状态定义:dp[mask][u],其中mask表示已经访问过的城市集合,u表示当前访问的城市
dp[mask][u]表示从城市u触发,经过mask表示的城市合集,最后回到起点的最小花费
3.状态转移:对于每一个状态(mask,u),遍历所有未访问的城市v,更新dp[mask | (1<<v)][v]
为dp[mask][u] + m[u][v]的最小值
4.初始化和结果:初始化时,dp[(1 << n) - 1][0](从起点北京出发,没有访问其他城市时我的花费0)
最终结果是dp[(1<<n)-1][0],访问其他节点回到起点的最小值
"""
def solve():
    import sys
    input = sys.stdin.read().split() #读取所有输入数据并拆分成列表
    ptr = 0#初始化指针,用来逐个访问输入列表中的元素

    #读取城市数量n
    n = int(input[ptr])
    ptr += 1

    #初始化车票价格矩阵m
    m = []
    for _ in range(n):
        #读取每一行车票的价格,并转化为整数列表
        row = list(map(int, input[ptr:ptr + n]))
        m.append(row)
        ptr += n

    #初始化动态规划表dp
    size = 1 << n #结算状态的总数,即2^n
    INF = float('inf')
    #dp[mask][u]表示在状态mask下,当前所在城市u的最小花费
    dp = [[INF] * n for _ in range(size)]
    #初始状态,从北京(城市0)出发,仅访问过北京
    dp[1][0] = 0 

    #动态规划填表过程
    for mask in range(size):#遍历所有可能的状态mask
        for u in range(n): #遍历所有当前所在城市u
            if dp[mask][u] == INF: #如果当前状态不可以到达,跳过
                continue
            for v in range(n): #遍历所有可能的下一城市v
                if not (mask & (1 << v)): #如果城市没有被访问过
                    new_mask = mask | (1 << v) #更新状态,标记城市v已经被访问

                    #新状态花钱少,选择新状态
                    if dp[new_mask][v] > dp[mask][u] + m[u][v]:
                        dp[new_mask][v] = dp[mask][u] + m[u][v]

    #计算回来的最小花费
    full_mask = (1 << n) - 1 #全访问状态,所有城市都已经被标记
    min_cost = INF #初始化最小花费为无穷大
    for u in range(1,n): #遍历除了北京以外的所有城市
        #计算从城市u回到北京的花费,并更新最小花费
        if dp[full_mask][u] + m[u][0] < min_cost:
            min_cost = dp[full_mask][u] +m[u][0]
    #答案
    print(min_cost)

solve()

6.找零

Z国的货币系统包含面值1元、4元、16元、64元共计4种硬币,以及面值1024元的纸币。现在小Y使用1024元的纸币购买了一件价值为N(0<N≤1024)N (0 < N \le 1024)N(0<N≤1024)的商品,请问最少他会收到多少硬币?

输入描述:
一行,包含一个数N。
输出描述:
一行,包含一个数,表示最少收到的硬币数。

示例1

输入

200

输出

17

说明

花200,需要找零824块,找12个64元硬币,3个16元硬币,2个4元硬币即可。
"""
1、问题分析:很典型的贪心问题,为了最小化我们取得的硬币数量,每次我们最好选择硬币面值最大的
"""
def solve(N):
    #需要找零的金额
    change = 1024 - N
    #定义硬币的面值,按照从大到小排序
    coins = [64,16,4,1]
    #最后答案
    count = 0
    for coin in coins:
        #检查当前剩余零钱是不是大于当前硬币面值
        if change >= coin:
            #计算当前change最多能换硬币的数量
            num = change // coin
            #将使用的硬币数量num加到总计数量count中
            count += num
            #更新剩余零钱的数量
            change -= num * coin
        #找零完成了,不用在找了
        if change == 0:
            break
    return count
N = int(input())
print(solve(N))         

7.机器人跳跃问题

机器人正在玩一个古老的基于DOS的游戏。游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位。

起初, 机器人在编号为0的建筑处。每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E, 下一步它将跳到第个k+1建筑。它将会得到或者失去正比于与H(k+1)与E之差的能量。如果 H(k+1) > E 那么机器人就失去 H(k+1) - E 的能量值,否则它将得到 E - H(k+1) 的能量值。

游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位。现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?

输入描述:
第一行输入,表示一共有 N 组数据.

第二个是 N 个空格分隔的整数,H1, H2, H3, ..., Hn 代表建筑物的高度
输出描述:
输出一个单独的数表示完成游戏所需的最少单位的初始能量

示例1

输入

5
3 4 3 2 4

输出

4

示例2

输入

3
4 4 4

输出

4

示例3

输入

3
1 6 4

输出

3
备注:
数据约束:
1 <= N <= 10^5
1 <= H(i) <= 10^5
"""
1、问题分析:能量的变化规律:如果当前能量为E,跳到高度为H(k+1)的建筑,新的能量
E_new为:
(1)如果H(k + 1)> E,则E_new = E - (H(k + 1) - E) = 2E - H(k + 1)
(2)如果H(k + 1) < E,则E_new = E + (E - H(k + 1)) = 2E - H(k + 1)
综上所述,无论怎么样,新的能量总是2E - H(k + 1)
但是在任何一个环节中都必须保证E_new > 0
2、解决思路:逆推,即我们在最后一个位置的时候能量刚好为0
"""
def solve():
    #数据读入
    import sys
    input = sys.stdin.read().split()
    N = int(input[0])
    H = list(map(int, input[1:N+1]))
    
    #初始化能量E = 0,这是逆向计算的起点
    E = 0
    #逆向遍历所有建筑
    for i in range(N - 1, -1, -1):
        """
        1、计算到达当前建筑i所需的最小能量
        2、这里加1是为了确保在奇数情况下能够向上取整
        """
        E = (E + H[i] + 1) // 2
    print(E)


solve()

题单总结

题目核心考点时间复杂度空间复杂度关键技巧
聪明的编辑字符串处理O(n)O(1)双指针(易)
抓捕孔连顺组合数学O(n)O(1)滑动窗口(易)
雀魂启动回溯算法O(1)O(1)模拟(中)
特征提取哈希表O(n)O(n)滑动窗口(中)
毕业旅行状态压缩DPO(n^2 * 2^n)O(n*2^n)位运算(难)
找零贪心算法O(1)O(1)数学计算(易)
机器人跳跃逆向思维O(n)O(1)数学推导(中)

这些题目涵盖了算法竞赛中的经典题型,考察了不同的解题思维和算法技巧。从字符串处理到动态规划,从数学推导到贪心选择,都是编程面试和算法竞赛中的常见考点。

解题模板

  • 滑动窗口

    常见问题类型及适用场景:

    问题类型判断条件窗口调整策略
    最小覆盖子串包含所有目标字符满足条件时收缩窗口
    最长无重复子串窗口内有重复字符出现重复时收缩窗口
    长度不超过K的最长子数组窗口内元素和>K超过阈值时收缩窗口
    固定大小窗口问题窗口大小固定同步移动左右边界
    """
    滑动窗口算法通用模板
    :param nums:输入数组/字符串
    :param target:目标值/条件
    :return: 符合条件的结果
    """
    #初始化窗口边界和结果变量
    left = 0 #窗口左边界
    right = 0#窗口右边界
    window = {}#用于记录窗口内元素的状态(根据需要选择合适的数据结构)
    result = 0#存储最终结果
    vaild = 0#用于跟踪窗口内满足条件的元素数量(根据题目需求调整)
    
    #开始滑动窗口(外层循环移动右边界)
    while right < len(nums):
        #获取当前右边界元素
        c = nums[right]
        right += 1 #右边界向右移动
        #更新窗口内数据(根据题目需求修改)
        window[c] = window.get(c,0) + 1
        if window[c] == target:#示例条件:具体请根据题目修改
            vaild += 1
        #内层循环移动左边界(当窗口需要收缩时)
        while vaild > need:#示例收缩条件:实际请根据题目修改
            #获取当前元素左边界元素
            d = nums[left]
            left += 1#左边界向右移动
            
            #更新窗口内的数据
            if window[d] == target: #示例条件,根据实际需求进行修改
                vaild -= 1
            window[d] -= 1
    	return result
    # ==================使用示例================
    #示例1,最小覆盖子串问题
    def min_window(s:str,t:str) -> str:
        """
        找到s中包含t所有字符的最小子串
        :param:s:源字符串
        :param:t:目标字符集合
        :return:满足条件的最小字符串
        """
        from collections import defaultdict
        #记录t中每个字符需要匹配的数量
        need = defaultdict(int)
        for c in t:
    		need[c] += 1
        #初始化窗口指针和状态变量
        left,right = 0,0 
        window = defaultdic(int)
        vaild = 0
        start,length = 0,float('inf') #start为最小子串的起始下标,length为最小子串的长度
        #开始滑动窗口
        while right < len(s):
            #右移窗口的过程
            c = s[right]
            right += 1
            #更新窗口内的数据
            if c in need:
                window[c] += 1
                #当窗口内某个字符数量达到need的要求是,vaild+1
                if window[c] == need[c]:
                    vaild += 1
            #判断左侧窗口是否要收缩(当窗口满足所有字符需要时)
            while vaild == len(need):
                #更新最小覆盖子串
                if right - left < length:
    				start = left
                    length = right - left
                #d是将要移除窗口的字符
                d = s[left]
                #窗口左移
    			left += 1
                #更新窗口内的数据
                if d in need:
                    #当窗口内某个字符的数量不在满足need的要求时,vaild -1
                    if window[d] == need[d]:
                        vaild -= 1
                    window[d] -= 1
         return "" if length == float('inf') else s[start:start + length]
            
    def length_of_longest_substring(s:str)->int:
        """
        找到不包含重复字符的最长子串长度
        :param s:输入字符串】
        :return:最长无重复子串的长度
        """
        #初始化窗口指针和状态变量
        left,right = 0,0
        window = {}
        max_len = 0
        
        while right < len(s):
            c = s[right]
            window[c] += 1
            
            while window[c] > 1:
    			d = s[left]
                window[d] -= 1
                left += 1
           	max_len = max(max_len,right - left)
        return max_len
    
  • 动态规划

    常见问题类型适配:

    问题类型状态定义关键转移方程
    斐波那契dp[i]: 第i项的值dp[i] = dp[i-1] + dp[i-2]
    背包问题dp[i][w]: 前i件物品重量w的最大价值dp[i][w] = max(选/不选当前物品)
    最长递增子序列dp[i]: 以第i元素结尾的LIS长度dp[i] = max(dp[j])+1 (j<i且nums[j]<nums[i])
    股票买卖问题dp[i][k][0/1]: 第i天交易k次持有/不持有的最大收益根据状态机转移
#自顶向下记忆化搜索模板
def dp_template_memoization(nums):
	"""
	自顶向下记忆化搜索模板
	:param nums:输入参数
	:return: 最优解
	"""
    from functools import lru_cache
    #使用装饰器自动实现备忘录功能(python 3.9+)
    @lru_cache(maxsize = None)
    def dfs(state):
        #边界条件处理
        if is_terminal_state(state):
			return base_case_value
       	
        #遍历所有可能的选择
        min_res = float('inf') #求最小值问题初始化
        #max_res = -float('inf') #求最大值问题初始化
        for choice in get_available_choices(state):
			#状态转移
            new_state = state_transition(state,choice)
            #递归子问题
            subproblem = def(new_state)
            #根据问题类型更新结果
            min_res = min(min_res,cost(choice) + subproblem)
            #max_res = max(max_res,cost(choice) + subproblem)
        return min_res #或return max_res
    #从初始状态开始求解
    return dfs(initial_state)
# ===========斐波那契数列==========
@lru_cache(maxsize = None)
def fib(n):
    if n <= 1: return n
    return fib(n - 1) + fib(n - 2)
def dp_template_iterative(nums):
    """
    自底向上迭代动态规划模板
    :param nums:输入餐宿
    :return :最优解
    """
    n = len(nums)
    #1.定义dp数组/表(根据问题维度调整)
    #一维示例(斐波那契)
    dp = [0] * (n + 1)
    #二维示例(背包问题)
    # dp = [[o] * (capacity - 1) for _ in range(n + 1)]
    
    #2.初始化基础情况
    dp[0] = base_case_value_0
    dp[1] = base_case_value_1
    
    #3.状态转移(填表顺序很重要)
    for i in range(2,n + 1):#一维填写示例
        for j in range(1, i):#根据问题调整循环条件
            #状态转移方程
            dp[i] = min(dp[i],dp[j] + cost)#最小化问题
            #dp[i] = max(dp[i],dp[j] + value)#最大化问题
    #4.返回最终结果
    return dp[n]
# ==================使用示例:爬楼梯问题====================
def climb_stairs(n:int)->int:
    if n<= 2:return n
    dp = [0] * (n + 1)
    dp[1],dp[2] = 1,2
    for i in range(3,n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]
def dp_essential_template(problem_input):
    """
    动态规划四要素模板
    1.定义状态
    2.状态转移方程
    3.初始条件
    4.计算顺序
    """
    #1.定义状态(DP数组的含义)
    #dp[i][j]表示...(根据具体的问题进行定义)

    #2.初始化基础情况
    dp = [[0] * cols for _ in range(rows)]
    dp[0][0] = base_values

    #3.状态转移(注意计算顺序)
    for i in range(rows):	
        for j in range(cols):
            #根据状态转移方程更新
            if condition1:
                dp[i][j] = dp[i - 1][j] + value1
            elif condition2:
                dp[i][j] = min(dp[i][j - 1],dp[i - 1][j]) + value2
    #4.返回最终结果
    return dp[-1][-1]
# =====================使用示例:最长公共子序列===============
def longestCommonSubsequence(text1: str,text2: str) -> int:
    m,n = len(text1),len(text2)
    #1.定义dp数组:dp[i][j]表示text1前i个字符和text2前j个字符的LCS长度
    #使用(m + 1)*(n + 1)的二维数组(包含空字符串的情况)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    #2.初始化:第一行和第一列默认为0(空字符串和任何字符串的LCS都为0)
    #3.状态转移(按行填充)
    for i in range(1,m + 1):
        for j in range(1,n + 1):
			if text1[i - 1] == text2[j - 1]
            #LCS长度 = 左上角 + 1
            	dp[i][j] = dp[i - 1][j - 1] + 1
            else: #当前字符串不匹配
                # LCS长度 = 上方或者下方的最大值
                dp[i][j] = max(dp[i-1][j],dp[i][j - 1])
    return dp[m][n]
最长公共子序列DP表(text1=“abcde”, text2=“ace”)
‘’ace
‘’0000
a0111
b0111
c0122
d0122
e0123
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图论难的离谱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值