-
数字分组求偶数和
问题描述
小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。
numbers: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。
例如对于[123, 456, 789],14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369。
思路
-
初始化:
even_count用于记录形成偶数和的组合数,odd_count用于记录形成奇数和的组合数。初始状态下,没有选择任何数字,所以有一个选择,即不选择任何数字,因此even_count初始化为 1。 -
遍历数字:对于输入列表
numbers中的每个数字,代码会遍历该数字的每一位。 -
统计偶奇数位:对于当前数字的每一位,代码会判断它是偶数还是奇数,并相应地增加
even或odd计数器。 -
更新组合数:根据当前数字的偶奇位统计结果,代码会更新
even_count和odd_count。这里使用了两个公式:-
new_even_count = even_count * even + odd_count * odd:这个公式表示,要形成一个新的偶数和,可以通过以下两种方式:- 将当前数字的偶数位与之前形成的偶数和组合。
- 将当前数字的奇数位与之前形成的奇数和组合。
-
new_odd_count = even_count * odd + odd_count * even:这个公式表示,要形成一个新的奇数和,可以通过以下两种方式:- 将当前数字的偶数位与之前形成的奇数和组合。
- 将当前数字的奇数位与之前形成的偶数和组合。
-
-
迭代更新:对于列表中的每个数字,都会根据上述规则更新
even_count和odd_count。 -
返回结果:最后,函数返回
even_count,即形成偶数和的所有可能组合数。
代码
def solution(numbers):
even_count = 0 # 记录偶数选择的组合数
odd_count = 0 # 记录奇数选择的组合数
# 初始情况下,没有任何选择,所以组合数为0
even_count = 1 # 初始化为1,因为至少有一种选择(即不选择任何数字)
for num in numbers:
even = 0
odd = 0
str_num = str(num)
# 统计当前数字组中的偶数和奇数
for ch in str_num:
if int(ch) % 2 == 0:
even += 1
else:
odd += 1
# 更新组合数
new_even_count = even_count * even + odd_count * odd # 偶数和偶数的组合 + 奇数和奇数的组合
new_odd_count = even_count * odd + odd_count * even # 偶数和奇数的组合
even_count = new_even_count
odd_count = new_odd_count
# 返回符合条件的组合数(偶数和的组合)
return even_count # 返回偶数和的组合数
# 测试用例
if __name__ == "__main__":
print(solution([123, 456, 789]) == 14) # 应输出 True
print(solution([123456789]) == 4) # 应输出 True
print(solution([14329, 7568]) == 10) # 应输出 True
-
DNA序列编辑距离
问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
思路
-
初始化变量:
m和n分别是两个输入字符串dna1和dna2的长度。
-
创建动态规划表:
- 创建一个
(m+1) x (n+1)的二维数组dp,用于存储从dna1的前i个字符到dna2的前j个字符的最小编辑距离。
- 创建一个
-
初始化边界条件:
dp[i][0]表示将dna1的前i个字符转换成空字符串需要的编辑次数,这相当于删除i个字符,因此初始化为i。dp[0][j]表示将空字符串转换成dna2的前j个字符需要的编辑次数,这相当于插入j个字符,因此初始化为j。
-
填充动态规划表:
-
使用两层循环遍历
dp表的每个单元格。 -
如果
dna1的第i个字符和dna2的第j个字符相同,则dp[i][j]的值等于dp[i-1][j-1],因为不需要额外的操作。 -
如果字符不同,则考虑三种操作:
- 删除操作:
dp[i-1][j] + 1,即删除dna1的第i个字符。 - 插入操作:
dp[i][j-1] + 1,即在dna1中插入一个字符以匹配dna2的第j个字符。 - 替换操作:
dp[i-1][j-1] + 1,即将dna1的第i个字符替换为dna2的第j个字符。
- 删除操作:
-
dp[i][j]的值是这三种操作中编辑次数最少的一个。
-
-
返回结果:
- 最终,
dp[m][n]存储了将整个dna1转换成整个dna2所需的最少编辑次数,即两个字符串之间的编辑距离。
- 最终,
代码
def solution(dna1, dna2):
m = len(dna1)
n = len(dna2)
# 创建一个(m+1) x (n+1)的二维列表来保存编辑距离
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化第一行和第一列
for i in range(m + 1):
dp[i][0] = i # 从dna1到空字符串的操作数为i(删除操作)
for j in range(n + 1):
dp[0][j] = j # 从空字符串到dna2的操作数为j(插入操作)
# 填充DP表
for i in range(1, m + 1):
for j in range(1, n + 1):
if dna1[i - 1] == dna2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] # 字符匹配,无需操作
else:
dp[i][j] = min(dp[i - 1][j] + 1, # 删除操作
dp[i][j - 1] + 1, # 插入操作
dp[i - 1][j - 1] + 1) # 替换操作
return dp[m][n] # 返回最终的编辑距离
if __name__ == "__main__":
# You can add more test cases here
print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
print(solution("AGCCGAGC", "GCTAGCT") == 4)
-
最小替换子串长度
问题描述
小F得到了一个特殊的字符串,这个字符串只包含字符A、S、D、F,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得A、S、D、F这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
思路
-
初始化变量:
n是输入字符串input的长度。target是每个字符 'A', 'S', 'D', 'F' 需要达到的目标频次,即n // 4。
-
统计频次:
- 使用字典
freq来统计字符串中每个字符 'A', 'S', 'D', 'F' 的出现频次。
- 使用字典
-
检查是否已经平衡:
- 如果
freq中每个字符的频次都等于target,则字符串已经平衡,返回 0。
- 如果
-
尝试每个可能的长度:
- 遍历所有可能的子串长度,从 1 到
n。 - 对于每个长度,遍历所有可能的起始位置。
- 遍历所有可能的子串长度,从 1 到
-
计算替换子串后的频次:
- 对于每个子串,创建
temp字典的副本,用于计算替换子串后的频次。 - 遍历子串中的每个字符,减少
temp中相应字符的计数。
- 对于每个子串,创建
-
检查是否可以通过添加字符达到平衡:
- 计算
temp中的最大频次max_count和最小频次min_count。 - 如果
max_count - min_count小于等于子串长度length,则有可能通过添加字符达到平衡。
- 计算
-
检查是否可以分配字符来达到平衡:
- 计算达到平衡所需的额外字符总数
needed。 - 如果
needed小于等于子串长度length,则找到了一个可以替换的子串,返回length。
- 计算达到平衡所需的额外字符总数
-
返回结果:
- 如果没有找到可以替换的子串,则返回字符串的长度
n,表示需要替换整个字符串。
- 如果没有找到可以替换的子串,则返回字符串的长度
代码
def solution(input):
n = len(input)
target = n // 4 # 目标频次
# 统计频次
freq = {'A': 0, 'S': 0, 'D': 0, 'F': 0}
for c in input:
freq[c] += 1
# 已经平衡
if all(f == target for f in freq.values()):
return 0
# 尝试每个可能的长度
for length in range(1, n + 1):
# 检查每个起始位置
for start in range(n - length + 1):
# 计算替换该子串后的频次
temp = freq.copy()
for i in range(start, start + length):
temp[input[i]] -= 1
# 检查是否可以通过添加length个字符达到平衡
max_count = max(temp.values())
min_count = min(temp.values())
if max_count - min_count <= length:
# 检查是否可以分配length个字符来达到平衡
needed = sum(max(0, target - count) for count in temp.values())
if needed <= length:
return length
return n
if __name__ == "__main__":
# 测试用例
print(solution("ADDF") == 1) # True
print(solution("ASAFASAFADDD") == 3) # True
print(solution("SSDDFFFFAAAS") == 1) # True
print(solution("AAAASSSSDDDDFFFF") == 0) # True
print(solution("AAAADDDDAAAASSSS") == 4) # True
-
环状 DNA 序列的最小表示法
问题描述
小C正在研究一种环状的 DNA 结构,它由四种碱基A、C、G、T构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 n 的碱基序列可以有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。
例如:碱基序列 ATCA 从不同位置读取可能的表示有 ATCA, TCAA, CAAT, AATC,其中 AATC 是字典序最小的表示。
思路
-
初始化变量:
n是输入的 DNA 序列dna_sequence的长度。min_rotation初始化为原始序列,这是比较的基准。
-
生成所有旋转:
- 通过一个循环,从 1 到
n-1(因为包括原始序列,所以不需要从 0 开始),生成序列的所有可能旋转。 - 对于每个
i,通过dna_sequence[i:] + dna_sequence[:i]生成第i次旋转。这个表达式意味着从索引i到序列末尾的子串,加上从序列开始到索引i的子串。
- 通过一个循环,从 1 到
-
比较字典序:
- 对于每个生成的旋转,使用
<运算符比较其字典序与当前min_rotation的字典序。 - 如果新生成的旋转的字典序小于
min_rotation的字典序,则更新min_rotation为这个新的旋转。
- 对于每个生成的旋转,使用
-
返回结果:
- 循环结束后,
min_rotation将包含所有旋转中字典序最小的旋转,函数返回这个值。
- 循环结束后,
代码
def solution(dna_sequence):
n = len(dna_sequence)
min_rotation = dna_sequence # 初始化最小旋转为原始序列
# 生成所有旋转并找出最小的字典序
for i in range(1, n):
rotation = dna_sequence[i:] + dna_sequence[:i] # 生成第 i 次旋转
if rotation < min_rotation: # 比较字典序
min_rotation = rotation # 更新最小旋转
return min_rotation # 返回最小表示
def main():
# 添加测试用例
print(solution("ATCA") == "AATC")
print(solution("CGAGTC") == "AGTCCG")
print(solution("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG") == "AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG")
main()
-
优化青海湖至景点X的租车路线成本
问题描述
小F计划从青海湖出发,前往一个遥远的景点X进行旅游。景点X可能是“敦煌”或“月牙泉”,线路的路径是唯一的。由于油价的不断上涨,小F希望尽量减少行程中的燃油成本。车辆的油箱容量为400L,在起始点租车时,车内剩余油量为 200L。每行驶 1km 消耗 1L 油。沿途设有多个加油站,小F可以在这些加油站补充燃油;此外,到达目标景点X还车的时候,需要保证车内剩余的油至少有 200L。
小F需要你帮助他计算,如果合理规划加油站的加油顺序和数量,最小化从青海湖到景点X的旅行成本(元)。
思路
-
初始化变量:
maxCapacity是汽车油箱的最大容量。inf是一个很大的数,用于初始化动态规划(DP)数组中的值,表示无穷大。
-
排序和计算距离:
- 将加油站按照位置升序排序。
- 计算每个加油站之间的距离,并存储在
dis数组中。
-
初始化 DP 数组:
- 创建一个 DP 数组
dp,其大小为(n+2) x (maxCapacity+1),其中n是加油站的数量。dp[i][j]表示到达第i个加油站时,剩余油量为j时的最小油量消耗。
- 创建一个 DP 数组
-
动态规划计算最小花费:
-
使用三层循环填充 DP 数组:
- 外层循环遍历每个加油站。
- 中层循环遍历可能的剩余油量。
- 内层循环遍历可能的油量消耗。
-
对于每个加油站,计算从上一个加油站到达当前加油站的最小油量消耗,考虑到加油站提供的油量和两个加油站之间的距离。
-
-
判断是否可以到达终点:
- 计算到达终点后剩余的油量
remaining_fuel。 - 如果
remaining_fuel超出油箱容量或者为负数,返回-1,表示无法到达终点。
- 计算到达终点后剩余的油量
-
计算结果:
- 在 DP 数组的最后一行中找到最小的油量消耗,这是到达终点的最小油量消耗。
-
返回结果:
- 如果结果仍然是
inf,表示无法到达终点,返回-1。 - 否则,返回计算出的最小油量消耗。
- 如果结果仍然是
代码
import sys
def solution(distance, n, gasStations):
maxCapacity = 400
inf = sys.maxsize
# 按照加油站位置升序排序
gasStations.sort(key=lambda x: x[0])
# 计算每个加油站之间的距离
dis = [gasStations[0][0]]
for i in range(1, len(gasStations)):
dis.append(gasStations[i][0] - gasStations[i-1][0])
# 初始化 dp 数组
dp = [[inf] * (maxCapacity + 1) for _ in range(n + 2)]
dp[0][200] = 0 # 初始状态,容量为200,花费为0
# 动态规划计算最小花费
for i in range(1, n + 1):
for j in range(maxCapacity + 1):
for k in range(maxCapacity + 1):
if j + dis[i-1] - k >= 0 and k >= dis[i-1]:
dp[i][j] = min(dp[i][j], dp[i-1][k] + (j + dis[i-1] - k) * gasStations[i-1][1])
# 判断是否可以到达终点
remaining_fuel = 200 + distance - gasStations[n-1][0]
if remaining_fuel > maxCapacity or remaining_fuel < 0:
return -1
result = inf
for i in range(remaining_fuel, maxCapacity + 1):
result = min(result, dp[n][i])
if result == inf:
return -1
return result
if __name__ == "__main__":
# 测试样例
gas_stations1 = [[100, 1], [200, 30], [400, 40], [300, 20]]
gas_stations2 = [[300, 25], [600, 35], [900, 5]]
gas_stations3 = [[100, 50], [150, 45]]
gas_stations4 = [[100, 10], [200, 20], [300, 30], [400, 40], [600, 15]]
gas_stations5 = [[25, 100]]
# 测试用例
print(solution(500, 4, gas_stations1) == 4300)
print(solution(1000, 3, gas_stations2) == -1)
print(solution(200, 2, gas_stations3) == 9000)
print(solution(700, 5, gas_stations4) == 9500)
print(solution(50, 1, gas_stations5) == 5000)