找到最小的"分解"一个int到平方数

时间:2016-01-06 13:50:19

标签: java algorithm dynamic-programming pseudocode factorization

我想解决的问题:

给定int n,返回最小的"分解"这个int对于所有正方形的数字 我们在此处不按常规方式定义分解:km数字(m1, m2, m3...)的分解将是:m1 + m2 + m3 + ... = k

例如:let 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) UNION {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)。我的解决方案可能充满了错误 - 这对我来说并不重要。我将非常感谢您找到更优化解决方案的任何指导(伪代码或其他方式)。

2 个答案:

答案 0 :(得分:2)

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

  1. 在小于或等于n的方块上使用穷举搜索,以查明n是否为方形本身,或者是否小于n的任意两个或三个方格的总和。这可以在sqrt(n)^3时间内完成,即O(n sqrt n)
  2. 如果失败,则在四个方格中找到n的“因子分解”。
  3. 要递归查找数字m的4分解,现在有三种情况:

    1. m是素数,m mod 4 = 1。根据{{​​3}},我们知道n是两个正方形的乘积。无论是简单的详尽搜索还是更多“肮脏”的方法都应该给出一个简单的答案。
    2. m是素数,m mod 4 = 3。这种情况仍然需要计算细节,但可以使用math
    3. 中描述的数学方法来实现
    4. m是一个综合数字。这是递归的情况。首先将m分解为两个因素,即整数uv,以便u*v=m。出于性能原因,它们应该尽可能接近,但这只是一个小细节。
    5. 之后,递归地找到uv的4分解。

      然后,使用link

      (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因子分解是已知的。

答案 1 :(得分:1)

更简单的解决方案:

这是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