11月8日 小E的射击训练
问题描述
小E正在训练场进行射击练习,靶有10个环,靶心位于坐标(0, 0)。每个环对应不同的得分,靶心内(半径为1)得10分,依次向外的每个环分数减少1分。若射击点在某个半径为i的圆内,则得11-i分。如果射击点超出所有的环,则得0分。 根据给定的射击坐标(x, y),请计算小E的射击得分。
思考
这道题我们只需要依次判断点是否位于半径从1到10的圆内。 找到点所在的第一个圆即返回对应的分数,无需继续判断。
代码
def solution(x: int, y: int) -> int: # 计算距离平方 distance_sq = x * x + y * y # 判断在哪个环内,半径从 1 到 10 for i in range(1, 11): if distance_sq <= i * i: return 11 - i # 超出所有环得 0 分 return 0
分析
本道题比较简单,属于经典的计算几何问题,考察对点与圆关系的理解和实现能力。 我们可以通过直接平方比较的方法,避免了不必要的浮点数误差。
11月9日 找出整型数组中占比超过一半的数
问题描述
小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。
思考
给定一个数组,要求找出一个数字,该数字出现的次数超过数组长度的一半。 根据题目描述,必然存在这样的数字。 最简单的方法是统计每个数字的出现次数,但这会消耗O(n) 的空间复杂度。我们这里使用使用 摩尔投票法(Boyer-Moore Voting Algorithm)。值得注意的是,虽然摩尔投票法可以找到候选数字,但仍需验证该数字的出现次数是否超过一半。
摩尔投票法(Boyer-Moore Voting Algorithm)是一种高效算法,用于在一组数据中找到 多数元素(出现次数超过数组长度一半的元素)。该算法利用计数抵消策略来确定候选元素,时间复杂度为O(n),空间复杂度为 O(1)。第一遍遍历需要我们确定候选元素: 使用计数器统计抵消过程,最终得到一个可能的候选元素。 第二遍遍历需要我们验证候选元素: 统计候选元素的实际出现次数,判断其是否超过数组长度的一半。
代码
def solution(array): # 初始候选人设置为空,计数为0 candidate = None count = 0 # 通过摩尔投票法找到候选元素 for num in array: if count == 0: candidate = num count += 1 if num == candidate else -1 # 检查候选元素是否满足条件 if array.count(candidate) > len(array) // 2: return candidate else: return None # 如果不存在超过一半的元素
分析
- 摩尔投票法的核心在于通过计数器抵消掉不可能的候选人,最终找到潜在的候选数字。 只需两次遍历即可完成,效率高且实现简洁。
摩尔投票法的优雅之处在于通过“抵消”减少无关元素的影响,而不需要额外的存储和复杂计算。它的本质利用了多数元素的数学性质,提供了一种高效而直观的解决方案。
11月10日 小R的随机播放顺序
问题描述
小R有一个特殊的随机播放规则。他首先播放歌单中的第一首歌,播放后将其从歌单中移除。如果歌单中还有歌曲,则会将当前第一首歌移到最后一首。这个过程会一直重复,直到歌单中没有任何歌曲。
例如,给定歌单 [5, 3, 2, 1, 4],真实的播放顺序是 [5, 2, 4, 1, 3]。 保证歌曲中的id两两不同。
思路
在这道题,我们通过列表的动态操作,模拟了题目中 "播放第一首" 和 "移到最后一首" 的规则,按逻辑构造播放顺序。pop 和 append 的结合非常适合这种“移除+重排”的操作模式。
代码
def solution(n: int, a: list) -> list: result = [] while a: # 播放第一首歌并移除 result.append(a.pop(0)) # 将当前第一首歌移到最后一首(如果还有歌) if a: a.append(a.pop(0)) return result
分析代码知识点
- list.pop(0):移除列表中的第一个元素并返回其值。pop 是一种修改性操作,时间复杂度为 O(n),因为它需要将剩余元素向前移动。
- list.append(x):将元素添加到列表末尾,时间复杂度为 O(1)。
通过这道题,我学会了如何结合逻辑和数据结构设计高效的算法,进一步巩固了对列表操作的理解。