代码随想录_代码集

一.数组

一.二分查找

def er_fen_cha_zhao(nums,target):
    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = (left + right)//2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1


nums = input("输入一个数组:").split(",")
nums = list(map(int,nums))
#输入要找的值
target = int(input("请输入要找的值: "))

result = er_fen_cha_zhao(nums, target)
print(result)

1.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

  • 时间复杂度: 在最坏情况下,二分查找算法的时间复杂度为 O(log n),其中 n 是数组的长度。每次迭代都将搜索范围缩小一半,因此时间复杂度是对数级别的。
  • 空间复杂度: 这段代码的空间复杂度为 O(1),即常数级别的空间。除了输入的数组和目标值外,不需要额外的空间来存储数据。
def suo_yin(nums, target):
    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = (right + left) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    nums.insert(left, target)   #insert:插入函数
    return left

nums = input("请输入一串数组 ").split(",")
nums = list(map(int, nums))
target = int(input("要找的数"))
result = suo_yin(nums, target)
print(result)
print(nums)

2.给你一个非负整数 x ,计算并返回 x算术平方根

def mySqrt(x):
    """
    :type x: int
    :rtype: int
    """
    if x == 0:
        return 0

    left, right = 1, x

    while left <= right:
        mid = (left + right) // 2
        if mid * mid == x:
            return mid
        elif mid * mid > x:
            right = mid - 1
        else:
            left = mid + 1

    return right


x = int(input("请输入一个数"))
result = mySqrt(x)
print("%d的算术平方根为%d"%(x,result))
def isPerfectSquare(num):
    if num == 0:
        return False

    left, right = 0, num

    while left <= right:
        mid = (left + right) // 2
        if mid * mid == num:
            return True
        elif mid * mid > num:
            right = mid - 1
        else:
            left = mid + 1

    return False


num = int(input("请输入一个数:"))
result = isPerfectSquare(num)
print(result)

这段代码是用来判断一个数是否为完全平方数的。它使用了二分查找的方法来进行判断,时间复杂度为 O(logn),空间复杂度为 O(1)。
这里我们来具体分析一下为什么该方法的时间复杂度为 O(logn) 和空间复杂度为 O(1)。
对于时间复杂度的分析:
  • 在二分查找过程中,每次将搜索范围缩小一半,直到找到目标值或搜索范围为空。
  • 假设输入的数为 n,则最多需要进行 logn 次操作才能找到结果,因此时间复杂度为 O(logn)。
对于空间复杂度的分析:
  • 代码中只使用了常量级别的额外空间,即两个变量 leftright、一个变量 mid。无论输入的数是多少,额外空间的使用都不会随输入规模的增加而增加,因此空间复杂度为 O(1)。

二.移除元素

3.给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。

def yi_chu_yuan_su(nums, val):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != val:
            nums[slow] = nums[fast]
            slow += 1
    return slow


nums = input("请输入一串数组").split(",")
nums = list(map(int, nums))
val = int(input("请输入要删除的数"))
result = yi_chu_yuan_su(nums, val)
print(result)
print(nums[:result])

4.给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

def removeDuplicates(nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    if len(nums) == 0:
        return 0
    slow = 0
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1

nums = input("请输入:").split(",")
nums = list(map(int, nums))
result = removeDuplicates(nums)
print(result)
print(nums[:result])

5.给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

def moveZeroes(nums):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow] = nums[fast]
            slow += 1
    for i in range(slow, len(nums)):
        nums[i] = 0

6.给定 st 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true# 代表退格字符。

def isEqual(s, t):
    i = len(s) - 1
    j = len(t) - 1

    while i >= 0 or j >= 0:
        count_s = 0
        count_t = 0

        # 找到s中退格字符的数量
        while i >= 0 and (s[i] == '#' or count_s > 0):
            if s[i] == '#':
                count_s += 1
            else:
                count_s -= 1
            i -= 1

        # 找到t中退格字符的数量
        while j >= 0 and (t[j] == '#' or count_t > 0):
            if t[j] == '#':
                count_t += 1
            else:
                count_t -= 1
            j -= 1

        # 比较当前位置的字符
        if i >= 0 and j >= 0 and s[i] != t[j]:
            return False

        # 如果一个字符串已经遍历完,但另一个字符串还有剩余字符,则不相等
        if (i < 0 and j >= 0) or (i >= 0 and j < 0):
            return False

        i -= 1
        j -= 1

    return True


s = input("请输入字符串s:")
t = input("请输入字符串t:")
result = isEqual(s, t)
print(result)

7.给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

def sortedSquares(nums):
    n = len(nums)
    result = [0] * n
    left, right = 0, n - 1
    index = n - 1

    while left <= right:
        if nums[left] ** 2 > nums[right] ** 2:
            result[index] = nums[left] ** 2
            left += 1
        else:
            result[index] = nums[right] ** 2
            right -= 1
        index -= 1

    return result

nums = input("请输入一个非递减数列:").split(",")
nums = list(map(int, nums))
result = sortedSquares(nums)
print(result)

三.长度最小的子数组

8.给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

def jie_fa(s, nums):
    fast = 0
    slow = 0
    n = len(nums)
    sum = 0
    min_len = float('intf')

    while fast < n:
        sum += nums[fast]
        while sum >= s:
            min_len = min(min_len, fast - slow + 1)
            sum -= nums[slow]
            slow += 1
        fast += 1
    if min_len == float('inf'):
        return 0
    else:
        return min_len

9.你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。

  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。

  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

def collectFruits(fruits):
    n = len(fruits)
    end = 0
    fruit1 = fruits[0]
    fruit2 = None
    count1 = 0
    count2 = 0
    maxCount = 0

    while end < n:
        if fruits[end] == fruit1:
            count1 += 1
            end += 1
        elif fruits[end] == fruit2:
            count2 += 1
            end += 1
        else:
            maxCount = max(maxCount, count1 + count2)
            count1 = count2
            fruit2 = fruits[end]
            count2 = 0

    maxCount = max(maxCount, count1 + count2)

    return maxCount


fruits = [3, 3, 3, 1, 2, 1, 1, 2, 3, 3, 4]
maxFruits = collectFruits(fruits)
print(maxFruits)

10.给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

def min_window(s: str, t: str) -> str:
    from collections import Counter
    target = Counter(t)
    window = {}
    left = 0
    right = 0
    match = 0
    min_len = float('inf')
    start = 0
    while right < len(s):
        c = s[right]
        if c in target:
            window[c] = window.get(c, 0) + 1
            if window[c] == target[c]:
                match += 1
        right += 1
        # 缩小窗口
        while match == len(target):
            if right - left < min_len:
                min_len = right - left
                start = left
            c = s[left]
            if c in target:
                window[c] -= 1
                if window[c] < target[c]:
                    match -= 1
            left += 1
        if min_len != float('inf'):
            return s[start:start + min_len]
        else :
            return ''

11.给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

from typing import List


def generate_matrix(n: int) -> List[List[int]]:
    # 创建二维数组
    matrix = [[0] * n for _ in range(n)]

    top, bottom = 0, n - 1  # 上下边界
    left, right = 0, n - 1  # 左右边界
    num = 1  # 当前要填充的数字

    while top <= bottom and left <= right:
        # 填充上边
        for i in range(left, right + 1):
            matrix[top][i] = num
            num += 1
        top += 1

        # 填充右边
        for i in range(top, bottom + 1):
            matrix[i][right] = num
            num += 1
        right -= 1

        # 填充下边
        if top <= bottom:
            for i in range(right, left - 1, -1):
                matrix[bottom][i] = num
                num += 1
            bottom -= 1

        # 填充左边
        if left <= right:
            for i in range(bottom, top - 1, -1):
                matrix[i][left] = num
                num += 1
            left += 1

    return matrix


def print_matrix(matrix: List[List[int]]):
    for row in matrix:
        print(' '.join(str(i) for i in row))


n = 5
matrix = generate_matrix(n)
print_matrix(matrix)

二.链表

1.移除链表元素

12.题意:删除链表中等于给定值 val 的所有节点。

方法一.虚拟头结点

from typing import Any


class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def removeElements(head: ListNode, val: int) -> ListNode | None | Any:
    # 创建虚拟头节点
    dummy = ListNode(-1)
    dummy.next = head

    curr = dummy
    while curr.next:
        if curr.next.val == val:
            curr.next = curr.next.next
        else:
            curr = curr.next

    return dummy.next

def printLinkedList(head: ListNode) -> None:
    result = []
    while head:
        result.append(head.val)
        head = head.next
    print(result)


head = ListNode(6, ListNode(2, ListNode(6, ListNode(3, ListNode(4, ListNode(5, ListNode(6)))))))
val = 6
head = removeElements(head, val)
printLinkedList(head)

方法二.

from typing import Any


class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def removeElements(head: ListNode, val: int) -> ListNode | None | Any:
    # 处理链表为空的情况
    if not head:
        return None

    # 处理头部节点等于 val 的情况
    # while head.val == val:
    #     head = head.next

    curr = head
    while curr and curr.next:
        if curr.next.val == val:
            curr.next = curr.next.next
        else:
            curr = curr.next

    return head


def printLinkedList(head: ListNode) -> None:
    result = []
    while head:
        result.append(head.val)
        head = head.next
    print(result)


head = ListNode(6, ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5, ListNode(6)))))))
val = 6
while head.val == val:
    head = head.next
removeElements(head, val)
printLinkedList(head)

题意:

13.在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。

  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。

  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。

  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。

  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。


class MyLinkedList:
    def __init__(self):
        self.head = None  # 链表头节点指针,默认为空
        self.length = 0  # 链表长度,默认为0
    
    def get(self, index: int) -> int:
        """
        获取链表中第 index 个节点的值。如果索引无效,则返回-1。
        """
        if index < 0 or index >= self.length:  # 索引无效,返回-1
            return -1
        
        curr = self.head
        for _ in range(index):
            curr = curr.next  # 遍历链表,找到对应索引的节点
        return curr.val
    
    def addAtHead(self, val: int) -> None:
        """
        在链表的第一个元素之前添加一个值为 val 的节点。
        插入后,新节点将成为链表的第一个节点。
        """
        newNode = ListNode(val)  # 创建新节点
        newNode.next = self.head  # 将新节点的 next 指针指向原先的头节点
        self.head = newNode  # 将新节点设置为头节点
        self.length += 1
    
    def addAtTail(self, val: int) -> None:
        """
        将值为 val 的节点追加到链表的最后一个元素。
        """
        newNode = ListNode(val)  # 创建新节点
        
        if not self.head:  # 如果链表为空,将新节点设置为头节点
            self.head = newNode
        else:
            curr = self.head
            while curr.next:  # 找到链表的最后一个节点
                curr = curr.next
            curr.next = newNode  # 将新节点连接到最后一个节点的 next 指针上
        
        self.length += 1
    
    def addAtIndex(self, index: int, val: int) -> None:
        """
        在链表中的第 index 个节点之前添加值为 val 的节点。
        如果 index 等于链表的长度,则该节点将附加到链表的末尾。
        如果 index 大于链表长度,则不会插入节点。
        如果 index 小于0,则在头部插入节点。
        """
        if index < 0 or index > self.length:  # 索引无效,不插入节点
            return
        
        if index == 0:  # 在头部插入节点
            self.addAtHead(val)
            return
        
        if index == self.length:  # 在末尾插入节点
            self.addAtTail(val)
            return
        
        newNode = ListNode(val)  # 创建新节点
        curr = self.head
        for _ in range(index - 1):  # 找到插入位置的前一个节点
            curr = curr.next
        newNode.next = curr.next  # 将新节点的 next 指针指向原先索引位置的节点
        curr.next = newNode  # 将前一个节点的 next 指针指向新节点
        self.length += 1
    
    def deleteAtIndex(self, index: int) -> None:
        """
        如果索引 index 有效,则删除链表中的第 index 个节点。
        """
        if index < 0 or index >= self.length:  # 索引无效,不删除节点
            return
        
        if index == 0:  # 删除头节点
            self.head = self.head.next
        else:
            curr = self.head
            for _ in range(index - 1):  # 找到要删除节点的前一个节点
                curr = curr.next
            curr.next = curr.next.next  # 将前一个节点的 next 指针指向要删除节点的下一个节点
        
        self.length -= 1

