小S的最小化数组分数问题
一、问题分析
小S有一个整数数组 nums
和一个整数 k
,他可以对数组中的每个元素 nums[i]
进行调整,每次调整可以选择增加 k
或减少 k
,即 nums[i] + k
或 nums[i] - k
。经过这些操作后,数组的分数定义为调整后的数组中最大值与最小值的差值。问题的目标是通过对数组进行合适的调整,使得最终的分数尽可能小。
从问题本质来看,数组的分数完全由其最大值和最小值决定。调整的关键在于尽可能缩小这两个值之间的差距,同时保证操作覆盖数组中的每个元素。这种调整具有多种可能性,因此暴力枚举所有情况显然不可行。我们需要找到一种高效的方法,在有限的计算时间内找到最优的调整方案。
二、思路解析
为了简化问题并找到最优解,我们可以先对数组 nums
进行升序排序。排序后的数组从小到大排列,使得最大值和最小值的位置固定为数组的两端,即 nums[0]
为最小值,nums[-1]
为最大值。这种排列方式使得调整的目标变得更加明确,即通过对数组进行合理分割,使一部分元素尽量增加,另一部分元素尽量减少,从而最大限度地缩小两端差距。
初始情况下,数组的分数为未调整时的最大值与最小值的差,即 nums[-1] - nums[0]
。这个值是未调整时的结果,也是后续优化的起点。在调整过程中,我们可以选择将数组分为两部分,前一部分的元素统一增加 k
,后一部分的元素统一减少 k
。调整后,数组的最大值和最小值分别可能来自两部分的极端情况:最大值为 max(nums[i] + k, nums[-1] - k)
,最小值为 min(nums[0] + k, nums[i + 1] - k)
。通过枚举分割点,将上述公式应用于所有可能的分割情况,我们可以找到最优的调整方案。
在计算过程中,我们动态更新最小分数的值。通过比较每种分割方式下的最大值与最小值的差,保留最小的结果作为最终答案。由于数组经过排序,最大值和最小值的计算仅涉及局部元素,因此整个过程可以高效完成。最终,我们返回计算出的最小分数,确保结果为非负值。
三、代码详解
def solution(nums: list, k: int) -> int:
# 对数组进行排序
nums.sort()
n = len(nums)
# 初始化最小分数为未调整时的最大值与最小值的差
ans = nums[-1] - nums[0]
# 遍历所有分割点
for i in range(n - 1):
# 计算分割后数组的最大值和最小值
high = max(nums[i] + k, nums[-1] - k)
low = min(nums[0] + k, nums[i + 1] - k)
# 更新最小分数
ans = min(ans, high - low)
# 返回最终结果
return ans if ans >= 0 else 0
这段代码的核心逻辑是通过以下步骤实现问题的高效解决:首先,对数组 nums
进行排序,以固定元素的排列顺序,确保在后续处理时最大值和最小值始终位于明确的位置(排序后的两端)。排序的作用不仅是简化逻辑,还能帮助我们快速找到调整后的可能极值范围。对于一个经过排序的数组,调整后最大值和最小值的候选值范围会显著缩小,从而避免对每个可能的组合逐一计算。
在排序完成后,代码通过遍历数组中的分割点(索引 i
)来动态调整可能的最大值和最小值范围。对于每个分割点,数组被分成两部分:前 i+1
个元素尝试通过增加 k
来缩小范围,而后续元素通过减少 k
来优化极值。这种分割方式确保了不同调整策略的充分尝试,同时将问题复杂度限制在合理范围内。
具体而言,在每个分割点下,通过计算增加 k
和减少 k
后的可能最大值 high
和最小值 low
,我们能够快速确定调整后的分数,即 high - low
。每次计算后,更新当前的最小分数,从而动态追踪最优结果。最终,代码输出遍历过程中找到的最小分数,这一结果代表了所有调整方式中最优的情况。这种方法不仅逻辑清晰,而且在时间复杂度上实现了从暴力求解到高效算法的转变,使其适用于更大规模的输入数据。
四、总结
通过排序和分割的策略,该算法有效地解决了数组分数最小化问题。排序后的数组通过分割点的枚举,将全局问题简化为局部问题,每次只需计算两端的极值。最终,通过动态更新最小分数的值,能够快速找到最优解。这种方法避免了暴力穷举的高时间复杂度,同时在逻辑上清晰易懂,是处理类似问题的高效方案。