题目解析 Day 1 | 豆包MarsCode AI刷题

451 阅读26分钟

1. 找单独的数

问题描述

在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。

要求:

  1. 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
  2. 尽量减少额外空间的使用,以体现你的算法优化能力。

解题思路

  1. 算法步骤: 使用异或运算(XOR)来解决这个问题。异或运算有一个特性:a ^ a = 0 和 a ^ 0 = a。因此,如果我们将数组中所有数字进行异或运算,最终结果就是那个只出现一次的数字
  2. 时空复杂度:由于我们只需要从前往后遍历一遍数组,因此时间复杂度是O(n)。我们只用了一个变量来存储每次异或的结果,所以空间复杂度是O(1)

代码实现

def solution(cards):
    # 初始化结果变量为0
    result = 0
    
    # 遍历数组中的每个元素
    for card in cards:
        # 将结果与当前元素进行异或运算
        result ^= card
    
    # 返回结果
    return result

2. 数字分组求偶数和

问题描述

小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。

  • numbers: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。

例如对于[123, 456, 789],14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369

解题思路

  1. 拆分数字组: 对于每个数字组,将其拆分为单个数字。例如,123 可以拆分为 [1, 2, 3]

  2. 组合选择: 面对组合问题,我们一般选择回溯法。需要从每个数字组中选择一个数字,并计算所有可能的组合。

  3. 判断偶数和: 对于每一种组合,计算其各位数字之和,判断是否为偶数。

  4. 计数: 统计所有符合条件的组合数量。

代码实现

def solution(numbers):
    # 将每个数字组拆分为单个数字
    groups = [[int(digit) for digit in str(num)] for num in numbers]

    # 生成所有可能的组合并判断是否符合条件
    count = generate_combinations(groups, 0, [], 0)

    return count

def generate_combinations(groups: List[List[int]], index: int, current_combination: List[int], count: int) -> int:
    if index == len(groups):
        # 计算当前组合的各位数字之和
        sum_digits = sum(current_combination)
        # 判断是否为偶数
        if sum_digits % 2 == 0:
            count += 1
        return count

    # 递归生成组合
    current_group = groups[index]
    for num in current_group:
        current_combination.append(num)
        count = generate_combinations(groups, index + 1, current_combination, count)
        current_combination.pop()  # 回溯

    return count

3. 寻找最大葫芦

问题描述

在一场经典的德州扑克游戏中,有一种牌型叫做“葫芦”。“葫芦”由五张牌组成,其中包括三张相同牌面值的牌 aa 和另外两张相同牌面值的牌 bb。如果两个人同时拥有“葫芦”,我们会优先比较牌 aa 的大小,若牌 aa 相同则再比较牌 bb 的大小。

在这个问题中,我们对“葫芦”增加了一个限制:组成“葫芦”的五张牌牌面值之和不能超过给定的最大值 maxmax。牌面值的大小规则为:A > K > Q > J > 10 > 9 > ... > 2,其中 A 的牌面值为1,K 为13,依此类推。

给定一组牌,你需要找到符合规则的最大的“葫芦”组合,并输出其中三张相同的牌面和两张相同的牌面。如果找不到符合条件的“葫芦”,则输出 “0, 0”。

解题思路

  1. 统计牌的出现次数:首先,我们需要统计每种牌面值出现的次数。可以使用一个字典来存储每个牌面值及其出现的次数。
  2. 排序牌面值:由于我们需要找到最大的“葫芦”组合,可以先将牌面值从大到小排序。

这里要注意A虽然牌面值是1,但却是比较规则中最大的那张。所以我们要定制一个排序函数,如果是1的话,我们让它排在最前面,其它牌按从大到小排列。

  1. 查找符合条件的“葫芦” :遍历排序后的牌面值,尝试找到三张相同牌面值的牌和两张相同牌面值的牌,并且它们的和不超过给定的最大值 max
  2. 返回结果:如果找到符合条件的“葫芦”,返回三张相同牌面值和两张相同牌面值;否则返回 [0, 0]

