Hard

题目描述

Alice 有一个由 n 个正整数组成的下标从 0 开始的数组 arr。她选择一个任意的正整数 k,并按照下述方式创建两个新的下标从 0 开始的整数数组 lower 和 higher:

  • lower[i] = arr[i] - k,对于每个满足 0 <= i < n 的下标 i
  • higher[i] = arr[i] + k,对于每个满足 0 <= i < n 的下标 i

不幸的是,Alice 丢失了所有三个数组。但是,她记得在数组 lower 和 higher 中出现的整数,但不记得每个整数属于哪个数组。请你帮助 Alice 恢复原数组。

给你一个由 2n 个整数组成的数组 nums,其中恰好 n 个整数出现在 lower 中,剩下的出现在 higher 中,请你返回原数组 arr。如果答案不唯一,返回任意一个有效的数组。

注意:生成的测试用例保证至少存在一个有效的数组 arr。

示例 1:

输入:nums = [2,10,6,4,8,12]
输出:[3,7,11]
解释:
如果 arr = [3,7,11] 且 k = 1,我们可以得到 lower = [2,6,10] 和 higher = [4,8,12]。
组合 lower 和 higher 得到 [2,6,10,4,8,12],这是 nums 的一个排列。
另一个有效的可能是 arr = [5,7,9] 且 k = 3。在这种情况下,lower = [2,4,6] 且 higher = [8,10,12]。

示例 2:

输入:nums = [1,1,3,3]
输出:[2,2]
解释:
如果 arr = [2,2] 且 k = 1,我们可以得到 lower = [1,1] 和 higher = [3,3]。
组合 lower 和 higher 得到 [1,1,3,3],这就等于 nums。
注意 arr 不能是 [1,3],因为在这种情况下,获得 [1,1,3,3] 的唯一可能方式是 k = 0。
这无效,因为 k 必须是正数。

提示:

  • 2 * n == nums.length
  • 1 <= n <= 1000
  • 1 <= nums[i] <= 10^9
  • 生成的测试用例保证至少存在一个有效的数组 arr

解题思路

解题思路

这道题的关键在于如何确定参数 k 的值。我们需要理解题目的本质:给定数组 nums 包含了原数组中每个元素减去 k 和加上 k 的结果。

核心观察:

  1. 原数组的最小值减去 k 后,一定是 nums 中的最小值
  2. 对于任意可能的 k 值,我们可以尝试重构原数组并验证其有效性

算法步骤:

  1. 排序数组:首先对 nums 进行排序,这样可以方便地处理配对问题

  2. 枚举可能的 k 值

    • nums[0] 一定来自某个原数组元素减去 k
    • 对于每个 nums[i](i > 0),如果它来自同一个原数组元素加上 k,那么 k = (nums[i] - nums[0]) / 2
    • 由于 k 必须是正整数,所以 nums[i] - nums[0] 必须是正偶数
  3. 验证 k 值的有效性

    • 使用贪心策略:对于排序后的数组,尝试将每个未配对的最小元素作为 lower 值
    • 在剩余元素中寻找对应的 higher 值(即当前值 + 2k)
    • 如果能成功配对所有元素,说明这个 k 值有效
  4. 重构原数组:一旦找到有效的 k,原数组中的每个元素就是对应的 lower 值加上 k

时间复杂度分析:

  • 外层循环最多 n 次(枚举可能的 k)
  • 内层验证过程需要 O(n log n)(主要是查找操作)
  • 总体时间复杂度为 O(n² log n)

代码实现

class Solution {
public:
    vector<int> recoverArray(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size() / 2;
        
        for (int i = 1; i < nums.size(); i++) {
            if ((nums[i] - nums[0]) % 2 != 0) continue;
            
            int k = (nums[i] - nums[0]) / 2;
            if (k == 0) continue;
            
            multiset<int> remaining(nums.begin(), nums.end());
            vector<int> result;
            bool valid = true;
            
            while (!remaining.empty() && valid) {
                int lower = *remaining.begin();
                remaining.erase(remaining.begin());
                
                int higher = lower + 2 * k;
                auto it = remaining.find(higher);
                if (it != remaining.end()) {
                    remaining.erase(it);
                    result.push_back(lower + k);
                } else {
                    valid = false;
                }
            }
            
            if (valid && result.size() == n) {
                return result;
            }
        }
        
        return {};
    }
};
class Solution:
    def recoverArray(self, nums: List[int]) -> List[int]:
        nums.sort()
        n = len(nums) // 2
        
        for i in range(1, len(nums)):
            if (nums[i] - nums[0]) % 2 != 0:
                continue
                
            k = (nums[i] - nums[0]) // 2
            if k == 0:
                continue
                
            remaining = Counter(nums)
            result = []
            valid = True
            
            for num in sorted(remaining.keys()):
                while remaining[num] > 0 and valid:
                    lower = num
                    higher = lower + 2 * k
                    
                    if remaining[higher] > 0:
                        remaining[lower] -= 1
                        remaining[higher] -= 1
                        result.append(lower + k)
                    else:
                        valid = False
                        break
            
            if valid and len(result) == n:
                return result
                
        return []
public class Solution {
    public int[] RecoverArray(int[] nums) {
        Array.Sort(nums);
        int n = nums.Length / 2;
        
        for (int i = 1; i < nums.Length; i++) {
            if ((nums[i] - nums[0]) % 2 != 0) continue;
            
            int k = (nums[i] - nums[0]) / 2;
            if (k == 0) continue;
            
            var remaining = new Dictionary<int, int>();
            foreach (int num in nums) {
                remaining[num] = remaining.GetValueOrDefault(num, 0) + 1;
            }
            
            var result = new List<int>();
            bool valid = true;
            
            foreach (int num in nums.OrderBy(x => x)) {
                while (remaining.GetValueOrDefault(num, 0) > 0 && valid) {
                    int lower = num;
                    int higher = lower + 2 * k;
                    
                    if (remaining.GetValueOrDefault(higher, 0) > 0) {
                        remaining[lower]--;
                        remaining[higher]--;
                        result.Add(lower + k);
                    } else {
                        valid = false;
                        break;
                    }
                }
                if (!valid) break;
            }
            
            if (valid && result.Count == n) {
                return result.ToArray();
            }
        }
        
        return new int[0];
    }
}
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var recoverArray = function(nums) {
    nums.sort((a, b) => a - b);
    const n = nums.length / 2;
    
    for (let i = 1; i < nums.length; i++) {
        if (nums[i] === nums[0]) continue;
        
        const diff = nums[i] - nums[0];
        if (diff % 2 !== 0) continue;
        
        const k = diff / 2;
        const used = new Array(nums.length).fill(false);
        const result = [];
        
        let j = 0;
        while (j < nums.length && result.length < n) {
            if (used[j]) {
                j++;
                continue;
            }
            
            const lower = nums[j];
            const higher = lower + 2 * k;
            
            let higherIndex = -1;
            for (let l = j + 1; l < nums.length; l++) {
                if (!used[l] && nums[l] === higher) {
                    higherIndex = l;
                    break;
                }
            }
            
            if (higherIndex === -1) break;
            
            used[j] = true;
            used[higherIndex] = true;
            result.push(lower + k);
            j++;
        }
        
        if (result.length === n) {
            return result;
        }
    }
    
    return [];
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n² log n)外层循环 O(n),内层验证 O(n log n)
空间复杂度O(n)存储计数器和结果数组

相关题目