• 困难
  • 中等
  • 简单

    题目描述

    给定一个已排序的正整数数组 nums,和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用 nums 中某几个数字的和来表示。请输出满足上述要求的最少需要补充的数字个数。

    来源,leetcode 每日一题 330. 按要求补齐数组

示例:

  1. 输入: nums = [1,3], n = 6
  2. 输出: 1
  3. 解释:
  4. 根据 nums 里现有的组合 [1], [3], [1,3],可以得出 1, 3, 4
  5. 现在如果我们将 2 添加到 nums 中, 组合变为: [1], [2], [3], [1,3], [2,3], [1,2,3]。
  6. 其和可以表示数字 1, 2, 3, 4, 5, 6,能够覆盖 [1, 6] 区间里所有的数。
  7. 所以我们最少需要添加一个数字。
  8. 输入: nums = [1,5,10], n = 20
  9. 输出: 2
  10. 解释: 我们需要添加 [2, 4]。
  11. 输入: nums = [1,2,2], n = 5
  12. 输出: 0

解题思路

  1. 对于正整数 xx,如果区间 [1,x-1][1,x−1] 内的所有数字都已经被覆盖,且 xx 在数组中,则区间 [1,2x-1][1,2x−1] 内的所有数字也都被覆盖。

    对于任意 1 \le y<x1≤y<x,yy 已经被覆盖,xx 在数组中,因此 y+xy+x 也被覆盖,区间 [x+1,2x-1][x+1,2x−1](即区间 [1,x-1][1,x−1] 内的每个数字加上 xx 之后得到的区间)内的所有数字也被覆盖,由此可得区间 [1,2x-1][1,2x−1] 内的所有数字都被覆盖。

假设数字 x 缺失,则至少需要在数组中补充一个小于或等于 x 的数,才能覆盖到 x,否则无法覆盖到 x。

如果区间 [1,x-1][1,x−1] 内的所有数字都已经被覆盖,则从贪心的角度考虑,补充 x 之后即可覆盖到 x,且满足补充的数字个数最少。在补充 x 之后,区间 [1,2x-1][1,2x−1] 内的所有数字都被覆盖,下一个缺失的数字一定不会小于 2x。

由此可以提出一个贪心的方案。每次找到未被数组 330. 按要求补齐数组 - 图1覆盖的最小的整数 xx,在数组中补充 x,然后寻找下一个未被覆盖的最小的整数,重复上述步骤直到区间 [1,n]中的所有数字都被覆盖。

具体实现方面,任何时候都应满足区间 [1,x-1][1,x−1] 内的所有数字都被覆盖。令 x 的初始值为 1,数组下标 330. 按要求补齐数组 - 图2的初始值为 0,则初始状态下区间 [1,x-1][1,x−1] 为空区间,满足区间内的所有数字都被覆盖。进行如下操作。

如果 330. 按要求补齐数组 - 图3 在数组 330. 按要求补齐数组 - 图4的下标范围内且330. 按要求补齐数组 - 图5,则将 330. 按要求补齐数组 - 图6 的值加给 x,并将 330. 按要求补齐数组 - 图7的值加 1。

被覆盖的区间从 [1,x-1][1,x−1] 扩展到 330. 按要求补齐数组 - 图8,对 x 的值更新以后,被覆盖的区间为 [1,x-1][1,x−1]。
否则,x 没有被覆盖,因此需要在数组中补充 x,然后将 x 的值乘以 2。

在数组中补充 xx 之后,被覆盖的区间从 [1,x-1][1,x−1] 扩展到 [1,2x-1][1,2x−1],对 x 的值更新以后,被覆盖的区间为 [1,x-1][1,x−1]。
重复上述操作,直到 x 的值大于 n。

代码

class Solution {
public:
    int minPatches(vector<int>& nums, int n) {
        int patches = 0;
        long long x = 1;
        int length = nums.size(), index = 0;
        while (x <= n) {
            if (index < length && nums[index] <= x) {
                x += nums[index];
                index++;
            } else {
                x <<= 1;
                patches++;
            }
        }
        return patches;
    }
};