代码实现

def solution(n, max, array):
    # 统计每种牌面值出现的次数
    from collections import Counter
    count = Counter(array)
    
    # 将牌面值从大到小排序,但将1排在最前面
    def custom_sort(x):
        if x == 1:
            return 14  # 1 排在最前面
        else:
            return x  # 其他牌面值按从大到小的顺序排列
    
    # 将牌面值按自定义排序函数排序
    sorted_values = sorted(count.keys(), key=custom_sort, reverse=True)
    
    # 遍历排序后的牌面值,查找符合条件的“葫芦”
    for a in sorted_values:
        if count[a] >= 3:
            for b in sorted_values:
                if a != b and count[b] >= 2:
                    # 计算当前“葫芦”的和
                    if 3 * a + 2 * b <= max:
                        return [a, b]
    
    # 如果没有找到符合条件的“葫芦”,返回 [0, 0]
    return [0, 0]

4. 创意标题匹配问题

问题描述

在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 bidword 对创意中的通配符(通配符是用成对 {} 括起来的字符串,可以包含 0 个或者多个字符)进行替换,用来提升广告投放体验。例如:“{末日血战} 上线送 SSR 英雄,三天集齐无敌阵容!”,会被替换成“帝国时代游戏下载上线送 SSR 英雄,三天集齐无敌阵容!”。给定一个含有通配符的创意和n个标题,判断这句标题是否从该创意替换生成的。

解题思路

这里直接考虑字符串匹配的话,有点难。我们使用了一个技巧,将将模板中的 {...} 替换为 *,这样就转化为我们熟悉的含通配符*的字符串匹配问题了,经典的动态规划问题。注意在初始化时,如果模板的开头是 *,那么 * 可以匹配标题中的任意字符序列,包括空字符序列。因此,对于所有 jdp[1][j] 都应该设为 True

  1. 通配符替换

    • 将模板中的 {...} 替换为 *,表示任意字符序列。
  2. 动态规划

    • 使用一个二维数组 dp[i][j],其中 dp[i][j] 表示模板的前 i 个字符和标题的前 j 个字符是否匹配。
    • 初始化 dp[0][0] = True,表示空模板和空标题匹配。
    • 状态转移方程:
      • 如果模板当前字符是 *,则 dp[i][j] = dp[i-1][j] or dp[i][j-1],表示 * 可以匹配任意字符序列。
      • 如果模板当前字符是固定字符,则 dp[i][j] = dp[i-1][j-1] and (template[i-1] == title[j-1])

代码实现

def solution(n, template, titles):
    # 将模板中的通配符 {...} 替换为 *
    new_template = []
    i = 0
    while i < len(template):
        if template[i] == '{':
            new_template.append('*')
            while i < len(template) and template[i] != '}':
                i += 1
        else:
            new_template.append(template[i])
        i += 1
    new_template = ''.join(new_template)
    
    results = []
    
    for title in titles:
        m, n = len(new_template), len(title)
        dp = [[False] * (n + 1) for _ in range(m + 1)]
        dp[0][0] = True
        
        # 如果模板的开头是 *,则 dp[1][j] 全设为 True
        if new_template[0] == '*':
            for j in range(n + 1):
                dp[1][j] = True
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if new_template[i-1] == '*':
                    dp[i][j] = dp[i-1][j] or dp[i][j-1]
                else:
                    dp[i][j] = (dp[i-1][j-1] and (new_template[i-1] == title[j-1]))
        
        if dp[m][n]:
            results.append("True")
        else:
            results.append("False")
    
    return ",".join(results)

5. 找出整型数组中占比超过一半的数

问题描述

小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。

解题思路