题意:反转一个单链表。

14.示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

class ListNode:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next


def reserve(self, head:ListNode)->ListNode:
    prev = None
    curr = head
    while curr:
        next = curr.next
        curr.next = prev
        prev = curr
        curr = next

    return prev


head = ListNode(1,ListNode(2,ListNode(3,ListNode(4,ListNode(5)))))
curr = reserve(head)
while curr:
    print(curr.val,end='->')
    curr = curr.next

print("NULL")

15.给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。


class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def jiao_huan(head: ListNode) -> ListNode:
    if not head or not head.next:
        return head

    node1 = head
    node2 = head.next

    # 交换节点
    node1.next = jiao_huan(node2.next)
    node2.next = node1

    return node2


head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5, ListNode(6))))))
curr = jiao_huan(head)
while curr:
    print(curr.val, end='->')
    curr = curr.next
print('\b'*2)
当我们递归调用 jiaohuan 函数进行链表节点交换时,每次递归的链表变化如下所示:
初始链表:1 -> 2 -> 3 -> 4 -> 5 -> 6
第一次递归:
  • node1: 1
  • node2: 2
  • 交换节点:node1.next = jiaohuan(node2.next),即 1 -> 3 -> 4 -> 5 -> 6
  • 交换节点:node2.next = node1,即 2 -> 1 -> 3 -> 4 -> 5 -> 6
  • 返回交换后的新头节点:2 -> 1 -> 3 -> 4 -> 5 -> 6
第二次递归:
  • node1: 3
  • node2: 4
  • 交换节点:node1.next = jiaohuan(node2.next),即 3 -> 5 -> 6
  • 交换节点:node2.next = node1,即 4 -> 3 -> 5 -> 6
  • 返回交换后的新头节点:4 -> 3 -> 5 -> 6
第三次递归:
  • node1: 5
  • node2: 6
  • 交换节点:node1.next = jiaohuan(node2.next),即 5 -> None
  • 交换节点:node2.next = node1,即 6 -> 5 -> None
  • 返回交换后的新头节点:6 -> 5 -> None
最终链表:2 -> 1 -> 4 -> 3 -> 6 -> 5 -> None
每次递归调用都是处理两个相邻节点的交换,直到链表末尾或只剩下一个节点时结束。通过这样的递归交换过程,我们实现了链表中相邻节点的交换。
该算法使用递归方式对链表进行交换,每次递归都会处理两个节点,因此时间复杂度与链表长度成正比。假设链表长度为n,那么时间复杂度为O(n)。
空间复杂度: 该算法使用递归方式对链表进行交换,递归调用会消耗一定的栈空间。最坏情况下,递归深度等于链表的长度n,因此空间复杂度为O(n)。

16.删除倒数第n个节点

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def removeNthFromEnd(head, n):
    dummy = ListNode(0)
    dummy.next = head
    slow = dummy
    fast = dummy

    # 将 fast 指针向前移动 n+1 步
    for _ in range(n + 1):
        fast = fast.next

    # 同时移动 slow 和 fast
    while fast:
        slow = slow.next
        fast = fast.next

    # 删除倒数第 n 个节点
    slow.next = slow.next.next

    return dummy.next


head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5, ListNode(6))))))
n = 3
curr = removeNthFromEnd(head, n)
while curr:
    print(curr.val, end='->')
    curr = curr.next
print('\b'*2)
首先,我们创建了一个 dummy 节点,并将其 next 指向 head,这样可以处理删除头节点的情况。然后,我们初始化 slow 和 fast 指针都指向 dummy 节点。
接下来,我们将 fast 指针向前移动 n+1 步,以保持 slow 和 fast 之间的距离为 n。
然后,同时移动 slow 和 fast 指针,直到 fast 指针到达链表的末尾。这样,当 fast 指针到达末尾时,slow 指针将指向倒数第 n+1 个节点。
最后,我们删除倒数第 n 个节点,即将 slow.next 指向 slow.next.next。
整体的时间复杂度为 O(L),其中 L 是链表的长度。我们只需遍历一次链表来找到要删除的节点。
对于空间复杂度,我们只使用了常数个额外变量,因此空间复杂度为 O(1)。
综上所述,这段代码的时间复杂度为 O(L),空间复杂度为 O(1)。

17.给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

class ListNode:
    def __init__(self, val, next):
        self.val = val
        self.next = next


def getIntersectionNode(headA, headB):
    lenA = lenB = 0
    pA, pB = headA, headB

    # 计算链表 headA 的长度
    while pA:
        lenA += 1
        pA = pA.next

    # 计算链表 headB 的长度
    while pB:
        lenB += 1
        pB = pB.next

    # 让较长的链表先走 lenA - lenB 步
    if lenA > lenB:
        for i in range(lenA - lenB):
            headA = headA.next
    else:
        for i in range(lenB - lenA):
            headB = headB.next

    # 同时在两个链表上遍历,找到第一个相同的节点
    while headA != headB:
        headA = headA.next
        headB = headB.next

    return headA  # 返回相交的起始节点,或者是 None
在上面的代码中,我们首先需要遍历两个链表分别获取它们的长度,时间复杂度为 O(m+n),其中 m 和 n 分别是两个链表的长度。
接下来,将较长链表的指针向后移动与较短链表相同长度的距离,时间复杂度为 O(max(m,n))。
最后,同时遍历两个链表,比较节点是否相等,时间复杂度为 O(min(m,n))。
因此,整体的时间复杂度为 O(m+n) + O(max(m,n)) + O(min(m,n)),即 O(m+n)。
对于空间复杂度,我们只使用了常数个额外变量来存储一些临时状态,因此空间复杂度为 O(1)。
综上所述,这段代码的时间复杂度为 O(m+n),空间复杂度为 O(1)。

18.环型链表二:题意:

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def detectCycle(head: ListNode) -> ListNode:
    if not head or not head.next:
        return None

    slow = fast = head

    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

        # 快慢指针相遇,有环
        if slow == fast:
            fast = head  # 将快指针重新指向链表头节点

            # 快慢指针同时以每次移动一步的速度前进,再次相遇即为环的起始节点
            while fast != slow:
                fast = fast.next
                slow = slow.next

            return slow

    return None


# 创建带环链表
head = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node5 = ListNode(5)
head.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node2  # 在节点2处形成环

# 检测环的起始节点
start_node = detectCycle(head)
if start_node:
    print("环的起始节点为:", start_node.val)
else:
    print("没有环")
这个算法的时间复杂度为 O(n),其中 n 是链表的长度。在最坏情况下,需要遍历整个链表一次来检测环的存在,并且找到环的起始节点。
空间复杂度为 O(1),因为算法只使用了常数级别的额外空间来存储快指针和慢指针,不随着输入规模的增加而增加。

三.哈希表

19.编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

1.回溯算法
from collections import Counter


def kuai_le_shu(num):
    sum = 0
    while num > 0:
        i = num % 10
        num = num // 10
        sum += i * i
    return sum


num = 7
slow = num
fast = kuai_le_shu(num)
while slow != fast and fast != 1:
    slow = kuai_le_shu(slow)
    fast = kuai_le_shu(kuai_le_shu(fast))

if fast == 1:
    print("true")
else:
    print("false")

这段代码的时间复杂度和空间复杂度如下:
  1. 时间复杂度:在最坏情况下,输入的数不是快乐数,且最终会进入一个循环。假设输入的数有 d 位数字,那么计算每个位上数字的平方和的时间复杂度是 O(d),而每次更新快慢指针的时间复杂度是 O(1)。因此,总的时间复杂度是 O(d * k),其中 k 是循环的次数。由于循环次数是有限的,所以时间复杂度可以看作是 O(d)。
  2. 空间复杂度:这段代码的空间复杂度主要取决于存储中间计算结果的变量。在计算每个位上数字的平方和时,使用了一个变量 sum 存储中间结果,因此空间复杂度是 O(1)。此外,还使用了两个变量 slowfast 来保存快慢指针的值,它们只是用来追踪计算过程中的状态,不随输入规模变化,所以也可以看作是 O(1) 的空间复杂度。
综上所述,这段代码的时间复杂度是 O(d),空间复杂度是 O(1)。其中,d 是输入数字的位数。

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

def combinationSum(target, nums):
    def backtrack(start, target, path):
        if target == 0 and len(path) == 2:  # 只考虑两个数的组合
            res.append(path[:])  # 将当前路径添加到结果列表中
        if target < 0 or len(path) >= 2:  # 如果目标数小于0或者路径长度已经达到2,则终止当前递归
            return
        for i in range(start, len(nums)):
            if i > start and nums[i] == nums[i-1]:  # 去重:如果当前数字和上一个数字相同,则跳过当前循环
                continue
            path.append(nums[i])  # 将当前数字加入路径
            backtrack(i+1, target-nums[i], path)  # 继续向下递归,更新起始位置和目标数
            path.pop()  # 回溯,移除最后一个数字,准备尝试下一个数字
            
    nums.sort()  # 先将数组排序,方便去重
    res = []  # 存储结果的列表
    backtrack(0, target, [])  # 调用回溯函数,初始起始位置为0,初始目标数为target,初始路径为空列表
    return res  # 返回结果列表

