LeetCode 面试热门 100 题(第 038 ~ 050题)

LeetCode 面试热门 100 题(第 026 ~ 037 题)

  • day013
    • 0002. 两数相加
    • 155. 最小栈
    • 20. 有效的括号
  • day014
    • 227. 基本计算器 II
    • 232. 用栈实现队列
    • 394. 字符串解码
  • day015
    • 32. 最长有效括号
    • 42. 接雨水
    • 225. 用队列实现栈
  • day016
    • 1. 两数之和
    • 15. 三数之和
    • 41. 缺失的第一个正数
    • 128. 最长连续序列

day013

0002. 两数相加

LeetCode链接: https://leetcode.cn/problems/add-two-numbers/description/

题目要求: 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

解题思路一: 通过创建一个哨兵节点作为结果链表的开始,可以简化边界条件的处理。在遍历结束后,我们返回哨兵节点的下一个节点,这是实际结果链表的头节点。

  1. 初始化:创建一个新的链表用于存储结果,同时创建一个变量 carry 用于存储进位。
  2. 遍历链表:同时遍历两个链表 l1 和 l2,直到两个链表都遍历完毕。
  3. 相加和进位:
    每一步将 l1 和 l2 当前节点的值相加,再加上进位 carry。
    如果相加的结果大于或等于10,则将进位设置为1,否则设置为0。
    将相加结果的个位数作为新节点添加到结果链表中。
  4. 处理链表长度不同的情况:
    如果 l1 或 l2 有一个还没有遍历完,继续遍历剩下的链表,并将值加上进位后添加到结果链表。
  5. 处理最后的进位:
    遍历完两个链表后,如果仍有进位(carry为1),则在结果链表的末尾添加一个值为1的新节点。
  6. 返回结果:返回新链表的头节点。
class ListNode(object):
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # 初始化结果链表的哨兵节点
        head = ListNode(0)
        # 当前节点指针
        current = head
        carry = 0

        # 遍历两个链表
        while l1 or l2:
            # 计算两个节点的值之和,加上之前的进位
            x = l1.val if l1 else 0
            y = l2.val if l2 else 0
            sum = x + y + carry

            # 更新进位和当前节点的值
            carry = sum // 10
            current.next = ListNode(sum % 10)
            current = current.next

            # 移动链表指针
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next

        # 检查最后是否有进位
        if carry > 0:
            current.next = ListNode(carry)

        # 返回结果链表的头部
        return head.next

155. 最小栈

LeetCode链接: https://leetcode.cn/problems/min-stack/description/

题目要求: 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

解题思路一: 要实现一个能够在常数时间内检索到最小元素的栈(MinStack),我们需要在栈内部保持一个额外的数据结构来跟踪最小值。这里有一个有效的方法是使用一个辅助栈(minStack),该栈顶总是存储当前栈的最小元素。
构来跟踪最小值。这里有一个有效的方法是使用一个辅助栈(minStack),该栈顶总是存储当前栈的最小元素。

  1. 初始化:我们需要两个栈,一个是主栈来存储所有元素(stack),另一个是辅助栈来存储每个状态下的最小值(minStack)。
  2. push 操作:
    将新元素推入主栈。
    如果辅助栈为空或新元素小于等于辅助栈的栈顶元素,也将新元素推入辅助栈。这确保了辅助栈的栈顶总是当前主栈的最小元素。
  3. pop 操作:
    从主栈中弹出栈顶元素。
    如果弹出的元素等于辅助栈的栈顶元素,也从辅助栈中弹出栈顶元素。这维护了辅助栈栈顶元素的正确性。
  4. top 操作:
    直接返回主栈的栈顶元素。
  5. getMin 操作:
    返回辅助栈的栈顶元素,即为当前主栈的最小元素。
class MinStack(object):

    def __init__(self):
        # 初始化两个栈,一个主栈,一个辅助栈
        self.stack = []
        self.minStack = []

    def push(self, val):
        # 将元素推入主栈
        self.stack.append(val)
        # 如果辅助栈为空,或当前元素小于等于辅助栈栈顶,也将其推入辅助栈
        if not self.minStack or val <= self.minStack[-1]:
            self.minStack.append(val)

    def pop(self):
        # 从主栈弹出元素
        val = self.stack.pop()
        # 如果弹出的元素是当前最小元素,也从辅助栈弹出
        if val == self.minStack[-1]:
            self.minStack.pop()

    def top(self):
        # 返回主栈的栈顶元素
        return self.stack[-1]

    def getMin(self):
        # 返回辅助栈的栈顶元素,即为当前最小元素
        return self.minStack[-1]