Boyer-Moore投票算法是一种用于在数组中找到出现次数超过一半的元素的高效算法。它的核心思想是通过“投票”机制来确定可能的多数元素,然后通过一次遍历来验证这个元素是否确实是多数元素。

  1. 初始化:我们需要一个变量candidate来存储当前的候选元素,以及一个计数器count来记录该元素的票数。

  2. 遍历数组:对于数组中的每一个元素:

    • 如果count为0,将当前元素设为candidate,并将count设为1。
    • 如果当前元素等于candidate,则count加1。
    • 如果当前元素不等于candidate,则count减1。

代码实现

def solution(array):
    # 初始化候选元素和计数器
    candidate = None
    count = 0
    
    # 遍历数组,找出候选元素
    for num in array:
        if count == 0:
            candidate = num
            count = 1
        elif num == candidate:
            count += 1
        else:
            count -= 1
    
    # 最终candidate保存的就是众数 
    return candidate

6. DNA序列编辑距离

问题描述

小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。

解题思路

我们可以使用动态规划来解决这个问题。动态规划可以帮助我们计算从一个字符串转换到另一个字符串所需的最少编辑步骤。

  1. 定义状态:我们可以使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤。

  2. 初始化

    • dp[0][j] 表示将空字符串转换成 dna2 的前 j 个字符,显然需要 j 次插入操作。
    • dp[i][0] 表示将 dna1 的前 i 个字符转换成空字符串,显然需要 i 次删除操作。
  3. 状态转移

    • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1],因为不需要任何编辑操作。

    • 否则,dp[i][j] 可以通过以下三种操作之一得到:

      • 插入:dp[i][j-1] + 1
      • 删除:dp[i-1][j] + 1
      • 替换:dp[i-1][j-1] + 1
    • 取这三种操作的最小值作为 dp[i][j]

  4. 最终结果dp[len(dna1)][len(dna2)] 即为将 dna1 转换成 dna2 所需的最少编辑步骤。

代码实现

def solution(dna1, dna2):
    m, n = len(dna1), len(dna2)
    # 创建一个 (m+1) x (n+1) 的二维数组 dp
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 初始化 dp 数组
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = 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], dp[i][j-1], dp[i-1][j-1]) + 1
    
    # 返回最终结果
    return dp[m][n]

7. 小U的数字插入问题

问题描述

小U手中有两个数字 a 和 b。第一个数字是一个任意的正整数,而第二个数字是一个非负整数。她的任务是将第二个数字 b 插入到第一个数字 a 的某个位置,以形成一个最大的可能数字。

你需要帮助小U找到这个插入位置,输出插入后的最大结果。

解题思路

这里我采用了暴力算法,考虑所有可能的插入位置,并选择使得结果最大的插入位置。具体来说,我们可以遍历字符串 a 的每一个位置,尝试将 b 插入到该位置,并比较插入后的结果,选择最大的结果。如果有更好的解题办法,欢迎在评论区讨论。

代码实现

def solution(a: int, b: int) -> int:
    # 将数字 a 和 b 转换为字符串
    str_a = str(a)
    str_b = str(b)
    
    # 初始化最大结果为插入到最前面的情况
    max_result = int(str_b + str_a)
    
    # 遍历字符串 a 的每一个位置
    for i in range(len(str_a) + 1):
        # 尝试将 str_b 插入到位置 i
        new_str = str_a[:i] + str_b + str_a[i:]
        
        # 将新字符串转换为整数
        new_num = int(new_str)
        
        # 比较并更新最大结果
        if new_num > max_result:
            max_result = new_num
    
    return max_result

8. 我好想逃却逃不掉

问题描述

曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……

对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。

——我好想逃。

但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!

在一个 N × M 的竞技场迷宫中,你的任务是找出在迷宫中,所有"危险位置"的数量。
"危险位置"定义为:如果站在该位置上,无论采取什么移动策略,都无法到达出口

