作者: admin2025

  • Find Minimum in Rotated Sorted Array,LeetCode,153

    题目描述:

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。

    例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2]

    请找出其中最小的元素。

    你可以假设数组中不存在重复元素。

    示例 1:

    输入: [3,4,5,1,2] 输出: 1

    示例 2:

    输入: [4,5,6,7,0,1,2] 输出: 0

    解题思路:

    这个问题可以通过二分查找来解决。由于数组是旋转过的有序数组,我们可以利用二分查找的特性来找到最小值。

    1. 初始化:设置两个指针 leftright,分别指向数组的起始和末尾位置。
    2. 二分查找
      • 计算中间位置 mid
      • 比较 mid 位置的值与 right 位置的值:
        • 如果 nums[mid] > nums[right],说明最小值在 mid 的右侧,调整 leftmid + 1
        • 否则,说明最小值在 mid 的左侧或者就是 mid,调整 rightmid
    3. 终止条件:当 leftright 相等时,left(或 right)指向的就是最小值。

    代码实现:

    def findMin(nums): left, right = 0, len(nums) - 1

    while left < right:
        mid = (left + right) // 2
    
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    
    return nums[left]

    示例

    print(findMin([3,4,5,1,2])) # 输出: 1 print(findMin([4,5,6,7,0,1,2])) # 输出: 0

    详细解释:

    1. 初始化
      • left = 0right = len(nums) - 1
    2. 二分查找过程
      • 计算中间位置 mid
      • 比较 nums[mid]nums[right]
        • 如果 nums[mid] > nums[right],说明最小值在 mid 的右侧,调整 leftmid + 1
        • 否则,说明最小值在 mid 的左侧或者就是 mid,调整 rightmid
    3. 终止条件
      • leftright 相等时,循环结束,此时 left(或 right)指向的就是最小值。

    复杂度分析:

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

    通过这种方法,我们可以在对数时间内高效地找到旋转有序数组中的最小值。

  • Reverse Words in a String,LeetCode,151

    好的,我们来详细讲解一下 LeetCode 上的第 151 题:Reverse Words in a String。

    题目描述

    给定一个字符串,逐个翻转字符串中的每个单词。

    示例 1:

    输入: "the sky is blue" 输出: "blue is sky the"

    示例 2:

    输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

    示例 3:

    输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

    说明:

    • 无空格字符构成一个单词。
    • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
    • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

    解题思路

    1. 去除首尾空格:首先去除字符串首尾的空格,以便后续处理。
    2. 分割字符串:使用空格将字符串分割成单词列表。
    3. 反转列表:将单词列表反转。
    4. 拼接字符串:将反转后的单词列表拼接成一个字符串,单词间用一个空格隔开。

    代码实现

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

    class Solution: def reverseWords(self, s: str) -> str:

    Step 1: 去除首尾空格

        s = s.strip()
    
        # Step 2: 分割字符串
        words = s.split()
    
        # Step 3: 反转列表
        words.reverse()
    
        # Step 4: 拼接字符串
        return ' '.join(words)

    详细解释

    1. 去除首尾空格s = s.strip() 这一步使用 strip() 方法去除字符串首尾的空格。
    2. 分割字符串words = s.split() 使用 split() 方法按空格分割字符串,默认情况下 split() 会自动处理多个连续空格,只返回有效的单词。
    3. 反转列表words.reverse() 使用 reverse() 方法将单词列表反转。
    4. 拼接字符串return ' '.join(words) 使用 ' '.join(words) 将反转后的单词列表拼接成一个字符串,单词间用一个空格隔开。

    复杂度分析

    • 时间复杂度:O(n),其中 n 是字符串的长度。去除首尾空格、分割字符串和拼接字符串的操作都是线性的。
    • 空间复杂度:O(n),用于存储分割后的单词列表。

    其他语言实现

    如果你需要其他语言的实现,比如 Java 或 C++,可以参考以下代码:

    Java 实现

    public class Solution { public String reverseWords(String s) { // Step 1: 去除首尾空格 s = s.trim();

        // Step 2: 分割字符串
        String[] words = s.split("\\s+");
    
        // Step 3: 反转列表
        StringBuilder result = new StringBuilder();
        for (int i = words.length - 1; i >= 0; i--) {
            result.append(words[i]);
            if (i > 0) {
                result.append(" ");
            }
        }
    
        // Step 4: 返回结果
        return result.toString();
    }

    }

    C++ 实现

    #include #include #include #include using namespace std;

    class Solution { public: string reverseWords(string s) { // Step 1: 去除首尾空格 trim(s);

        // Step 2: 分割字符串
        istringstream iss(s);
        vector words;
        string word;
        while (iss >> word) {
            words.push_back(word);
        }
    
        // Step 3: 反转列表
        reverse(words.begin(), words.end());
    
        // Step 4: 拼接字符串
        string result;
        for (int i = 0; i < words.size(); i++) {
            result += words[i];
            if (i < words.size() - 1) {
                result += " ";
            }
        }
    
        return result;
    }

    private: void trim(string& s) { int start = 0, end = s.size() - 1; while (start <= end && s[start] == ' ') start++; while (end >= start && s[end] == ' ') end--; s = s.substr(start, end - start + 1); } };

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

  • Maximum Product Subarray,LeetCode,152

    题目描述

    给定一个整数数组 nums,找出一个连续子数组(至少包含一个数字),使得该子数组的乘积最大。

    示例

    输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 的乘积最大为 6。

    解题思路

    这道题可以使用动态规划来解决。由于数组中可能包含负数,我们需要维护两个变量:max_productmin_product,分别表示当前的最大乘积和最小乘积。因为负数乘以负数会变成正数,所以当前的最小乘积可能会成为未来的最大乘积。

    具体步骤

    1. 初始化 max_productmin_product 为数组的第一个元素。
    2. 遍历数组,对于每个元素 num
      • 如果 num 是负数,交换 max_productmin_product
      • 更新 max_productmax(num, max_product * num)
      • 更新 min_productmin(num, min_product * num)
      • 更新全局最大乘积 resultmax(result, max_product)
    3. 返回 result

    代码实现

    def maxProduct(nums): if not nums: return 0

    max_product = min_product = result = nums[0]
    
    for num in nums[1:]:
        if num < 0:
            max_product, min_product = min_product, max_product
    
        max_product = max(num, max_product * num)
        min_product = min(num, min_product * num)
    
        result = max(result, max_product)
    
    return result

    示例

    nums = [2, 3, -2, 4] print(maxProduct(nums)) # 输出: 6

    解释

    1. 初始化时,max_productmin_productresult 都设置为数组的第一个元素 2
    2. 遍历数组:
      • 对于 3
        • max_product 更新为 max(3, 2 * 3) = 6
        • min_product 更新为 min(3, 2 * 3) = 3
        • result 更新为 max(2, 6) = 6
      • 对于 -2
        • 由于 -2 是负数,交换 max_productmin_product,得到 max_product = 3, min_product = 6
        • max_product 更新为 max(-2, 3 * -2) = -2
        • min_product 更新为 min(-2, 6 * -2) = -12
        • result 保持为 6(因为 -2 小于 6
      • 对于 4
        • max_product 更新为 max(4, -2 * 4) = 4
        • min_product 更新为 min(4, -12 * 4) = -48
        • result 保持为 6(因为 4 小于 6

    最终返回的 result6,即子数组 [2, 3] 的乘积。

    复杂度分析

    • 时间复杂度:O(n),其中 n 是数组的长度,因为我们只需要遍历一次数组。
    • 空间复杂度:O(1),因为我们只需要常数级别的额外空间来存储变量。

    这种方法有效地处理了数组中包含负数的情况,确保能够找到最大的子数组乘积。

  • LRU Cache,LeetCode,146

    LRU Cache(最近最少使用缓存)是计算机科学中的一种缓存淘汰策略,用于在内存有限的情况下,自动淘汰最久未使用的数据,以便为新的数据腾出空间。LeetCode上的第146题就是关于实现一个LRU缓存机制的问题。

    题目描述

    LeetCode 146题的描述如下:

    运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put
    • 获取数据 get(key) – 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
    • 写入数据 put(key, value) – 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

    要求

    • 你是否可以在 O(1) 时间复杂度内完成这两种操作?

    示例

    LRUCache cache = new LRUCache( 2 / 缓存容量 / );

    cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 该操作会使得关键字 2 作废 cache.get(2); // 返回 -1 (未找到) cache.put(4, 4); // 该操作会使得关键字 1 作废 cache.get(1); // 返回 -1 (未找到) cache.get(3); // 返回 3 cache.get(4); // 返回 4

    解题思路

    为了实现 O(1) 时间复杂度的 getput 操作,我们需要使用一种高效的数据结构。哈希表可以提供 O(1) 的查找时间,但无法维护元素的顺序;双向链表可以维护元素的顺序,但查找时间为 O(n)。因此,我们可以结合这两种数据结构,使用 哈希表 + 双向链表 的方式来实现 LRU 缓存。

    具体实现如下:

    1. 哈希表:用于存储键到双向链表节点的映射,以便快速查找节点。
    2. 双向链表:用于维护节点的使用顺序,最近使用的节点放在链表头部,最久未使用的节点放在链表尾部。

    代码实现(Python)

    class ListNode: def init(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None

    class LRUCache: def init(self, capacity: int): self.capacity = capacity self.cache = {} self.head = ListNode() self.tail = ListNode() self.head.next = self.tail self.tail.prev = self.head

    def get(self, key: int) -> int:
        if key in self.cache:
            node = self.cache[key]
            self._move_to_head(node)
            return node.value
        return -1
    
    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)
        else:
            if len(self.cache) >= self.capacity:
                self._remove_tail()
            new_node = ListNode(key, value)
            self.cache[key] = new_node
            self._add_to_head(new_node)
    
    def _add_to_head(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node):
        self._remove_node(node)
        self._add_to_head(node)
    
    def _remove_tail(self):
        node = self.tail.prev
        self._remove_node(node)
        del self.cache[node.key]

    示例使用

    cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) print(cache.get(1)) # 返回 1 cache.put(3, 3) # 该操作会使得关键字 2 作废 print(cache.get(2)) # 返回 -1 (未找到) cache.put(4, 4) # 该操作会使得关键字 1 作废 print(cache.get(1)) # 返回 -1 (未找到) print(cache.get(3)) # 返回 3 print(cache.get(4)) # 返回 4

    解释

    1. ListNode 类:定义双向链表的节点,包含键、值以及前驱和后继指针。
    2. LRUCache 类
      • __init__:初始化缓存容量、哈希表、双向链表的头尾节点。
      • get:查找键对应的节点,如果存在则移动到链表头部,返回值;否则返回 -1。
      • put:如果键存在则更新值并移动到链表头部;如果键不存在则添加新节点到链表头部,并检查是否需要淘汰最久未使用的节点。
      • _add_to_head_remove_node_move_to_head_remove_tail:辅助函数,用于操作双向链表。

    通过这种方式,我们确保了 getput 操作的时间复杂度均为 O(1)。

  • Surrounded Regions,LeetCode,130

    题目描述(LeetCode 130: Surrounded Regions)

    给定一个二维字符数组 board,包含 'X''O',捕获所有被 'X' 围绕的区域,并将这些区域中的 'O' 全部变为 'X'

    一个区域被围绕是指该区域中所有 'O' 都与 board 的边界不相邻。

    示例

    X X X X X O O X X X O X X O X X

    运行你的函数后,board 应该被修改为:

    X X X X X X X X X X X X X O X X

    解题思路

    1. 深度优先搜索(DFS)
      • board 的边界开始,找到所有与边界相连的 'O',将这些 'O' 标记为 '-'(或其他非 'X''O' 的字符)。
      • 遍历整个 board,将所有未被标记为 '-''O' 变为 'X',将所有 '-' 变回 'O'
    2. 广度优先搜索(BFS)
      • 类似于 DFS,但从边界上的 'O' 开始,使用队列进行广度优先搜索,标记所有与边界相连的 'O'
      • 最后同样遍历 board 进行替换。
    3. 并查集(Union-Find)
      • 使用并查集将所有边界上的 'O' 与一个虚拟节点连接。
      • 遍历 board,将所有 'O' 进行合并。
      • 最后检查每个 'O' 是否与虚拟节点相连,不相连的变为 'X'

    详细代码实现(DFS 方法)

    class Solution: def solve(self, board: List[List[str]]) -> None: if not board or not board[0]: return

        rows, cols = len(board), len(board[0])
    
        def dfs(r, c):
            if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != 'O':
                return
            board[r][c] = '-'
            dfs(r + 1, c)
            dfs(r - 1, c)
            dfs(r, c + 1)
            dfs(r, c - 1)
    
        # 遍历边界上的 'O' 并进行 DFS
        for r in range(rows):
            dfs(r, 0)
            dfs(r, cols - 1)
        for c in range(cols):
            dfs(0, c)
            dfs(rows - 1, c)
    
        # 替换字符
        for r in range(rows):
            for c in range(cols):
                if board[r][c] == '-':
                    board[r][c] = 'O'
                else:
                    board[r][c] = 'X'

    解释

    1. 边界检查
      • 首先检查 board 是否为空或没有元素。
    2. DFS 函数
      • dfs 函数用于深度优先搜索,将边界上的 'O' 及其相连的 'O' 标记为 '-'
    3. 遍历边界
      • 遍历 board 的四条边界,对每个边界上的 'O' 调用 dfs 函数。
    4. 替换字符
      • 最后遍历整个 board,将所有 '-' 变回 'O',其余的 'O' 变为 'X'

    复杂度分析

    • 时间复杂度:O(rows * cols),需要遍历整个 board
    • 空间复杂度:O(rows * cols),最坏情况下递归栈的深度。

    这种方法有效地利用了深度优先搜索的特性,从边界出发,逐步标记所有不被 'X' 围绕的 'O',最终完成题目要求。

  • Word Ladder,LeetCode,127

    题目描述:

    Word Ladder 是 LeetCode 上的第 127 题,题目要求我们找到一个从起始单词到结束单词的最短转换序列的长度,每次转换只能改变一个字母,并且转换后的单词必须在给定的单词列表中。

    题目链接: LeetCode 127. Word Ladder

    解题思路:

    这个问题可以使用广度优先搜索(BFS)来解决。BFS 是一种逐层搜索的算法,非常适合用来求解最短路径问题。

    具体步骤如下:

    1. 初始化:
      • 创建一个队列(Queue),将起始单词加入队列。
      • 创建一个集合(Set),存储所有单词,方便快速查找。
      • 记录转换的步数,初始为 1。
    2. BFS 遍历:
      • 当队列不为空时,进行以下操作:
        • 获取当前队列的长度(即当前层的单词数量)。
        • 遍历当前层的所有单词:
        • 如果当前单词是结束单词,返回步数。
        • 否则,尝试改变当前单词的每一个字母,生成新的单词:
          • 如果新单词在单词集合中,将其加入队列,并从集合中移除,以避免重复访问。
        • 当前层遍历结束后,步数加 1。
    3. 返回结果:
      • 如果队列为空且未找到结束单词,返回 0。

    代码实现(Python):

    from collections import deque

    def ladderLength(beginWord, endWord, wordList): wordSet = set(wordList) if endWord not in wordSet: return 0

    queue = deque([beginWord])
    steps = 1
    
    while queue:
        for _ in range(len(queue)):
            current_word = queue.popleft()
            if current_word == endWord:
                return steps
    
            for i in range(len(current_word)):
                for c in 'abcdefghijklmnopqrstuvwxyz':
                    next_word = current_word[:i] + c + current_word[i+1:]
                    if next_word in wordSet:
                        queue.append(next_word)
                        wordSet.remove(next_word)
        steps += 1
    
    return 0

    示例用法

    beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log","cog"] print(ladderLength(beginWord, endWord, wordList)) # 输出: 5

    复杂度分析:

    • 时间复杂度: O(M^2 N),其中 M 是单词的长度,N 是单词列表的长度。每次生成新单词需要 O(M) 时间,总共需要生成 O(M N) 个新单词。
    • 空间复杂度: O(N),使用了集合和队列来存储单词。

    优化建议:

    • 预处理单词列表: 可以预先构建一个字典,键是单词的通用状态(如 “hit” -> “it”, “ht”, “hi*”),值是具有相同通用状态的单词列表。这样可以更快地找到下一个可能的单词。
    • 双向 BFS: 从起始单词和结束单词同时进行 BFS,当两个搜索相遇时,即为最短路径。

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

  • Minimum Window Substring,LeetCode,76

    题目描述

    给定一个字符串 S 和一个字符串 T,请在字符串 S 中找出包含 T 中所有字符的最小子串。

    示例

    输入: S = "ADOBECODEBANC", T = "ABC" 输出: "BANC"

    说明

    • 如果 S 中不存这样的子串,则返回空字符串 ""
    • 如果存在多个满足条件的最小子串,返回任意一个即可。

    解题思路

    这个问题可以通过滑动窗口(Sliding Window)的方法来解决。滑动窗口是一种常用的双指针技巧,适用于一维数组或字符串等线性结构。

    具体步骤

    1. 初始化变量
      • 使用两个指针 leftright 表示滑动窗口的左右边界,初始都指向 S 的起始位置。
      • 使用一个字典 need 来记录字符串 T 中每个字符的需求量。
      • 使用一个字典 window 来记录当前窗口中每个字符的数量。
      • 使用一个变量 valid 来记录窗口中满足 T 中字符需求的字符数量。
    2. 扩展右边界
      • 移动 right 指针,将 S[right] 加入窗口,并更新 windowvalid
    3. 收缩左边界
      • 当窗口中的字符已经满足 T 的需求时,尝试收缩窗口,移动 left 指针,更新 windowvalid,并记录当前窗口的长度和起始位置。
    4. 更新最小窗口
      • 在收缩左边界的过程中,不断更新最小窗口的长度和起始位置。
    5. 返回结果
      • 根据记录的最小窗口长度和起始位置,返回最小窗口子串。

    代码实现

    def minWindow(s: str, t: str) -> str: need = {} window = {} for char in t: need[char] = need.get(char, 0) + 1

    left, right = 0, 0
    valid = 0
    start, length = 0, float('inf')
    
    while right < len(s):
        # 扩展右边界
        c = s[right]
        right += 1
        if c in need:
            window[c] = window.get(c, 0) + 1
            if window[c] == need[c]:
                valid += 1
    
        # 收缩左边界
        while valid == len(need):
            if right - left < length:
                start = left
                length = right - left
    
            d = s[left]
            left += 1
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1
    
    return "" if length == float('inf') else s[start:start + length]

    示例

    s = "ADOBECODEBANC" t = "ABC" print(minWindow(s, t)) # 输出: "BANC"

    复杂度分析

    • 时间复杂度:O(N),其中 N 是字符串 S 的长度。每个字符最多被访问两次(一次被 right 指针访问,一次被 left 指针访问)。
    • 空间复杂度:O(|T|),其中 |T| 是字符串 T 的长度。主要是用于存储 needwindow 字典。

    总结

    滑动窗口是一种高效解决字符串子串问题的方法,通过双指针的移动来维护一个动态的窗口,并在窗口满足条件时进行收缩,从而找到满足条件的最小子串。这个方法在很多类似的问题中都有广泛的应用。

  • Edit Distance,LeetCode,72

    题目描述:

    LeetCode 72题,编辑距离(Edit Distance),是一道经典的动态规划问题。题目要求计算将一个字符串转换为另一个字符串所需的最少编辑操作次数。允许的编辑操作包括:

    1. 插入一个字符
    2. 删除一个字符
    3. 替换一个字符

    题目示例:

    输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')

    解题思路:

    我们可以使用动态规划(DP)来解决这个问题。定义一个二维数组 dp,其中 dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少编辑操作次数。

    状态转移方程:

    1. word1[i-1] == word2[j-1],不需要进行操作,dp[i][j] = dp[i-1][j-1]
    2. word1[i-1] != word2[j-1],需要进行操作,取以下三种操作的最小值:
      • 插入操作:dp[i][j] = dp[i][j-1] + 1
      • 删除操作:dp[i][j] = dp[i-1][j] + 1
      • 替换操作:dp[i][j] = dp[i-1][j-1] + 1

    初始化:

    • dp[0][j] 表示将空字符串转换为 word2 的前 j 个字符,需要 j 次插入操作。
    • dp[i][0] 表示将 word1 的前 i 个字符转换为空字符串,需要 i 次删除操作。

    代码实现:

    def minDistance(word1, word2): m, n = len(word1), len(word2) dp = [[0] * (n + 1) for _ in range(m + 1)]

    # 初始化
    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 word1[i - 1] == word2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
            else:
                dp[i][j] = min(dp[i - 1][j] + 1,  # 删除
                               dp[i][j - 1] + 1,  # 插入
                               dp[i - 1][j - 1] + 1)  # 替换
    
    return dp[m][n]

    示例

    word1 = "horse" word2 = "ros" print(minDistance(word1, word2)) # 输出:3

    复杂度分析:

    • 时间复杂度: O(m * n),其中 mn 分别是 word1word2 的长度。
    • 空间复杂度: O(m * n),用于存储 dp 表。

    优化空间复杂度:

    可以通过滚动数组的方式将空间复杂度优化到 O(min(m, n)),具体实现时只需使用一维数组即可。

    总结:

    编辑距离问题是一个典型的动态规划问题,通过定义合适的状态和状态转移方程,可以有效地求解。该问题在字符串处理、自然语言处理等领域有广泛的应用。掌握该问题的解法有助于理解和应用动态规划思想。

  • Climbing Stairs,LeetCode,70

    题目描述:

    你正在爬楼梯。需要 n 阶你才能到达楼顶。

    每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

    示例 1:

    输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。

    1. 1 阶 + 1 阶
    2. 2 阶

    示例 2:

    输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。

    1. 1 阶 + 1 阶 + 1 阶
    2. 1 阶 + 2 阶
    3. 2 阶 + 1 阶

    解题思路:

    这个问题实际上是一个斐波那契数列问题。假设 f(n) 表示爬到第 n 阶的方法数,那么有以下递推关系:

    • f(1) = 1,只有一种方法,即爬 1 阶。
    • f(2) = 2,有两种方法,即爬两个 1 阶或者一次爬 2 阶。
    • 对于 n > 2,可以从第 n-1 阶爬 1 阶到达第 n 阶,或者从第 n-2 阶爬 2 阶到达第 n 阶。因此,f(n) = f(n-1) + f(n-2)

    基于这个递推关系,可以使用动态规划的方法来求解。

    动态规划解法:

    1. 定义状态:dp[i] 表示爬到第 i 阶的方法数。
    2. 状态转移方程: dp[i] = dp[i-1] + dp[i-2]
    3. 初始状态: dp[1] = 1dp[2] = 2
    4. 返回结果: dp[n]

    代码实现(Python):

    class Solution: def climbStairs(self, n: int) -> int: if n == 1: return 1 if n == 2: return 2

        dp = [0] * (n + 1)
        dp[1] = 1
        dp[2] = 2
    
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
    
        return dp[n]

    优化空间复杂度:

    由于每次状态转移只依赖于前两个状态,可以使用两个变量来存储这两个状态,从而将空间复杂度从 O(n) 优化到 O(1)。

    优化后的代码(Python):

    class Solution: def climbStairs(self, n: int) -> int: if n == 1: return 1 if n == 2: return 2

        a, b = 1, 2
        for i in range(3, n + 1):
            a, b = b, a + b
    
        return b

    总结:

    这个问题通过动态规划的方法可以高效解决。关键在于识别出问题的递推关系,并利用动态规划的思想进行状态转移。优化空间复杂度后,代码更加简洁且效率更高。

    希望这个详细的解答对你有所帮助!如果有其他问题,欢迎继续提问。

  • Maximum Subarray,LeetCode,53

    题目描述:

    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    示例:

    输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

    解题思路:

    这个问题可以使用动态规划来解决,经典的算法是Kadane算法。其核心思想是:

    1. 初始化两个变量,max_currentmax_globalmax_current 表示以当前元素结尾的最大子数组和,max_global 表示全局的最大子数组和。
    2. 遍历数组,对于每一个元素:
      • 更新 max_current 为当前元素和 max_current + 当前元素 中的较大值。
      • 更新 max_globalmax_globalmax_current 中的较大值。
    3. 最终,max_global 就是最大子数组的和。

    代码实现(Python):

    def maxSubArray(nums): if not nums: return 0

    max_current = max_global = nums[0]
    
    for i in range(1, len(nums)):
        max_current = max(nums[i], max_current + nums[i])
        max_global = max(max_global, max_current)
    
    return max_global

    示例

    nums = [-2,1,-3,4,-1,2,1,-5,4] print(maxSubArray(nums)) # 输出: 6

    详细解释:

    1. 初始化:
      • max_currentmax_global 都初始化为数组的第一个元素 nums[0]。这是因为子数组至少包含一个元素,所以初始的最大子数组和就是第一个元素本身。
    2. 遍历数组:
      • 从数组的第二个元素开始遍历(即索引为1的元素)。
      • 对于每一个元素 nums[i],计算以 nums[i] 结尾的最大子数组和 max_current。这个值是当前元素 nums[i]max_current + nums[i] 中的较大值。为什么要这样计算?因为如果 max_current 是负数,加上当前元素可能会使和变小,所以不如从当前元素重新开始计算子数组和。
      • 更新全局最大子数组和 max_global,这个值是 max_globalmax_current 中的较大值。
    3. 返回结果:
      • 遍历完数组后,max_global 就是最大子数组的和。

    复杂度分析:

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

    扩展:

    • 如果数组中所有元素都是负数,Kadane算法仍然适用,最终返回的是最大的负数。
    • 该算法可以扩展到二维数组,寻找最大子矩阵和的问题。

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