Решение задач с LeetCode TOP-150 (Блок 5/5: Задачи 121-150)
121. Kth Largest Element in an Array
Описание: Найти k-й наибольший элемент в несортированном массиве.
import heapq
import random
def findKthLargest(nums, k):
"""
Поиск k-го наибольшего элемента
Способ 1: Используя минимальную кучу
"""
# Создаем минимальную кучу размера k
heap = []
for num in nums:
if len(heap) < k:
heapq.heappush(heap, num)
elif num > heap[0]:
heapq.heappushpop(heap, num)
return heap[0]
def findKthLargestQuickSelect(nums, k):
"""
Способ 2: QuickSelect (быстрый выбор)
"""
def partition(left, right, pivot_index):
"""Разделение массива относительно опорного элемента"""
pivot_value = nums[pivot_index]
# Перемещаем опорный элемент в конец
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
store_index = left
for i in range(left, right):
if nums[i] < pivot_value:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1
# Возвращаем опорный элемент на правильное место
nums[right], nums[store_index] = nums[store_index], nums[right]
return store_index
def quickselect(left, right, k_smallest):
"""Рекурсивный QuickSelect"""
if left == right:
return nums[left]
# Выбираем случайный опорный элемент
pivot_index = random.randint(left, right)
# Разделяем и получаем позицию опорного элемента
pivot_index = partition(left, right, pivot_index)
# Нашли k-й наименьший элемент
if k_smallest == pivot_index:
return nums[k_smallest]
elif k_smallest < pivot_index:
return quickselect(left, pivot_index - 1, k_smallest)
else:
return quickselect(pivot_index + 1, right, k_smallest)
# k-й наибольший = (n-k)-й наименьший
return quickselect(0, len(nums) - 1, len(nums) - k)
122. IPO
Описание: Выбрать проекты для максимизации капитала после k инвестиций.
import heapq
def findMaximizedCapital(k, w, profits, capital):
"""
Максимизация капитала после k инвестиций
"""
n = len(profits)
projects = list(zip(capital, profits))
projects.sort() # Сортируем по требуемому капиталу
max_heap = [] # Максимальная куча для доступных проектов
i = 0
for _ in range(k):
# Добавляем все проекты, которые можем себе позволить
while i < n and projects[i][0] <= w:
heapq.heappush(max_heap, -projects[i][1]) # Отрицательное значение для max-heap
i += 1
if not max_heap:
break
# Берем самый прибыльный проект
w += -heapq.heappop(max_heap)
return w
123. Find K Pairs with Smallest Sums
Описание: Найти k пар с наименьшими суммами из двух массивов.
import heapq
def kSmallestPairs(nums1, nums2, k):
"""
Поиск k пар с наименьшими суммами
"""
if not nums1 or not nums2:
return []
result = []
heap = []
# Добавляем первые k пар (nums1[i] + nums2[0])
for i in range(min(k, len(nums1))):
heapq.heappush(heap, (nums1[i] + nums2[0], i, 0))
while heap and len(result) < k:
_, i, j = heapq.heappop(heap)
result.append([nums1[i], nums2[j]])
# Добавляем следующую пару из того же элемента nums1
if j + 1 < len(nums2):
heapq.heappush(heap, (nums1[i] + nums2[j + 1], i, j + 1))
return result
124. Find Median from Data Stream
Описание: Реализовать структуру данных для нахождения медианы из потока чисел.
import heapq
class MedianFinder:
def __init__(self):
"""
Инициализация структуры данных
Две кучи: max_heap для левой половины, min_heap для правой
"""
self.max_heap = [] # Левая половина (отрицательные значения для max-heap)
self.min_heap = [] # Правая половина (min-heap)
def addNum(self, num):
"""
Добавление числа в структуру
"""
# Добавляем в max_heap (левую половину)
heapq.heappush(self.max_heap, -num)
# Балансировка: перемещаем максимальный элемент из левой в правую
max_left = -heapq.heappop(self.max_heap)
heapq.heappush(self.min_heap, max_left)
# Если правая куча стала больше левой, балансируем обратно
if len(self.min_heap) > len(self.max_heap):
min_right = heapq.heappop(self.min_heap)
heapq.heappush(self.max_heap, -min_right)
def findMedian(self):
"""
Нахождение медианы
"""
if len(self.max_heap) > len(self.min_heap):
return -self.max_heap[0]
else:
return (-self.max_heap[0] + self.min_heap[0]) / 2
# Альтернативная реализация с приоритетами
class MedianFinder2:
def __init__(self):
self.small = [] # Max-heap (отрицательные значения)
self.large = [] # Min-heap
def addNum(self, num):
# Всегда добавляем в small, затем балансируем
heapq.heappush(self.small, -num)
# Убедимся, что все элементы в small <= все элементы в large
if self.small and self.large and (-self.small[0] > self.large[0]):
val = -heapq.heappop(self.small)
heapq.heappush(self.large, val)
# Балансировка размеров
if len(self.small) > len(self.large) + 1:
val = -heapq.heappop(self.small)
heapq.heappush(self.large, val)
elif len(self.large) > len(self.small):
val = heapq.heappop(self.large)
heapq.heappush(self.small, -val)
def findMedian(self):
if len(self.small) > len(self.large):
return -self.small[0]
return (-self.small[0] + self.large[0]) / 2
125. Add Binary
Описание: Сложить два бинарных числа, представленных строками.
def addBinary(a, b):
"""
Сложение бинарных чисел
"""
result = []
carry = 0
i, j = len(a) - 1, len(b) - 1
while i >= 0 or j >= 0 or carry:
# Получаем цифры (или 0 если строка закончилась)
digit_a = int(a[i]) if i >= 0 else 0
digit_b = int(b[j]) if j >= 0 else 0
# Суммируем с переносом
total = digit_a + digit_b + carry
digit = total % 2
carry = total // 2
result.append(str(digit))
i -= 1
j -= 1
# Результат в обратном порядке
return ''.join(reversed(result))
# Альтернативное решение с использованием встроенных функций
def addBinary2(a, b):
return bin(int(a, 2) + int(b, 2))[2:]
126. Reverse Bits
Описание: Перевернуть биты 32-битного беззнакового целого числа.
def reverseBits(n):
"""
Переворот битов 32-битного числа
"""
result = 0
for i in range(32):
# Сдвигаем результат влево
result <<= 1
# Добавляем младший бит n
result |= (n & 1)
# Сдвигаем n вправо
n >>= 1
return result
# Оптимизированная версия
def reverseBitsOptimized(n):
n = ((n & 0xffff0000) >> 16) | ((n & 0x0000ffff) << 16)
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8)
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4)
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2)
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1)
return n
127. Number of 1 Bits
Описание: Подсчитать количество единичных битов в числе (Hamming weight).
def hammingWeight(n):
"""
Подсчет единичных битов (Hamming weight)
"""
count = 0
while n:
# Удаляем младший установленный бит
n &= (n - 1)
count += 1
return count
# Альтернативные способы
def hammingWeight2(n):
return bin(n).count('1')
def hammingWeight3(n):
count = 0
while n:
count += n & 1
n >>= 1
return count
128. Single Number
Описание: Найти элемент, встречающийся один раз в массиве, где все остальные встречаются дважды.
def singleNumber(nums):
"""
Поиск единственного элемента (используя XOR)
"""
result = 0
for num in nums:
result ^= num
return result
129. Single Number II
Описание: Найти элемент, встречающийся один раз в массиве, где все остальные встречаются трижды.
def singleNumber(nums):
"""
Поиск единственного элемента (все остальные встречаются 3 раза)
"""
ones = 0 # Биты, появившиеся один раз
twos = 0 # Биты, появившиеся два раза
for num in nums:
# Добавляем в ones, если не в twos
ones = (ones ^ num) & ~twos
# Добавляем в twos, если не в ones
twos = (twos ^ num) & ~ones
return ones
# Альтернативное решение с подсчетом битов
def singleNumber2(nums):
result = 0
for i in range(32):
count = 0
for num in nums:
# Считаем i-й бит
if (num >> i) & 1:
count += 1
# Если количество не кратно 3, устанавливаем бит
if count % 3:
result |= (1 << i)
# Преобразуем в signed 32-bit integer
if result >= (1 << 31):
result -= (1 << 32)
return result
130. Bitwise AND of Numbers Range
Описание: Найти побитовое И всех чисел в диапазоне [left, right].
def rangeBitwiseAnd(left, right):
"""
Побитовое И чисел в диапазоне
"""
shift = 0
# Находим общий префикс
while left < right:
left >>= 1
right >>= 1
shift += 1
# Возвращаем общий префикс с добавлением нулей
return left << shift
# Альтернативное решение
def rangeBitwiseAnd2(left, right):
while left < right:
# Удаляем младший бит справа
right &= (right - 1)
return right
131. Palindrome Number
Описание: Проверить, является ли целое число палиндромом.
def isPalindrome(x):
"""
Проверка, является ли число палиндромом
"""
# Отрицательные числа не могут быть палиндромами
if x < 0:
return False
original = x
reversed_num = 0
while x > 0:
digit = x % 10
reversed_num = reversed_num * 10 + digit
x //= 10
return original == reversed_num
# Решение без использования дополнительной памяти
def isPalindrome2(x):
if x < 0 or (x % 10 == 0 and x != 0):
return False
reversed_half = 0
while x > reversed_half:
reversed_half = reversed_half * 10 + x % 10
x //= 10
return x == reversed_half or x == reversed_half // 10
132. Plus One
Описание: Увеличить большое число, представленное массивом цифр, на 1.
def plusOne(digits):
"""
Увеличение числа, представленного массивом цифр, на 1
"""
n = len(digits)
for i in range(n - 1, -1, -1):
if digits[i] < 9:
digits[i] += 1
return digits
digits[i] = 0
# Если все цифры были 9
return [1] + digits
# Рекурсивное решение
def plusOneRecursive(digits):
def helper(idx):
if idx < 0:
digits.insert(0, 1)
elif digits[idx] == 9:
digits[idx] = 0
helper(idx - 1)
else:
digits[idx] += 1
helper(len(digits) - 1)
return digits
133. Factorial Trailing Zeroes
Описание: Найти количество нулей в конце n!.
def trailingZeroes(n):
"""
Подсчет нулей в конце факториала
"""
count = 0
# Считаем количество множителей 5
while n > 0:
n //= 5
count += n
return count
# Рекурсивное решение
def trailingZeroesRecursive(n):
if n == 0:
return 0
return n // 5 + trailingZeroesRecursive(n // 5)
134. Sqrt(x)
Описание: Вычислить целочисленный квадратный корень.
def mySqrt(x):
"""
Вычисление целочисленного квадратного корня
"""
if x < 2:
return x
left, right = 1, x // 2
while left <= right:
mid = (left + right) // 2
square = mid * mid
if square == x:
return mid
elif square < x:
left = mid + 1
else:
right = mid - 1
return right # Округляем вниз
# Метод Ньютона (быстрее)
def mySqrtNewton(x):
if x < 2:
return x
guess = x // 2
while guess * guess > x:
guess = (guess + x // guess) // 2
return guess
135. Pow(x, n)
Описание: Вычислить x в степени n.
def myPow(x, n):
"""
Быстрое возведение в степень
"""
if n == 0:
return 1
if n < 0:
x = 1 / x
n = -n
result = 1
current_product = x
while n > 0:
if n % 2 == 1:
result *= current_product
current_product *= current_product
n //= 2
return result
# Рекурсивное решение
def myPowRecursive(x, n):
if n == 0:
return 1
if n < 0:
return 1 / myPowRecursive(x, -n)
half = myPowRecursive(x, n // 2)
if n % 2 == 0:
return half * half
else:
return half * half * x
136. Max Points on a Line
Описание: Найти максимальное количество точек, лежащих на одной прямой.
from collections import defaultdict
import math
def maxPoints(points):
"""
Максимальное количество точек на одной прямой
"""
n = len(points)
if n < 3:
return n
max_points = 1
for i in range(n):
slopes = defaultdict(int)
same_points = 1 # Текущая точка
for j in range(i + 1, n):
x1, y1 = points[i]
x2, y2 = points[j]
# Одинаковые точки
if x1 == x2 and y1 == y2:
same_points += 1
continue
# Вертикальная линия
if x1 == x2:
slope = float('inf')
else:
# Используем дробь для представления наклона
dx = x2 - x1
dy = y2 - y1
gcd_val = math.gcd(dx, dy)
slope = (dx // gcd_val, dy // gcd_val)
slopes[slope] += 1
# Находим максимальное количество точек для текущей точки
current_max = same_points
if slopes:
current_max = max(current_max, same_points + max(slopes.values()))
max_points = max(max_points, current_max)
return max_points
137. Climbing Stairs
Описание: Найти количество способов подняться по лестнице из n ступенек.
def climbStairs(n):
"""
Количество способов подняться по лестнице (фибоначчи)
"""
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
# Оптимизированное решение (O(1) памяти)
def climbStairsOptimized(n):
if n <= 2:
return n
prev1, prev2 = 2, 1 # для i=2 и i=1
for i in range(3, n + 1):
current = prev1 + prev2
prev2 = prev1
prev1 = current
return prev1
# Используя формулу Бине
def climbStairsFormula(n):
sqrt5 = 5 ** 0.5
phi = (1 + sqrt5) / 2
return int((phi ** (n + 1) - (1 - phi) ** (n + 1)) / sqrt5)
138. House Robber
Описание: Максимизировать сумму кражи, не грабя два соседних дома.
def rob(nums):
"""
Максимальная сумма без грабежа соседних домов
"""
if not nums:
return 0
if len(nums) == 1:
return nums[0]
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[-1]
# Оптимизированное решение
def robOptimized(nums):
prev1 = prev2 = 0
for num in nums:
current = max(prev1, prev2 + num)
prev2 = prev1
prev1 = current
return prev1
139. Word Break
Описание: Определить, можно ли разбить строку на слова из словаря.
def wordBreak(s, wordDict):
"""
Проверка возможности разбиения строки на слова из словаря
"""
word_set = set(wordDict)
n = len(s)
# dp[i] = можно ли разбить s[:i]
dp = [False] * (n + 1)
dp[0] = True # Пустая строка
for i in range(1, n + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
# С оптимизацией по максимальной длине слова
def wordBreakOptimized(s, wordDict):
word_set = set(wordDict)
max_len = max(len(word) for word in wordDict) if wordDict else 0
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for j in range(max(0, i - max_len), i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
140. Coin Change
Описание: Найти минимальное количество монет для суммы.
def coinChange(coins, amount):
"""
Минимальное количество монет для суммы
"""
# dp[i] = минимальное количество монет для суммы i
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
# Другая реализация
def coinChange2(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if i >= coin:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
141. Longest Increasing Subsequence
Описание: Найти длину самой длинной возрастающей подпоследовательности.
def lengthOfLIS(nums):
"""
Длина самой длинной возрастающей подпоследовательности
"""
if not nums:
return 0
n = len(nums)
dp = [1] * n # dp[i] = LIS заканчивающаяся на nums[i]
for i in range(n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
# Оптимизированное решение с бинарным поиском O(n log n)
def lengthOfLISOptimized(nums):
tails = [] # tails[i] = минимальное последнее значение LIS длины i+1
for num in nums:
# Бинарный поиск позиции для вставки
left, right = 0, len(tails)
while left < right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
if left == len(tails):
tails.append(num)
else:
tails[left] = num
return len(tails)
142. Triangle
Описание: Найти минимальную сумму пути от вершины до основания треугольника.
def minimumTotal(triangle):
"""
Минимальная сумма пути в треугольнике
"""
if not triangle:
return 0
n = len(triangle)
# Начинаем с предпоследней строки
for i in range(n - 2, -1, -1):
for j in range(len(triangle[i])):
triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1])
return triangle[0][0]
# С дополнительной памятью
def minimumTotal2(triangle):
if not triangle:
return 0
n = len(triangle)
dp = triangle[-1][:] # Копируем последнюю строку
for i in range(n - 2, -1, -1):
for j in range(len(triangle[i])):
dp[j] = triangle[i][j] + min(dp[j], dp[j + 1])
return dp[0]
143. Minimum Path Sum
Описание: Найти минимальную сумму пути от левого верхнего до правого нижнего угла сетки.
def minPathSum(grid):
"""
Минимальная сумма пути в сетке
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
# Инициализируем первую строку и первый столбец
for i in range(1, m):
grid[i][0] += grid[i - 1][0]
for j in range(1, n):
grid[0][j] += grid[0][j - 1]
# Заполняем остальные ячейки
for i in range(1, m):
for j in range(1, n):
grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
return grid[m - 1][n - 1]
# С дополнительной памятью
def minPathSum2(grid):
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
# Первая строка
for j in range(1, n):
dp[0][j] = dp[0][j - 1] + grid[0][j]
# Первый столбец
for i in range(1, m):
dp[i][0] = dp[i - 1][0] + grid[i][0]
# Остальные ячейки
for i in range(1, m):
for j in range(1, n):
dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])
return dp[m - 1][n - 1]
144. Unique Paths II
Описание: Найти количество уникальных путей в сетке с препятствиями.
def uniquePathsWithObstacles(obstacleGrid):
"""
Количество уникальных путей с препятствиями
"""
if not obstacleGrid or not obstacleGrid[0]:
return 0
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 j in range(1, n):
if obstacleGrid[0][j] == 0:
dp[0][j] = dp[0][j - 1]
# Первый столбец
for i in range(1, m):
if obstacleGrid[i][0] == 0:
dp[i][0] = dp[i - 1][0]
# Остальные ячейки
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]
# Оптимизированная версия с O(n) памяти
def uniquePathsWithObstaclesOptimized(obstacleGrid):
if not obstacleGrid or not obstacleGrid[0]:
return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
if obstacleGrid[0][0] == 1 or obstacleGrid[m - 1][n - 1] == 1:
return 0
dp = [0] * n
dp[0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
elif j > 0:
dp[j] += dp[j - 1]
return dp[n - 1]
145. Longest Palindromic Substring
Описание: Найти самую длинную палиндромную подстроку.
def longestPalindrome(s):
"""
Поиск самой длинной палиндромной подстроки
"""
if not s:
return ""
n = len(s)
start, max_len = 0, 1
# dp[i][j] = является ли s[i:j+1] палиндромом
dp = [[False] * n for _ in range(n)]
# Все подстроки длины 1 - палиндромы
for i in range(n):
dp[i][i] = True
# Подстроки длины 2
for i in range(n - 1):
if s[i] == s[i + 1]:
dp[i][i + 1] = True
start = i
max_len = 2
# Подстроки длины >= 3
for length in range(3, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j] and dp[i + 1][j - 1]:
dp[i][j] = True
if length > max_len:
start = i
max_len = length
return s[start:start + max_len]
# Оптимизированное решение с расширением от центра
def longestPalindromeOptimized(s):
if not s:
return ""
def expand_around_center(left, right):
"""Расширяемся от центра, пока символы совпадают"""
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1 # Длина палиндрома
start, end = 0, 0
for i in range(len(s)):
# Нечетная длина (центр - один символ)
len1 = expand_around_center(i, i)
# Четная длина (центр - два символа)
len2 = expand_around_center(i, i + 1)
length = max(len1, len2)
if length > (end - start):
start = i - (length - 1) // 2
end = i + length // 2
return s[start:end + 1]
146. Interleaving String
Описание: Проверить, является ли строка s3 интерливингом строк s1 и s2.
def isInterleave(s1, s2, s3):
"""
Проверка, является ли s3 интерливингом s1 и s2
"""
if len(s1) + len(s2) != len(s3):
return False
m, n = len(s1), len(s2)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
# Только s1
for i in range(1, m + 1):
dp[i][0] = dp[i - 1][0] and s1[i - 1] == s3[i - 1]
# Только s2
for j in range(1, n + 1):
dp[0][j] = dp[0][j - 1] and s2[j - 1] == s3[j - 1]
# Оба
for i in range(1, m + 1):
for j in range(1, n + 1):
# Проверяем, можем ли взять символ из s1 или из s2
take_from_s1 = dp[i - 1][j] and s1[i - 1] == s3[i + j - 1]
take_from_s2 = dp[i][j - 1] and s2[j - 1] == s3[i + j - 1]
dp[i][j] = take_from_s1 or take_from_s2
return dp[m][n]
147. Edit Distance
Описание: Найти минимальное количество операций для преобразования одной строки в другую.
def minDistance(word1, word2):
"""
Минимальное расстояние редактирования (Левенштейн)
"""
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Инициализация
for i in range(m + 1):
dp[i][0] = i # Удаление всех символов из word1
for j in range(n + 1):
dp[0][j] = j # Вставка всех символов в word1
# Заполнение таблицы
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] # Символы совпадают
else:
# Выбираем минимальную операцию
dp[i][j] = 1 + min(
dp[i - 1][j], # Удаление
dp[i][j - 1], # Вставка
dp[i - 1][j - 1] # Замена
)
return dp[m][n]
# Оптимизированная версия с O(n) памяти
def minDistanceOptimized(word1, word2):
m, n = len(word1), len(word2)
prev = [0] * (n + 1)
# Инициализация первой строки
for j in range(n + 1):
prev[j] = j
for i in range(1, m + 1):
curr = [0] * (n + 1)
curr[0] = i # Удаление i символов
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
curr[j] = prev[j - 1]
else:
curr[j] = 1 + min(prev[j], curr[j - 1], prev[j - 1])
prev = curr
return prev[n]
148. Best Time to Buy and Sell Stock III
Описание: Максимальная прибыль при максимум двух сделках.
def maxProfit(prices):
"""
Максимальная прибыль при максимум двух сделках
"""
if not prices:
return 0
n = len(prices)
# Первая транзакция: слева направо
first_profit = [0] * n
min_price = prices[0]
for i in range(1, n):
min_price = min(min_price, prices[i])
first_profit[i] = max(first_profit[i - 1], prices[i] - min_price)
# Вторая транзакция: справа налево
second_profit = [0] * n
max_price = prices[-1]
for i in range(n - 2, -1, -1):
max_price = max(max_price, prices[i])
second_profit[i] = max(second_profit[i + 1], max_price - prices[i])
# Находим максимальную общую прибыль
max_total = 0
for i in range(n):
max_total = max(max_total, first_profit[i] + second_profit[i])
return max_total
# Оптимизированная версия
def maxProfitOptimized(prices):
if not prices:
return 0
# 4 состояния: после первой покупки, первой продажи, второй покупки, второй продажи
buy1 = buy2 = float('-inf')
sell1 = sell2 = 0
for price in prices:
# Первая покупка
buy1 = max(buy1, -price)
# Первая продажа
sell1 = max(sell1, buy1 + price)
# Вторая покупка (используем прибыль от первой продажи)
buy2 = max(buy2, sell1 - price)
# Вторая продажа
sell2 = max(sell2, buy2 + price)
return sell2
149. Best Time to Buy and Sell Stock IV
Описание: Максимальная прибыль при максимум k сделках.
def maxProfit(k, prices):
"""
Максимальная прибыль при максимум k сделках
"""
n = len(prices)
if k == 0 or n < 2:
return 0
# Если k достаточно велико, можно совершать любые сделки
if k >= n // 2:
profit = 0
for i in range(1, n):
if prices[i] > prices[i - 1]:
profit += prices[i] - prices[i - 1]
return profit
# dp[i][j] = максимальная прибыль после i сделок до дня j
dp = [[0] * n for _ in range(k + 1)]
for transaction in range(1, k + 1):
max_diff = -prices[0] # Максимальная разница после transaction-1 сделок
for day in range(1, n):
# Либо не совершаем сделку в день j, либо совершаем
dp[transaction][day] = max(
dp[transaction][day - 1], # Не продаем
prices[day] + max_diff # Продаем
)
# Обновляем max_diff для следующего дня
max_diff = max(max_diff, dp[transaction - 1][day] - prices[day])
return dp[k][n - 1]
# Оптимизированная версия с O(n) памяти
def maxProfitOptimized(k, prices):
n = len(prices)
if k == 0 or n < 2:
return 0
if k >= n // 2:
profit = 0
for i in range(1, n):
if prices[i] > prices[i - 1]:
profit += prices[i] - prices[i - 1]
return profit
# buy[j] = максимальная прибыль после j покупок
# sell[j] = максимальная прибыль после j продаж
buy = [float('-inf')] * (k + 1)
sell = [0] * (k + 1)
for price in prices:
for j in range(1, k + 1):
buy[j] = max(buy[j], sell[j - 1] - price)
sell[j] = max(sell[j], buy[j] + price)
return sell[k]
150. Maximal Square
Описание: Найти максимальный квадрат из '1' в бинарной матрице.
def maximalSquare(matrix):
"""
Максимальный квадрат из '1' в матрице
"""
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
dp = [[0] * (n + 1) for _ in range(m + 1)]
max_side = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if matrix[i - 1][j - 1] == '1':
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
max_side = max(max_side, dp[i][j])
return max_side * max_side
# Оптимизированная версия с O(n) памяти
def maximalSquareOptimized(matrix):
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
dp = [0] * (n + 1)
max_side = 0
prev = 0 # Для хранения dp[i-1][j-1]
for i in range(1, m + 1):
for j in range(1, n + 1):
temp = dp[j] # Сохраняем для следующей итерации
if matrix[i - 1][j - 1] == '1':
dp[j] = min(dp[j], dp[j - 1], prev) + 1
max_side = max(max_side, dp[j])
else:
dp[j] = 0
prev = temp
return max_side * max_side
Ключевые моменты пятого блока:
- Heap и приоритетные очереди (121-124): QuickSelect, IPO, медиана из потока
- Битовые операции (125-130): Двоичная арифметика, подсчет битов, битовые маски
- Математические задачи (131-136): Числа, степени, геометрия
- Динамическое программирование (137-150): Классические задачи DP
Основные алгоритмы:
- QuickSelect для k-го элемента
- Две кучи для медианы из потока
- Алгоритм Кадане для максимального подмассива
- Двоичный поиск для LIS
- Динамическое программирование в 2D
Сложности:
- Heap задачи: O(n log k) или O(n log n)
- DP задачи: O(n²) или O(nm) для 2D задач
- Битовые операции: O(1) или O(n)