# 测试代码
minStack = MinStack()
minStack.push(-2)
minStack.push(0)
minStack.push(-3)
print(minStack.getMin()) # 返回 -3
minStack.pop()
print(minStack.top())    # 返回 0
print(minStack.getMin()) # 返回 -2

20. 有效的括号

LeetCode链接: https://leetcode.cn/problems/valid-parentheses/description/

题目要求: 给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

解题思路一: 为了判断给定的字符串 s 是否是一个有效的括号组合,我们可以使用一个栈来实现。栈是一种先进后出的数据结构,非常适合处理这种成对出现且需要匹配的字符问题。

  1. 初始化一个栈:创建一个空栈用于存储未匹配的左括号。
  2. 遍历字符串:遍历字符串 s 中的每一个字符。
  3. 处理左括号:对于每一个左括号 ‘(’,‘{’,或者 ‘[’,将其压入栈中。
  4. 处理右括号:对于每一个右括号 ‘)’,‘}’,或者 ‘]’,执行以下操作:
    如果栈为空,表示没有相应的左括号与之匹配,返回 False。
    否则,从栈中弹出一个元素,并检查它是否与当前右括号匹配:
    如果匹配(即 ‘(’ 与 ‘)’,‘{’ 与 ‘}’,‘[’ 与 ‘]’),则继续处理下一个字符。
    如果不匹配,返回 False。
  5. 检查栈的状态:遍历完字符串后,检查栈是否为空。
    如果栈为空,表示所有括号都被正确匹配,返回 True。
    如果栈不为空,表示有未匹配的左括号,返回 False。
class Solution(object):
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        # 初始化栈
        stack = []
        # 括号映射
        mapping = {")": "(", "}": "{", "]": "["}

        # 遍历字符串中的每个字符
        for char in s:
            if char in mapping:
                # 如果是右括号,则检查栈顶元素
                top_element = stack.pop() if stack else '#'
                if mapping[char] != top_element:
                    return False
            else:
                # 如果是左括号,压入栈中
                stack.append(char)

        # 如果栈为空,则所有括号正确匹配
        return not stack

# 测试代码
solution = Solution()
print(solution.isValid("()"))     # True
print(solution.isValid("()[]{}")) # True
print(solution.isValid("(]"))     # False

day014

227. 基本计算器 II

LeetCode链接: https://leetcode.cn/problems/basic-calculator-ii/

题目要求: 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