竞技场中包含以下几种元素:

  • .:表示普通地板,可以自由移动到上下左右相邻的格子(不可以走斜线)
  • O:表示出口
  • U:表示向上的传送器,踩上去会被强制传送到上方的格子
  • D:表示向下的传送器,踩上去会被强制传送到下方的格子
  • L:表示向左的传送器,踩上去会被强制传送到左方的格子
  • R:表示向右的传送器,踩上去会被强制传送到右方的格子

注意,如果被传送出了竞技场之外,则算作死亡。

解题思路

迷宫问题,地图问题,我们一般可以采用图的深度优先遍历或广度优先遍历求解,这里我们使用了一个小技巧,从出口开始遍历,如果能从出口遍历到某个位置,反之,也能从该位置到达出口。

  1. 标记出口位置:首先找到所有出口的位置。
  2. 逆向思维:从出口位置开始,逆向追踪可以到达出口的所有位置。
  3. 标记可达位置:使用广度优先搜索(BFS)或深度优先搜索(DFS)从出口位置开始,标记所有可以到达的位置。
  4. 计算危险位置:所有未被标记的位置即为危险位置。

代码实现

def solution(N, M, data):
    # 定义方向数组
    DX = [0, 0, 1, -1]  # 代表行
    DY = [1, -1, 0, 0]  # 代表列
    REVERSE = ['L', 'R', 'U', 'D']
    
    # 初始化 visited 数组
    visited = [[False] * M for _ in range(N)]
    
    # 定义 DFS 函数
    def dfs(x, y, direct):
        if x < 0 or x >= N or y < 0 or y >= M or visited[x][y]:
            return
        if data[x][y] != '.' and data[x][y] != REVERSE[direct]:
            return
        visited[x][y] = True
        for i in range(4):
            newX = x + DX[i]
            newY = y + DY[i]
            dfs(newX, newY, i)
    
    # 找到所有出口位置
    for i in range(N):
        for j in range(M):
            if data[i][j] == 'O':
                visited[i][j] = True
                for k in range(4):
                    dfs(i + DX[k], j + DY[k], k)
    
    # 计算危险位置的数量
    danger_count = 0
    for i in range(N):
        for j in range(M):
            if not visited[i][j]:
                danger_count += 1
    
    return danger_count

9. 小D的 abc 变换问题

问题描述

小D拿到了一个仅由 "abc" 三种字母组成的字符串。她每次操作会对所有字符同时进行以下变换:

  • 将 'a' 变成 'bc'
  • 将 'b' 变成 'ca'
  • 将 'c' 变成 'ab'

小D将重复该操作 k 次。你的任务是输出经过 k 次变换后,得到的最终字符串。

例如:对于初始字符串 "abc",执行 2 次操作后,字符串将变为 "caababbcbcca"

解题思路

这里我们使用了一个字典,存储已经计算过的变化结果,避免大量的重复计算

  1. 定义变换规则:使用字典 transform 来存储每个字符的变换结果。
  2. 逐步应用变换:在每次迭代中,遍历当前字符串中的每个字符,并根据变换规则生成新的字符串。
  3. 更新字符串:将新生成的字符串赋值给 s,以便进行下一次迭代。

代码实现

def solution(s: str, k: int) -> str:
    # write code here
    transform = {
        'a': 'bc',
        'b': 'ca',
        'c': 'ab'
    }
    
    # 记忆化字典,存储已经计算过的结果
    memo = {}
    
    def transform_string(s, k):
        # 如果已经计算过,直接返回结果
        if (s, k) in memo:
            return memo[(s, k)]
        
        # 如果 k 为 0,直接返回原字符串
        if k == 0:
            return s
        
        # 初始化新的字符串
        new_s = []
        # 遍历当前字符串中的每个字符
        for char in s:
            # 根据变换规则生成新的字符串
            new_s.append(transform[char])
        # 更新字符串为新的字符串
        s = ''.join(new_s)
        
        # 递归进行下一次变换
        result = transform_string(s, k - 1)
        
        # 存储结果到记忆化字典
        memo[(s, k)] = result
        
        return result
    
    return transform_string(s, k)

