寻找整数到平方数的最小 "factorization"

Finding minimal "factorization" of an int to square-numbers

我要解决的问题:

给定一个 int n,return 这个 int 的最小 "factorization" 到全是正方形的数字。
我们在这里定义因式分解不是通常的方式:km 数字 (m1, m2, m3...) 的因式分解将是这样的:m1 + m2 + m3 + ... = k.

例如:设n = 12。最佳解决方案是:[4,4,4] 因为 4 是 2 和 4 + 4 + 4 = 12 的平方。还有 [9,1,1,1] 虽然它不是最小的,因为它是 4 个数字而不是前者的 3 个。


我尝试解决这个问题:

我的想法是给定的数字n我们将执行以下算法:
首先我们会找到最接近 n 的平方数(例如如果 n = 82 我们会找到 81.
然后我们将递归地计算我们得到的数字减去最接近它的平方。
这是一个流程示例:假设 n = 12 并且我们的函数是 f,我们计算 f(3) UNION {9} 然后 f(12-4) UNION {4} 然后f(12-2) 联盟 {2}。从每一个中我们得到一个方形组合列表,我们从中获取最小列表。我们将它们保存在 HashMap 中以避免重复(动态编程风格)。

Java 中的代码尝试(不完整):

public List<Integer> getShortestSquareList(int n){
    HashMap<Integer,List<Integer>> map = new HashMap<Integer,List<Integer>();
    map.put(1, 1);
    List<Integer> squareList = getSquareList(n);
    return internalGetShortestSquareList(n, map, squareList);
}

List<Integer> getSquareList(int n){
    List<Integer> result=new ArrayList<Integer>();
    int i = 1;
    while(i*i <= n){
        result.add(i*i);
        i++;
    }
    return result;
}

public int getClosestSquare(int n,List<Integer> squareList){
    // getting the closestSquareIndex
}

public List<Integer> internalGetShortestSquareList(int n, HashMap<Integer m, HashMap<Integer,List<Integer>> map, List<Integer> squareList){
    if (map.contains(n)) {return map.get(n);}
    int closestSqureIndex=getClosestSquare(m,squareList);
    List<Integer> minSquareList;
    int minSize=Integer.MAX_INT;

    for(int i=closestSqureIndex; i>-1; i--) {
            int square = squareList.get(closestSqureIndex);
            List<Integer> tempSquares= new ArrayList<Integer>(square);
            tempSquares.addAll(f(n-square, map, squareList));

            if (tempSquares.size() < minSize) {
                minSize = tempSize;
                minSquareList = tempSquares;
            }

    }
    map.put(n, minSquareList);       
    return map.get(n);              
}

我的问题:

看来我的解决方案不是最优的(imo)。我认为我的解决方案的时间复杂度是 O(n)*O(Sqrt(n)),因为最大递归深度是 n,最大子节点数是 Sqrt(n)。我的解决方案可能充满了错误——目前这对我来说无关紧要。我将不胜感激任何指导以找到更优化的解决方案(伪代码或其他方式)。

基于@trincot 的 link,我会建议一个简单的 O(n sqrt n) 算法。这个想法是:

  1. 对小于或等于 n 的正方形进行穷举搜索,以确定 n 是否是一个正方形本身,或者是任何两个或三个小于 n 的正方形的总和.这可以在 sqrt(n)^3 时间内完成,即 O(n sqrt n).
  2. 如果失败,则在四个方格中找到 "factorization" 个 n

递归求一个数m的4次因式分解,现在有三种情况:

  1. m是素数而m mod 4 = 1。根据math,我们知道n是两个平方的乘积。简单的详尽搜索或更多 "mathy" 方法都应该给出一个简单的答案。
  2. m是素数而m mod 4 = 3。这种情况仍然需要计算出细节,但可以使用 link.
  3. 中描述的数学来实现
  4. m是合数。这是递归的情况。首先将 m 分解为两个因子,即整数 uv,以便 u*v=m。出于性能原因,它们应该尽可能接近,但这是次要细节。

之后,递归求uv的四次分解。

然后,使用 the formula:

(a^2+b^2+c^2+d^2) (A^2+B^2+C^2+D^2) = (aA+bB+cC+dD)^2 + (aB-bA+cD-dC)^2 + (aC-bD-cA+dB)^2 + (aD-dA+bC-cB)^2

找到 m 的 4 次因式分解。在这里我表示 u = (a^2+b^2+c^2+d^2)v = (A^2+B^2+C^2+D^2),因为此时它们的 4 分解是已知的。

更简单的解决方案:

这是 Coin Change 问题的一个版本。 您可以使用硬币调用以下方法作为小于金额的平方数列表(在您的示例中为n)。

示例:amount=12coins={1,2,4,9}

public int coinChange(int[] coins, int amount) {
    int max = amount + 1;             
    int[] dp = new int[amount + 1];  
    Arrays.fill(dp, max);  
    dp[0] = 0;   
    for (int i = 1; i <= amount; i++) {
        for (int j = 0; j < coins.length; j++) {
            if (coins[j] <= i) {
                dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
            }
        }
    }
    return dp[amount] > amount ? -1 : dp[amount];
}

它的复杂度是 O(n*m),其中 m 是硬币的数量。因此,在您的示例中,它的复杂性与您提到的相同 O(n*sqrt(n)) 它用动态规划解决了 - 自下而上的方法。 代码取自 here.