青训营第三课 | 豆包MarsCode AI 刷题

45 阅读11分钟

问题描述 :

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

代码:

def solution(a, b):    # 初始化两个指针    i, j = 0, 0    result = []        # 遍历两个数组    while i < len(a) and j < len(b):        if a[i] == b[j]:            # 找到交集元素,加入结果列表            result.append(a[i])            i += 1            j += 1        elif a[i] < b[j]:            # 移动较小的指针            i += 1        else:            # 移动较小的指针            j += 1        # 反转结果列表以满足从大到小的顺序    result.reverse()        return resultif __name__ == '__main__':    print(solution([1, 2, 3, 7], [2, 5, 7]) == [7, 2])    print(solution([1, 4, 8, 10], [2, 4, 8, 10]) == [10, 8, 4])    print(solution([3, 5, 9], [1, 4, 6]) == [])    print(solution([1, 2, 3], [1, 2, 3]) == [3, 2, 1])

问题理解

背景与目标

小S正在帮助她的朋友们建立一个搜索引擎,为了提高搜索效率,她决定使用倒排索引(Inverted Index)。倒排索引是一种常见的数据结构,用于快速查找包含特定单词的文档或帖子。具体来说,每个单词都会关联一个帖子ID的列表,这些帖子包含该单词,并且这些ID是按从小到大的顺序排列的。例如,单词“夏天”可能出现在帖子1、帖子3和帖子7中,那么这个单词的倒排链就是 [1, 3, 7]

在这个问题中,用户希望找到同时包含两个特定单词的帖子ID,并且要求这些ID按从大到小的顺序返回。给定两个单词的倒排链数组 ab,我们需要找出这两个数组的交集,并将结果按从大到小的顺序输出。

问题分解

  1. 输入与输出

    • 输入:两个有序数组 ab,分别表示两个单词的倒排链。
    • 输出:一个有序数组,表示同时包含这两个单词的帖子ID,按从大到小的顺序排列。
  2. 关键点

    • 有序数组:由于输入的数组是有序的(从小到大),我们可以利用这一特性来优化查找交集的过程。
    • 交集查找:我们需要找出两个数组中相同的元素,这些元素就是同时包含两个单词的帖子ID。
    • 排序要求:结果需要按从大到小的顺序排列,这意味着在找到交集后,我们需要对结果进行反转。
  3. 数据结构选择

    • 双指针法:由于数组是有序的,可以使用双指针法来高效地查找交集。一个指针指向数组 a,另一个指针指向数组 b,通过比较两个指针指向的元素来决定如何移动指针。
    • 结果列表:使用一个列表来存储交集元素,最后反转该列表以满足输出要求。
  4. 算法步骤

    • 初始化指针:初始化两个指针 ij,分别指向数组 ab 的开头。
    • 遍历数组:使用 while 循环,直到其中一个数组遍历完毕。
      • 比较元素:如果 a[i] 等于 b[j],则将该元素加入结果列表,并同时移动两个指针。
      • 移动指针:如果 a[i] 小于 b[j],则移动 i 指针;如果 a[i] 大于 b[j],则移动 j 指针。
    • 反转结果:遍历结束后,反转结果列表以满足从大到小的顺序。
  5. 复杂度分析

    • 时间复杂度:由于我们使用双指针法遍历两个数组,时间复杂度为 O(n + m),其中 nm 分别是数组 ab 的长度。
    • 空间复杂度:结果列表的空间复杂度为 O(min(n, m)),因为我们只存储交集元素。

测试样例分析

  • 样例1a = [1, 2, 3, 7], b = [2, 5, 7]

    • 交集为 [2, 7],反转后输出 [7, 2]
  • 样例2a = [1, 4, 8, 10], b = [2, 4, 8, 10]

    • 交集为 [4, 8, 10],反转后输出 [10, 8, 4]
  • 样例3a = [3, 5, 9], b = [1, 4, 6]

    • 没有交集,输出 []
  • 样例4a = [1, 2, 3], b = [1, 2, 3]

    • 交集为 [1, 2, 3],反转后输出 [3, 2, 1]

总结

通过理解问题的背景和目标,我们可以设计一个高效的算法来解决这个问题。利用有序数组的特性,使用双指针法可以快速找到交集,并通过反转结果列表来满足输出要求。这个算法的时间复杂度为 O(n + m),空间复杂度为 O(min(n, m)),非常适合处理大规模数据。

  1. 函数定义

    • def solution(a, b)::定义了一个名为 solution 的函数,接受两个参数 ab,分别表示两个有序数组。
  2. 初始化指针和结果列表

    • i, j = 0, 0:初始化两个指针 ij,分别指向数组 ab 的开头。
    • result = []:初始化一个空列表 result,用于存储交集元素。
  3. 遍历两个数组

    • while i < len(a) and j < len(b)::使用 while 循环,直到其中一个数组遍历完毕。
      • if a[i] == b[j]::如果 a[i] 等于 b[j],说明找到了一个交集元素。
        • result.append(a[i]):将该元素加入结果列表 result
        • i += 1j += 1:同时移动两个指针,继续查找下一个元素。
      • elif a[i] < b[j]::如果 a[i] 小于 b[j],说明当前 a[i] 不可能在 b 中出现,因此移动 i 指针。
        • i += 1:移动 i 指针。
      • else::如果 a[i] 大于 b[j],说明当前 b[j] 不可能在 a 中出现,因此移动 j 指针。
        • j += 1:移动 j 指针。
  4. 反转结果列表

    • result.reverse():遍历结束后,反转结果列表 result,以满足从大到小的顺序。
  5. 返回结果

    • return result:返回结果列表 result
  6. 测试用例

    • if __name__ == '__main__'::确保测试代码只在直接运行脚本时执行。
    • print(solution([1, 2, 3, 7], [2, 5, 7]) == [7, 2]):测试第一个样例,预期输出 [7, 2]
    • print(solution([1, 4, 8, 10], [2, 4, 8, 10]) == [10, 8, 4]):测试第二个样例,预期输出 [10, 8, 4]
    • print(solution([3, 5, 9], [1, 4, 6]) == []):测试第三个样例,预期输出 []
    • print(solution([1, 2, 3], [1, 2, 3]) == [3, 2, 1]):测试第四个样例,预期输出 [3, 2, 1]

