石子移动问题
问题描述
小S正在玩一个关于石子的游戏,给定了一些石子,它们位于一维数轴的不同位置,位置用数组 stones 表示。如果某个石子处于最小或最大的一个位置,我们称其为端点石子。
在每个回合,小S可以将一颗端点石子移动到一个未占用的位置,使其不再是端点石子。游戏继续,直到石子的位置变得连续,无法再进行任何移动操作。
你需要帮助小S找到可以移动的最大次数。
题目分析
本题要求我们尽可能多地移动端点石子。我们分析一下我们可以怎么样去移动端点石子,对于选择的移动的石子,我们有两种选择:
- 移动左端点的石子
- 移动右端点的石子
由于同时计算两种移动方式的结果也仅仅会增多1倍的时间,而且我认为是会很明显降低讨论选择哪个石子的思考难度,所以我们选择都去计算。
接着我们讨论移动选择的这个石子该移动到哪里去,我们首先可以想到两种比较抽象的位置的集合:
- 石子密度比较大的区域
- 石子密度比较小的区域
很显然,如果每次都移动到前者,那不可避免的会减少我们最终移动的次数。所以,我们应该尽可能地将石子移动到石子密度更小的区域。虽然这样只是猜测,但是这里我们可以比较粗略地分析一下。题目要求我们求得石子全部连续排布时我们移动了多少步,这是移动了最多次数之后的石子的状态,同时也是最密集的。而初始状态由于我们不能将端点石子继续保持为端点石子,所以不可避免地,所有的石子都会越来越接近,我们需要做的就是延缓这个趋势。
我们分析一下什么时候这个趋势最慢。我们可以发现,如果我们首先找到两端点中密度最小的一点,我们选择靠近那一端中最靠近端点的一个空位,那么这个就会是密度提升最慢的情况。
这个算法也很容易实现,我们分别讨论出移动两端的石子到那一点后的数组,再调用solution函数计算那之后会移动多少步,那么很显然,步数最多的那一个端点就是密度相比之下更小的端点。这里我们可以使用一个递归实现。
def solution(stones: list) -> int:
if len(stones) <= 2:
return 0
stones.sort()
not_in_list = []
for i in range(stones[0], stones[-1]):
if i not in stones:
not_in_list.append(i)
if len(not_in_list) == 0:
return 0
list1 = stones.copy()
list1[0] = not_in_list[-1]
list2 = stones.copy()
list2[-1] = not_in_list[0]
ans = 1 + max(solution(list1), solution(list2))
return ans
测试用例全部通过了,代码在测试用例的范围内是正确的。