解题思路一: 通过维护一个栈来实现。对于加减法,我们可以直接将数值和符号入栈;对于乘除法,我们需要立即计算并更新栈顶元素。

  1. 初始化一个栈来存储整数,一个变量存储当前数值,一个变量存储上一个符号(默认为’+')。
  2. 遍历字符串,处理每个字符:
    如果是数字,则更新当前数值。
    如果是运算符或字符串末尾,根据上一个符号处理当前数值:
    如果是加号或减号,将当前数值(考虑符号)入栈。
    如果是乘号或除号,将栈顶元素与当前数值运算后的结果入栈。
    更新当前数值为0,更新上一个符号为当前符号。
  3. 遍历结束后,栈中存储的是所有未进行加法或减法运算的数值。将栈中的所有数值相加得到最终结果。
class Solution(object):
    def calculate(self, s):
        """
        :type s: str
        :rtype: int
        """
        # 如果字符串为空,则返回0
        if not s:
            return 0

        # 初始化栈来存储中间计算结果,当前数值和上一个运算符
        stack = []
        current_number = 0
        operation = '+'

        # 遍历字符串
        for i, char in enumerate(s):
            # 如果字符是数字,则更新当前数值
            if char.isdigit():
                current_number = current_number * 10 + int(char)

            # 如果字符是运算符或到达字符串的末尾
            if char in "+-*/" or i == len(s) - 1:
                # 根据上一个运算符处理当前数值
                if operation == '+':
                    stack.append(current_number)
                elif operation == '-':
                    stack.append(-current_number)
                elif operation == '*':
                    stack.append(stack.pop() * current_number)
                elif operation == '/':
                    # Python整数除法是向下取整,对负数除法进行特殊处理以满足题目要求
                    tmp = stack.pop()
                    if tmp // current_number < 0 and tmp % current_number != 0:
                        stack.append(tmp // current_number + 1)
                    else:
                        stack.append(tmp // current_number)
                # 重置当前数值,更新上一个运算符
                current_number = 0
                operation = char

        # 最终结果是栈中所有数值的和
        return sum(stack)

# 测试代码
solution = Solution()
print(solution.calculate("3+2*2")) # 7
print(solution.calculate(" 3/2 ")) # 1
print(solution.calculate(" 3+5 / 2 ")) # 5

232. 用栈实现队列

LeetCode链接: https://leetcode.cn/problems/implement-queue-using-stacks/

题目要求: 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:

你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

解题思路一:每个操作的均摊时间复杂度为 O(1)
为了使用两个栈实现先入先出队列的功能,我们可以使用一个栈(称为 stackIn)来处理入队操作,另一个栈(称为 stackOut)来处理出队操作。核心思想是,当需要出队时,如果 stackOut 为空,就将 stackIn 中的所有元素转移到 stackOut 中,这样 stackOut 的栈顶元素就是队列的首元素。

  1. 初始化:初始化两个栈,stackInstackOut

  2. push 操作

    • 将元素 x 推入 stackIn 栈。
  3. pop 操作

    • 如果 stackOut 为空,则将 stackIn 中的所有元素依次弹出并推入 stackOut
    • stackOut 弹出栈顶元素并返回。
  4. peek 操作

    • 如果 stackOut 为空,则将 stackIn 中的所有元素依次弹出并推入 stackOut
    • 返回 stackOut 的栈顶元素,但不弹出。
  5. empty 操作

    • 如果 stackInstackOut 都为空,则队列为空,返回 true;否则,返回 false
class MyQueue(object):

    def __init__(self):
        # 使用两个栈实现队列
        self.stackIn = []
        self.stackOut = []

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        # 入队操作,直接将元素压入 stackIn 栈
        self.stackIn.append(x)

    def pop(self):
        """
        :rtype: int
        """
        # 如果 stackOut 为空,则将 stackIn 中的所有元素转移到 stackOut
        if not self.stackOut:
            while self.stackIn:
                self.stackOut.append(self.stackIn.pop())
        # 出队操作,弹出 stackOut 的栈顶元素
        return self.stackOut.pop()

    def peek(self):
        """
        :rtype: int
        """
        # 同 pop 操作,但不弹出元素
        if not self.stackOut:
            while self.stackIn:
                self.stackOut.append(self.stackIn.pop())
        return self.stackOut[-1]

    def empty(self):
        """
        :rtype: bool
        """
        # 队列为空的条件是两个栈都为空
        return not self.stackIn and not self.stackOut

# 测试代码
queue = MyQueue()
queue.push(1)
queue.push(2)
print(queue.peek())  # 返回 1
print(queue.pop())   # 返回 1
print(queue.empty()) # 返回 False

这个方法有效地模拟了队列的行为,利用两个栈实现了先入先出的特性。需要注意的是,当 stackOut 为空时,我们才会从 stackIn 中转移元素。这是为了保证队列的顺序。

注意: 通过使用两个栈的方法,我们实际上可以达到每个操作的均摊时间复杂度为 O(1)。关键在于理解“均摊”时间复杂度的概念。

在这种实现中,大部分 pushpeek/pop 操作实际上是 O(1) 的。只有在 stackOut 为空时,peek/pop 操作需要将 stackIn 中的所有元素转移到 stackOut 中,这个操作的时间复杂度是 O(n)。但是,这种情况只会发生在 stackOut 完全为空的时候,也就是说,我们必须将 stackIn 中累积的所有元素都转移一次。

每个元素最多只会被转移一次(从 stackInstackOut),并且在被转移之后,每次 pop/peek 操作都是 O(1) 的。因此,即使某些单个操作可能花费较长时间,但平均到每个操作上,时间复杂度仍然是 O(1)。

换句话说,虽然某次特定的 pop/peek 操作可能需要 O(n) 的时间(当 stackOut 为空时),但这是因为它涵盖了多个先前 push 操作的工作。因此,当我们考虑所有操作的总成本时,每个操作的平均成本仍然是常数时间的。

这就是所谓的均摊分析(Amortized Analysis)。在均摊分析中,我们平摊了少数几个昂贵操作的成本到一系列便宜操作上,以此来保证总体上的效率。

总结来说,使用两个栈实现的队列,虽然在某些情况下单个操作可能需要 O(n) 的时间,但整体上看,执行 n 个操作的总时间复杂度仍然是 O(n),即每个操作的均摊时间复杂度是 O(1)。

394. 字符串解码

LeetCode链接: https://leetcode.cn/problems/decode-string/description/

题目要求: 给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

解题思路一:
要解码这样的字符串,我们可以使用栈来帮助处理嵌套的编码规则。我们可以使用两个栈:一个用于数字(即重复次数 k),另一个用于字符串(包括已解码的字符串和 [ 之前的字符)。遍历字符串时,我们可以根据字符的类型执行不同的操作:

  1. 数字字符:构建数字并压入数字栈。

  2. '[' 字符:将当前构建的数字和字符串分别压入各自的栈,并重置它们。

  3. ']' 字符:表示一个编码块的结束。此时,我们可以弹出数字栈的栈顶元素(即重复次数),并将字符串栈的栈顶元素(即 [ 之前的字符串)与当前构建的字符串重复相应的次数,然后合并。

  4. 字母字符:构建当前的字符串。

class Solution(object):
    def decodeString(self, s):
        """
        :type s: str
        :rtype: str
        """
        # 初始化栈和当前数字、字符串变量
        num_stack = []
        str_stack = []
        current_num = 0
        current_str = ''

        # 遍历每个字符
        for char in s:
            if char.isdigit():
                # 构建数字
                current_num = current_num * 10 + int(char)
            elif char == '[':
                # 压入数字栈和字符串栈,并重置
                num_stack.append(current_num)
                str_stack.append(current_str)
                current_num, current_str = 0, ''
            elif char == ']':
                # 弹出数字栈和字符串栈的栈顶元素,进行解码
                num = num_stack.pop()
                prev_str = str_stack.pop()
                current_str = prev_str + num * current_str
            else:
                # 构建当前字符串
                current_str += char

        return current_str

# 测试代码
solution = Solution()
print(solution.decodeString("3[a]2[bc]")) # "aaabcbc"
print(solution.decodeString("3[a2[c]]")) # "accaccacc"
print(solution.decodeString("2[abc]3[cd]ef")) # "abcabccdcdcdef"

在这个实现中,我们利用栈来处理嵌套的编码结构,并在遇到 ']' 时进行解码。通过这种方式,我们能够正确处理各种嵌套和重复的情况。

day015

32. 最长有效括号

LeetCode链接: https://leetcode.cn/problems/longest-valid-parentheses/description/

题目要求: 给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

解题思路一: 要找出最长有效(格式正确且连续)括号子串的长度,我们可以使用栈来帮助我们识别有效的括号对。栈可以帮助我们跟踪未匹配的括号,以及最长有效括号子串的起始位置。这个问题的关键在于如何处理匹配的括号,并在此基础上计算最长有效子串的长度。

  1. 初始化栈和结果变量:栈用于存储未匹配的括号的索引,结果变量用于存储最长有效括号子串的长度。

  2. 遍历字符串:对字符串 s 进行遍历。

  3. 处理左括号:如果当前字符是左括号 '(',将其索引压入栈。

  4. 处理右括号

    • 如果当前字符是右括号 ')',先弹出栈顶元素(如果栈非空)。
    • 如果弹出后栈为空,说明当前右括号与之前的左括号匹配,将当前右括号的索引压入栈。
    • 如果栈非空,更新结果变量为当前索引减去栈顶元素的值,这代表了当前找到的最长有效括号子串的长度。
  5. 返回结果:遍历结束后,返回结果变量。

class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        # 初始化栈和最长有效括号长度
        stack = [-1]
        max_length = 0

        # 遍历字符串中的每个字符
        for i, char in enumerate(s):
            if char == '(':
                # 如果是左括号,将索引压入栈
                stack.append(i)
            else:
                # 如果是右括号,先弹出栈顶元素
                stack.pop()
                if not stack:
                    # 如果栈为空,将当前索引压入栈
                    stack.append(i)
                else:
                    # 如果栈不为空,更新最长有效括号长度
                    max_length = max(max_length, i - stack[-1])

        return max_length

# 测试代码
solution = Solution()
print(solution.longestValidParentheses("(()")) # 2
print(solution.longestValidParentheses(")()())")) # 4
print(solution.longestValidParentheses("")) # 0

在这个实现中,我们使用了一个技巧:初始化栈时,先压入一个 -1。这样做的目的是为了方便计算有效括号子串的长度,当栈中只剩下这个 -1 时,表示之前的所有括号都已匹配完毕,当前右括号是下一个有效子串的开始位置。通过这种方法,我们可以在遍历字符串的同时计算出最长的有效括号子串长度。

42. 接雨水

LeetCode链接: https://leetcode.cn/problems/trapping-rain-water/description/

题目要求: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

解题思路一: 为了计算下雨之后能接多少雨水,我们可以利用栈来处理这个问题。核心思想是,对于每根柱子,找出其左侧和右侧的最高柱子,然后使用这两个柱子的高度来确定当前柱子能接多少水。

  1. 初始化栈和结果变量:栈用于存储柱子的索引,结果变量用于累计接水量。

  2. 遍历每个柱子:对 height 数组进行遍历。

  3. 处理栈中的柱子

    • 当前柱子高度大于栈顶柱子高度时,说明找到了一个可以接水的凹槽。
    • 弹出栈顶元素(凹槽底部),并计算凹槽宽度和高度,以此计算接水量。
    • 持续弹出栈中所有比当前柱子矮的柱子,并计算相应的接水量。
  4. 将当前柱子压入栈:处理完后,将当前柱子的索引压入栈中。

  5. 返回结果:遍历结束后,返回累计的接水量。

class Solution(object):
    def trap(self, height):
        """
        :type height: List[int]
        :rtype: int
        """
        # 初始化栈和接水量
        stack = []
        water = 0

        # 遍历每个柱子
        for i, h in enumerate(height):
            # 当前柱子高于栈顶柱子,处理凹槽接水
            while stack and height[stack[-1]] < h:
                # 弹出栈顶元素(凹槽底部)
                top = stack.pop()

                if not stack:
                    # 栈空,没有左侧边界,无法接水
                    break

                # 计算凹槽宽度和高度
                distance = i - stack[-1] - 1
                bounded_height = min(height[i], height[stack[-1]]) - height[top]

                # 累计接水量
                water += distance * bounded_height

            # 将当前柱子压入栈中
            stack.append(i)

        return water

# 测试代码
solution = Solution()
print(solution.trap([0,1,0,2,1,0,1,3,2,1,2,1])) # 6

在这个解法中,栈用于跟踪可能形成凹槽的柱子。通过计算每个凹槽的宽度和高度,我们可以确定每个凹槽能接多少雨水。这种方法可以有效地处理包括复杂形状在内的各种情况。

225. 用队列实现栈

LeetCode链接: https://leetcode.cn/problems/implement-stack-using-queues/

题目要求: 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

解题思路一: 要使用两个队列实现一个后入先出(LIFO)的栈,我们可以通过巧妙地排列队列中元素的顺序来模拟栈的行为。具体来说,我们可以使用两个队列:一个主队列(queue)和一个辅助队列(helper)。每次插入新元素时,我们首先将新元素插入到辅助队列,然后将主队列中的所有元素依次移至辅助队列,最后交换两个队列的角色。

  1. 初始化:创建两个队列,主队列和辅助队列。

  2. push 操作

    • 将新元素插入到辅助队列。
    • 将主队列中的所有元素依次移至辅助队列。
    • 交换主队列和辅助队列的角色。
  3. pop 操作

    • 从主队列中移除并返回队首元素(这是最后被压入栈的元素)。
  4. top 操作

    • 返回主队列的队首元素(不移除)。
  5. empty 操作

    • 检查主队列是否为空。
from collections import deque

class MyStack(object):

    def __init__(self):
        # 初始化两个队列
        self.queue = deque()
        self.helper = deque()

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        # 先将元素插入到辅助队列
        self.helper.append(x)
        # 然后将主队列中的所有元素依次移至辅助队列
        while self.queue:
            self.helper.append(self.queue.popleft())
        # 交换主队列和辅助队列的角色
        self.queue, self.helper = self.helper, self.queue

    def pop(self):
        """
        :rtype: int
        """
        # 移除并返回主队列的队首元素
        return self.queue.popleft()

    def top(self):
        """
        :rtype: int
        """
        # 返回主队列的队首元素
        return self.queue[0]

    def empty(self):
        """
        :rtype: bool
        """
        # 检查主队列是否为空
        return not self.queue

# 测试代码
myStack = MyStack()
myStack.push(1)
myStack.push(2)
print(myStack.top())    # 返回 2
print(myStack.pop())    # 返回 2
print(myStack.empty())  # 返回 False

在这个实现中,push 操作确保了最新压入的元素总是位于主队列的队首,这样就模拟了栈的后入先出特性。poptop 操作直接对主队列进行操作,而 empty 操作则检查主队列是否为空,从而实现了栈的所有基本操作。

day016

1. 两数之和

LeetCode链接: https://leetcode.cn/problems/two-sum/

题目要求: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

解题思路一:时间复杂度小于 O(n2)
为了解决这个问题,我们可以使用一个哈希表来存储数组中每个元素的索引。这样,我们可以在 O(1) 时间内查找目标值与当前元素的差值是否已经在数组中出现过。整体算法的时间复杂度是 O(n),因为我们只需要遍历数组一次。

  1. 初始化哈希表:创建一个哈希表用于存储数组元素值到其索引的映射。

  2. 遍历数组:遍历数组中的每个元素。

  3. 检查差值

    • 对于每个元素 nums[i],计算差值 complement = target - nums[i]
    • 检查 complement 是否已存在于哈希表中:
      • 如果存在,说明找到了一对数字,其和为 target。返回这两个数字的索引。
      • 如果不存在,将当前元素及其索引添加到哈希表中。
  4. 返回结果:如果遍历结束后仍未找到符合条件的两个数字,返回空数组(根据题目要求,这种情况不会发生)。

class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        # 初始化哈希表
        hashmap = {}

        # 遍历数组
        for i, num in enumerate(nums):
            # 计算差值
            complement = target - num
            # 检查差值是否存在于哈希表中
            if complement in hashmap:
                # 找到答案,返回两个数的索引
                return [hashmap[complement], i]
            # 将当前数及其索引添加到哈希表中
            hashmap[num] = i

        # 根据题目描述,这种情况不会发生
        return []

# 测试代码
solution = Solution()
print(solution.twoSum([2, 7, 11, 15], 9)) # 输出: [0, 1]

在这个实现中,我们利用哈希表来实现快速查找。对于数组中的每个元素,我们都在哈希表中查找是否有一个数与它相加等于目标值。如果找到了,我们返回这两个数的索引。这种方法在处理大数组时效率非常高。

15. 三数之和

LeetCode链接: https://leetcode.cn/problems/3sum/

题目要求: 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

解题思路一: 要找出数组中所有和为 0 的不重复三元组,一种有效的方法是使用双指针。首先对数组进行排序,然后固定一个数,用两个指针在剩下的数组中移动寻找其他两个数,使得它们的和为 0。

  1. 排序:首先对数组进行排序。

  2. 初始化结果列表:创建一个空列表用于存储所有满足条件的三元组。

  3. 遍历数组

    • 对于每个元素 nums[i],如果它与前一个元素相同,则跳过以避免重复。
    • 初始化两个指针:left 指向 i + 1right 指向数组的最后一个元素。
    • left < right 时,计算 sum = nums[i] + nums[left] + nums[right]
      • 如果 sum == 0,找到了一个有效的三元组,将其添加到结果列表中。然后移动 leftright 指针,并跳过所有重复的元素。
      • 如果 sum < 0,增加 left 指针。
      • 如果 sum > 0,减小 right 指针。
  4. 返回结果:返回存储所有三元组的列表。

class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        result = []

        for i in range(len(nums)):
            # 避免重复
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            left, right = i + 1, len(nums) - 1
            while left < right:
                sum = nums[i] + nums[left] + nums[right]
                if sum == 0:
                    result.append([nums[i], nums[left], nums[right]])
                    left += 1
                    right -= 1
                    # 跳过重复元素
                    while left < right and nums[left] == nums[left - 1]:
                        left += 1
                    while left < right and nums[right] == nums[right + 1]:
                        right -= 1
                elif sum < 0:
                    left += 1
                else:
                    right -= 1

        return result

# 测试代码
solution = Solution()
print(solution.threeSum([-1, 0, 1, 2, -1, -4])) # 输出: [[-1, -1, 2], [-1, 0, 1]]

在这个实现中,我们首先对数组进行排序,这是使用双指针技术的前提。通过固定一个数,然后在剩余的数组中调整 leftright 指针,我们可以找出所有不重复的三元组。注意在找到一个有效的三元组后,我们需要跳过所有重复的元素,以避免在结果列表中出现重复的三元组。

41. 缺失的第一个正数

LeetCode链接: https://leetcode.cn/problems/first-missing-positive/

题目要求: 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

解题思路一: 要在 O(n) 时间复杂度内找出未排序数组中没有出现的最小正整数,同时只使用常数级额外空间,我们可以通过原地修改数组来实现。基本思想是将数组视为哈希表,将每个正整数 n 放置到数组的第 n - 1 个位置上。通过这种方式,我们可以在 O(n) 时间内将数组“排序”,使得每个正整数出现在其对应的索引位置上。

  1. 遍历数组:遍历数组中的每个元素。

  2. 放置正整数到对应位置

    • 对于每个元素 nums[i],如果它是正整数且不超过数组长度,且它不在正确的位置上(即 nums[i] 不等于 i + 1),则将其与 nums[nums[i] - 1] 交换。重复这一过程,直到无法交换为止。
  3. 找出第一个缺失的正整数

    • 再次遍历数组,找到第一个位置上不正确的元素,其索引 i 加 1 即是缺失的最小正整数。
    • 如果数组中所有元素都在正确的位置上,则缺失的最小正整数是 数组长度 + 1
class Solution(object):
    def firstMissingPositive(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)

        for i in range(n):
            # 将 nums[i] 放置到正确的位置上
            while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
                # 交换 nums[i] 和 nums[nums[i] - 1]
                nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]

        # 找出第一个位置不正确的元素
        for i in range(n):
            if nums[i] != i + 1:
                return i + 1

        # 所有元素都在正确的位置上
        return n + 1

# 测试代码
solution = Solution()
print(solution.firstMissingPositive([1,2,0])) # 输出: 3
print(solution.firstMissingPositive([3,4,-1,1])) # 输出: 2
print(solution.firstMissingPositive([7,8,9,11,12])) # 输出: 1

在这个实现中,我们首先通过交换操作将数组中的正整数放置到它们应该出现的位置。然后,我们再次遍历数组,找出第一个位置不正确的元素,其索引加 1 就是缺失的最小正整数。如果数组中所有元素都在正确的位置上,则说明缺失的最小正整数是数组长度加 1。这种方法利用了数组本身作为哈希表,避免了使用额外的空间。

128. 最长连续序列

LeetCode链接: https://leetcode.cn/problems/longest-consecutive-sequence/description/

题目要求: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

解题思路一: 为了找出数字连续的最长序列的长度,并且要求时间复杂度为 O(n),我们可以使用哈希集合来存储数组中的所有元素,然后遍历数组,针对每个元素尝试扩展其连续序列。

  1. 创建哈希集合:首先将所有元素放入哈希集合中,这样我们可以在 O(1) 的时间内判断一个元素是否存在于数组中。

  2. 初始化最长序列长度:创建一个变量来存储最长连续序列的长度。

  3. 遍历数组

    • 对于每个元素 num,只有当 num-1 不在哈希集合中时,才开始考虑以 num 为起点的连续序列(这样可以避免重复处理)。
    • 检查 num+1num+2num+3… 是否在哈希集合中,每找到一个就将序列长度加一。
    • 更新最长连续序列的长度。
  4. 返回结果:返回找到的最长连续序列的长度。

class Solution(object):
    def longestConsecutive(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 创建哈希集合存储所有元素
        num_set = set(nums)
        longest_streak = 0

        # 遍历数组中的每个元素
        for num in nums:
            # 只有当 num-1 不在集合中时,才考虑以 num 为起点的序列
            if num - 1 not in num_set:
                current_num = num
                current_streak = 1

                # 扩展当前序列
                while current_num + 1 in num_set:
                    current_num += 1
                    current_streak += 1

                # 更新最长序列长度
                longest_streak = max(longest_streak, current_streak)

        return longest_streak

# 测试代码
solution = Solution()
print(solution.longestConsecutive([100, 4, 200, 1, 3, 2])) # 输出: 4

在这个实现中,我们首先创建了一个哈希集合来存储所有数组元素,这使得我们能够快速判断某个元素是否存在。接着,对于数组中的每个元素,我们检查以它为起点的连续序列,并在找到更长的序列时更新最长序列长度。由于每个元素最多只被访问两次(一次在遍历数组时,一次在扩展序列时),所以总体时间复杂度为 O(n)。

你可能感兴趣的:(leetcode,面试,算法)