【面试】每日力扣 Day3


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]:

  1. 排序后得到 [6, 5, 3, 1, 0]。
  2. 遍历排序后的数组:
    • 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,跳出循环。
  3. 最终返回 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

分析:

  1. 创建一个长度为 n+1 的数组 count,其中 n 是论文的数量。count[i] 表示引用次数为 i 的论文数量。
  2. 遍历 citations 数组,填充 count 数组。如果引用次数大于 n,则将其计入 count[n]。
  3. 从 count 数组的末尾开始,累加论文数量,直到找到满足条件的最大 h 值。

示例:

假设 citations = [3, 0, 6, 1, 5]:

  1. 初始化 n = 5,count = [0, 0, 0, 0, 0, 0]。
  2. 填充 count 数组后,count = [1, 1, 0, 1, 0, 2]。
  3. 从后向前累加论文数量:
    • 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) 时间插入、删除和获取随机元素

链接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、除自身以外数组的乘积

链接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

具体解释:

  1. 初始化, 获取加油站的数量。
  2. 遍历每个加油站作为起点
    • 遍历每个加油站 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

代码逻辑:

  1. 初始化变量:
    • curSum:当前油量总和。
    • min_gas:从起点出发,油箱里的油量最小值,初始为正无穷大。
    • n:加油站的数量。
  2. 第一次遍历:
    • 遍历每个加油站,计算当前油量总和 curSum 和当前油量总和的最小值 min_gas。
    • rest 表示在第 i 个加油站加油后的剩余油量。
    • 如果 curSum 小于 min_gas,更新 min_gas。
  3. 判断是否可以绕环路行驶一圈:
    • 如果 curSum 小于 0,说明总油量不足以绕环路行驶一圈,返回 -1。
    • 如果 min_gas 大于等于 0,说明从第 0 个加油站出发可以绕环路行驶一圈,返回 0。
  4. 第二次遍历:
    • 从最后一个加油站开始,向前遍历,计算 min_gas 的值。
    • 如果 min_gas 大于等于 0,说明从当前加油站出发可以绕环路行驶一圈,返回当前加油站的索引。
  5. 返回结果:
    • 如果没有找到合适的起点,返回 -1。

示例:

  1. 假设输入
    • gas = [1, 2, 3, 4, 5]
    • cost = [3, 4, 5, 1, 2]
  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。
  3. 第二次遍历:
    • 从最后一个加油站开始,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。

最终结果为 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

解题关键:

  1. 判断 gas 总量 >= cost 总量;
  2. 当前站的 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)

解释:

  1. 初始化 candies 数组,每个孩子至少分配 1 个糖果。
  2. 从左到右遍历,如果当前孩子的评分比前一个孩子高,则当前孩子的糖果数比前一个孩子多 1。
  3. 从右到左遍历,如果当前孩子的评分比后一个孩子高,并且当前孩子的糖果数不比后一个孩子多,则当前孩子的糖果数比后一个孩子多 1。
  4. 最后,计算 candies 数组的总和,即为最少需要准备的糖果数目。

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值