学习记录:猫星球分发鱼干问题解析与实现
问题背景与描述
在猫星球的某一天,小R面临一个棘手的任务:给一排排队的猫分发鱼干。猫星球的居民以猫为主,每只猫都有自己的等级,等级越高的猫需要获得更多的鱼干。为了公平起见,小R需要按照以下规则进行分配:
- 每只猫至少分到一斤鱼干。
- 如果一只猫的等级高于相邻的猫,那么它应该分到比相邻猫更多的鱼干。
小R的目标是通过满足上述规则,找到分发鱼干的最小总量。
这个问题表面上看是一个简单的分配问题,但实际上它隐含了两个方向的约束(左到右和右到左),需要综合考虑每只猫的等级与其相邻猫之间的关系。通过分析这个问题,我们发现这是一个局部递增与递减约束的优化问题,需要对每只猫在两个方向上满足约束。我们可以学到如何在复杂约束条件下优化资源分配。
问题分析
为了实现公平分配,我们可以将问题拆分成两个阶段:
- 确保从左向右满足规则:即如果当前猫的等级高于前一只猫,当前猫的鱼干数量应比前一只猫多。
- 确保从右向左满足规则:即如果当前猫的等级高于后一只猫,当前猫的鱼干数量应比后一只猫多。
最终通过两次遍历,找到满足双向约束的最小鱼干总量。
从算法上看,这是一个典型的动态规划问题,两次遍历分别解决不同方向的约束,并通过逐步调整优化分配。
算法思路
1. 初始化
所有猫最开始都分到一斤鱼干,这是满足规则的最低要求。我们使用一个数组 fish_count 记录每只猫的鱼干数量,初始状态为 [1, 1, ..., 1]。
2. 第一次遍历:从左到右
从左到右遍历猫的等级数组 cats_levels,依次比较每只猫与前一只猫的等级关系:
- 如果当前猫的等级
cats_levels[i]高于前一只猫cats_levels[i-1],则当前猫的鱼干数量fish_count[i]应比前一只猫多 1。 - 否则,保持不变。
此遍历的目的是确保从左向右的等级约束得到满足。
3. 第二次遍历:从右到左
从右到左遍历数组 cats_levels,依次比较每只猫与后一只猫的等级关系:
-
如果当前猫的等级
cats_levels[i]高于后一只猫cats_levels[i+1],则当前猫的鱼干数量fish_count[i]应至少比后一只猫多 1。 -
为避免覆盖之前的结果,需取两次遍历计算值的最大值,即:
fish_count[i] = max(fish_count[i], fish_count[i+1] + 1)
此遍历的目的是确保从右向左的等级约束得到满足,同时兼顾第一次遍历的结果。
4. 计算总鱼干
最后,将数组 fish_count 中的所有值累加,得到满足规则的最小鱼干总量。
这个问题可以通过动态规划思想分两次遍历解决:
- 第一次遍历(从左向右) :确保每只猫的鱼干满足它与前一只猫的等级差异要求。
- 第二次遍历(从右向左) :进一步调整鱼干数量,确保每只猫与后一只猫的等级差异也得到满足。
最后,将所有猫的鱼干数量相加,得到所需的最小总数。
示例与分析
示例 1
输入:n = 3, cats_levels = [1, 2, 2]
-
初始状态:每只猫至少得到 1 斤鱼干,
fish_count = [1, 1, 1]。 -
第一次遍历(从左到右):
cats_levels[1] > cats_levels[0],fish_count[1] = fish_count[0] + 1 = 2。cats_levels[2] <= cats_levels[1],保持不变。- 状态更新为:
fish_count = [1, 2, 1]。
-
第二次遍历(从右到左):
cats_levels[1] > cats_levels[2]不成立,保持不变。- 状态保持为:
fish_count = [1, 2, 1]。
-
总鱼干数:
1 + 2 + 1 = 4。
代码详解
def solution(n, cats_levels):
# 初始化每只猫的鱼干数量,每只猫至少得到一斤鱼干
fish_count = [1] * n
# 第一次遍历,确保每只猫的鱼干数量满足等级差异的要求
for i in range(1, n):
if cats_levels[i] > cats_levels[i - 1]:
# 如果当前猫的等级高于前一只猫,增加鱼干数量
fish_count[i] = fish_count[i - 1] + 1
# 第二次遍历,确保每只猫的鱼干数量满足等级差异的要求
for i in range(n - 2, -1, -1):
if cats_levels[i] > cats_levels[i + 1]:
# 如果当前猫的等级高于后一只猫,增加鱼干数量
fish_count[i] = max(fish_count[i], fish_count[i + 1] + 1)
# 计算总的鱼干数量
total_fish = sum(fish_count)
return total_fish
if __name__ == "__main__":
# 你可以添加更多测试用例
cats_levels1 = [1, 2, 2]
cats_levels2 = [6, 5, 4, 3, 2, 16]
cats_levels3 = [1, 2, 2, 3, 3, 20, 1, 2, 3, 3, 2, 1, 5, 6, 6, 5, 5, 7, 7, 4]
print(solution(3, cats_levels1) == 4)
print(solution(6, cats_levels2) == 17)
print(solution(20, cats_levels3) == 35)
时间复杂度与空间复杂度
- 时间复杂度:
两次遍历各需 ,总时间复杂度为 。 - 空间复杂度:
额外使用了fish_count数组,空间复杂度为 。
学习心得与总结
通过解决这个问题,我有以下收获:
- 两次遍历法的高效性:
在资源分配和约束优化问题中,逐步调整是一个重要技巧。通过两次遍历,分别从两个方向处理约束条件,可以有效减少复杂度。 - 动态规划思想的应用:
本问题虽然未明确使用动态规划表,但通过记录中间状态(fish_count),逐步优化结果的过程与动态规划思想类似。这提醒我在解决实际问题时,动态规划的思想往往可以灵活应用。 - 边界条件的重要性:
通过增加多个测试用例(如等级完全相等或大幅波动),确保算法对各种边界情况都能正确处理。例如,当猫的等级完全相等时,每只猫应分到相同数量的鱼干。 - 代码调试与优化:
初次实现时容易忽略双向遍历的结合点,比如在第二次遍历中不取最大值可能导致错误结果。通过调试,我理解了如何在更新过程中保留较优解。
总之,本问题是一道经典的资源分配问题,通过细致的分析和高效的实现方法,我进一步提升了对动态规划和双向遍历算法的理解,也学到了如何在约束优化问题中找到全局最优解。