作者: admin2025

  • Serialize and Deserialize BST,LeetCode,449

    LeetCode 449题是关于二叉搜索树(BST)的序列化和反序列化问题。序列化是将一个数据结构或对象状态转换为可存储或传输的格式的过程,以便在需要时可以恢复该数据结构或对象状态。反序列化是序列化的逆过程。

    题目描述

    你需要设计一个算法来实现二叉搜索树的序列化和反序列化。序列化是将一棵二叉搜索树转换为一个字符串,而反序列化是将一个字符串转换回一棵二叉搜索树。

    要求

    • 序列化和反序列化的算法应该是高效的,即时间复杂度和空间复杂度尽可能低。
    • 序列化的结果应该是一个字符串,反序列化的输入也应该是一个字符串。

    解决思路

    由于二叉搜索树具有有序性,可以利用前序遍历和中序遍历的特性来进行序列化和反序列化。但更常见的方法是使用前序遍历结合null标记来进行序列化,这样可以简化反序列化的过程。

    1. 序列化

    使用前序遍历(根-左-右)来遍历树,并将每个节点的值转换为字符串,如果遇到null节点,则用特殊字符(如”#”)来表示。

    2. 反序列化

    将序列化后的字符串按前序遍历的顺序重新构建二叉搜索树。使用一个队列来存储节点值,逐个构建节点并按前序遍历的顺序连接左右子树。

    代码实现(Python)

    class TreeNode: def init(self, x): self.val = x self.left = None self.right = None

    class Codec: def serialize(self, root): """Encodes a tree to a single string.""" if not root: return ""

        def preorder(node):
            if not node:
                return "# "
            return str(node.val) + " " + preorder(node.left) + preorder(node.right)
    
        return preorder(root)
    
    def deserialize(self, data):
        """Decodes your encoded data to tree."""
        if not data:
            return None
    
        def build_tree(values):
            val = values.pop(0)
            if val == "#":
                return None
            node = TreeNode(int(val))
            node.left = build_tree(values)
            node.right = build_tree(values)
            return node
    
        values = data.split()
        return build_tree(values)

    Your Codec object will be instantiated and called as such:

    ser = Codec()

    deser = Codec()

    tree = deser.deserialize(ser.serialize(root))

    详细解释

    1. 序列化(serialize):
      • 使用递归函数preorder进行前序遍历。
      • 如果当前节点为null,返回”#”和一个空格。
      • 否则,返回当前节点的值和一个空格,然后递归处理左子树和右子树。
    2. 反序列化(deserialize):
      • 将输入的字符串按空格分割成列表values
      • 使用递归函数build_tree来构建树:
        • values中取出第一个元素,如果是”#”,则返回None。
        • 否则,创建一个新节点,递归构建其左子树和右子树。

    复杂度分析

    • 时间复杂度:序列化和反序列化均为O(n),其中n是树中节点的数量。
    • 空间复杂度:序列化使用O(h)的递归栈空间,反序列化使用O(n)的空间来存储节点值。

    这种方法利用了二叉搜索树的性质和前序遍历的特性,实现了高效的序列化和反序列化。

  • Arranging Coins,LeetCode,441

    LeetCode 441题 “Arranging Coins” 是一个中等难度的数学问题。题目描述如下:

    你总共有 n 枚硬币,并希望将它们排列成一个阶梯形状,第 k 行必须正好有 k 枚硬币。

    给你一个整数 n,返回你可以完整排列的行数。

    示例 1:

    输入: n = 5 输出: 2 解释: 因为第三行不完整,所以返回 2.

    示例 2:

    输入: n = 8 输出: 3 解释: 因为第四行不完整,所以返回 3.

    解题思路

    这个问题可以通过数学公式和二分查找来解决。

    方法一:数学公式

    我们知道,前 k 行需要的硬币总数是: [ S = \frac{k(k + 1)}{2} ]

    我们需要找到一个最大的 k,使得: [ \frac{k(k + 1)}{2} \leq n ]

    这可以通过解以下不等式得到: [ k^2 + k – 2n \leq 0 ]

    使用求根公式: [ k = \frac{-b \pm \sqrt{b^2 – 4ac}}{2a} ] 其中,a = 1b = 1c = -2n

    所以: [ k = \frac{-1 \pm \sqrt{1 + 8n}}{2} ]

    我们取正根: [ k = \frac{-1 + \sqrt{1 + 8n}}{2} ]

    最后取整即可。

    方法二:二分查找

    我们可以使用二分查找来找到最大的 k,使得: [ \frac{k(k + 1)}{2} \leq n ]

    初始化 left = 1right = n,然后进行二分查找。

    代码实现

    方法一:数学公式

    import math

    def arrangeCoins(n: int) -> int: return int((-1 + math.sqrt(1 + 8 * n)) // 2)

    示例

    print(arrangeCoins(5)) # 输出: 2 print(arrangeCoins(8)) # 输出: 3

    方法二:二分查找

    def arrangeCoins(n: int) -> int: left, right = 1, n while left <= right: mid = (left + right) // 2 current = mid * (mid + 1) // 2 if current == n: return mid elif current < n: left = mid + 1 else: right = mid - 1 return right

    示例

    print(arrangeCoins(5)) # 输出: 2 print(arrangeCoins(8)) # 输出: 3

    总结

    • 数学公式法:通过求解一元二次方程来直接计算结果,时间复杂度为 (O(1))。
    • 二分查找法:通过二分查找逐步逼近结果,时间复杂度为 (O(\log n))。

    两种方法各有优劣,数学公式法更直观且速度快,但可能存在浮点数精度问题;二分查找法更通用且精度高,但稍微复杂一些。

    希望这些详细的解释和代码示例能帮助你理解和解决这个题目!如果有更多问题,欢迎继续提问。

  • K-th Smallest in Lexicographical Order,LeetCode,440

    LeetCode 440题 “K-th Smallest in Lexicographical Order” 是一个中等难度的题目,主要考察的是字典序(lexicographical order)和数字的排列组合。

    题目描述

    给定整数 nk,找到从 1n 的所有整数按字典序排列后的第 k 个数。

    字典序排列是指将数字视为字符串,按照字典顺序排列。例如,对于 n = 13,字典序排列为 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9]

    解法思路

    这个问题可以通过递归和数学的方法来解决。核心思想是利用字典树的性质,逐步缩小范围,找到第 k 个数。

    步骤:

    1. 初始化:从根节点 1 开始。
    2. 计算子节点数量:对于当前节点 curr,计算从 currcurr + 1 之间的所有子节点的数量。
    3. 比较 k 和子节点数量
      • 如果 k 小于等于子节点数量,说明第 k 个数在当前节点的子树中,更新 currcurr * 10 并继续搜索。
      • 如果 k 大于子节点数量,说明第 k 个数不在当前节点的子树中,更新 currcurr + 1 并减去当前子树节点的数量,继续搜索。

    关键函数:

    • countSteps(curr, n):计算从 currcurr + 1 之间的所有子节点的数量。

    代码实现

    class Solution: def findKthNumber(self, n, k): curr = 1 k -= 1 # 因为从1开始计数,所以k减1

        while k > 0:
            steps = self.countSteps(curr, n)
            if steps <= k:
                # 第k个数不在当前子树中
                curr += 1
                k -= steps
            else:
                # 第k个数在当前子树中
                curr *= 10
                k -= 1
    
        return curr
    
    def countSteps(self, curr, n):
        steps = 0
        first = curr
        last = curr
    
        while first <= n:
            steps += min(n + 1, last + 1) - first
            first *= 10
            last = last * 10 + 9
    
        return steps

    示例

    sol = Solution() print(sol.findKthNumber(13, 2)) # 输出: 10 print(sol.findKthNumber(13, 5)) # 输出: 13

    解释

    1. 初始化curr1 开始,k1 是因为从 1 开始计数。
    2. 循环:当 k 大于 时,计算从 currcurr + 1 之间的子节点数量。
    3. 子节点数量计算
      • firstlast 分别表示当前层的起始和结束节点。
      • 通过循环扩展 firstlast 到下一层,累加每层的节点数量。
    4. 更新 currk
      • 如果 k 大于子节点数量,说明第 k 个数不在当前子树中,curr 增加 1k 减去当前子树节点数量。
      • 如果 k 小于等于子节点数量,说明第 k 个数在当前子树中,curr 扩展到下一层(curr * 10),k 减去 1(因为进入下一层)。

    通过这种方法,逐步缩小范围,最终找到第 k 个数。

    希望这个详细的解释和代码实现能帮助你理解并解决这个题目!如果有任何进一步的问题,欢迎继续提问。

  • Combination Sum IV,LeetCode,377

    题目描述:

    LeetCode 377题 “Combination Sum IV” 是一个关于组合数学的动态规划问题。题目要求给定一个由正整数组成的数组 nums 和一个目标值 target,找出所有可能的组合,使得这些组合中的数字之和等于 target。每个数字在组合中可以无限次使用,但组合中的顺序不同的序列被认为是不同的组合。

    示例:

    输入: nums = [1, 2, 3], target = 4 输出: 7 解释: 所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

    解题思路:

    这个问题可以通过动态规划来解决。我们定义一个数组 dp,其中 dp[i] 表示和为 i 的组合数。初始时,dp[0] = 1,因为和为0的组合只有一种,即什么也不选。

    接下来,我们遍历从1到target的每一个数,对于每一个数i,我们再遍历nums数组中的每一个数num,如果i >= num,则dp[i] += dp[i - num]。这是因为如果我们可以通过某种方式组合出i - num,那么再加上num就可以组合出i

    代码实现:

    def combinationSum4(nums, target):

    初始化dp数组,长度为target + 1,初始值全为0

    dp = [0] * (target + 1)
    # 和为0的组合只有一种,即什么也不选
    dp[0] = 1
    
    # 遍历从1到target的每一个数
    for i in range(1, target + 1):
        # 遍历nums数组中的每一个数
        for num in nums:
            # 如果当前数i大于等于num,则更新dp[i]
            if i >= num:
                dp[i] += dp[i - num]
    
    # 返回和为target的组合数
    return dp[target]

    示例

    nums = [1, 2, 3] target = 4 print(combinationSum4(nums, target)) # 输出: 7

    详细解释:

    1. 初始化dp数组:
      • dp数组的大小为target + 1,因为我们需要计算从0到target的所有可能的组合数。
      • dp[0] = 1是因为和为0的组合只有一种,即什么也不选。
    2. 外层循环:
      • 遍历从1到target的每一个数i,表示我们当前要计算和为i的组合数。
    3. 内层循环:
      • 遍历nums数组中的每一个数num,表示我们尝试使用num来组合出和为i的情况。
      • 如果i >= num,说明我们可以使用num来组合出和为i的情况,此时dp[i]应该加上dp[i - num],因为dp[i - num]表示和为i - num的组合数,加上num后就能组合出和为i的情况。
    4. 返回结果:
      • 最终dp[target]就是我们要求的和为target的组合数。

    时间复杂度:

    • 时间复杂度为O(target * n),其中nnums数组的长度。这是因为我们需要遍历从1到target的每一个数,并且对于每一个数,我们还需要遍历nums数组中的每一个数。

    空间复杂度:

    • 空间复杂度为O(target),因为我们使用了一个长度为target + 1的数组来存储中间结果。

    通过上述分析和代码实现,我们可以有效地解决LeetCode 377题 “Combination Sum IV”。

  • Wiggle Subsequence,LeetCode,376

    题目描述:

    LeetCode 376题 Wiggle Subsequence(摆动序列)要求我们找到一个序列的最长摆动子序列的长度。摆动序列的定义是:序列中的元素在相邻元素之间交替上升和下降。

    题目示例:

    输入: [1,7,4,9,2,5] 输出: 6 解释: 整个序列就是一个摆动序列。

    输入: [1,17,5,10,13,15,10,5,16,8] 输出: 7 解释: 序列中的最长摆动子序列是 [1,17,10,13,10,16,8]。

    解题思路:

    我们可以使用贪心算法来解决这个问题。具体思路如下:

    1. 初始化: 定义两个变量 updown,分别表示当前序列中上升和下降的最长摆动子序列的长度。初始时都设为1,因为任何一个单独的元素都可以看作是一个摆动序列。
    2. 遍历序列: 从第二个元素开始遍历整个序列,对于每一个元素,根据它与前一个元素的关系来更新 updown
      • 如果当前元素大于前一个元素,说明这是一个上升的过程,此时 up 应该更新为 down + 1,因为上升序列可以接在之前的下降序列后面。
      • 如果当前元素小于前一个元素,说明这是一个下降的过程,此时 down 应该更新为 up + 1,因为下降序列可以接在之前的上升序列后面。
    3. 结果: 遍历完成后,updown 中的最大值就是最长摆动子序列的长度。

    代码实现(Python):

    def wiggleMaxLength(nums): if not nums: return 0 up = down = 1 for i in range(1, len(nums)): if nums[i] > nums[i - 1]: up = down + 1 elif nums[i] < nums[i - 1]: down = up + 1 return max(up, down)

    示例

    print(wiggleMaxLength([1,7,4,9,2,5])) # 输出: 6 print(wiggleMaxLength([1,17,5,10,13,15,10,5,16,8])) # 输出: 7

    复杂度分析:

    • 时间复杂度: O(n),其中 n 是序列的长度。我们只需要遍历一次序列。
    • 空间复杂度: O(1),我们只使用了常数级别的额外空间。

    总结:

    这个题目通过贪心算法可以高效解决,关键在于正确理解和维护上升和下降序列的长度。通过简单的遍历和更新操作,我们能够在线性时间内找到最长摆动子序列的长度。这种方法不仅简洁,而且效率高,适合处理大规模数据。

  • Sum of Two Integers,LeetCode,371

    LeetCode 371题是“两整数之和”(Sum of Two Integers),这道题要求在不使用加号(+)和减号(-)的情况下,计算两个整数的和。

    题目描述

    给定两个整数 ab,求它们的和。

    注意

    • 你不能使用加号(+)和减号(-)。
    • 你可以使用位运算。

    解题思路

    这道题可以通过位运算来解决。位运算主要包括以下几种:

    • 与运算(&):用于找出两个数的二进制表示中同时为1的位。
    • 异或运算(^):用于找出两个数的二进制表示中不同时为1的位。
    • 左移运算(<<):用于将数的二进制表示向左移动指定的位数。

    具体步骤如下:

    1. 异或运算a ^ b 可以得到两个数相加但不考虑进位的结果。
    2. 与运算和左移(a & b) << 1 可以得到两个数相加时的进位结果。
    3. 重复上述两个步骤,直到进位结果为0。

    代码实现

    以下是使用Python实现的代码:

    class Solution: def getSum(self, a: int, b: int) -> int:

    32位整数的最大值和最小值

        MAX = 0x7FFFFFFF
        MIN = 0x80000000
        # 掩码,用于获取32位整数
        mask = 0xFFFFFFFF
    
        while b != 0:
            # 计算不进位的和
            a, b = (a ^ b) & mask, ((a & b) << 1) & mask
    
        # 如果a是负数,将其转换成补码形式
        return a if a <= MAX else ~(a ^ mask)

    代码解释

    1. 掩码(mask):用于确保计算结果始终在32位整数的范围内。
    2. 循环:当 b 不为0时,继续计算不进位的和和进位。
    3. 返回结果:如果 a 是负数,需要将其转换成补码形式。

    示例

    假设 a = 1b = 2

    1. 第一次循环:
      • a = 1 ^ 2 = 3
      • b = (1 & 2) << 1 = 0
    2. 由于 b 为0,循环结束,返回 a = 3

    复杂度分析

    • 时间复杂度:O(1),因为每次循环都会减少进位的位数,最坏情况下循环的次数是32(整数的位数)。
    • 空间复杂度:O(1),只使用了常数空间。

    通过这种方法,我们可以在不使用加号和减号的情况下,计算出两个整数的和。希望这个解释对你有帮助!如果有更多问题,欢迎继续提问。

  • Valid Perfect Square,LeetCode,367

    题目描述:

    给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 true,否则返回 false

    示例:

    输入: 16 输出: true

    输入: 14 输出: false

    解题思路:

    判断一个数是否为完全平方数,可以通过以下几种方法:

    方法一:直接开平方

    1. 计算平方根:使用 sqrt 函数计算 num 的平方根。
    2. 判断平方根是否为整数:如果平方根的整数部分平方后等于 num,则 num 是完全平方数。

    代码实现:

    import math

    def isPerfectSquare(num): sqrt_num = math.sqrt(num) return int(sqrt_num) ** 2 == num

    示例

    print(isPerfectSquare(16)) # 输出: true print(isPerfectSquare(14)) # 输出: false

    方法二:二分查找

    1. 初始化边界:设置左边界 left 为 1,右边界 rightnum
    2. 二分查找:在 leftright 之间进行二分查找,计算中间值 mid,判断 mid * midnum 的关系。
      • 如果 mid * mid == num,则 num 是完全平方数。
      • 如果 mid * mid < num,则调整左边界 left = mid + 1
      • 如果 mid * mid > num,则调整右边界 right = mid - 1
    3. 结束条件:当 left 超过 right 时,结束循环。

    代码实现:

    def isPerfectSquare(num): left, right = 1, num while left <= right: mid = (left + right) // 2 if mid mid == num: return True elif mid mid < num: left = mid + 1 else: right = mid - 1 return False

    示例

    print(isPerfectSquare(16)) # 输出: true print(isPerfectSquare(14)) # 输出: false

    方法三:牛顿迭代法

    1. 初始化:设置初始值 r = num
    2. 迭代计算:使用牛顿迭代公式 r = (r + num / r) / 2,直到 r * rnum 的差值小于等于 1。
    3. 判断结果:如果 r * r == num,则 num 是完全平方数。

    代码实现:

    def isPerfectSquare(num): r = num while r r > num: r = (r + num // r) // 2 return r r == num

    示例

    print(isPerfectSquare(16)) # 输出: true print(isPerfectSquare(14)) # 输出: false

    总结:

    • 直接开平方 方法简单直观,但依赖 sqrt 函数,可能存在精度问题。
    • 二分查找 方法不依赖库函数,时间复杂度为 O(log n),适合大数处理。
    • 牛顿迭代法 方法效率高,迭代次数少,适合快速计算。

    根据具体需求选择合适的方法即可。希望这些解答对你有所帮助!如果有更多问题,欢迎继续提问。

  • Insert Delete GetRandom O(1),LeetCode,380

    LeetCode 上的第 380 题 “Insert Delete GetRandom O(1)” 是一个设计数据结构的问题,要求实现一个数据结构,支持以下操作,并且每个操作的时间复杂度都是 O(1):

    1. insert(val):当元素 val 不存在时,向集合中插入该项。
    2. remove(val):当元素 val 存在时,从集合中移除该项。
    3. getRandom():随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有相同的概率被返回。

    解决思路

    要实现这三个操作的时间复杂度都是 O(1),我们需要结合哈希表和数组的特点:

    1. 哈希表:用于快速查找和删除元素,时间复杂度为 O(1)。
    2. 数组:用于快速随机访问元素,时间复杂度为 O(1)。

    具体实现如下:

    • 使用一个哈希表 unordered_map 来存储每个元素值及其在数组中的索引。
    • 使用一个动态数组 vector 来存储所有元素。

    操作实现

    1. 插入操作 (insert(val))
      • 检查哈希表中是否已经存在该元素,如果存在则直接返回 false
      • 将元素插入到数组的末尾,并在哈希表中记录该元素的索引。
      • 返回 true
    2. 删除操作 (remove(val))
      • 检查哈希表中是否已经存在该元素,如果不存在则直接返回 false
      • 从哈希表中获取该元素的索引,然后将数组末尾的元素移动到该索引位置,并更新哈希表中末尾元素的索引。
      • 删除数组末尾的元素,并从哈希表中删除该元素。
      • 返回 true
    3. 随机访问操作 (getRandom())
      • 直接从数组中随机选择一个元素返回。

    代码实现

    #include #include #include #include

    class RandomizedSet { private: std::unordered_map hash; std::vector nums;

    public: RandomizedSet() { srand(time(nullptr)); // 初始化随机数种子 }

    bool insert(int val) {
        if (hash.find(val) != hash.end()) {
            return false;
        }
        nums.push_back(val);
        hash[val] = nums.size() - 1;
        return true;
    }
    
    bool remove(int val) {
        if (hash.find(val) == hash.end()) {
            return false;
        }
        int last = nums.back();
        int idx = hash[val];
        nums[idx] = last;
        hash[last] = idx;
        nums.pop_back();
        hash.erase(val);
        return true;
    }
    
    int getRandom() {
        int randomIndex = rand() % nums.size();
        return nums[randomIndex];
    }

    };

    /**

    • Your RandomizedSet object will be instantiated and called as such:
    • RandomizedSet* obj = new RandomizedSet();
    • bool param_1 = obj->insert(val);
    • bool param_2 = obj->remove(val);
    • int param_3 = obj->getRandom(); */

      解释

      • 构造函数:初始化随机数种子,以便在 getRandom() 中使用。
      • 插入操作:首先检查元素是否已存在,如果不存在则插入到数组和哈希表中。
      • 删除操作:首先检查元素是否已存在,如果存在则通过交换数组中的元素和更新哈希表来实现 O(1) 的删除。
      • 随机访问操作:直接从数组中随机选择一个元素返回。

    这种设计确保了每个操作的时间复杂度都是 O(1),同时利用了哈希表和数组的各自优势。

  • Guess Number Higher or Lower,LeetCode,374

    LeetCode 374题 “猜数字大小”(Guess Number Higher or Lower)是一个经典的二分查找问题。题目描述如下:

    题目描述:

    我们正在玩一个猜数字游戏。游戏规则如下:

    • 我从1到n选择一个数字。你来猜我选了哪个数字。
    • 每次你猜错了,我会告诉你这个数字是大了还是小了。

    你调用一个预先定义好的接口 int guess(int num),它会返回三个可能的结果:

    • -1:我选的数字比你猜的小
    • 1:我选的数字比你猜的大
    • :恭喜!你猜对了!

    示例:

    输入: n = 10, 我选择 6

    你的猜测序列应该是 1, 2, 5, 7, 6

    解题思路:

    这个问题可以通过二分查找来解决。二分查找是一种高效的查找算法,适用于有序数组。在这个问题中,虽然我们没有具体的数组,但我们可以将数字范围 [1, n] 看作一个有序数组。

    具体步骤:

    1. 初始化两个指针 leftright,分别指向范围的最小值和最大值,即 left = 1right = n
    2. leftright 之间进行二分查找:
      • 计算中间值 mid,即 mid = (left + right) // 2
      • 使用 guess(mid) 来判断中间值与目标值的关系。
      • 根据 guess 的返回值调整 leftright
        • 如果 guess(mid) == 0,说明猜对了,返回 mid
        • 如果 guess(mid) == -1,说明目标值小于 mid,将 right 调整为 mid - 1
        • 如果 guess(mid) == 1,说明目标值大于 mid,将 left 调整为 mid + 1
    3. 重复步骤2,直到找到目标值。

    代码实现:

    # The guess API is already defined for you.

    @param num, your guess

    @return -1 if my number is lower, 1 if my number is higher, otherwise return 0

    def guess(num: int) -> int: pass

    class Solution: def guessNumber(self, n: int) -> int: left, right = 1, n while left <= right: mid = (left + right) // 2 result = guess(mid) if result == 0: return mid elif result == -1: right = mid - 1 else: left = mid + 1 return -1 # This line should never be reached if the input is valid

    解释:

    • leftright 分别初始化为 1n,表示搜索范围。
    • 在循环中,计算中间值 mid
    • 根据 guess(mid) 的返回值调整搜索范围。
    • guess(mid) == 0 时,表示找到了目标值,直接返回 mid
    • 循环直到 left 超过 right,理论上在有效输入下不会出现这种情况。

    复杂度分析:

    • 时间复杂度: O(log n),二分查找的时间复杂度为对数级别。
    • 空间复杂度: O(1),只使用了常数级别的额外空间。

    通过这种方式,我们可以高效地解决这个猜数字问题。希望这个解释对你有帮助!如果有更多问题,欢迎继续提问。

  • Find K Pairs with Smallest Sums,LeetCode,373

    LeetCode 373题 “Find K Pairs with Smallest Sums” 是一个中等难度的题目,要求我们从两个整数数组中找到和最小的k对数字。具体描述如下:

    题目描述

    给定两个以升序排列的整数数组 nums1nums2,以及一个整数 k。你需要找到和最小的 k 对数字(u1,v1),(u2,v2)…,其中 u1 来自 nums1v1 来自 nums2

    示例

    输入:

    nums1 = [1,7,11] nums2 = [2,4,6] k = 3

    输出:

    [[1,2],[1,4],[1,6]]

    解释: 返回序列中的前 3 对数分别是:

    • [1,2],[1,4],[1,6] 的和分别是 3,5,7,是最小的。

    方法一:最小堆(优先队列)

    我们可以使用最小堆来解决这个问题。具体步骤如下:

    1. 初始化最小堆
      • nums1 中的每个元素与 nums2 中的第一个元素的组合放入最小堆中。堆中的元素是一个三元组 (sum, i, j),其中 sum = nums1[i] + nums2[j]ij 分别是 nums1nums2 中的索引。
    2. 提取最小元素并扩展
      • 每次从堆中提取最小元素 (sum, i, j),将其加入结果列表。
      • 如果 j < nums2.length - 1,将新的组合 (nums1[i] + nums2[j+1], i, j+1) 加入堆中。
    3. 重复步骤2直到找到k对

    代码实现(Python)

    import heapq

    def kSmallestPairs(nums1, nums2, k): if not nums1 or not nums2 or k <= 0: return []

    min_heap = []
    # 初始化堆
    for i in range(min(k, len(nums1))):  # 只需要前k个元素,如果nums1长度大于k
        heapq.heappush(min_heap, (nums1[i] + nums2[0], i, 0))
    
    result = []
    while k > 0 and min_heap:
        current_sum, i, j = heapq.heappop(min_heap)
        result.append([nums1[i], nums2[j]])
        if j + 1 < len(nums2):
            heapq.heappush(min_heap, (nums1[i] + nums2[j+1], i, j+1))
        k -= 1
    
    return result

    示例

    nums1 = [1,7,11] nums2 = [2,4,6] k = 3 print(kSmallestPairs(nums1, nums2, k)) # 输出: [[1,2],[1,4],[1,6]]

    复杂度分析

    • 时间复杂度:O(k * log(min(k, m))),其中 mnums1 的长度。每次从堆中提取最小元素的时间复杂度为 O(log(min(k, m))),最多进行 k 次。
    • 空间复杂度:O(min(k, m)),堆的大小最多为 min(k, m)

    其他方法

    除了最小堆,还可以考虑使用二分查找 + 双指针的方法,但实现起来相对复杂,且在某些情况下效率不如最小堆。

    总结

    使用最小堆的方法可以高效地解决此问题,通过维护一个大小为 min(k, m) 的堆,确保每次都能找到当前最小的组合,逐步构建结果列表。这种方法在处理大规模数据时表现良好,且实现相对简单。

    希望这个详细的解答能帮助你理解并解决这个题目!如果有任何进一步的问题,欢迎继续提问。