nums = [2, 7, 1, 8]
target = 9
result = combinationSum(target, nums)
print(result)
这段代码的时间复杂度取决于生成的组合数量,最坏情况下,如果没有重复元素,时间复杂度为O(2^n),其中n是数组nums的长度。这是因为对于每个元素,都有两种选择:选取或不选取。
空间复杂度方面,主要消耗的空间是递归调用栈和存储结果的列表。递归调用栈的空间复杂度取决于递归的深度,最坏情况下为O(n),其中n是数组nums的长度。存储结果的列表的空间复杂度取决于生成的组合数量,最坏情况下为O(2^n)。
因此,该代码的时间复杂度为O(2^n),空间复杂度为O(n)。
2.双指针法
def combinationSum(target, nums):
    # 对数组进行排序
    nums.sort()
    # 初始化头指针和尾指针
    head = 0
    last = len(nums) - 1
    # 初始化路径数组和结果数组
    path = []
    res = []

    # 双指针法遍历数组
    while head != last:
        # 如果头指针指向的元素与下一个元素相同,则将头指针向后移动
        while nums[head] == nums[head + 1]:
            head = head + 1
        # 如果尾指针指向的元素与前一个元素相同,则将尾指针向前移动
        while nums[last] == nums[last - 1]:
            last = last - 1

        # 如果头指针和尾指针指向的元素之和等于目标值
        if nums[head] + nums[last] == target:
            # 将头指针和尾指针指向的元素添加到路径数组中
            path.append(nums[head])
            path.append(nums[last])
            # 将路径数组添加到结果数组中
            res.append(path[:])
            # 清空路径数组,为下一组数对做准备
            path = []

        # 如果头指针和尾指针指向的元素之和大于目标值
        while nums[head] + nums[last] > target:
            # 将尾指针向前移动
            last = last - 1
            # 如果头指针和尾指针指向的元素之和小于等于目标值
            if nums[head] + nums[last] <= target:
                # 将头指针向前移动
                head -= 1
                # 跳出循环,继续寻找下一组数对
                break

        # 将头指针向后移动,继续寻找下一组数对
        head += 1

    # 返回结果数组
    return res


# 输入的数组
nums = [2, 7, 1, 8, 3, 6, 1, 1]
# 目标值
target = 9
# 调用函数求解组合数问题
result = combinationSum(target, nums)
# 输出结果
print(result)
这段代码的时间复杂度是 O(n^2),其中 n是数组的长度。具体来说,双指针法需要遍历一遍排序后的数组,即需要 O(n) 的时间复杂度;对于每个数对,都需要判断它们的和是否等于目标值,因此需要O(n)的时间复杂度。因此,总的时间复杂度为O(n^2)。

21.给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

def si_shu_xiang_jia(nums1, nums2, nums3, nums4):
    # 使用字典存储nums1和nums2中的元素及其和
    hashmap = dict()
    for n1 in nums1:
        for n2 in nums2:
            if n1 + n2 in hashmap:
                hashmap[n1 + n2] += 1
            else:
                hashmap[n1 + n2] = 1

    # 如果 -(n1+n2) 存在于nums3和nums4, 存入结果
    count = 0
    for n3 in nums3:
        for n4 in nums4:
            key = - n3 - n4
            if key in hashmap:
                count += hashmap[key]
    return count

这段代码的时间复杂度为 O(n^2),其中 n为nums1nums2的长度。原因是使用了两层嵌套的循环来构建字典 hashmap,每个循环的时间复杂度都是O(n^2)。然后,在第二个双重循环中,同样使用了两层嵌套的循环来遍历 nums3nums4,并在字典中查找是否存在 - (n3+n4),每个循环的时间复杂度也是 O(n)。因此,总的时间复杂度为 O(n^2)。
空间复杂度方面,额外使用了一个字典 hashmap 来存储 nums1nums2 中元素的和及其出现的次数。字典的大小取决于 nums1nums2 中所有可能的和,而不是输入数组的长度。因此,空间复杂度可以看作是 O(m),其中 m 是字典 hashmap 的大小,最坏情况下是 O(n^2)。

22.三数之和

def threeSum(nums, target):
    nums.sort()  # 对数组进行排序,便于跳过重复元素和确定左右指针移动的方向
    n = len(nums)  # 数组的长度
    res = []  # 存储结果的列表

    for i in range(n - 2):  # 遍历数组,i 最多到倒数第三个位置
        if nums[i] > target:  # 如果第一个元素已经大于 target,则直接返回结果
            return res
        if i > 0 and nums[i] == nums[i - 1]:  # 跳过重复的第一个数,避免重复计算
            continue

        left = i + 1  # 左指针初始位置为 i 的下一个位置
        right = n - 1  # 右指针初始位置为数组末尾

        while left < right:  # 当左指针小于右指针时进行循环
            total = nums[i] + nums[left] + nums[right]  # 计算当前三个数的和

            if total == target:  # 如果和等于目标值
                res.append([nums[i], nums[left], nums[right]])  # 将结果添加到 res 中
                while left < right and nums[left] == nums[left + 1]:  # 跳过重复的第二个数,避免重复计算
                    left += 1
                while left < right and nums[right] == nums[right - 1]:  # 跳过重复的第三个数,避免重复计算
                    right -= 1
                left += 1  # 移动左指针到下一个位置
                right -= 1  # 移动右指针到前一个位置
            elif total < target:  # 如果和小于目标值,移动左指针增大和
                left += 1
            else:  # 如果和大于目标值,移动右指针减小和
                right -= 1

    return res  # 返回结果列表


nums = [-1, 0, 1, 2, -1, -4]  # 示例输入数组
target = 0  # 示例目标值
result = threeSum(nums, target)  # 调用函数进行三数之和查找
print(result)  # 打印结果

四.字符串:

23.给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。

如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例:

输入: s = “abcdefghijklmn”, k = 4

输出: “dcbaefghlkjimn”

def reverseString(s, k):
    # 计算s的长度除以2k的余数
    m = len(s) % (2 * k)
    # 计算最后一个完整的2k长子字符串结束的索引n
    n = len(s) - m - 1

    # 反转每个2k长子字符串,从索引0开始,步长为2k
    for i in range(0, n, 2 * k):
        left = i
        right = i + k - 1
        while left < right:
            # 交换左右索引的字符
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1

    # 如果剩余子字符串的长度m小于k,则反转该字符串
    if m < k:
        left = n + 1
        right = len(s) - 1
        while left < right:
            # 交换左右索引的字符
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1
    else:
        # 如果剩余子字符串的长度m大于等于k,则反转该字符串的前k个字符
        left = n + 1
        right = n + k
        while left < right:
            # 交换左右索引的字符
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1

    # 将字符数组转换为字符串并返回
    return ''.join(s)


# 示例输入
input_str = "abcdefghijklmn"
input_str = list(input_str)  # 将字符串转换为字符数组
k = 4
result = reverseString(input_str, k)
print(result)  # 打印反转后的结果

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

示例 1:

输入: “the sky is blue”

输出: “blue is sky the”

def reverseString(s):
    n = len(s) - 1
    left = 0
    right = n
    fast = slow = 0
    while left < right:
        s[left], s[right] = s[right], s[left]
        left += 1
        right -= 1
    s.append(' ')
    for i in range(len(s)):
        if s[i] == ' ':
            fast = i - 1
            while slow < fast:
                s[slow], s[fast] = s[fast], s[slow]
                slow += 1
                fast -= 1
            slow = i + 1
    return ''.join(s)


# 示例输入
input_str = "hello world!"
input_str = list(input_str)  # 将字符串转换为字符数组
result = reverseString(input_str)
print(result)  # 打印反转后的结果

字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。

例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。

输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。

输出:输出共一行,为进行了右旋转操作后的字符串。

def rightRotateString(s, k):
    # 获取字符串长度
    n = len(s)

    # 特判:如果字符串为空或者 k 小于等于 0 或者 k 大于等于字符串长度,则不需要旋转,直接返回原字符串
    if not s or k <= 0 or k >= n:
        return s

    # 将字符串转换为列表
    s = list(s)

    # 计算需要旋转的部分的长度
    rotate_len = n - k

    # 对前一部分进行反转
    left = 0
    right = rotate_len - 1
    while left < right:
        s[left], s[right] = s[right], s[left]
        left += 1
        right -= 1

    # 对后一部分进行反转
    left = rotate_len
    right = n - 1
    while left < right:
        s[left], s[right] = s[right], s[left]
        left += 1
        right -= 1

    # 反转整个字符串
    s.reverse()

    # 将列表转换为字符串并返回结果
    return ''.join(s)


# 示例输入
k = 2
s = "abcdefg"
result = rightRotateString(s, k)
print(result)  # 打印右旋转后的字符串

25.实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

示例 1: 输入: haystack = “hello”, needle = “ll” 输出: 2

示例 2: 输入: haystack = “aaaaa”, needle = “bba” 输出: -1

说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

def getNext(next, s):
    j = 0
    for i in range(1, len(s)):
        while j > 0 and s[i] != s[j]:
            j = next[j - 1]
        if s[i] == s[j]:
            j += 1
        next[i] = j


def strStr(haystack, needle):
    if len(needle) == 0:
        return 0
    next = [0] * len(needle)
    getNext(next, needle)
    j = 0
    for i in range(len(haystack)):
        while j > 0 and haystack[i] != needle[j]:
            j = next[j - 1]
        if haystack[i] == needle[j]:
            j += 1
        if j == len(needle):
            return i - len(needle) + 1
    return -1


haystack = "aaaaaaaaaaaabaabaaf"
needle = "aabaabaaf"
result = strStr(haystack, needle)
print(result)
1.该代码的时间复杂度是O(m + n),其中mhaystack的长度,nneedle的长度
2.代码的空间复杂度是O(n),需要额外使用一个next数组来存储模式串needle的最长公共前缀和后缀的长度。

五.栈和队列

26.使用栈实现队列的下列操作:

push(x) – 将一个元素放入队列的尾部。

pop() – 从队列首部移除元素。

peek() – 返回队列首部的元素。

empty() – 返回队列是否为空。

class MyQueue:
    def __init__(self):
        # 初始化栈1和栈2
        self.stack1 = []
        self.stack2 = []

    def push(self, x: int) -> None:
        # push操作,直接将元素x入栈stack1
        self.stack1.append(x)

    def pop(self) -> int:
        # pop操作,如果stack2不为空,则直接从stack2中弹出栈顶元素并返回
        if not self.stack2:
            # 如果stack2为空,则将stack1中的所有元素依次弹出并压入stack2中
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        # 从stack2中弹出栈顶元素并返回
        return self.stack2.pop()

    def peek(self) -> int:
        # peek操作,与pop类似,只是在弹出栈顶元素之前不执行删除操作
        if not self.stack2:
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        # 返回stack2的栈顶元素
        return self.stack2[-1]

    def empty(self) -> bool:
        # 判断stack1和stack2是否均为空即可
        return not self.stack1 and not self.stack2


# 创建队列对象
queue = MyQueue()

# 入队操作
queue.push(1)
queue.push(2)
queue.push(3)

# 出队操作
print(queue.pop())  # 输出:1

# 获取队首元素
print(queue.peek())  # 输出:2

# 判断队列是否为空
print(queue.empty())  # 输出:False

27.给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。

  • 左括号必须以正确的顺序闭合。

  • 注意空字符串可被认为是有效字符串。

