分类: 未分类

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

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

  • 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” 问题。希望这个解释对你有所帮助!如果有更多问题,欢迎继续提问。