LeetCode 1040 移动石子直到连续 II

1040 移动石子直到连续 II

模...本来想归类到模拟的, 看了看下面的标签还是放到滑动窗口好了

  • 最大移动次数

选择一边的端点石子, 每次都移动到与原位置尽可能近的位置

除了首次选择的起点, 剩余空位置的总会被填充一次石子

由此可以得到最大值的公式, 用间距之和减去首尾较短的那段间距

maxNum = sumGap - min(gap.front(), gap.back())
  • 最小移动次数

从结果反推, 最终所有石子都会落在一个连续区间中

为了移动次数最小, 区间外每个石子只移动一次就到达最终位置

随着区间不断缩小, 最后区间的端点石子至少有一颗没有移动过

此时稍微转换一下问题, 就能依靠原始石子的位置选择区间范围, 区间外的石子可视为自由石子, 最终目的为尽可能填充满区间(忽略细节)

为了便于判断, 划分出2个我们需要的变量

  1. sum: 区间外的自由石子数, 可以用来自由移动
  2. gap: 区间内需要填充的空白位置数

在不断变化区间(移动窗口)的过程中, 再划分区间改变的几个条件

  1. sum < gap: 自由石子数量小于空白位置, 需要额外移动一次端点石子, 完成移动需要的次数为sum + 1, 期望减小区间
  2. sum == gap: 自由石子数量等于空白位置, 完成移动需要的次数恰好为为sum, 期望移动区间
  3. sum > gap: 自由石子数量大于空白位置, 期望增大区间
  4. sum == 0: 自由石子数量不足, 期望减小区间

要是不嫌烦的话, 还能对具体实现做一下优化...(懒癌发作)

C艹

class Solution {
public:
    vector<int> numMovesStonesII(vector<int>& stones) {
        std::sort(stones.begin(), stones.end());
        int sumGap = (int)stones.back() - (int)stones.front() - (int)stones.size() + 1;
        for (int i = (int)stones.size()-1; i > 0; --i) stones[i] -= stones[i-1] + 1;
        stones.erase(stones.begin());
        int maxNum = sumGap - std::min(stones.front(), stones.back());
        
        int minNum = maxNum;
        for (int l = 0, r = 0, gapNum = stones[0]; r < stones.size();) {
            sumGap = (int)stones.size() - (r - l + 1);
            if (sumGap <= 0) {
                gapNum -= stones[l++];
                continue;
            }
            
            if (sumGap < gapNum) {
                minNum = std::min(minNum, sumGap + 1);
                gapNum -= stones[l++];
                if (l > r) gapNum += stones[++r];
            } else if (sumGap == gapNum) {
                minNum = std::min(minNum, sumGap);
                gapNum -= stones[l++];
                gapNum += stones[++r];
            } else {
                gapNum += stones[++r];
            }
        }
        
        return {minNum, maxNum};
    }
};
原文地址:https://www.cnblogs.com/Simon-X/p/13624120.html