10. 二分数字组合

问题描述

小F面临一个有趣的挑战:给定一个数组,她需要将数组中的数字分为两组。分组的目标是使得一组数字的和的个位数等于给定的 A,另一组数字的和的个位数等于给定的 B。除此之外,还有一种特殊情况允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。小F需要计算所有可能的划分方式。

例如,对于数组 [1, 1, 1] 和目标 A = 1,B = 2,可行的划分包括三种:每个 1 单独作为一组,其余两个 1 形成另一组。如果 A = 3,B = 5,当所有数字加和的个位数为 3 或 5 时,可以有一组为非空,另一组为空。

解题思路

计算所有的划分方式是组合问题,采用回溯算法求解。java和python运行速度相差100倍左右,尤其是面对回溯算法这种指数级别,差异更大,所以会出现用java能ac而用python不能ac的情况。尝试了动态规划,没做出来。

代码实现

def solution(n, A, B, array_a):
    
    # Helper function to calculate the last digit of a sum
    def last_digit(x):
        return x % 10

    # Recursive function to explore all partitions
    def dfs(index, sum1, sum2):
        # Base case: all elements have been processed
        if index == n:
            # Check if current partition satisfies the conditions
            valid_partition = ((last_digit(sum1) == A and last_digit(sum2) == B) or
                               (last_digit(sum1) == A and sum2 == 0) or
                               (last_digit(sum2) == B and sum1 == 0))
            return 1 if valid_partition else 0
        
        # Recursive case: try adding current element to either group1 or group2
        num = array_a[index]
        count = 0
        # Case 1: Add to group1
        count += dfs(index + 1, sum1 + num, sum2)
        # Case 2: Add to group2
        count += dfs(index + 1, sum1, sum2 + num)
        
        return count

    # Start recursive search from the first element
    return dfs(0, 0, 0)

11. 小S的倒排索引

问题描述

小S正在帮助她的朋友们建立一个搜索引擎。为了让用户能够更快地找到他们感兴趣的帖子,小S决定使用倒排索引。倒排索引的工作原理是:每个单词都会关联一个帖子ID的列表,这些帖子包含该单词,且ID按从小到大的顺序排列。
例如,单词“夏天”可能出现在帖子1、帖子3和帖子7中,那么这个单词的倒排链就是 [1, 3, 7]。如果用户想同时找到包含“夏天”和“海滩”的帖子,小S需要找出两个倒排链的交集,且将结果按照从大到小的顺序输出。现在,给定两个单词的倒排链数组 a 和 b,请你帮助小S找出同时包含这两个单词的帖子ID,并按从大到小的顺序返回结果。

解题思路

  1. 初始化结果列表:先创建一个空列表来存储交集的结果。
  2. 使用集合操作:Python 的集合(set)类型非常适合用来求交集。先将列表转换为集合,然后使用集合的交集操作。
  3. 排序结果:求得交集后,使用 sorted 函数对结果进行排序,并指定 reverse=True 来按从大到小的顺序排列。
  4. 返回结果:最后,将排序后的结果返回。

代码实现

def solution(a, b):
    # 将列表转换为集合
    set_a = set(a)
    set_b = set(b)
    
    # 求交集
    intersection = set_a & set_b
    
    # 将交集结果按从大到小排序
    result = sorted(intersection, reverse=True)
    
    # 返回结果
    return result

12.补给站最优问题

问题描述

小U计划进行一场从地点A到地点B的徒步旅行,旅行总共需要 M 天。为了在旅途中确保安全,小U每天都需要消耗一份食物。在路程中,小U会经过一些补给站,这些补给站分布在不同的天数上,且每个补给站的食物价格各不相同。

