274、H 指数
链接:274、H 指数
题目描述:
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。
根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。
方法 1 ------ 排序法
class Solution:
def hIndex(self, citations: List[int]) -> int:
h = 0
# 将 citations 数组按降序排序
citations.sort(reverse=True)
for i, citation in enumerate(citations):
# 更新 h
if citation >= i + 1:
h += 1
else:
break
return h
分析:
- 解题关键:至少 有 h 篇论文被引用次数大于等于 h
- 解决方案:降序排序后,citation表示引用量, i+1表示当前发表论文的数目,只需要比较这两者的关系,就可以解题。
示例:
假设 citations = [3, 0, 6, 1, 5]:
- 排序后得到 [6, 5, 3, 1, 0]。
- 遍历排序后的数组:
- i = 0, citation = 6,满足 6 >= 1,更新 h = 1。
- i = 1, citation = 5,满足 5 >= 2,更新 h = 2。
- i = 2, citation = 3,满足 3 >= 3,更新 h = 3。
- i = 3, citation = 1,不满足 1 >= 4,跳出循环。
- 最终返回 h = 3。
复杂度分析:
- 时间复杂度: O(n log n),因为排序操作是主要的时间消耗。
- 空间复杂度: O(n)
方法 2 ------ 计数排序法
class Solution:
def hIndex(self, citations: List[int]) -> int:
# 初始化
n = len(citations)
count = [0] * (n + 1)
# 填充 count 数组
for citation in citations:
if citation >= n:
count[n] += 1
else:
count[citation] += 1
# 从后向前累加论文数量
total = 0
for i in range(n, -1, -1):
total += count[i]
if total >= i:
return i
return 0
分析:
- 创建一个长度为 n+1 的数组 count,其中 n 是论文的数量。count[i] 表示引用次数为 i 的论文数量。
- 遍历 citations 数组,填充 count 数组。如果引用次数大于 n,则将其计入 count[n]。
- 从 count 数组的末尾开始,累加论文数量,直到找到满足条件的最大 h 值。
示例:
假设 citations = [3, 0, 6, 1, 5]:
- 初始化 n = 5,count = [0, 0, 0, 0, 0, 0]。
- 填充 count 数组后,count = [1, 1, 0, 1, 0, 2]。
- 从后向前累加论文数量:
- i = 5, total = 2(不满足 total >= 5)。
- i = 4, total = 2(不满足 total >= 4)。
- i = 3, total = 3(满足 total >= 3),返回 3。
复杂度分析:
- 时间复杂度: O(n),因为只需遍历 citations 数组和 count 数组各一次。
- 空间复杂度: O(n),因为使用了一个长度为 n+1 的数组 count。
380、O(1) 时间插入、删除和获取随机元素
题目描述:
实现RandomizedSet 类:
- RandomizedSet() 初始化 RandomizedSet 对象
- bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
- bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
- int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。
示例:
- 输入
[“RandomizedSet”, “insert”, “remove”, “insert”, “getRandom”, “remove”, “insert”, “getRandom”]
[[ ], [1], [2], [2], [ ], [1], [2], [ ]] - 输出
[null, true, false, true, 2, true, false, 2]
解释
RandomizedSet randomizedSet = new RandomizedSet( );
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。
randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
代码
要实现 RandomizedSet 类,并满足每个函数的平均时间复杂度为 O(1),可以使用一个哈希表和一个列表来实现。哈希表用于快速查找和删除元素,列表用于快速获取随机元素。
class RandomizedSet:
def __init__(self):
# 初始化一个空的列表 nums 和一个空的字典 indices 。
self.nums = []
self.indices = {}
def insert(self, val: int) -> bool:
# 检查 val 是否在 indices 中,
# 如果不在,则将 val 添加到 nums 列表的末尾,并在 indices 中记录其索引。
if val in self.indices:
return False
self.indices[val] = len(self.nums)
self.nums.append(val)
return True
def remove(self, val: int) -> bool:
if val not in self.indices:
return False
# 从哈希表中获得 val 的下标 index;
id = self.indices[val]
# 将变长数组的最后一个元素 last 移动到下标 index 处,在哈希表中将 last 的下标更新为 index;
self.nums[id] = self.nums[-1]
self.indices[self.nums[id]] = id
# 在变长数组中删除最后一个元素,在哈希表中删除 val;
self.nums.pop()
del self.indices[val]
return True
def getRandom(self) -> int:
return choice(self.nums)
238、除自身以外数组的乘积
题目描述:
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法,且在 O(n) 时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
进阶:你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
方法
class Solution:
def productExceptSelf(self, nums):
n = len(nums)
answer = [1] * n
# 计算前缀积
prefix = 1
for i in range(n):
answer[i] = prefix
prefix *= nums[i]
# 计算后缀积并更新结果
suffix = 1
for i in range(n - 1, -1, -1):
answer[i] *= suffix
suffix *= nums[i]
return answer
具体解释:
假设 nums = [1,2,3,4]
- 计算前缀积:(从前往后)
当 i = 0,初始化 prefix = 1,answer[0] = 1;
当 i = 1, prefix = prefix * nums[0] = 1,answer[1] = 1;
当 i = 2, prefix = prefix * nums[1] = 2,answer[2] = 2;
当 i = 3, prefix = prefix * nums[2] = 6,answer[3] = 6;
所以 answer = [1,1,2,6] - 计算后缀积:(从后往前)
当 i = 3,初始化suffix = 1,answer[3] = 前缀积得到的answe[3] * 后缀积 = answer[3] * 1 = 6;
当 i = 2,suffix = suffix * nums[i+1] = 1 * 4 = 4, answer[2] = 前缀积answe[2] * 后缀积 = 2 * 4 =8;
当 i = 1,suffix = suffix * nums[i+1] = 4 * 3 = 12, answer[1] = 前缀积answe[1] * 后缀积 = 1* 12 =12;
当 i = 0,suffix = suffix * nums[i+1] = 12 * 2 = 24, answer[0] = 前缀积answe[0] * 后缀积 = 1* 24=24;
复杂度分析
- 时间复杂度:O(n),因为我们只需遍历 nums 数组两次。
- 空间复杂度:O(1),因为我们只使用了常数个额外的变量(prefix 和 suffix),不包括返回结果 answer。
134、加油站
链接:134、加油站
题目描述:
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
示例 1:
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:
输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
方法1
# 暴力解法 --- 超出时间限制
class Solution:
def canCompleteCircuit(self, gas, cost):
n = len(gas)
for i in range(n):
rest = gas[i] - cost[i] # 记录剩余油量
index = (i + 1) % n
while rest > 0 and index != i: # 模拟以i为起点行驶一圈
rest += gas[index] - cost[index]
index = (index + 1) % n
# 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
if rest >= 0 and index == i:
return i
return -1
具体解释:
- 初始化, 获取加油站的数量。
- 遍历每个加油站作为起点
- 遍历每个加油站 i 作为起点。
- 计算从当前加油站出发的剩余油量 rest。
- 使用 while 循环模拟从当前加油站出发行驶一圈:
- 更新剩余油量 rest。
- 更新下一个加油站的索引 index。
- 如果能够绕环路行驶一圈且剩余油量 rest 大于等于 0,返回当前起点 i。
复杂度分析:
- 时间复杂度:O( n 2 n^2 n2)
- 空间复杂度:O(1)
方法 2
# 贪心算法 1
class Solution:
def canCompleteCircuit(self, gas, cost):
# 初始化
curSum = 0 # 记录当前的汽油盈余
min_gas = float('inf') # 从起点出发,油箱里的油量最小值
n = len(gas) # 加油站的数量
for i in range(n):
rest = gas[i] - cost[i]
curSum += rest
if curSum < min_gas:
min_gas = curSum
if curSum < 0:
return -1 # 情况1:总的汽油盈余为负
if min_gas >= 0:
return 0 # 情况2:从第一个加油站出发即可完成环路
# 情况3
for i in range(n - 1, -1, -1):
rest = gas[i] - cost[i]
min_gas += rest
if min_gas >= 0:
return i
return -1
代码逻辑:
- 初始化变量:
- curSum:当前油量总和。
- min_gas:从起点出发,油箱里的油量最小值,初始为正无穷大。
- n:加油站的数量。
- 第一次遍历:
- 遍历每个加油站,计算当前油量总和 curSum 和当前油量总和的最小值 min_gas。
- rest 表示在第 i 个加油站加油后的剩余油量。
- 如果 curSum 小于 min_gas,更新 min_gas。
- 判断是否可以绕环路行驶一圈:
- 如果 curSum 小于 0,说明总油量不足以绕环路行驶一圈,返回 -1。
- 如果 min_gas 大于等于 0,说明从第 0 个加油站出发可以绕环路行驶一圈,返回 0。
- 第二次遍历:
- 从最后一个加油站开始,向前遍历,计算 min_gas 的值。
- 如果 min_gas 大于等于 0,说明从当前加油站出发可以绕环路行驶一圈,返回当前加油站的索引。
- 返回结果:
- 如果没有找到合适的起点,返回 -1。
示例:
- 假设输入
- gas = [1, 2, 3, 4, 5]
- cost = [3, 4, 5, 1, 2]
- 第一次遍历:
- curSum 和 min_gas 的变化如下:
- i = 0: rest = 1 - 3 = -2, curSum = -2, min_gas = -2
- i = 1: rest = 2 - 4 = -2, curSum = -4, min_gas = -4
- i = 2: rest = 3 - 5 = -2, curSum = -6, min_gas = -6
- i = 3: rest = 4 - 1 = 3, curSum = -3, min_gas = -6
- i = 4: rest = 5 - 2 = 3, curSum = 0, min_gas = -6
- curSum 最终为 0,不小于 0,继续判断 min_gas。
- curSum 和 min_gas 的变化如下:
- 第二次遍历:
- 从最后一个加油站开始,min_gas 的变化如下:
- i = 4: rest = 5 - 2 = 3, min_gas = -3
- i = 3: rest = 4 - 1 = 3, min_gas = 0
- 当 i = 3 时,min_gas 大于等于 0,返回 3。
- 从最后一个加油站开始,min_gas 的变化如下:
最终结果为 3,即从第 3 个加油站出发可以绕环路行驶一圈。
复杂度分析:
- 时间复杂度:O(n),代码包含两个独立的循环;
- 空间复杂度:O(n), 代码使用了一个长度为 n 的数组 answer 来存储结果。
方法 3
# 贪心算法 2
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
n = len(gas)
# 初始化
total_tank = 0
curr_tank = 0
starting_station = 0
# 遍历加油站
for i in range(n):
# 计算总油量
total_tank += gas[i] - cost[i]
# 更新每次当前的油量
curr_tank += gas[i] - cost[i]
# 如果当前油量 < 0,则起始加油站+1,且清空当前油量
if curr_tank < 0:
starting_station = i + 1
curr_tank = 0
# 如果总油量非负,则返回加油站的位置,否则返回-1
return starting_station if total_tank >= 0 else -1
解题关键:
- 判断 gas 总量 >= cost 总量;
- 当前站的 gas[i] >= cost[i];
复杂度分析
- 时间复杂度: O(n),因为我们只需遍历 gas 和 cost 数组一次。
- 空间复杂度:O(1),因为我们只使用了常数个额外的变量。
135、分发糖果
链接:135、分发糖果
题目描述:
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
方法
class Solution:
def candy(self, ratings):
n = len(ratings)
if n == 0:
return 0
# 初始化每个孩子至少分配 1 个糖果
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]:
candies[i] = max(candies[i], candies[i + 1] + 1)
# 计算总糖果数
return sum(candies)
解释:
- 初始化 candies 数组,每个孩子至少分配 1 个糖果。
- 从左到右遍历,如果当前孩子的评分比前一个孩子高,则当前孩子的糖果数比前一个孩子多 1。
- 从右到左遍历,如果当前孩子的评分比后一个孩子高,并且当前孩子的糖果数不比后一个孩子多,则当前孩子的糖果数比后一个孩子多 1。
- 最后,计算 candies 数组的总和,即为最少需要准备的糖果数目。
复杂度分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)