中等题 和的逆运算问题 | 豆包MarsCode AI刷题

72 阅读6分钟

和的逆运算

问题描述

n 个整数两两相加可以得到 n(n - 1) / 2 个和。我们的目标是:根据这些和找出原来的 n 个整数 按非降序排序返回这 n 个数,如果无解,输出 "Impossible"。

测试样例

样例1:

输入:n = 3, sums = [1269, 1160, 1663]
输出:"383 777 886"

样例2:

输入:n = 3, sums = [1, 1, 1]
输出:"Impossible"

样例3:

输入:n = 5, sums = [226, 223, 225, 224, 227, 229, 228, 226, 225, 227]
输出:"111 112 113 114 115"

样例4:

输入:n = 5, sums = [-1, 0, -1, -2, 1, 0, -1, 1, 0, -1]
输出:"-1 -1 0 0 1"

样例5:

输入:n = 5, sums = [79950, 79936, 79942, 79962, 79954, 79972, 79960, 79968, 79924, 79932]
输出:"39953 39971 39979 39983 39989"

解题思路

  1. 对两两和数组进行排序 sums
    首先,对 sums 进行排序,方便后续处理。已排序的和列表可以帮助我们更方便地推导出原始数列的最小数值,从而减少复杂性。

  2. 假设数列的结构
    假设我们要恢复的数列按从小到大排序,分别为 x1,x2,...,xnx_1, x_2, ..., x_n。 根据数列的排列顺序,我们可以确定一些和的大小关系,例如:

    x1+x2<x1+x3<x2+x3x1+x2<x1+x3<x1+x4x_1 + x_2 < x_1 + x_3 < x_2 + x_3 \\ x_1 + x_2 < x_1 + x_3 < x_1 + x_4

    这样一来,能确定 sums[0]=x1+x2sums[0] = x_{1} + x_{2}sums[1]=x1+x3sums[1] = x_{1} + x_{3},但还不能确定 x2+x3x_{2} + x_{3} 对应的元素,不过我们可以知道它必定在 sums[n] 之前。

  3. 遍历确定 x2+x3x_{2} + x_{3} 的位置
    通过遍历 sums 数组中的位置 i < n,我们可以不断假设 x2+x3=sums[i]x_{2} + x_{3} = sums[i]。这一步用于尝试不同的排列,确保找到正确的组合。 设定:

x1+x2=sums[0]x1+x3=sums[1]x2+x3=sums[i]x_1 + x_2 = sums[0] \\ x_1 + x_3 = sums[1] \\ x_2 + x_3 = sums[i]
  1. 根据公式推导 x1x_{1}, x2x_{2}, 和 x3x_{3}
    利用已知的三条和关系,可以推导出 x1x_{1}, x2x_{2}, 和 x3x_{3} 的值:

  2. 递归恢复剩余的数
    使用回溯法递归推导其余的数,每次求出一个数,并逐步验证生成的数列是否符合条件。对于每次生成的数 xkx_{k},我们都要验证其和已生成的数列的和是否在 sums 中。

  3. 验证结果的正确性
    生成一个新的数后,计算 numbers 中所有数的两两和,逐步从 sums 中移除这些和,确保 sums 最终为空。若满足条件,则说明恢复成功。

  4. 输出结果
    如果成功恢复了所有 n 个数并满足条件,将 numbers 排序后输出。如果找不到符合条件的组合,则返回 "Impossible"

代码

import java.util.*;

public class Main {
    /**
     * 根据给定的两两和恢复出原始的 n 个整数序列
     * @param n 原始整数的个数
     * @param sums 两两和的数组
     * @return 恢复出的整数序列(按非降序排列),或者 "Impossible" 如果无解
     */
    public static String solution(int n, int[] sums) {
        // 将所有的两两和排序,方便后续处理
        Arrays.sort(sums);
        List<Integer> sortedSums = new ArrayList<>();
        for(int s : sums){
            sortedSums.add(s);
        }

        // 初始化已确定的数和最终结果
        List<Integer> numbers = new ArrayList<>();
        List<Integer> result = new ArrayList<>();
        // 遍历确定b+c的位置
        for(int i = 2; i < n; i++){
            if(backtrack(n, sortedSums, numbers, result, i)){
                Collections.sort(result);
                StringBuilder sb = new StringBuilder();
                for(int num : result){
                    sb.append(num).append(" ");
                }
                return sb.toString().trim();
            }
        }
        return "Impossible";
        
    }