方法一.
使用字典
def isValid(s: str) -> bool:
    stack = []
    brackets = {'(': ')', '[': ']', '{': '}'}
    for ch in s:
        if ch in brackets:
            stack.append(ch)
        elif ch in brackets.values():
            if not stack or brackets[stack.pop()] != ch:
                # 当遍历到一个右括号时,我们需要查看它是否与栈顶元素所对应的左括号匹配。
                # 如果匹配,则将栈顶元素弹出栈;如果不匹配,或者栈为空,则说明括号不匹配,
                # 返回 False。
                return False
    return not stack


# 测试样例
print(isValid("()"))  # True
print(isValid("()[]{}"))  # True
print(isValid("(]"))  # False
print(isValid("([)]"))  # False
print(isValid("{[]}"))  # True


方法二.
def isValid(s: str) -> bool:
    stack = []
    for item in s:
        if item == '(':
            stack.append(')')
        elif item == '[':
            stack.append(']')
        elif item == '{':
            stack.append('}')
        elif item != stack[-1] or not stack:
            return False
        else:
            stack.pop()
    return not stack


# 测试样例
print(isValid("()"))  # True
print(isValid("()[]{}"))  # True
print(isValid("(]"))  # False
print(isValid("([)]"))  # False
print(isValid("{[]}"))  # True

27.删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

方法一.
使用栈
def isValid(s: list[str]) -> str:
    stack = [str[0]]
    n = len(s)
    for i in range(1, n):
        k = len(stack)
        if not stack:
            stack.append(s[i])
            continue
        if s[i] == stack[k - 1]:
            stack.pop()
        else:
            stack.append(s[i])
    return ''.join(stack)


# 测试样例
str = "abbaca"
str = list(str)
result = isValid(str)
print(result)

方法二.
使用双指针
def removeDuplicates(s: str) -> str:
    res = list(s)
    slow = fast = 0
    length = len(res)

    while fast < length:
        # 如果一样直接换,不一样会把后面的填在slow的位置
        res[slow] = res[fast]

        # 如果发现和前一个一样,就退一格指针
        if slow > 0 and res[slow] == res[slow - 1]:
            slow -= 1
        else:
            slow += 1
        fast += 1

    return ''.join(res[0: slow])
# 测试样例
str = "abbaca"
result = removeDuplicates(str)
print(result)

28.给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

  • 输入: nums = [1,1,1,2,2,3], k = 2

  • 输出: [1,2]

示例 2:

  • 输入: nums = [1], k = 1

  • 输出: [1]

import heapq
from collections import defaultdict


def topKFrequent(nums, k):
    # 统计每个元素的出现频率
    freq = defaultdict(int)
    for num in nums:
        freq[num] += 1

    # 使用最小堆来维护出现频率前 k 高的元素
    heap = []
    for num, count in freq.items():
        # 使用 heapq.heappush() 方法将一个元组 (count, num) 插入到堆 heap 中。
        heapq.heappush(heap, (count, num))
        if len(heap) > k:
            heapq.heappop(heap)

    # 构造结果列表
    result = []
    while heap:
        # 将最小堆里的 num 值弹出并放进 result 数组
        result.append(heapq.heappop(heap)[1])
    #  #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组,所以可将数组陷阱性转制
    result.reverse()
    return result


nums = [1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5]
k = 3
result = topKFrequent(nums, k)
print(result)
这段代码的时间复杂度为O(nlog(k)),其中n是数组nums的长度,k是要求的前k个高频元素的个数。空间复杂度为O(n)。
首先,通过循环遍历数组nums并使用defaultdict统计每个元素的出现频率,这一步的时间复杂度为O(n),其中n是数组的长度。
接下来,使用最小堆来维护出现频率前k高的元素。对于每个元素和频率的键值对,使用heapq.heappush()方法将其插入堆中,这个操作的时间复杂度为O(log(k))。如果堆的大小超过了k,就使用heapq.heappop()方法将堆顶元素弹出,这个操作的时间复杂度同样为O(log(k))。由于元素的个数为n,所以整个过程需要进行n次插入和弹出操作,总的时间复杂度为O(nlog(k))。
最后,构造结果列表时,从堆中依次弹出元素,并将其放入结果数组中。这个过程需要进行k次弹出操作,每次弹出操作的时间复杂度为O(log(k)),所以总的时间复杂度为O(klog(k))。在构造结果列表时,额外使用了一个result数组来存储结果,所以空间复杂度为O(n)。

29.逆波兰表达式

def evalRPN(tokens):
    # 创建一个空栈用来存储数字
    stack = []

    # 遍历逆波兰表达式中的每个元素
    for token in tokens:
        # 如果当前元素为运算符,则从栈中弹出两个数字进行相应的运算
        if token in ['+', '-', '*', '/']:
            num2 = stack.pop()
            num1 = stack.pop()

            # 根据运算符进行相应的运算,并将结果压入栈中
            if token == '+':
                stack.append(num1 + num2)
            elif token == '-':
                stack.append(num1 - num2)
            elif token == '*':
                stack.append(num1 * num2)
            else:
                # 除法需要注意取整
                stack.append(int(num1 / num2))
        else:
            # 如果当前元素是数字,则将其转换为整数并压入栈中
            stack.append(int(token))

    # 最终栈中剩余的唯一元素即为计算结果
    return stack[0]


# 测试样例
tokens = ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
result = evalRPN(tokens)
print(result)

六.二叉树

一.调用递归函数对二叉树进行遍历

30.调用递归函数对二叉树进行前序遍历

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


def preorderTraversal(root):
    if not root:
        return []
    result = []
    result.append(root.val)
    result += preorderTraversal(root.left)
    result += preorderTraversal(root.right)
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用递归函数进行前序遍历
result = preorderTraversal(root)
print(result)

31.调用递归函数进行后序遍历

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


def preorderTraversal(root):
    if not root:
        return []
    result = []
    result += preorderTraversal(root.left)
    result += preorderTraversal(root.right)
    result.append(root.val)
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用递归函数进行前序遍历
result = preorderTraversal(root)
print(result)

32.调用递归函数对二叉树进行中序遍历

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


def preorderTraversal(root):
    if not root:
        return []
    result = []
    result += preorderTraversal(root.left)
        result.append(root.val)
    result += preorderTraversal(root.right)
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用递归函数进行前序遍历
result = preorderTraversal(root)
print(result)

二.使用迭代的方法进行遍历

33.使用迭代法进行后序遍历

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


def postorderTraversal(root):
    if not root:
        return []
    stack1, stack2, result = [root], [], []
    while stack1:
        node = stack1.pop()
        if node.left:
            stack1.append(node.left)
        if node.right:
            stack1.append(node.right)
        stack2.append(node)  # 将节点标记为已访问,并入栈
    while stack2:
        node = stack2.pop()
        result.append(node.val)  # 弹出节点并输出其值
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用迭代法进行后序遍历
result = postorderTraversal(root)
print(result)

34.使用迭代法进行前序遍历

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


def preorderTraversal(root):
    if not root:
        return []
    stack, result = [root], []
    while stack:
        node = stack.pop()
        # 此处出栈,防止第一个val丢失
        result.append(node.val)
        if node.right:
            stack.append(node.right)
        # 由于pop会在栈的最右弹出里面的内容,最后的结果应是先左后右,所以在入栈的时候应该先右后左
        if node.left:
            stack.append(node.left)
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用迭代法进行前序遍历
result = preorderTraversal(root)
print(result)

35.使用迭代进行中序遍历

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


def inorderTraversal(root):
    if not root:
        return []
    stack, result = [], []
    node = root
    while stack or node:
        while node:
            stack.append(node)
            node = node.left
        node = stack.pop()
        result.append(node.val)
        node = node.right
    return result


# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)
# 调用迭代法进行中序遍历
result = inorderTraversal(root)
print(result)

三.二叉数的层序遍历(递归)

36.给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

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


def levelOrder(root):
    if not root:
        return []

    result = []

    def helper(node, level):
        if len(result) == level:
            result.append([])

        result[level].append(node.val)

        if node.left:
            helper(node.left, level + 1)
        if node.right:
            helper(node.right, level + 1)

    helper(root, 0)

    return result

root = TreeNode(6)
root.left = TreeNode(4)
root.right = TreeNode(7)
root.left.left = TreeNode(2)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(8)
result = levelOrder(root)
print(result)

37.给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

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


def levelOrder(root):
    result = []
    level = 0
    if not root:
        return []

    def helper(node, level):
        if len(result) == level:
            result.append([])
        result[level].append(node.val)
        if node.left:
            helper(node.left, level + 1)
        if node.right:
            helper(node.right, level + 1)

    helper(root, level)

    return result


root = TreeNode(6)
root.left = TreeNode(4)
root.right = TreeNode(7)
root.left.left = TreeNode(2)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(8)
end = []
result = levelOrder(root)
for seq in result:
    end.append(seq[-1])
print(end)

38.给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

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


def levelOrder(root):
    result = []
    level = 0
    if not root:
        return []

    def helper(node, level):
        if len(result) == level:
            result.append([])
        result[level].append(node.val)
        if node.left:
            helper(node.left, level + 1)
        if node.right:
            helper(node.right, level + 1)

    helper(root, level)

    return result


root = TreeNode(6)
root.left = TreeNode(4)
root.right = TreeNode(7)
root.left.left = TreeNode(2)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(8)
end = []
result = levelOrder(root)
for seq in result:
    sum = 0
    count = 0
    for inseq in seq:
        sum += inseq
        count += 1
    end.append(sum/count)
print(end)
print(result)

39.给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children if children is not None else []


# 递归实现层序遍历
def levelOrder(root):
    result = []
    if not root:
        return result

    def dfs(node, level):
        if len(result) == level:
            result.append([])
        result[level].append(node.val)
        for child in node.children:
            dfs(child, level + 1)

    dfs(root, 0)
    return result


# 构造一个N叉树并进行层序遍历
tree = Node(1, [Node(3, [Node(5), Node(6)]),
                Node(2),
                Node(4, [Node(7), Node(8)])])
print(levelOrder(tree))

注意: self.children = children if children is not None else []
  1. 如果 children 不为 None,则将 children 赋值给 self.children
  2. 如果 childrenNone,则将空列表 [] 赋值给 self.children
这样做的目的是为了防止在实例化 Node 对象时不传入 children 参数,或者传入 None 值。如果没有设置默认值并且传入的参数为 None,那么 self.children 就会被设置为 None,而不是一个空列表。
如果传入的是None,就会出现类型错误(‘NoneType’ object is not iterable)
例如,在下面代码中:
my_list = None
for x in my_list:
    print(x)
因为 my_list 被设置为 None,它不是一个可迭代的对象,因此在 for 循环中尝试对它进行迭代时,就会抛出 'NoneType' object is not iterable 的错误。
在错误信息中,TypeError 表示错误类型为类型错误,而 'NoneType' object is not iterable' 则是具体的错误信息,说明了出错的原因。

40.给定一个二叉树,要求给其添加一个右侧指针,用于指向其右侧元素

