【问题标题】:Divide an Array in equal size, such that value of given function is minimum将一个数组分成相等的大小,使得给定函数的值最小
【发布时间】:2017-06-15 02:49:12
【问题描述】:

我遇到了以下问题陈述。

您有一个大小为N 的自然数列表,您必须将这些值分布在大小为N/2 的两个列表AB 中,以便A 元素的平方和最接近可以乘以B 元素。

示例: 考虑清单 7 11 1 9 10 3 5 13 9 12。
优化后的分布是:
列表 A:5 9 9 12 13
清单 B:1 3 7 10 11
这导致差异 abs( (5+9+9+12+13)^2 - (1*3*7*10*11) ) = 6
因此,您的程序应该输出 6,这是可以达到的最小差异。

我的尝试:

我尝试过贪婪方法来解决这个问题。我取了两个变量summul。现在我开始一个一个地从给定的集合中获取元素,并尝试将它添加到变量和计算电流中 和和乘法的平方。现在确定两个集合之一中的元素,使组合给出最小的可能值。

但是这种方法在给定的示例中不起作用。我不知道这里可以使用什么方法。

要求提供解决方案的确切代码。任何可能的方法及其有效的原因都可以。

编辑:

来源:CodinGame, Community puzzle

【问题讨论】:

  • 你的方法遇到了什么问题?
  • 如何为每个组合获取大小 n/2 的所有可能组合(这里是一个示例 stackoverflow.com/questions/2201113/…)计算 sqrSum 和产品将所有结果放在一个集合中将看到 sqrtSum 和 product,因为邻居会找到差异最小的对
  • @urag 值得注意的是,这需要指数级的时间——如果 n 甚至小到 50 左右,你就会遇到问题。通常指数时间的蛮力解决方案对于此类问题是显而易见的,关键是要找到解决问题的更聪明的方法。
  • @urag 是的。你的算法。将有指数时间。难道不能用一些多项式时间算法来解决吗?
  • @AdriaanKoster 在我的方法中差异并不总是最小的。

标签: java algorithm


【解决方案1】:

试试这个:

import java.util.Arrays;

public class Test {

    public static void main(String [] args){
        int [] arr = {7, 11, 1, 9, 10, 3, 5, 13, 9, 12}; 
        int [][] res = combinations(5, arr);
        int N = Arrays.stream(arr).reduce(1, (a, b) -> a * b);
        int min = Integer.MAX_VALUE; 
        int [] opt = new int [5];
        for (int [] i : res){
            int k = (int) Math.abs( Math.pow(Arrays.stream(i).sum(), 2) - N/(Arrays.stream(i).reduce(1, (a, b) -> a * b)));
            if(k < min){
                min = k;
                opt = i;
            }
        }
        Arrays.sort(opt);
        System.out.println("minimum difference is "+ min + " with the subset containing this elements " + Arrays.toString(opt));
    }

    // returns all k-sized subsets of a n-sized set 
    public static int[][] combinations(int k, int[] set) {
        int c = (int) binomial(set.length, k);
        int[][] res = new int[c][Math.max(0, k)];
        int[] ind = k < 0 ? null : new int[k];
        for (int i = 0; i < k; ++i) { 
            ind[i] = i; 
        }
        for (int i = 0; i < c; ++i) {
            for (int j = 0; j < k; ++j) {
                res[i][j] = set[ind[j]];
            }
            int x = ind.length - 1;
            boolean loop;
            do {
                loop = false;
                ind[x] = ind[x] + 1;
                if (ind[x] > set.length - (k - x)) {
                    --x;
                    loop = x >= 0;
                } else {
                    for (int x1 = x + 1; x1 < ind.length; ++x1) {
                        ind[x1] = ind[x1 - 1] + 1;
                    }
                }
            } while (loop);
        }
        return res;
    }
    // returns n choose k; 
    // there are n choose k combinations without repetition and without observance of the sequence
    // 
    private static long binomial(int n, int k) {
        if (k < 0 || k > n) return 0;
        if (k > n - k) {
            k = n - k;
        }
        long c = 1;
        for (int i = 1; i < k+1; ++i) {
            c = c * (n - (k - i));
            c = c / i;
        }
        return c;
    }
}

代码取自this stackoverflow answer,另请查看this 维基百科关于组合的文章。

【讨论】:

  • 嗨!谢谢你的时间。但不幸的是,这不起作用。它为给定的示例给出了正确的答案,但没有通过进一步的测试用例。我忘了提及我的问题的来源。请查看其他测试用例的编辑,我在此处添加问题链接。
【解决方案2】:

我不确定多项式时间内是否有任何精确解。但您可以尝试基于模拟退火的方法。

我的做法是:

  1. 将 listA 和 listB 初始化为随机状态
  2. 以概率 p 运行贪婪步,否则运行随机步
  3. 跟踪状态和相应的错误(使用 HashMap)

贪婪步骤:找到一个可以在优化错误的列表之间移动的元素。

随机步骤:从这两个集合中的任意一个中选择一个随机元素并计算误差。如果错误更好,请保留它。否则以 q 的概率保留它。

在这两个步骤中的任何一个步骤中,确保尚未探索新状态(或至少阻止它)。

将 p 设置为一个较小的值 (

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-16
    • 1970-01-01
    • 1970-01-01
    • 2020-01-14
    • 1970-01-01
    • 2014-08-07
    • 1970-01-01
    相关资源
    最近更新 更多