    /**
     * 递归回溯函数,用于恢复原始的整数序列
     * @param n 需要恢复的整数个数
     * @param sums 当前剩余的两两和列表(已排序)
     * @param numbers 当前已恢复的整数列表
     * @param result 最终的恢复结果
     * @return 如果找到合法的整数序列,返回 true;否则,返回 false
     */
    static boolean backtrack(int n, List<Integer> sums, List<Integer> numbers, List<Integer> result, int first) {
        // 基本情况:如果已经恢复了 n 个数,并且所有的和都已被消耗,成功
        if(numbers.size() == n){
            if(sums.isEmpty()){
                result.addAll(numbers);
                return true;
            }
            else{
                return false;
            }
        }

        // 如果当前已恢复的数少于 3 个,需要使用前三个和来推断前三个数
        if(numbers.size() < 3){
            if(numbers.size() == 0 && sums.size() >= 3){
                // 获取前三个最小的和
                int s1 = sums.get(0); // x1 + x2
                int s2 = sums.get(1); // x1 + x3
                int s3 = sums.get(first); // x2 + x3

                // 根据公式计算 x1, x2, x3
                if( (s1 + s2 - s3) % 2 != 0 ){
                    // 如果结果不是整数,无法恢复,继续
                    return false;
                }
                int x1 = (s1 + s2 - s3)/2;
                int x2 = s1 - x1;
                int x3 = s2 - x1;
                
                // 检查计算结果的合理性
                if(x1 > x2 || x1 > x3 || x2 > x3){
                    return false;
                }
                if(x1 + x2 != s1 || x1 + x3 != s2 || x2 + x3 != s3){
                    return false;
                }

                // 创建新的和列表,移除已使用的和
                List<Integer> newSums = new ArrayList<>(sums);
                if(!removeSum(newSums, s1)) return false;
                if(!removeSum(newSums, s2)) return false;
                if(!removeSum(newSums, s3)) return false;

                // 添加确定的数到已恢复列表
                List<Integer> newNumbers = new ArrayList<>(numbers);
                newNumbers.add(x1);
                newNumbers.add(x2);
                newNumbers.add(x3);
                Collections.sort(newNumbers); // 保持有序

                // 递归调用
                if(backtrack(n, newSums, newNumbers, result, first)){
                    return true;
                }
            }
            else{
                // 对于当前已恢复 1 或 2 个数的情况,暂不处理,直接返回失败
                return false;
            }
        }
        else{
            // 已恢复至少 3 个数,尝试恢复下一个数
            // 假设当前最大的和是 xn + xk,其中 xk 是已恢复数中的一个数
            // 这里选择 xk 为已恢复数中的第一个数(最小数)
            int x1 = numbers.get(0); // 最小的已恢复数
            int s = sums.get(0); // 当前最大的和
            int xn = s - x1; // 推断出新的数

            // 确保新的数不小于已恢复的数,保持非降序
            if(!numbers.isEmpty()){
                int last = numbers.get(numbers.size()-1);
                if(xn < last){
                    return false;
                }
            }

            // 计算 xn 与已恢复数的所有和
            List<Integer> requiredSums = new ArrayList<>();
            for(int num : numbers){
                requiredSums.add(xn + num);
            }

            // 创建新的和列表并移除所需的和
            List<Integer> newSums = new ArrayList<>(sums);
            boolean valid = true;
            for(int reqSum : requiredSums){
                if(!removeSum(newSums, reqSum)){
                    valid = false;
                    break;
                }
            }

            if(valid){
                // 插入新的数,保持有序
                List<Integer> newNumbers = new ArrayList<>(numbers);
                int insertPos = Collections.binarySearch(newNumbers, xn);
                if(insertPos < 0){
                    insertPos = -insertPos -1;
                }
                newNumbers.add(insertPos, xn);

                // 递归调用
                if(backtrack(n, newSums, newNumbers, result, first)){
                    return true;
                }
            }

            // 如果无法找到有效的下一个数,返回失败
            return false;
        }

        // 如果所有尝试都失败,返回失败
        return false;
    }
   

    /**
     * 从和列表中移除一个指定的和。如果存在多个相同的和,则只移除一个
     * @param sums 当前的和列表
     * @param target 需要移除的和
     * @return 如果成功移除,返回 true;否则,返回 false
     */
    static boolean removeSum(List<Integer> sums, int target){
        for(int i=0; i<sums.size(); i++){
            if(sums.get(i) == target){
                sums.remove(i);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        // You can add more test cases here
        int[] sums1 = {1269, 1160, 1663};
        int[] sums2 = {1, 1, 1};
        int[] sums3 = {226, 223, 225, 224, 227, 229, 228, 226, 225, 227};
        int[] sums4 = {-1, 0, -1, -2, 1, 0, -1, 1, 0, -1};
        int[] sums5 = {79950, 79936, 79942, 79962, 79954, 79972, 79960, 79968, 79924, 79932};

        System.out.println(solution(3, sums1).equals("383 777 886"));
        System.out.println(solution(3, sums2).equals("Impossible"));
        System.out.println(solution(5, sums3).equals("111 112 113 114 115"));
        System.out.println(solution(5, sums4).equals("-1 -1 0 0 1"));
        System.out.println(solution(5, sums5).equals("39953 39971 39979 39983 39989"));
    }
}