关键步骤解释

  1. 双指针法

    • 使用双指针法可以高效地查找两个有序数组的交集。通过比较两个指针指向的元素,决定如何移动指针,避免了嵌套循环,从而将时间复杂度降低到 O(n + m)
  2. 交集查找

    • while 循环中,通过比较 a[i]b[j] 的大小关系,决定如何移动指针。如果找到交集元素,将其加入结果列表,并同时移动两个指针。
  3. 结果反转

    • 由于题目要求结果按从大到小的顺序输出,因此在找到所有交集元素后,需要反转结果列表。result.reverse() 可以高效地完成这一操作。
  4. 边界条件

    • while 循环中,使用 i < len(a) and j < len(b) 确保不会越界访问数组。
    • 如果两个数组没有交集,结果列表将为空,直接返回空列表即可。

复杂度分析

  • 时间复杂度

    • 遍历两个数组的时间复杂度为 O(n + m),其中 nm 分别是数组 ab 的长度。
    • 反转结果列表的时间复杂度为 O(k),其中 k 是交集元素的数量。
    • 总体时间复杂度为 O(n + m)
  • 空间复杂度

    • 结果列表的空间复杂度为 O(min(n, m)),因为我们只存储交集元素。
    • 总体空间复杂度为 O(min(n, m))

总结

通过使用双指针法,我们可以高效地找到两个有序数组的交集,并通过反转结果列表来满足输出要求。这个算法的时间复杂度为 O(n + m),空间复杂度为 O(min(n, m)),非常适合处理大规模数据。

总结与反思

问题解决思路总结

在这个问题中,我们的目标是找到两个有序数组 ab 的交集,并将结果按从大到小的顺序输出。通过使用双指针法,我们能够高效地解决这个问题。以下是解决问题的关键步骤和思路总结:

  1. 理解问题

    • 我们需要找到两个有序数组的交集,并按从大到小的顺序输出结果。
    • 输入是有序数组,这为我们提供了优化的机会。
  2. 数据结构选择

    • 使用双指针法来遍历两个有序数组,可以高效地找到交集。
    • 使用一个列表来存储交集元素,并在最后反转列表以满足输出要求。
  3. 算法步骤

    • 初始化两个指针 ij,分别指向数组 ab 的开头。
    • 遍历两个数组,比较当前指针指向的元素:
      • 如果 a[i] 等于 b[j],则将该元素加入结果列表,并移动两个指针。
      • 如果 a[i] 小于 b[j],则移动 i 指针。
      • 如果 a[i] 大于 b[j],则移动 j 指针。
    • 遍历结束后,反转结果列表以满足从大到小的顺序。
  4. 复杂度分析

    • 时间复杂度为 O(n + m),其中 nm 分别是数组 ab 的长度。
    • 空间复杂度为 O(min(n, m)),因为我们只存储交集元素。

代码实现反思

  1. 代码结构

    • 代码结构清晰,逻辑明确。通过使用双指针法,我们避免了嵌套循环,从而提高了算法的效率。
    • 使用 while 循环来遍历数组,确保不会越界访问。
  2. 指针移动逻辑

    • while 循环中,通过比较 a[i]b[j] 的大小关系,决定如何移动指针。这种逻辑确保了我们能够高效地找到交集元素。
    • 如果找到交集元素,同时移动两个指针,避免了重复比较。
  3. 结果反转

    • 在找到所有交集元素后,使用 result.reverse() 来反转结果列表,以满足从大到小的顺序。这个操作的时间复杂度为 O(k),其中 k 是交集元素的数量。
  4. 边界条件处理

    • while 循环中,使用 i < len(a) and j < len(b) 确保不会越界访问数组。
    • 如果两个数组没有交集,结果列表将为空,直接返回空列表即可。

优化与改进

  1. 代码可读性

    • 代码的可读性较好,但可以通过添加注释来进一步提高代码的可读性。例如,在关键步骤添加注释,解释每一步的逻辑。
  2. 性能优化

    • 当前的算法已经非常高效,时间复杂度为 O(n + m),空间复杂度为 O(min(n, m))。如果需要进一步优化,可以考虑使用更高级的数据结构或算法,但在这个问题中,双指针法已经是最优解。
  3. 错误处理

    • 当前代码没有包含错误处理逻辑。如果输入的数组不是有序的,或者包含非整数元素,代码可能会出错。可以添加一些基本的错误处理逻辑,例如检查输入是否为有序数组,或者是否包含非整数元素。
  4. 测试用例

    • 当前的测试用例覆盖了大部分常见情况,但可以进一步增加一些边界测试用例,例如空数组、单个元素数组、重复元素数组等。

总结

通过使用双指针法,我们能够高效地找到两个有序数组的交集,并通过反转结果列表来满足输出要求。这个算法的时间复杂度为 O(n + m),空间复杂度为 O(min(n, m)),非常适合处理大规模数据。代码结构清晰,逻辑明确,但可以通过添加注释和增加错误处理逻辑来进一步提高代码的可读性和健壮性。

在实际应用中,倒排索引是一种非常常见的数据结构,用于快速查找包含特定单词的文档或帖子。通过理解和掌握这种数据结构及其应用,我们可以更好地设计和实现高效的搜索引擎和其他相关应用。