小U需要在这些补给站中购买食物,以确保每天都有足够的食物。现在她想知道,如何规划在不同补给站的购买策略,以使她能够花费最少的钱顺利完成这次旅行。

  • M:总路程所需的天数。
  • N:路上补给站的数量。
  • p:每个补给站的描述,包含两个数字 A 和 B,表示第 A 天有一个补给站,并且该站每份食物的价格为 B 元。

保证第0天一定有一个补给站,并且补给站是按顺序出现的。

解题思路

对于最优解问题,我们一般采用动态规划算法或贪心算法求解。这里我采用的是动态规划算法,如果有小伙伴用的贪心算法,欢迎在评论区一起交流。ps:之前用贪心写过青岛湖之旅,没有ac,可能是我太菜了吧。

  1. 定义状态

dp[i] 表示在第 i 天到达并拥有足够食物所需的最小花费。

  1. 状态转移方程

为了保证每天有足够的食物,可以通过以下方式来更新 dp[i]

  • 若从第 j 天的补给站购买 k 份食物,可以满足从第 j+1 天至第 i 天的需求,则有: dp[i]=min(dp[i],dp[j]+k×p[j]) dp[i]=min⁡(dp[i],dp[j]+k×p[j])

其中 k 表示从第 j 天购买的食物份数,而 p[j] 表示第 j 天补给站的食物单价。

  1. 初始化
  • 因为第 0 天总是有一个补给站,所以 dp[0] = 0,即从出发点起步时不需要花费任何费用。
  • 其他天的费用初始设置为无穷大(或一个较大的数),表示尚未计算出花费。

最终在第 M 天的 dp[M] 值即为完成旅行的最小花费。

代码实现

def solution(m: int, n: int, p: list[list[int]]) -> int:
    # Edit your code here
    dp = [sys.maxsize] * (m + 1)
    dp[0] = 0  # 初始条件,第 0 天的花费为 0
    
    # 遍历补给站信息
    for i in range(n):
        day, cost_per_food = p[i]
        
        # 从第 `day` 天向后更新 dp 数组
        for j in range(day + 1, m + 1):
            needed_food = j - day
            dp[j] = min(dp[j], dp[day] + needed_food * cost_per_food)
    
    # 返回最终的最小花费
    return dp[m] if dp[m] != sys.maxsize else -1  # 如果无法到达则返回 -1

13. 计算从位置 x 到 y 的最少步数

问题描述

小F正在进行一个 AB 实验,需要从整数位置 x 移动到整数位置 y。每一步可以将当前位置增加或减少,且每步的增加或减少的值必须是连续的整数(即每步的移动范围是上一步的 -1+0 或 +1)。首末两步的步长必须是 1。求从 x 到 y 的最少步数。

解题思路

为了简化问题,我们假设都是从小坐标出发到达大坐标。通过分析发现,整个步长的变化趋势是先递增,然后递减的,这里递减是非严格的。问题的关键就是确定递增和递减的边界。我们考虑临界情况,当步长增加到某个值后立即严格递减到一,这时刚刚好到达坐标y,步长变化如1,2,3,2,1。在step从2变到3之前,已经走的距离sum = 3,剩余距离distance = 6,这时step + 1 + sum <= distance。反之,如果step + 1 + sum > distance,你还要继续增加步长,那么接下来即使你每走一步都将步长减一,最后步数到达一时所走的位置一定超过了y。

代码实现

def solution(x_position, y_position):
   # 计算位置差
    diff = abs(x_position - y_position)
    
    # 如果位置差小于3,直接返回位置差
    if diff < 3:
        return diff
    
    # 初始化步数和累加和
    ans = 2
    step = 1
    sum = 0
    
    # 减去前两步的步长
    diff -= 2
    
    # 循环计算步数
    while True:
        # 如果下一步的步长加上当前累加和不超过剩余距离,增加步长
        if step + 1 + sum <= diff:
            step += 1
            sum += step
        
        # 如果累加和超过剩余距离,减少步长
        if sum > diff:
            sum -= step
            step -= 1
        
        # 减去当前步长的距离
        diff -= step
        
        # 增加步数
        ans += 1
        
        # 如果剩余距离为0,跳出循环
        if diff == 0:
            break
    
    return ans

