作者: admin2025

  • 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) 的堆,确保每次都能找到当前最小的组合,逐步构建结果列表。这种方法在处理大规模数据时表现良好,且实现相对简单。

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

  • Water and Jug Problem,LeetCode,365

    LeetCode 365题 “水壶问题”(Water and Jug Problem)是一个经典的数学问题,可以用深度优先搜索(DFS)、广度优先搜索(BFS)或者数学方法(贝祖定理)来解决。下面我会详细解释这个问题的背景、解题思路和代码实现。

    问题背景

    你有两个水壶,容量分别为 x 升和 y 升。你还有无限多的水。你需要判断是否能够通过以下操作得到恰好 z 升的水:

    1. 装满任意一个水壶。
    2. 清空任意一个水壶。
    3. 将一个水壶中的水倒入另一个水壶,直到一个水壶空或者另一个水壶满。

    解题思路

    方法一:深度优先搜索(DFS)

    我们可以用DFS来遍历所有可能的状态。每个状态可以用 (当前x壶中的水量, 当前y壶中的水量) 来表示。我们从 (0, 0) 开始,通过上述操作进行状态转移,看是否能达到 (z, 0)(0, z)

    方法二:广度优先搜索(BFS)

    BFS也是一种常见的搜索方法,适用于这种需要遍历所有可能状态的问题。我们用一个队列来存储所有可能的状态,并逐层扩展。

    方法三:数学方法(贝祖定理)

    贝祖定理告诉我们,对于任意的整数 abax + by = z 有解当且仅当 gcd(a, b) | z(即 zab 的最大公约数的倍数)。在这个问题中,ab 分别是 xy,所以我们只需要判断 z 是否是 gcd(x, y) 的倍数。

    代码实现

    这里我们以数学方法为例,因为它最为简洁高效。

    from math import gcd

    def canMeasureWater(x, y, z): if x + y < z: return False if x == 0 or y == 0: return z == 0 or x + y == z return z % gcd(x, y) == 0

    示例

    x, y, z = 3, 5, 4 print(canMeasureWater(x, y, z)) # 输出: True

    详细解释

    1. 边界条件
      • 如果 x + y < z,显然不可能得到 z 升水,直接返回 False
      • 如果 xy 为0,那么只能通过另一个水壶来得到水。此时只有 z 为0或者 z 等于另一个水壶的容量时才可能成功。
    2. 贝祖定理应用
      • 计算 xy 的最大公约数 gcd(x, y)
      • 判断 z 是否是 gcd(x, y) 的倍数,如果是则返回 True,否则返回 False

    复杂度分析

    • 时间复杂度:O(log(min(x, y))),因为计算最大公约数的时间复杂度是 O(log(min(x, y)))。
    • 空间复杂度:O(1),只需要常数级别的额外空间。

    通过上述方法,我们可以高效地解决这个水壶问题。如果你有更多问题或者需要其他方法的实现,欢迎继续提问!

  • Russian Doll Envelopes,LeetCode,354

    LeetCode 354 – Russian Doll Envelopes (俄罗斯套娃信封问题)

    题目描述

    给你一个二维整数数组 envelopes,其中 envelopes[i] = [wi, hi] 表示第 i 个信封的宽度和高度。

    当另一个信封的宽度和高度都比这个信封大时,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

    请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

    注意:不允许旋转信封。

    示例

    输入:envelopes = [[5,4],[6,4],[6,7],[2,3]] 输出:3 解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

    解题思路

    这个问题可以转化为一个最长递增子序列 (LIS) 的问题,但需要一些巧妙的处理:

    1. 排序:首先对信封按照宽度 w 进行升序排序,如果宽度相同,则按照高度 h 降序排序。这样做的目的是确保在宽度相同的情况下,高度较大的信封不会被选入 LIS 中,避免出现宽度相同但高度不同的信封互相嵌套的情况。
    2. 提取高度数组:将排序后的信封的高度提取出来,形成一个一维数组。问题就转化为了在这个高度数组中寻找最长递增子序列。
    3. 动态规划求 LIS:可以使用动态规划或二分查找的方法来求解 LIS。动态规划的时间复杂度为 O(n^2),而二分查找的时间复杂度为 O(n log n)。

    代码实现 (二分查找)

    def maxEnvelopes(envelopes):

    按宽度升序,高度降序排序

    envelopes.sort(key=lambda x: (x[0], -x[1]))
    
    # 提取高度数组
    heights = [h for _, h in envelopes]
    
    # 二分查找求 LIS
    def binary_search(tails, h):
        left, right = 0, len(tails)
        while left < right:
            mid = (left + right) // 2
            if tails[mid] < h:
                left = mid + 1
            else:
                right = mid
        return left
    
    tails = []
    for h in heights:
        idx = binary_search(tails, h)
        if idx == len(tails):
            tails.append(h)
        else:
            tails[idx] = h
    
    return len(tails)

    示例

    envelopes = [[5,4],[6,4],[6,7],[2,3]] print(maxEnvelopes(envelopes)) # 输出:3

    解释

    1. 排序envelopes.sort(key=lambda x: (x[0], -x[1])) 将信封按宽度升序,高度降序排列。
    2. 提取高度heights = [h for _, h in envelopes] 提取排序后的信封高度。
    3. 二分查找binary_search 函数用于在 tails 数组中找到 h 应该插入的位置。
    4. 构建 LIS:遍历 heights,使用二分查找更新 tails 数组,最终 tails 的长度即为 LIS 的长度。

    复杂度分析

    • 时间复杂度:O(n log n),其中 n 是信封的数量。排序需要 O(n log n),二分查找更新 LIS 需要 O(n log n)。
    • 空间复杂度:O(n),用于存储 heightstails 数组。

    总结

    Russian Doll Envelopes 问题通过巧妙的排序和转化为 LIS 问题,可以使用高效的二分查找方法求解,是一个经典的动态规划与二分查找结合的例子。理解其核心思想对于解决类似的 LIS 问题很有帮助。

  • Flatten Nested List Iterator,LeetCode,341

    LeetCode 341题 “Flatten Nested List Iterator” 是一个中等难度的题目,主要考察的是设计数据结构和迭代器的概念。题目要求你设计一个迭代器来扁平化一个嵌套的列表。

    题目描述

    给你一个嵌套的整型列表 nestedList,实现一个迭代器将其扁平化。

    示例:

    输入: [[1,1],2,[1,1]] 输出: [1,1,2,1,1] 解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,1,2,1,1]。

    解题思路

    1. 理解嵌套列表:嵌套列表可以包含整数或其他的嵌套列表。
    2. 迭代器设计:需要实现两个方法 next()hasNext()
    3. 栈的使用:使用栈来存储待处理的嵌套列表,这样可以方便地进行回溯。

    具体实现

    我们可以使用一个栈来存储待处理的元素,每次调用 next() 时,从栈顶取出一个元素。如果这个元素是整数,直接返回;如果是列表,将其展开并逆序压入栈中。

    Python代码实现

    # """

    This is the interface that allows for creating nested lists.

    You should not implement it, or speculate about its implementation

    """

    #class NestedInteger:

    def isInteger(self) -> bool:

    """

    @return True if this NestedInteger holds a single integer, rather than a nested list.

    """

    #

    def getInteger(self) -> int:

    """

    @return the single integer that this NestedInteger holds, if it holds a single integer

    Return None if this NestedInteger holds a nested list

    """

    #

    def getList(self) -> [NestedInteger]:

    """

    @return the nested list that this NestedInteger holds, if it holds a nested list

    Return None if this NestedInteger holds a single integer

    """

    class NestedIterator: def init(self, nestedList: [NestedInteger]): self.stack = []

    初始化时,将nestedList逆序压入栈中

        for item in reversed(nestedList):
            self.stack.append(item)
    
    def next(self) -> int:
        # next()方法返回栈顶的整数
        return self.stack.pop().getInteger()
    
    def hasNext(self) -> bool:
        # hasNext()方法检查栈顶元素是否为整数,如果不是,则展开
        while self.stack:
            top = self.stack[-1]
            if top.isInteger():
                return True
            self.stack.pop()
            # 将列表中的元素逆序压入栈中
            for item in reversed(top.getList()):
                self.stack.append(item)
        return False

    Your NestedIterator object will be instantiated and called as such:

    i, v = NestedIterator(nestedList), []

    while i.hasNext(): v.append(i.next())

    解释

    1. 初始化:在 __init__ 方法中,我们将输入的 nestedList 逆序压入栈中。
    2. next() 方法:直接从栈顶取出一个整数并返回。
    3. hasNext() 方法:检查栈顶元素,如果是整数,返回 True;如果是列表,将其展开并逆序压入栈中,继续检查。

    复杂度分析

    • 时间复杂度next()hasNext() 方法的时间复杂度均为 O(1) 平摊。
    • 空间复杂度:O(N),其中 N 是嵌套列表的最大深度。

    通过这种方式,我们可以高效地扁平化嵌套列表并进行迭代。希望这个解释对你理解并解决这个题目有所帮助!如果有更多问题,欢迎继续提问。

  • Counting Bits,LeetCode,338

    LeetCode 338题 “Counting Bits” 是一个中等难度的题目,要求我们计算从0到n每个数的二进制表示中1的个数。这个问题可以通过动态规划的方法高效解决。

    题目描述

    给定一个非负整数 n,对于 0 ≤ i ≤ n 中的每个数 i,计算其二进制表示中1的个数,并将结果作为一个数组返回。

    示例

    输入: 2 输出: [0,1,1] 解释: 0的二进制表示为0,有0个1。 1的二进制表示为1,有1个1。 2的二进制表示为10,有1个1。

    解法思路

    我们可以利用动态规划的思想来解决这个问题。具体思路如下:

    1. 初始化:创建一个长度为 n+1 的数组 dp,其中 dp[i] 表示数字 i 的二进制表示中1的个数。
    2. 基准情况dp[0] = 0,因为0的二进制表示中没有1。
    3. 递推关系
      • 对于任意一个数 i,我们可以将其表示为 i = 2 * k + b,其中 k 是一个整数,b 是0或1。
      • 如果 b 是0,那么 i 的二进制表示中1的个数与 k 相同,即 dp[i] = dp[k]
      • 如果 b 是1,那么 i 的二进制表示中1的个数比 k 多1,即 dp[i] = dp[k] + 1
      • 由于 b 只能是0或1,我们可以通过 i & 1 来判断 b 的值。

    代码实现

    以下是Python实现的代码:

    def countBits(n):

    初始化dp数组

    dp = [0] * (n + 1)
    
    # 遍历每个数,计算其二进制表示中1的个数
    for i in range(1, n + 1):
        # dp[i] = dp[i >> 1] + (i & 1)
        # i >> 1 相当于 i // 2
        # i & 1 判断i的最低位是0还是1
        dp[i] = dp[i >> 1] + (i & 1)
    
    return dp

    示例

    n = 2 print(countBits(n)) # 输出: [0, 1, 1]

    解释

    • i >> 1 是将 i 右移一位,相当于 i 除以2。
    • i & 1 是取 i 的最低位,判断是0还是1。

    复杂度分析

    • 时间复杂度:O(n),因为我们只需要遍历一次从0到n的所有数。
    • 空间复杂度:O(n),因为我们需要一个长度为 n+1 的数组来存储结果。

    通过这种方法,我们可以高效地解决 “Counting Bits” 问题。希望这个解释对你有所帮助!如果有更多问题,欢迎继续提问。

  • Largest BST Subtree,LeetCode,333

    题目描述:

    给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树中节点的数量。

    题目链接:

    LeetCode 333. Largest BST Subtree

    解题思路:

    要解决这个问题,我们可以使用递归的方法来遍历整个二叉树,并在每个节点处检查以该节点为根的子树是否是一个BST。如果是BST,我们还需要知道该子树中的节点数量。为了高效地完成这个任务,我们可以在递归过程中返回一些额外的信息,具体包括:

    1. 当前子树是否是BST。
    2. 当前子树中的节点数量。
    3. 当前子树的最小值和最大值。

    通过这些信息,我们可以在遍历过程中判断当前节点是否可以构成一个BST,并且可以合并左右子树的信息来确定以当前节点为根的子树是否是BST。

    具体步骤:

    1. 定义一个递归函数 dfs(node),返回一个包含四个元素的元组:
      • 是否是BST(布尔值)
      • 子树中的节点数量(整数)
      • 子树中的最小值(整数)
      • 子树中的最大值(整数)
    2. 在递归函数中:
      • 如果当前节点为空,返回 (True, 0, inf, -inf),表示空树是BST,节点数量为0,最小值为无穷大,最大值为负无穷大。
      • 递归处理左右子树,获取左右子树的信息。
      • 判断当前节点是否可以构成BST:
        • 当前节点值必须大于左子树的最大值且小于右子树的最小值。
        • 左右子树都必须是BST。
      • 如果当前节点可以构成BST,返回 (True, 左子树节点数 + 右子树节点数 + 1, min(当前节点值, 左子树最小值), max(当前节点值, 右子树最大值))
      • 如果当前节点不能构成BST,返回 (False, max(左子树节点数, 右子树节点数), -inf, inf),表示当前子树不是BST,节点数量为左右子树中的较大值。
    3. 在主函数中调用递归函数,并返回结果中的节点数量。

    代码实现:

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

    class Solution: def largestBSTSubtree(self, root: TreeNode) -> int: def dfs(node): if not node: return True, 0, float('inf'), float('-inf')

            left_is_bst, left_size, left_min, left_max = dfs(node.left)
            right_is_bst, right_size, right_min, right_max = dfs(node.right)
    
            if left_is_bst and right_is_bst and left_max < node.val < right_min:
                return True, left_size + right_size + 1, min(node.val, left_min), max(node.val, right_max)
            else:
                return False, max(left_size, right_size), float('-inf'), float('inf')
    
        is_bst, size, _, _ = dfs(root)
        return size

    示例用法

    root = TreeNode(10) root.left = TreeNode(5) root.right = TreeNode(15) root.left.left = TreeNode(1) root.left.right = TreeNode(8) root.right.right = TreeNode(7)

    sol = Solution() print(sol.largestBSTSubtree(root)) # 输出应为 3,因为最大的BST子树是 [10, 5, 15]

    复杂度分析:

    • 时间复杂度:O(N),其中N是二叉树中的节点数。每个节点只被访问一次。
    • 空间复杂度:O(H),其中H是二叉树的高度。递归栈的深度最多为H。

    通过这种方法,我们可以高效地找到最大的BST子树,并返回其节点数量。

  • Count of Smaller Numbers After Self,LeetCode,315

    LeetCode 上的第 315 题 “Count of Smaller Numbers After Self” 是一个中等难度的题目。题目要求对于数组中的每一个元素,计算其右边所有比它小的元素的数量。

    题目描述

    给定一个整数数组 nums,返回一个新的数组 counts,其中 counts[i] 表示 nums[i] 右边所有比 nums[i] 小的元素的数量。

    示例

    输入: [5, 2, 6, 1]

    输出: [2, 1, 1, 0]

    解释:

    • 5 的右边有 21,比 5 小,所以 counts[0] = 2
    • 2 的右边有 1,比 2 小,所以 counts[1] = 1
    • 6 的右边有 1,比 6 小,所以 counts[2] = 1
    • 1 的右边没有比 1 小的元素,所以 counts[3] = 0

    解法思路

    有多种方法可以解决这个问题,以下是几种常见的思路:

    1. 暴力解法

    最直接的方法是使用双重循环,对于每个元素,遍历其右边的所有元素并计数。这种方法的时间复杂度是 O(n^2),在数据量较大时效率较低。

    2. 归并排序

    使用归并排序的思想,可以在排序的同时统计逆序对的数量。具体步骤如下:

    1. 分解:将数组分成两半,递归处理每一半。
    2. 合并:在合并过程中,统计左边元素比右边元素大的情况,这样可以知道左边元素右边有多少比它小的元素。

    这种方法的时间复杂度是 O(n log n)。

    3. 树状数组(Binary Indexed Tree,BIT)

    树状数组是一种高效的数据结构,可以用来处理区间和的问题。具体步骤如下:

    1. 离散化:由于树状数组需要处理下标,先将数组进行离散化处理。
    2. 逆序遍历:从右到左遍历数组,使用树状数组统计当前元素之前比它小的元素的数量。

    这种方法的时间复杂度也是 O(n log n)。

    代码实现

    以下是使用归并排序思想的 Python 代码实现:

    class Solution: def countSmaller(self, nums): def merge_sort(enum): half = len(enum) // 2 if half: left, right = merge_sort(enum[:half]), merge_sort(enum[half:]) for i in range(len(enum))[::-1]: if not right or left and left[-1][1] > right[-1][1]: smaller[left[-1][0]] += len(right) enum[i] = left.pop() else: enum[i] = right.pop() return enum

        smaller = [0] * len(nums)
        merge_sort(list(enumerate(nums)))
        return smaller

    解释

    1. enumerate(nums):将 nums 数组的每个元素与其下标绑定,形成 (index, value) 的元组。
    2. merge_sort:自定义的归并排序函数,不仅排序,还在合并过程中统计逆序对。
    3. smaller:用于存储每个元素右边比它小的元素的数量。

    总结

    “Count of Smaller Numbers After Self” 是一个考察数据结构和算法的题目,通过归并排序或树状数组可以实现高效的解决方案。理解这些方法不仅有助于解决本题,还能提升对排序和区间查询问题的处理能力。