46. 全排列
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
示例 1:
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40]
输出:5
提示:
1 <= stones.length <= 30
1 <= stones[i] <= 100
动态规划(自己写的)
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
n = len(stones)
target = sum(stones)//2
dp = [[0 for _ in range(target+1)] for _ in range(n)]
for j in range(1,target+1):
if j > stones[0]:
dp[0][j] = stones[0]
for i in range(n):
for j in range(1,target+1):
if j >= stones[i]:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-stones[i]] + stones[i])
else:
dp[i][j] = dp[i-1][j]
return sum(stones) - 2*dp[-1][-1]
动态规划(优化了空间和初始化的)
from typing import List
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
total = sum(stones)
target = total // 2
# 使用一维数组进行空间优化
dp = [0] * (target + 1)
for stone in stones:
# 逆序遍历以避免覆盖未处理的数据
for j in range(target, stone - 1, -1):
dp[j] = max(dp[j], dp[j - stone] + stone)
return total - 2 * dp[target]
1143. 最长公共子序列
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1
和text2
仅由小写英文字符组成。
动态规划记忆化
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n = len(text1)
m = len(text2)
# cache装饰器:实现记忆化
@cache
def dfs(i, j):
if i < 0 or j < 0:
return 0
if text1[i] == text2[j]:
return dfs(i-1, j-1) + 1
return max(dfs(i-1, j), dfs(i, j-1))
return dfs(n-1, m-1)
动态规划法
超出时间限制
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n = len(text1)
m = len(text2)
def dfs(i, j):
if i < 0 or j < 0:
return 0
if text1[i] == text2[j]:
return dfs(i-1, j-1) + 1
return max(dfs(i-1, j), dfs(i, j-1))
return dfs(n-1, m-1)
动态规划(超出内存限制)
from typing import List
import bisect
class Solution:
def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:
# 按价格升序排列;若价格相同,则按美丽值降序排列
items.sort(key=lambda x: (x[0], -x[1]))
# 构建前缀最大美丽值映射
prefix_max_beauty = {}
max_beauty = 0
for price, beauty in items:
if price not in prefix_max_beauty:
max_beauty = max(max_beauty, beauty)
prefix_max_beauty[price] = max_beauty
else:
# 如果已经有这个价格了,因为排序保证了美丽值递减,所以无需更新
pass
# 提取所有已记录的价格形成有序列表,用于二分查找
prices = sorted(prefix_max_beauty.keys())
result = []
for query in queries:
# 使用二分查找找到不大于查询价格的最大价格
idx = bisect.bisect_right(prices, query) - 1
if idx >= 0:
result.append(prefix_max_beauty[prices[idx]])
else:
result.append(0)
return result
2070. 每一个查询的最大美丽值
给你一个二维整数数组 items
,其中 items[i] = [pricei, beautyi]
分别表示每一个物品的 价格 和 美丽值 。
同时给你一个下标从 0 开始的整数数组 queries
。对于每个查询 queries[j]
,你想求出价格小于等于 queries[j]
的物品中,最大的美丽值 是多少。如果不存在符合条件的物品,那么查询的结果为 0
。
请你返回一个长度与 queries
相同的数组 answer
,其中 answer[j]
是第 j
个查询的答案。
示例 1:
输入:items = [[1,2],[3,2],[2,4],[5,6],[3,5]], queries = [1,2,3,4,5,6]
输出:[2,4,5,5,6,6]
解释:
- queries[0]=1 ,[1,2] 是唯一价格 <= 1 的物品。所以这个查询的答案为 2 。
- queries[1]=2 ,符合条件的物品有 [1,2] 和 [2,4] 。
它们中的最大美丽值为 4 。
- queries[2]=3 和 queries[3]=4 ,符合条件的物品都为 [1,2] ,[3,2] ,[2,4] 和 [3,5] 。
它们中的最大美丽值为 5 。
- queries[4]=5 和 queries[5]=6 ,所有物品都符合条件。
所以,答案为所有物品中的最大美丽值,为 6 。
示例 2:
输入:items = [[1,2],[1,2],[1,3],[1,4]], queries = [1]
输出:[4]
解释:
每个物品的价格均为 1 ,所以我们选择最大美丽值 4 。
注意,多个物品可能有相同的价格和美丽值。
示例 3:
输入:items = [[10,1000]], queries = [5]
输出:[0]
解释:
没有物品的价格小于等于 5 ,所以没有物品可以选择。
因此,查询的结果为 0 。
提示:
1 <= items.length, queries.length <= 105
items[i].length == 2
1 <= pricei, beautyi, queries[j] <= 109
动态规划
超出内存限制
class Solution:
def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:
price = lambda x:items[x][0]
beauty = lambda x:items[x][1]
items.sort(key = lambda x:(x[0],x[1]))
dp = [0 for _ in range(price(-1)+1)]
# 动态规划遍历items数组得到每个查询的最大美丽值
for i in range(len(items)):
dp[price(i)] = max(dp[price(i)],dp[price(i)-1],beauty(i))
# 再遍历一遍构成前缀最大值
for i in range(1,len(dp)):
dp[i] = max(dp[i-1],dp[i])
result = []
for query in queries:
if query <= len(dp)-1:
result.append(dp[query])
else:
result.append(dp[-1])
return result
哈希+二分
159ms 击败75.36%
from typing import List
import bisect
class Solution:
def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:
# 按价格升序排列;若价格相同,则按美丽值降序排列
items.sort(key=lambda x: (x[0], -x[1]))
# 构建前缀最大美丽值映射
prefix_max_beauty = {}
max_beauty = 0
for price, beauty in items:
if price not in prefix_max_beauty:
max_beauty = max(max_beauty, beauty)
prefix_max_beauty[price] = max_beauty
else:
# 如果已经有这个价格了,因为排序保证了美丽值递减,所以无需更新
pass
# 提取所有已记录的价格形成有序列表,用于二分查找
prices = sorted(prefix_max_beauty.keys())
result = []
for query in queries:
# 使用二分查找找到不大于查询价格的最大价格
idx = bisect.bisect_right(prices, query) - 1
if idx >= 0:
result.append(prefix_max_beauty[prices[idx]])
else:
result.append(0)
return result
2502. 设计内存分配器
给你一个整数 n
,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。
请你设计一个具备以下功能的内存分配器:
- 分配 一块大小为
size
的连续空闲内存单元并赋 idmID
。 - 释放 给定 id
mID
对应的所有内存单元。
注意:
- 多个块可以被分配到同一个
mID
。 - 你必须释放
mID
对应的所有内存单元,即便这些内存单元被分配在不同的块中。
实现 Allocator
类:
Allocator(int n)
使用一个大小为n
的内存数组初始化Allocator
对象。int allocate(int size, int mID)
找出大小为size
个连续空闲内存单元且位于 最左侧 的块,分配并赋 idmID
。返回块的第一个下标。如果不存在这样的块,返回-1
。int freeMemory(int mID)
释放 idmID
对应的所有内存单元。返回释放的内存单元数目。
示例:
输入
["Allocator", "allocate", "allocate", "allocate", "freeMemory", "allocate", "allocate", "allocate", "freeMemory", "allocate", "freeMemory"]
[[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]]
输出
[null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]
解释
Allocator loc = new Allocator(10); // 初始化一个大小为 10 的内存数组,所有内存单元都是空闲的。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 0 。内存数组变为 [1, , , , , , , , , ]。返回 0 。
loc.allocate(1, 2); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,2, , , , , , , , ]。返回 1 。
loc.allocate(1, 3); // 最左侧的块的第一个下标是 2 。内存数组变为 [1,2,3, , , , , , , ]。返回 2 。
loc.freeMemory(2); // 释放 mID 为 2 的所有内存单元。内存数组变为 [1, ,3, , , , , , , ] 。返回 1 ,因为只有 1 个 mID 为 2 的内存单元。
loc.allocate(3, 4); // 最左侧的块的第一个下标是 3 。内存数组变为 [1, ,3,4,4,4, , , , ]。返回 3 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,1,3,4,4,4, , , , ]。返回 1 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 6 。内存数组变为 [1,1,3,4,4,4,1, , , ]。返回 6 。
loc.freeMemory(1); // 释放 mID 为 1 的所有内存单元。内存数组变为 [ , ,3,4,4,4, , , , ] 。返回 3 ,因为有 3 个 mID 为 1 的内存单元。
loc.allocate(10, 2); // 无法找出长度为 10 个连续空闲内存单元的空闲块,所有返回 -1 。
loc.freeMemory(7); // 释放 mID 为 7 的所有内存单元。内存数组保持原状,因为不存在 mID 为 7 的内存单元。返回 0 。
提示:
1 <= n, size, mID <= 1000
- 最多调用
allocate
和free
方法1000
次
正确写法
class Allocator:
def __init__(self, n: int):
self.size = n
self.memory_busy = [False for _ in range(n)]
def allocate(self, size: int, mID: int) -> int:
count = 0
temp_index = -1
for i in range(self.size):
if not self.memory_busy[i]:
if count == 0:
temp_index = i
count += 1
if count >= size:
# 标记mID
for j in range(size):
self.memory_busy[temp_index + j] = mID
return temp_index
else:
count = 0
return -1
def freeMemory(self, mID: int) -> int:
freed_blocks = 0
for i in range(self.size):
if self.memory_busy[i] == mID:
self.memory_busy[i] = False
freed_blocks += 1
return freed_blocks
我的写法(太冗杂了)
class Allocator:
def __init__(self, n: int):
self.size = n
self.memory_busy = [False for _ in range(n)]
def allocate(self, size: int, mID: int) -> int:
# 找到连续为False且长度>=size的串,然后将下标赋值mID
# 开头是特殊情况特殊处理
count_state = bool(self.memory_busy[0] == False)
count = int(count_state)
temp_index = 0
for i in range(1,self.size):
cur = self.memory_busy[i]
pre = self.memory_busy[i-1]
if count_state == True:
# 如果由空闲区域变为非空闲区域
if pre == False and cur != False:
count_state = False
if count >= size:
# 标记mID,memory_busy[temp_index:temp_index+size+1]为True
for i in range(size):
self.memory_busy[temp_index+i] = mID
return temp_index
count = 0
# 连续为False就增加Count
else:
count+=1
else:
# 如果由非空闲区域变为空闲区域
if pre != False and cur == False:
count_state = True
count = 1
temp_index = i
# 最后一轮进行额外判断
if i == size-1:
if count >= size:
# 标记mID
for i in range(size):
self.memory_busy[temp_index+i] = mID
return temp_index
return -1
def freeMemory(self, mID: int) -> int:
return -1
# Your Allocator object will be instantiated and called as such:
# obj = Allocator(n)
# param_1 = obj.allocate(size,mID)
# param_2 = obj.freememory(mID)
2588. 统计美丽子数组数目
给你一个下标从 0 开始的整数数组nums
。每次操作中,你可以:
- 选择两个满足
0 <= i, j < nums.length
的不同下标i
和j
。 - 选择一个非负整数
k
,满足nums[i]
和nums[j]
在二进制下的第k
位(下标编号从 0 开始)是1
。 - 将
nums[i]
和nums[j]
都减去2k
。
如果一个子数组内执行上述操作若干次后,该子数组可以变成一个全为 0
的数组,那么我们称它是一个 美丽 的子数组。
请你返回数组 nums
中 美丽子数组 的数目。
子数组是一个数组中一段连续 非空 的元素序列。
示例 1:
输入:nums = [4,3,1,2,4]
输出:2
解释:nums 中有 2 个美丽子数组:[4,3,1,2,4] 和 [4,3,1,2,4] 。
- 按照下述步骤,我们可以将子数组 [3,1,2] 中所有元素变成 0 :
- 选择 [3, 1, 2] 和 k = 1 。将 2 个数字都减去 21 ,子数组变成 [1, 1, 0] 。
- 选择 [1, 1, 0] 和 k = 0 。将 2 个数字都减去 20 ,子数组变成 [0, 0, 0] 。
- 按照下述步骤,我们可以将子数组 [4,3,1,2,4] 中所有元素变成 0 :
- 选择 [4, 3, 1, 2, 4] 和 k = 2 。将 2 个数字都减去 22 ,子数组变成 [0, 3, 1, 2, 0] 。
- 选择 [0, 3, 1, 2, 0] 和 k = 0 。将 2 个数字都减去 20 ,子数组变成 [0, 2, 0, 2, 0] 。
- 选择 [0, 2, 0, 2, 0] 和 k = 1 。将 2 个数字都减去 21 ,子数组变成 [0, 0, 0, 0, 0] 。
示例 2:
输入:nums = [1,10,4]
输出:0
解释:nums 中没有任何美丽子数组。
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 106
异或+哈希
from typing import List
from collections import defaultdict
class Solution:
def beautifulSubarrays(self, nums: List[int]) -> int:
# 记录前缀异或出现的次数
prefixXorCount = defaultdict(int)
# 初始化前缀异或为0的情况出现了一次
prefixXorCount[0] = 1
xorSum = 0
beautifulCount = 0
for num in nums:
xorSum ^= num
# 如果当前的xorSum之前出现过,那么从之前的位置到当前位置之间的子数组就是一个美丽子数组
if xorSum in prefixXorCount:
beautifulCount += prefixXorCount[xorSum]
# 更新当前xorSum出现的次数
prefixXorCount[xorSum] += 1
return beautifulCount