14. 环状 DNA 序列的最小表示法

问题描述

小C正在研究一种环状的 DNA 结构,它由四种碱基ACGT构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 n 的碱基序列可以有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。

例如:碱基序列 ATCA 从不同位置读取可能的表示有 ATCATCAACAATAATC,其中 AATC 是字典序最小的表示。

解题思路

  1. 找到序列中最小的字母,并记录所有最小字母的位置。
  2. 从每个最小字母的位置开始,生成环状表示。
  3. 比较这些表示,只有当第一个位置的字母相同时,才比较第二个位置的字母,依次类推,直到找到字典序最小的表示。

代码实现

def solution(dna_sequence):
    n = len(dna_sequence)
    # 找到序列中最小的字母
    min_char = min(dna_sequence)
    # 找到所有最小字母的位置
    min_positions = [i for i, char in enumerate(dna_sequence) if char == min_char]
    
    # 初始化最小表示为原始字符串
    min_representation = dna_sequence
    
    # 遍历所有最小字母的位置
    for start in min_positions:
        # 生成从当前位置开始的环状表示
        current_representation = dna_sequence[start:] + dna_sequence[:start]
        
        # 比较当前子串与最小表示
        for i in range(n):
            if current_representation[i] < min_representation[i]:
                min_representation = current_representation
                break
            elif current_representation[i] > min_representation[i]:
                break
    
    return min_representation

15.小E的射击训练

问题描述

小E正在训练场进行射击练习,靶有10个环,靶心位于坐标(0, 0)。每个环对应不同的得分,靶心内(半径为1)得10分,依次向外的每个环分数减少1分。若射击点在某个半径为i的圆内,则得11-i分。如果射击点超出所有的环,则得0分。

根据给定的射击坐标(x, y),请计算小E的射击得分。

解题思路

  1. 计算射击点到靶心的距离:使用欧几里得距离公式 distance = sqrt(x^2 + y^2)
  2. 向上取整:使用 math.ceil 函数将距离向上取整。
  3. 确定得分:根据取整后的距离判断得分。如果取整后的距离为1,得10分;如果取整后的距离为2,得9分;依此类推,直到取整后的距离大于10,得0分。

代码实现

def solution(x: int, y: int) -> int:
   # 计算射击点到靶心的距离
    distance = math.sqrt(x**2 + y**2)
    
    # 向上取整
    ceil_distance = math.ceil(distance)
    
    # 根据取整后的距离判断得分
    if ceil_distance <= 10:
        return 11 - ceil_distance
    else:
        return 0

16.完美偶数计数

问题描述

小C定义了一个“完美偶数”。一个正整数 xx 被认为是完美偶数需要满足以下两个条件:

  1. xx 是偶数;
  2. xx 的值在区间 [l,r] 之间。

现在,小C有一个长度为 nn 的数组 aa,她想知道在这个数组中有多少个完美偶数。

解题思路

  1. 遍历数组:我们需要遍历数组 a 中的每一个元素。
  2. 检查条件:对于每一个元素,检查它是否是偶数,并且是否在区间 [l, r] 之间。
  3. 计数:如果满足条件,则计数器加一。

代码实现

def solution(n: int, l: int, r: int, a: list) -> int:
    # 初始化计数器
    count = 0
    
    # 遍历数组中的每一个元素
    for num in a:
        # 检查是否是偶数并且是否在区间 [l, r] 之间
        if num % 2 == 0 and l <= num <= r:
            # 如果满足条件,计数器加一
            count += 1
    
    # 返回计数器的值
    return count

仅以此贴记录一枚小菜鸡的算法之路。以上都是个人的题解思路,能力有限,如有不对的,欢迎评论区指点。各位如果有更好的解题思路可以在评论区交流。