class Node:
    def __init__(self, val=0, left=None, right=None, next=None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next


def connect(root):
    pre = []

    def dfs(node, depth):
        if node is None:
            return
        if depth == len(pre):  # node 是这一层最左边的节点
            pre.append(node)
        else:  # pre[depth] 是 node 左边的节点
            pre[depth].next = node  # node 左边的节点指向 node
            pre[depth] = node
        dfs(node.left, depth + 1)
        dfs(node.right, depth + 1)

    dfs(root, 0)  # 根节点的深度为 0
    return root


root1 = Node(1)
root1.left = Node(2)
root1.right = Node(3)
root1.left.left = Node(5)
root1.left.right = Node(7)
root1.right.left = Node(6)
root1.right.right = Node(8)
root1.left.left.left = Node(4)
root1.right.left.left = Node(9)
root1.right.left.right = Node(11)
root1.right.right.left = Node(10)
root1.right.right.right = Node(12)

# 连接节点的操作
connect(root1)

print(root1.left.left.left.next.val)
该题可使用深度优先搜索(dfs)用递归的方法,在函数dfs中,node表示当前遍历到的节点,depth表示当前节点的深度。首先判断当前节点是否为空,若为空则直接返回;然后判断当前节点的深度是否等于pre数组的长度,如果相等说明当前节点是这一层最左边的节点,将其加入pre数组;如果深度不相等,则说明当前节点是当前层的某个中间节点或最右边的节点,将该节点与pre[depth]指向的节点连接起来,并更新pre[depth]为当前节点。
最后调用dfs(root, 0)开始遍历整棵树,根节点的深度为0。最终返回根节点root,其中每一层的节点都通过next指针连接起来了。

41.给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

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


class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        left_depth = self.maxDepth(root.left)
        right_depth = self.maxDepth(root.right)
        return max(left_depth, right_depth) + 1


# 构建二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 创建Solution实例
solution = Solution()
# 计算二叉树的最大深度
max_depth = solution.maxDepth(root)

print("二叉树的最大深度为:", max_depth)

在计算二叉树的最大深度时,我们可以通过递归的方式来计算左子树和右子树的深度,然后取两者中较大的深度加上当前节点的深度(即1)作为当前节点所在子树的深度。这样可以确保在递归过程中不断更新最大深度。
因此,在代码中使用 max(left_depth, right_depth) + 1 这样的表达式,可以正确计算出当前节点所在子树的深度。这个表达式的含义是取左子树的深度和右子树的深度中较大的值,再加上当前节点的深度(即1),就是当前节点所在子树的深度。
这样处理可以保证每次递归返回时,都能够得到当前节点所在子树的深度,并在递归过程中不断更新最大深度。

42.给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

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


class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        left_depth = self.maxDepth(root.left)
        right_depth = self.maxDepth(root.right)
        return min(left_depth, right_depth) + 1


# 构建二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 创建Solution实例
solution = Solution()
# 计算二叉树的最小深度
max_depth = solution.maxDepth(root)

print("二叉树的最小深度为:", max_depth)

43.通过上面两道还可以举一反三,例如求n叉数的最大深度如何求

class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children if children else []

class Solution:
    def maxDepth(self, root: 'Node') -> int:
        if not root:
            return 0
        max_child_depth = 0
        for child in root.children:
            max_child_depth = max(max_child_depth, self.maxDepth(child))
        return max_child_depth + 1

# 构建 N 叉树
root = Node(1, [
    Node(3, [Node(5), Node(6)]),
    Node(2),
    Node(4)
])

# 创建Solution实例
solution = Solution()
# 计算 N 叉树的最大深度
max_depth = solution.maxDepth(root)

print("N 叉树的最大深度为:", max_depth)

四.二叉树的层序遍历(迭代法)

36.给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

from collections import deque


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


def levelOrder(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        level_nodes = []

        for _ in range(level_size):
            node = queue.popleft()
            level_nodes.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(level_nodes)

    return result


# 示例:
# 构建二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 进行层序遍历
print(levelOrder(root))

37.给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

from collections import deque


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


def rightSideView(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        for i in range(level_size):
            node = queue.popleft()

            if i == level_size - 1:  # 只保留当前层最右边的节点值
                result.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

    return result


# 示例:
# 构建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.right = TreeNode(5)
root.right.right = TreeNode(4)

# 获取从右侧看到的节点值
print(rightSideView(root))
  1. 初始化一个空列表 result 用于存放结果。
  2. 初始化一个空队列 queue,并将根节点加入队列。
  3. 循环遍历队列,直到队列为空:
    • 获取当前层的节点个数 level_size
    • 遍历当前层的节点,将最右边的节点值加入到结果列表中。
    • 如果当前节点有左子节点,则将左子节点加入队列。
    • 如果当前节点有右子节点,则将右子节点加入队列。
  4. 返回结果列表。

38.给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

from collections import deque

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

def averageOfLevels(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        level_sum = 0
        
        for _ in range(level_size):
            node = queue.popleft()
            level_sum += node.val
            
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        
        level_avg = level_sum / level_size
        result.append(level_avg)

    return result

# 示例:
# 构建二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 获取每层节点平均值组成的数组
print(averageOfLevels(root))
  1. 初始化一个空列表 result 用于存放结果。
  2. 初始化一个空队列 queue,并将根节点加入队列。
  3. 循环遍历队列,直到队列为空:
    • 获取当前层的节点个数 level_size
    • 初始化当前层的节点值之和为0 level_sum
    • 遍历当前层的节点,累加节点值到 level_sum 中,并将左右子节点加入队列。
    • 计算当前层节点值的平均值 level_avg,并将其加入到结果列表中。
  4. 返回结果列表。

39.给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

from collections import deque

class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children

def levelOrder(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        level_values = []

        for _ in range(level_size):
            node = queue.popleft()
            level_values.append(node.val)

            if node.children:
                for child in node.children:
                    queue.append(child)

        result.append(level_values)

    return result

# 示例:
# 构建N叉树
root = Node(1, [Node(3, [Node(5), Node(6)]), Node(2), Node(4)])

# 获取节点值的层序遍历结果
print(levelOrder(root))

40.给定一个二叉树,要求给其添加一个右侧指针,用于指向其右侧元素

from collections import deque

# 二叉树节点定义
class Node:
    def __init__(self, val=0, left=None, right=None, next=None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next

def connect(root):
    if not root:
        return None

    queue = deque([root])

    while queue:
        level_size = len(queue)
        prev_node = None

        for _ in range(level_size):
            node = queue.popleft()

            if prev_node:
                prev_node.next = node

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

            prev_node = node

    return root

# 示例:
# 构建示例二叉树
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
root.right.right = Node(7)

# 添加右侧指针
connect(root)

# 输出结果
print(root.val, root.next)  # 1 None
print(root.left.val, root.left.next.val)  # 2 3
print(root.left.left.val, root.left.left.next.val)  # 4 5
print(root.left.right.val, root.left.right.next.val)  # 5 7

41.给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

from collections import deque

# 二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def maxDepth(root):
    if not root:
        return 0

    depth = 0
    queue = deque([root])

    while queue:
        depth += 1
        level_size = len(queue)

        for _ in range(level_size):
            node = queue.popleft()

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

    return depth

# 示例:
# 构建示例二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 计算二叉树的最大深度
print(maxDepth(root))  # 输出结果为3
使用迭代法求解二叉树的最大深度可以借助层序遍历的思想,每一层遍历完成后深度加1。具体步骤如下:
  1. 初始化一个队列,将根节点加入队列。
  2. 初始化深度为0。
  3. 循环遍历队列,直到队列为空:
    • 获取当前层的节点个数 level_size
    • 遍历当前层的节点,将子节点加入队列。
  4. 每遍历完一层,深度加1。
  5. 返回最终计算得到的深度。

42.给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

from collections import deque

# 二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def minDepth(root):
    if not root:
        return 0

    queue = deque([(root, 1)])

    while queue:
        node, depth = queue.popleft()

        if not node.left and not node.right:  # 叶子节点
            return depth

        if node.left:
            queue.append((node.left, depth + 1))

        if node.right:
            queue.append((node.right, depth + 1))

    return 0

# 示例:
# 构建示例二叉树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

# 计算二叉树的最小深度
print(minDepth(root))  # 输出结果为2
要找出二叉树的最小深度(即从根节点到最近叶子节点的最短路径上的节点数量),可以借助广度优先搜索(BFS)来实现。具体步骤如下:
  1. 初始化一个队列,将根节点和深度1加入队列。
  2. 循环遍历队列,直到队列为空:
    • 弹出队首节点和对应深度。
    • 如果该节点是叶子节点(即没有左右子节点),返回当前深度。
    • 否则,将该节点的非空子节点和对应深度加入队列。
  3. 如果在循环中没有找到叶子节点,则返回0。

五.翻转二叉树:

44.翻转一棵二叉树。

方法1:递归
class treeNode:
    def __init__(self, val = 0, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right


def fan_zhuan(root):
    if not root:
        return None
    if root.left and root.right:
        root.left, root.right = root.right, root.left
    if root.left:
        node = root.left
        fan_zhuan(root.left)
    if root.right:
        fan_zhuan(root.right)
    return root




root = treeNode(4)
root.left = treeNode(2)
root.right = treeNode(7)
root.left.left = treeNode(1)
root.left.right = treeNode(3)
root.right.left = treeNode(6)
root.right.right = treeNode(9)
result = fan_zhuan(root)
print(root.left.left.val)
print(root.left.right.val)
方法2:迭代
# 二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTree(root):
    if not root:
        return None

    stack = [root]

    while stack:
        node = stack.pop()
        # 交换当前节点的左右子节点
        node.left, node.right = node.right, node.left

        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)

    return root


# 示例:
# 构建示例二叉树
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)

# 翻转二叉树
inverted_root = invertTree(root)

六.完全二叉树的节点个数

45.给出一个完全二叉树,求出该树的节点个数。

方法1:递归
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def countNodes(root, count):
    if not root:
        return 0
    # 当前节点也要计入节点个数
    count += 1
    # 遍历左右子树
    if root.left:
        count = countNodes(root.left, count)
    if root.right:
        count = countNodes(root.right, count)
    return count

# 示例:
# 构建完全二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)

# 计算完全二叉树的节点个数
print(countNodes(root, 0))  # 输出结果为6
方法2:迭代
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def countNodes(root, count):
    if not root:
        return 0
    # 当前节点也要计入节点个数
    count += 1
    # 遍历左右子树
    if root.left:
        count = countNodes(root.left, count)
    if root.right:
        count = countNodes(root.right, count)
    return count

# 示例:
# 构建完全二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)

# 计算完全二叉树的节点个数
print(countNodes(root, 0))  # 输出结果为6

七.平衡二叉树

46.给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

方法1:递归
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def isBalanced(root):
    def checkBalance(node):
        if not node:
            return 0
        left_height = checkBalance(node.left)
        right_height = checkBalance(node.right)
        if left_height == -1 or right_height == -1 or abs(left_height - right_height) > 1:
            return -1
        return max(left_height, right_height) + 1

    return checkBalance(root) != -1


# 示例:
# 构建一个高度不平衡的二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(2)
root.left.left = TreeNode(3)
root.left.right = TreeNode(3)
root.left.left.left = TreeNode(4)
root.left.left.right = TreeNode(4)

# 判断给定二叉树是否是高度平衡的
print(isBalanced(root))  # 输出结果为 False

方法2:迭代
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def isBalanced(root):
    stack = [(root, 0)]  # 使用栈进行层序遍历,同时记录节点的深度

    while stack:
        node, depth = stack.pop()
        if node:
            left_height = get_height(node.left)
            right_height = get_height(node.right)
            if abs(left_height - right_height) > 1:
                return False
            stack.append((node.left, depth + 1))
            stack.append((node.right, depth + 1))

    return True

def get_height(node):
    if not node:
        return 0
    left_height = get_height(node.left)
    right_height = get_height(node.right)
    return max(left_height, right_height) + 1

# 示例:
# 构建一个高度不平衡的二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(2)
root.left.left = TreeNode(3)
root.left.right = TreeNode(3)
root.left.left.left = TreeNode(4)
root.left.left.right = TreeNode(4)

# 判断给定二叉树是否是高度平衡的
print(isBalanced(root))  # 输出结果为 False

八.二叉树的所有路径

47.给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

方法1:递归
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def binaryTreePaths(root):
    def dfs(node, path, paths):
        if not node:
            return
        path += str(node.val)
        if not node.left and not node.right:  # 到达叶子节点
            paths.append(path)
        else:
            path += '->'
            dfs(node.left, path, paths)
            dfs(node.right, path, paths)

    if not root:
        return []

    paths = []
    dfs(root, '', paths)
    return paths


# 示例:
# 构建一个二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.right = TreeNode(5)

# 返回所有从根节点到叶子节点的路径
print(binaryTreePaths(root))  # 输出结果为 ['1->2->5', '1->3']

方法2:迭代
# 完全二叉树节点定义
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def binaryTreePaths(root):
    if not root:
        return []

    stack = [(root, str(root.val))]  # 使用栈进行深度优先搜索,同时记录节点和路径

    paths = []
    while stack:
        node, path = stack.pop()
        if not node.left and not node.right:  # 到达叶子节点
            paths.append(path)
        if node.right:
            stack.append((node.right, path + '->' + str(node.right.val))
        if node.left:
            stack.append((node.left, path + '->' + str(node.left.val))

    return paths

# 示例:
# 构建一个二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.right = TreeNode(5)

# 返回所有从根节点到叶子节点的路径
print(binaryTreePaths(root))  # 输出结果为 ['1->2->5', '1->3']

九.左叶子之和

48.计算给定二叉树的所有左叶子之和

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sumOfLeftLeaves(self, root):
        if root is None:
            return 0
        if root.left is None and root.right is None:
            return 0
        
        leftValue = self.sumOfLeftLeaves(root.left)  # 左
        if root.left and not root.left.left and not root.left.right:  # 左子树是左叶子的情况
            leftValue = root.left.val
            
        rightValue = self.sumOfLeftLeaves(root.right)  # 右

        sum_val = leftValue + rightValue  # 中
        return sum_val

十.找树左下角的值

给定一个二叉树,在树的最后一行找到最左边的值。

方法一
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def findBottomLeftValue(root):
    queue = [root]
    while queue:
        node = queue.pop(0)  # 弹出队首节点
        if node.right:  # 先将右节点加入队列
            queue.append(node.right)
        if node.left:  # 再将左节点加入队列,这样保证左节点先出队
            queue.append(node.left)
    return node.val  # 最后出队的节点即为最底层最左边的节点

注意:由于要得到最左边的数据,则要先将右节点加入队列,再加入左节点,这样才能保证左节点先出列

方法二.

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

def findBottomLeftValue(root):
    def dfs(node, depth, result):
        if not node:
            return
        if depth > result[1]:  # 如果当前深度大于记录的最大深度,则更新结果
            result[0] = node.val
            result[1] = depth
        dfs(node.left, depth + 1, result)  # 先递归左子树
        dfs(node.right, depth + 1, result)  # 再递归右子树

    result = [root.val, 0]  # 初始化结果,[节点值, 最大深度]
    dfs(root, 1, result)  # 从根节点开始递归
    return result[0]  # 返回最底层最左边节点的值
其实和层序遍历一样,只是这一次只用录入第一个数就可以了,因此可以通过记录depth来确定是否是第一次到达该层,如果是则将第一个数记录,如果不是就继续遍历.

十一.路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False

        # 到达叶子节点时检查路径和是否等于目标和
        if not root.left and not root.right and root.val == targetSum:
            return True

        # 递归遍历左右子树,更新目标和为 targetSum 减去当前节点值
        return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)

十二.从中序与后序遍历序列构造二叉树

根据一棵树的中序遍历与后序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

例如,给出

  • 中序遍历 inorder = [9,3,15,20,7]

  • 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树:

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        if not inorder or not postorder:
            return None
        
        root_val = postorder.pop()
        root = TreeNode(root_val)
        
        root_idx = inorder.index(root_val)
        
        root.right = self.buildTree(inorder[root_idx + 1:], postorder)
        root.left = self.buildTree(inorder[:root_idx], postorder)
        
        return root
这里解释一下:通过后序遍历我们可以知道在最后的数就是树头结点然后通过中序遍历将其分隔为3部分,分别为9 3 15,20,7以及将后序遍历删掉最后一个数既变为了9,15,7,20然后分为左边和右边由于是后序遍历,因此应该将右边先进行遍历,因为后序遍历的最后一个数是上一节点的最大右儿子树的头结点.

十三.最大二叉树

给定一个不重复的整数数组 nums最大二叉树 可以用下面的算法从 nums 递归地构建:
  1. 创建一个根节点,其值为 nums 中的最大值。
  2. 递归地在最大值 左边子数组前缀上 构建左子树。
  3. 递归地在最大值 右边子数组后缀上 构建右子树。
返回 nums 构建的 *最大二叉树*
def constructMaximumBinaryTree(nums):
    if not nums:
        return None
    
    max_val = max(nums)
    max_index = nums.index(max_val)
    
    root = TreeNode(max_val)
    root.left = constructMaximumBinaryTree(nums[:max_index])
    root.right = constructMaximumBinaryTree(nums[max_index + 1:])
    
    return root

十四.合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

def mergeTrees(t1, t2):
    if not t1:
        return t2
    if not t2:
        return t1
    
    root = TreeNode(t1.val + t2.val)
    root.left = mergeTrees(t1.left, t2.left)
    root.right = mergeTrees(t1.right, t2.right)
    
    return root

十五.验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。

  • 节点的右子树只包含大于当前节点的数。

  • 所有左子树和右子树自身必须也是二叉搜索树。

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def helper(node, min_val, max_val):
            if not node:
                return True
            
            if node.val <= min_val or node.val >= max_val:
                return False
            
            return helper(node.left, min_val, node.val) and helper(node.right, node.val, max_val)
        
        return helper(root, float('-inf'), float('inf'))

十六.二叉搜索树的最小绝对差

给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。

class Solution:
    def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
        self.prev = None
        self.min_diff = float('inf')
        
        def inorder_traversal(node):
            if not node:
                return
            
            inorder_traversal(node.left)
            
            if self.prev is not None:
                self.min_diff = min(self.min_diff, node.val - self.prev)
            
            self.prev = node.val
            
            inorder_traversal(node.right)
        
        inorder_traversal(root)
        
        return self.min_diff

七.回溯算法

一.组合:

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

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

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        # 定义回溯函数
        def backtrack(combination, start, k):
            # 如果 k 等于 0,表示已经生成了一个长度为 k 的组合,将其添加到结果列表中
            if k == 0:
                result.append(combination[:])  # 将当前组合的副本加入结果列表
                return
            # 遍历可选的数字范围
            for i in range(start, n + 1):
                combination.append(i)  # 将当前数字 i 添加到组合中
                backtrack(combination, i + 1, k - 1)  # 递归调用,更新起始位置和 k
                combination.pop()  # 回溯,将最后添加的数字移除,尝试下一个数字

        result = []  # 初始化结果列表
        backtrack([], 1, k)  # 调用回溯函数,传入空的组合、起始位置为 1、需要生成的数字个数为 k
        return result  # 返回结果列表
首先定义了一个内部函数 backtrack,该函数接收三个参数:combination 表示当前已经生成的组合;start 表示当前的起始位置;k 表示还需要生成的数字个数。
backtrack 函数中,首先判断如果 k 的值为 0,说明已经生成了一个长度为 k 的组合,将其添加到结果列表 result 中,并返回。
然后,使用一个循环来遍历从 start 到 n 的数字,表示当前可选的数字范围。在每次循环中,将当前数字 i 添加到 combination 中,并递归调用 backtrack 函数,传入更新后的 combinationi + 1 作为新的起始位置,以及 k - 1 来表示还需要生成的数字个数减少了一个。
递归调用完成后,需要将最后添加的数字从 combination 中移除,以便进行下一次迭代。
最后,在主函数 combine 中,初始化结果列表 result,调用 backtrack 函数并传入空的 combination、起始位置为 1,以及需要生成的数字个数为 k。
最终,返回结果列表 result,其中包含了所有符合要求的数字组合。

二. 组合总和 III

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9

  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        def backtrack(start, k, n, path):
            # 当 k 和 n 都为 0 时,表示已经生成了一个满足条件的组合,将其添加到结果列表中
            if k == 0 and n == 0:
                result.append(path[:])  # 将当前组合的副本加入结果列表
                return
            # 当 k 或 n 小于 0 时,表示当前组合不符合要求,直接返回
            if k < 0 or n < 0:
                return
            # 遍历可选的数字范围,起始位置从 start 开始,上限为 9
            for i in range(start, 10):
                path.append(i)  # 将当前数字 i 添加到组合中
                # 递归调用,更新起始位置、k 和 n
                backtrack(i + 1, k - 1, n - i, path)
                path.pop()  # 回溯,将最后添加的数字移除,尝试下一个数字

        result = []  # 初始化结果列表
        backtrack(1, k, n, [])  # 调用回溯函数,传入起始位置为 1、需要生成的数字个数为 k、目标和为 n 的参数
        return result  # 返回结果列表
在回溯过程中,会根据不同的情况进行判断:
  • 如果 kn 都为 0,表示已经生成了一个满足条件的组合,将其添加到结果列表中。
  • 如果 kn 小于 0,表示当前组合不符合要求,直接返回。
  • 否则,遍历可选的数字范围,从 start 到 9,依次将数字添加到组合中,然后递归调用 backtrack 函数,更新起始位置、kn
每次递归结束后,会执行回溯操作,将最后添加的数字移除,以尝试下一个数字。这样可以确保得到所有符合条件的组合。
最终,在主函数 combinationSum3 中,初始化一个空的结果列表 result,然后调用 backtrack 函数,传入起始位置为 1、需要生成的数字个数为 k、目标和为 n,以及一个空的组合 []。最后,返回结果列表。

三.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []  # 如果输入的数字字符串为空,则返回空列表

        # 定义电话号码按键与字母的映射关系
        phone_map = {
            '2': ['a', 'b', 'c'],
            '3': ['d', 'e', 'f'],
            '4': ['g', 'h', 'i'],
            '5': ['j', 'k', 'l'],
            '6': ['m', 'n', 'o'],
            '7': ['p', 'q', 'r', 's'],
            '8': ['t', 'u', 'v'],
            '9': ['w', 'x', 'y', 'z']
        }

        def backtrack(combination, next_digits):
            # 如果没有剩余的数字需要处理,则将当前组合加入到输出列表中
            if not next_digits:
                output.append(combination)
            else:
                # 遍历当前数字按键对应的所有字母,并进行递归调用
                for letter in phone_map[next_digits[0]]:
                    # 将当前字母添加到当前组合中,并处理下一个数字按键
                    backtrack(combination + letter, next_digits[1:])

        output = []  # 初始化输出列表
        backtrack('', digits)  # 调用回溯函数,初始组合为空字符串
        return output  # 返回最终的输出列表

四.分隔回文串:

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def is_palindrome(sub):
            return sub == sub[::-1]  # 判断一个字符串是否为回文字符串

        def backtrack(start, path):
            if start == len(s):  # 当遍历到字符串末尾时,将当前分割方案加入结果列表
                res.append(path[:])
                return

            for end in range(start + 1, len(s) + 1):  # 从当前位置向后遍历所有可能的子串
                if is_palindrome(s[start:end]):  # 如果子串是回文字符串
                    path.append(s[start:end])  # 将子串加入当前分割方案
                    backtrack(end, path)  # 递归处理剩余部分
                    path.pop()  # 回溯,撤销选择,尝试下一个子串

        res = []  # 初始化结果列表
        backtrack(0, [])  # 调用回溯函数,初始起始位置为0,初始路径为空
        return res  # 返回最终的结果列表
主要逻辑:主要的逻辑在 backtrack 函数中,该函数通过回溯的方式搜索所有可能的回文分割方案。从起始位置开始遍历字符串 s,不断扩展子串范围,如果扩展出的子串是回文字符串,则将其加入当前的分割方案,并递归处理剩余部分。在递归结束后,需要进行回溯操作,撤销选择,以尝试下一个子串

五.复制IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201""192.168.1.1" 是 有效的 IP 地址,但是"0.011.255.245"、"192.168.1.312" 和 "192.168@1.1"是 无效的 IP 地址。

from typing import List

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        def backtrack(start, path, parts):
            if start == len(s) and parts == 4:
                res.append(".".join(path))
                return

            if parts > 4:
                return

            for end in range(start + 1, min(start + 4, len(s) + 1)):
                if s[start:end] and 0 <= int(s[start:end]) <= 255 and (s[start:end] == "0" or s[start] != "0"):
                    backtrack(end, path + [s[start:end]], parts + 1)

        res = []
        backtrack(0, [], 0)
        return res

解析:这题主要是一个深搜问题,part代表的是层数每多一层path就会相应的增加一组组合,为了确保每一层的for循环不会重复,会在每次在回溯的时候传入end,保证end在上一层循环之后增加,每当有一层的for循环遍历完后都要进行一次判断,既是否是第四层的时候遍历完的,因为只有这样才会是合法的IP地址,并且每一层要进行一次判断,既该层IP是否>= 0且<= 255并且每一层的IP地址的0不能在除0以外的数的前面

六.全排列.

给定一个可包含重复数字的序列nums,按任意顺序 返回所有不重复的全排列。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def backtrack(start, path):
            if len(path) == len(nums):
                res.append(path[:])
                return

            for i in range(len(nums)):
                if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
                    continue

                used[i] = True
                path.append(nums[i])
                backtrack(i+1, path)
                used[i] = False
                path.pop()

        res = []
        used = [False] * len(nums)
        nums.sort()
        backtrack(0, [])
        return res
该题需要先对数组进行排序,确保相同的数字是挨在一起的,然后会定义一个数组used(该数组长度和传入的数组相同),接着使用一个 for 循环遍历数组 nums,对于每个元素,检查是否已经被使用过或者之前的相同元素还未被使用,如果是,则跳过当前循环,避免生成重复的排列当递归返回后(即回溯到上一层),需要将当前元素的使用状态重置,并且将其从当前部分排列中移除,以便进行下一次尝试。

七.重新安排行程

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"]["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        graph = defaultdict(list)
        for start, end in tickets:
            graph[start].append(end)

        for start, destinations in graph.items():
            destinations.sort(reverse=True)

        route = []
        def visit_airport(airport):
            while graph[airport]:
                visit_airport(graph[airport].pop())
            route.append(airport)

        visit_airport('JFK')
        return route[::-1]
该题可能有点不好理解,大意其实就是要将全部的票用完,并返回行程最短的方案,路程的长短和票的字符ascll码的差距是成比例关系的,该题其实就是一个一笔画问题(【Leetcode】Python版每日一题(20200827):332. 重新安排行程(欧拉路径)_哔哩哔哩_bilibili)走过的路就要将其删掉,怎样删掉是个问题,defaultdict(list) 创建了一个空的字典 graph,用于存储每个起始地点对应的目的地列表。对于图中每个起始地点,按目的地进行降序排序,以便在后续访问时按照字母顺序取目的地,定义了一个内部函数 visit_airport,用于递归访问机场。从起始地点 JFK 开始,不断地访问下一个目的地,直到无法再继续前进为止,在 visit_airport 函数中,通过 while 循环不断地将下一个目的地弹出并递归访问,直到当前机场没有下一个目的地为止。在此过程中,递归调用确保了按照题目要求找到完整的行程路线, 将访问过的机场逆序添加到列表 route 中。
这里解释一下为什么要逆序:
具体来说,在DFS的过程中,当我们到达一个机场时,会不断地选择下一个目的地进行访问,直到不能再前进为止。由于我们是按照字母顺序排序目的地的,所以在递归访问的过程中,会先选择字母顺序靠后的目的地进行访问; 当无法再前进时,说明当前机场是该行程的结束点,我们将其加入路线列表中。这样,由于深度优先搜索的特性,最先访问到的结束点会排在列表的最后,而最后访问到的结束点会排在列表的最前面,因此最后得到的路线列表是逆序的。

八.N皇后

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def solve(row, cols, pie, na):
            if row == n:
                res.append(board[:])
                return
            for col in range(n):
                if col not in cols and row+col not in pie and row-col not in na:
                    board[row] = '.' * col + 'Q' + '.' * (n - col - 1)
                    solve(row + 1, cols | {col}, pie | {row+col}, na | {row-col})
        
        res = []
        board = ['.' * n for _ in range(n)]
        solve(0, set(), set(), set())
        return res

题目:按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

解释:N皇后问题的规律为每一行都有且只有一个皇后不予其他行的皇后的列数重合,由此推出每一行只能放一个皇后才可以解决该问题,该题要求每个皇后不能在同一个斜线上,再通一些线上的规律是啥呢?如果是/这种斜线,可以发他们的行数 - 他们的列数是相等的, \反之则可以发现他们的行数 + 他们的列数是相等的,由此可以添加两个数组用来存储出想过的那条斜线的行河埒打的关系,在每次加入数组时做个判断.

八.贪心

一.分发饼干

题目:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
      g.sort() 
      s.sort() 
      satisfied_children = 0 
      i = 0 
      j = 0 
      while i < len(g) and j < len(s):
       if s[j] >= g[i]:
          satisfied_children += 1
          i += 1 
       j += 1
      return satisfied_children
解释:该题比较简单,但可以初步体现出来贪心的思想局部最优来推导出全局最优贪得就是饼干尽可能多的满足更多的孩子

二.摆动数列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        n = len(nums)
        if n < 2:
            return n
        
        count = 1  # 初始化最长摆动序列长度为1
        prev_diff = 0  # 初始化前一个差值为0
        
        for i in range(1, n):
            diff = nums[i] - nums[i-1]
            if (diff > 0 and prev_diff <= 0) or (diff < 0 and prev_diff >= 0):
                count += 1
                prev_diff = diff
        
        return count
解释:该题的阶梯思路不唯一,可以用动态规划但我们这里用贪心来做:
  1. 初始化计数器count1,表示最长摆动子序列的长度至少为1
  2. 遍历数组nums,从第二个元素开始。
  3. 对于当前元素nums[i]和前一个元素nums[i-1],计算它们的差值diff
  4. 如果diff不为0(即相邻元素不相等),并且diff与前一个diff符号不同或者前一个diff0,则将count1
  5. 遍历完成后,count即为最长摆动子序列的长度。

三.最大子序和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。

from typing import List

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if not nums:
            return 0
        
        max_sum = current_sum = nums[0]
        
        for num in nums[1:]:
            current_sum = max(num, current_sum + num)
            max_sum = max(max_sum, current_sum)
        
        return max_sum
解释:
  1. 初始化两个变量max_sumcurrent_sum,它们都初始化为数组的第一个元素nums[0],这样可以保证算法的初始状态。
  2. 接下来使用循环遍历数组nums,从第二个元素开始,即索引为1
  3. 在循环中,对于数组中的每个元素num,我们比较当前元素num和当前元素与前一个元素相加的结果current_sum+num的大小:
    • 如果当前元素num大于current_sum+num,说明当前元素num本身比之前的子序列加上当前元素的结果更大,因此更新current_sum为当前元素num
    • 如果当前元素num小于或等于current_sum+num,说明之前的子序列加上当前元素的结果更大,因此更新current_sumcurrent_sum+num

四.买卖股票的最佳时机

题目:给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

from typing import List

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        
        max_profit = 0
        
        for i in range(1, len(prices)):
            if prices[i] > prices[i-1]:
                max_profit += prices[i] - prices[i-1]
        
        return max_profit
解释:
  1. 使用循环遍历价格列表prices,从第二个价格开始,即索引为1
  2. 在循环中,对于列表中的每个价格prices[i],与其前一个价格prices[i-1]进行比较:
    • 如果当前价格prices[i]大于前一个价格prices[i-1],说明可以在前一天买入后一天卖出,此时计算并累加这两天的利润,即prices[i] - prices[i-1],并将结果加到max_profit中。
    • 如果当前价格prices[i]小于或等于前一个价格prices[i-1],则不进行交易,因为买入价格比卖出价格高,不会有利润。
  3. 循环结束后,max_profit即为最大利润,将其返回即可。

五.跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        max_reach = 0
        
        for i in range(len(nums)):
            if i > max_reach:
                return False
            max_reach = max(max_reach, i + nums[i])
        
        return True

六.跳跃游戏二

给定一个长度为 n0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]

  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

from typing import List

class Solution:
    def jump(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:
            return 0
        
        cur_max_reach = 0
        next_max_reach = 0
        steps = 0
        
        for i in range(n-1):
            next_max_reach = max(next_max_reach, i + nums[i])
            
            if i == cur_max_reach:
                steps += 1
                cur_max_reach = next_max_reach
                
        if cur_max_reach < n-1:
            return -1
        
        return steps

解释:这段代码的思路是使用贪心的方法,在每一步选择下一步能够跳跃的最远位置,并更新步数。通过遍历列表来模拟跳跃的过程,最终得到到达最后一个位置所需的最小跳跃次数
  1. 首先,获取列表nums的长度n,并检查如果列表长度为1,则直接返回0,因为不需要跳跃。
  2. 初始化变量cur_max_reach和next_max_reach为0,分别表示当前步数能够到达的最远位置和下一步能够到达的最远位置,以及初始化变量steps为0,表示跳跃的步数。
  3. 使用循环遍历列表nums中的每个位置i,从0开始直到倒数第二个位置,因为在循环中需要访问到i+1。
  4. 在循环中,更新next_max_reach为当前位置i加上当前位置能够跳跃的最远距离nums[i]和next_max_reach的较大值。
  5. 检查如果当前位置i等于cur_max_reach,说明已经到达当前步数能够到达的最远位置,此时需要增加步数steps,并更新cur_max_reach为next_max_reach。
  6. 循环结束后,如果cur_max_reach仍然小于n-1,说明无法到达最后一个位置,返回-1。
  7. 否则,返回步数steps,表示到达最后一个位置所需的最小跳跃次数。

七.分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。

  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        candies = [1] * n

        for i in range(1, n):
            if ratings[i] > ratings[i - 1]:
                candies[i] = candies[i - 1] + 1

        for i in range(n - 2, -1, -1):
            if ratings[i] > ratings[i + 1] and candies[i] <= candies[i + 1]:
                candies[i] = candies[i + 1] + 1

        return sum(candies)
解释:这段代码的思路是从左到右遍历一次列表ratings,然后从右到左再遍历一次,确保每个孩子都获得了至少与其相邻孩子相比更多的糖果。最终得到分发糖果的总数。
  1. 获取列表ratings的长度n,并初始化一个长度为n的糖果列表candies,其中每个位置初始值都为1,表示每个孩子至少分到一个糖果。
  2. 第一个循环从索引1开始遍历到n-1,判断如果当前孩子的评分ratings[i]大于前一个孩子的评分ratings[i-1],则将当前孩子的糖果数设为前一个孩子糖果数加1,即candies[i] = candies[i-1] + 1。
  3. 第二个循环从倒数第二个孩子开始遍历到第一个孩子,倒序遍历是为了处理右边孩子的情况。在循环中,如果当前孩子的评分ratings[i]大于后一个孩子的评分ratings[i+1],并且当前孩子的糖果数小于等于后一个孩子的糖果数,则将当前孩子的糖果数设为后一个孩子糖果数加1,即candies[i] = candies[i+1] + 1。
  4. 最后,返回糖果列表candies中所有元素的总和,即分发糖果的总数。

八.柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false

class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        wukuai = 0
        shikuai = 0
        ershikuai = 0
        for i in range(len(bills)):
            if bills[i] == 5:
                wukuai += 1
            elif bills[i] == 10:
                wukuai -= 1
                shikuai += 1
            elif bills[i] == 20 and shikuai > 0:
                wukuai -= 1
                shikuai -= 1
            elif bills[i] == 20 and shikuai == 0:
                wukuai -= 3
            if wukuai < 0 :
                return False
        return True 

九.根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        people.sort(key=lambda x: (-x[0], x[1]))
        result = []
        for p in people:
            result.insert(p[1], p)
        return result

解释: 该题可以使用贪心的思维来做,首先对人员列表进行排序,排序规则是先按身高降序排列,如果身高相同,则按照在队列中的位置升序排列。这样排序的目的是为了确保在构建队列时,先排列身高较高的人,并且按照其在队列中的位置插入,使得身高较矮的人能够正确地站在他们前面,同时不影响身高较高的人的位置。接下来,代码创建了一个空列表 result 作为结果队列。然后遍历排序后的人员列表 people,对于每个人的信息 p,使用 insert 方法将其插入到 result列表的 p[1] 位置。由于按照身高降序排列,因此身高较高的人会先被插入到 result 中,而身高相同的人则根据其在队列中的位置顺序插入。

十.用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``startx``end, 且满足 xstart ≤ x ≤ x``end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points返回引爆所有气球所必须射出的 最小 弓箭数

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        if not points:
            return 0

        points.sort(key=lambda x: x[1])  
        arrows = 1
        end = points[0][1]

        for start, p_end in points:
            if start > end:  
                arrows += 1
                end = p_end

        return arrows
解释:
  1. 首先,对气球的数组 points 按照每个气球的结束位置进行排序,这样可以保证后面的气球一定在前面的气球之后。这个排序的目的是为了方便后续的判断。
  2. 初始化箭的数量为 1,并且设置变量 end 为第一个气球的结束位置。这是因为无论如何,至少需要一支箭。
  3. 遍历排序后的气球数组 points,对于每个气球的起始位置和结束位置 (start, p_end):
    • 如果当前气球的起始位置 start 大于变量 end,说明当前气球与之前的气球没有交集,需要再次射箭。此时箭的数量加一,并且更新 end 为当前气球的结束位置 p_end,表示需要再次射箭。
    • 如果当前气球的起始位置 start 小于或等于变量 end,说明当前气球与之前的气球有交集,不需要额外射箭。
  4. 最终返回箭的数量,即为最少需要多少支箭才能将所有气球都击穿。

十一.无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        if not intervals:
            return 0

        intervals.sort(key=lambda x: x[1]) 
        end = intervals[0][1]
        remove_count = 0

        for i in range(1, len(intervals)):
            if intervals[i][0] < end:
                remove_count += 1
            else:
                end = intervals[i][1]

        return remove_count
解释:
  1. 首先,对区间数组 intervals 按照每个区间的结束位置进行排序,这样可以保证后面的区间一定在前面的区间之后。这个排序的目的是为了方便后续的判断。
  2. 初始化变量 end 为第一个区间的结束位置,同时设置移除区间的数量为 0。
  3. 遍历排序后的区间数组 intervals,对于每个区间 (start, p_end):
    • 如果当前区间的起始位置 start 小于变量 end,说明当前区间与之前的区间有重叠,需要移除当前区间。此时移除区间的数量加一。
    • 如果当前区间的起始位置 start 大于或等于变量 end,说明当前区间与之前的区间没有重叠,更新 end 为当前区间的结束位置 p_end。
  4. 最终返回移除区间的数量,即为最少需要移除多少个区间才能使剩下的区间不重叠。

十二.监控二叉树

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

class Solution:
    def minCameraCover(self, root: Optional[TreeNode]) -> int:
        def dfs(node):
            if node == None:
                return inf, 0, 0
            l_choose, l_by_father, l_by_children = dfs(node.left)
            r_choose, r_by_father, r_by_children = dfs(node.right)
            choose = min(l_choose, l_by_father, l_by_children) + min(r_choose, r_by_father, r_by_children) + 1
            by_father = min(l_by_children, l_choose) + min(r_by_children, r_choose)
            by_children = min(l_choose + r_by_children, l_by_children + r_choose, l_choose + r_choose)
            return choose, by_father, by_children
        choose, _, by_children = dfs(root)
        return min(choose, by_children)
解释:
  1. 定义了一个递归函数 dfs(node),用于遍历二叉树节点,并返回三个值,分别是:在当前节点下,覆盖整棵子树所需的最小摄像头数量 choose、当前节点由其父节点所覆盖的情况下,覆盖整棵子树所需的最小摄像头数量 by_father、当前节点由其子节点所覆盖的情况下,覆盖整棵子树所需的最小摄像头数量 by_children
  2. 在递归函数中,对于空节点,直接返回三个值都为无穷大。
  3. 对于非空节点,分别递归计算其左右子树的情况,并将结果存储在 l_choose, l_by_father, l_by_childrenr_choose, r_by_father, r_by_children 中。
  4. 计算当前节点的三个值:
    • choose:选择当前节点时,需要考虑其左右子树的情况,选择最小值,并加上当前节点。
    • by_father:当前节点由其父节点覆盖时,需要考虑其左右子树的情况,选择最小值。
    • by_children:当前节点由其子节点覆盖时,需要考虑左右子树的情况,选择最小值。
  5. 最后,返回 chooseby_children 中的较小值,即为覆盖整棵树所需的最小摄像头数量。

九.动态规划

一.不同路径二

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
            return 0
        
        dp = [[0] * n for _ in range(m)]
        dp[0][0] = 1
        
        for i in range(1, m):
            if obstacleGrid[i][0] == 0:
                dp[i][0] = dp[i-1][0]
        
        for j in range(1, n):
            if obstacleGrid[0][j] == 0:
                dp[0][j] = dp[0][j-1]
        
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 0:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        
        return dp[m-1][n-1]
解释:
  1. 首先,获取矩阵的行数 m 和列数 n
  2. 如果起点 (0, 0) 或终点 (m-1, n-1) 有障碍物,则直接返回0,因为无法到达终点。
  3. 创建一个二维数组 dp 用于存储从起点到每个位置的不同路径数量,初始化所有元素为0,起点为1。
  4. 第一个循环处理第一列,如果当前位置没有障碍物,则路径数等于上一行相同列的路径数(因为只能往下走),更新 dp[i][0]
  5. 第二个循环处理第一行,如果当前位置没有障碍物,则路径数等于左边相同行的路径数(因为只能往右走),更新 dp[0][j]
  6. 最后两个嵌套循环遍历矩阵中除去第一行和第一列的其他位置,如果当前位置没有障碍物,则路径数等于上方和左方路径数之和,更新 dp[i][j]
  7. 返回 dp[m-1][n-1],即为从起点到终点的不同路径数量。

二.整数拆分

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

def integerBreak(n):
    # 创建一个数组来存储子问题的最优解
    dp = [0] * (n + 1)
    
    # 填充数组,dp[i] 表示数字 i 拆分后的最大乘积
    dp[2] = 1
    for i in range(3, n + 1):
        for j in range(1, i):
            # 尝试将 i 拆分成两部分 j 和 i-j,并比较乘积
            dp[i] = max(dp[i], max(j, dp[j]) * max(i - j, dp[i - j]))
    
    return dp[n]
解释:
  1. 创建一个长度为 n+1 的数组 dp,其中 dp[i] 表示数字 i 拆分后的最大乘积。
  2. 首先初始化 dp[2] = 1,因为题目要求至少拆分为两个正整数,所以数字 2 只能拆分为 1+1,乘积为 1。
  3. 然后从数字 3 开始遍历到 n,对每个数字 i,我们尝试将其拆分为两部分 j 和 i-j(其中 j 取值范围为 1 到 i-1),并计算乘积。
  4. 对于拆分的每种情况,我们计算乘积并与当前的最大乘积 dp[i] 进行比较,保留其中的最大值。
  5. 最终,dp[n] 就是整数 n 拆分后的最大乘积。
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值