繁体   English   中英

查找加起来为给定总和的最小子数组(允许重复)

[英]Finding the smallest sub-array that adds up to a given sum (Duplicates are allowed)

我想找到加到给定目标的最小子数组。
我的输入是输入数组和目标和。

我知道这个问题已经被问过很多次了,但是在大多数情况下,人们试图找到加总到他们目标上的所有可能组合,或者他们的解决方案不允许重复。

就我而言,我想要查找最小的子阵列和输入阵列的数据复制是允许的。

例如,给定[1,4,10,20,35]的输入数组和17的目标,我期望输出数组[10,4,1,1,1]
因此,允许我的算法在查找输出时复制输入数组中的任何值。

这是我到目前为止的内容:

public static ArrayList<Integer> representNumberAsSumOfArrayElements(ArrayList<Integer> inputs, int target)
{
    ArrayList<Integer> result = new ArrayList<>();

    //First sort the input array in descending order
    Collections.sort(inputs, Collections.reverseOrder());
    int i = 0;

    //Iterate through the input array and break down the target into a sum of the input array
    while (i < inputs.size() && target > 0) {
        if(target >= inputs.get(i) ) {
            result.add(inputs.get(i));
            target = target - inputs.get(i);
        } else {
            i++;
        }
    }
    return result;
}

只需一个简单的驱动程序即可在100个目标上测试代码:

public static void main(String[] args) {
    ArrayList<Integer> inputs = new ArrayList<>(Arrays.asList( 1, 4, 10, 20, 35, 56, 84));
    int n = 100;
    ArrayList<Integer> sumArray = new ArrayList<>();
    for (int i = 0; i <= n; i++)
    {
        sumArray = representNumberAsSumOfArrayElements(inputs, i); // O(n)
        System.out.println(i + " written as a sum of array elements " + sumArray);
    }
}

我已经实现了适用于大多数值的O(n)算法,但是在某些情况下,我得到了错误的结果。
例如,当我使用输入[1,4,10,20,35,56,84]和目标总和69运行代码时,正确的输出应为[35,20,10,4]但是我的算法输出[56,10,1,1,1]
我知道为什么我的算法错误,但是我不确定如何解决。

首先创建一个前缀和数组,使prefSum [i]给出给定数组的所有元素从索引0到i(包括两端)的和。如果您的数组包含所有正整数,则对prefSum数组进行排序,然后可以进行二进制搜索。 因此,如果您当前的索引是i,则将prefSum数组从0扫描到长度,并在0到(i-1)之间进行二进制搜索,并尝试找到最大的j,其中j在0到i-1之间,这样prefSum [i] -prefSum [j] =给定的目标。 总体复杂度将为nlogn。

术语subarray数组通常假定连续的数组(这就是为什么某些回答者意味着另一个问题的原因),但是您的排序告诉我们事实并非如此,并且您需要以最小的顺序排列最小的项列表来解决重复项的子集和问题。

您可以使用表方法通过动态编程解决当前问题(而您的代码采用贪婪方法-在一般情况下不适用)。 为了获得最小的子集,您只需要选择值列表较短的子问题解决方案即可。

似乎此Python代码正常运行(未经良好测试)。

def best(lst, summ):
    lst = sorted(lst, reverse = True)
    a = [[] for _ in range(summ + 1)]  # list of lists
    a[0].append(-1)             # fake value to provide  valid zero entry
    for l in lst:
        for i in range(summ + 1 - l):
            t = len(a[i])
            if t:
                if (len(a[i + l]) == 0) or (t < len(a[i + l])):
                    a[i + l] = a[i] +[l]   # concatenate lists
    return a[summ][1:]   #remove fake -1

 for s in range(55, 71):
     print(s, best([1,4,10,20,35,56,84], s))

55 [35, 20]
56 [56]
57 [56, 1]
58 [56, 1, 1]
59 [35, 20, 4]
60 [56, 4]
61 [56, 4, 1]
62 [56, 4, 1, 1]
63 [35, 20, 4, 4]
64 [56, 4, 4]
65 [35, 20, 10]
66 [56, 10]
67 [56, 10, 1]
68 [56, 10, 1, 1]
69 [35, 20, 10, 4]
70 [35, 35]

注意,我们不需要自己存储列表-我添加它们是为了调试和简化。 我们只需要存储给定总和中的最后一个增加值和项目数。
展开列表的解决方案:

def best1(lst, summ):
    a = [(0,0)] * (summ + 1)   # list contains tuples (value, bestcount)
    a[0] = (-1,1)
    for l in lst:
        for i in range(summ + 1 - l):
            t = a[i][1]
            if t:
                if (a[i + l][1] == 0) or (t < a[i + l][1]):
                    a[i + l] = (l, t + 1)
    res = []
    t = summ
    while a[t][1] > 1:
        res.append(a[t][0])
        t = t - a[t][0]

    return res

由于您使用的是Java ,因此我将使用动态编程 (在本例中为递归)在Java中添加一个实现。


SubSumDupComb.java:

import java.util.*;

public class SubSumDupComb {
    /**
     * Find shortest combination add to given sum.
     *
     * @param arr input array,
     * @param sum target sum,
     * @return
     */
    public static int[] find(int[] arr, int sum) {
        // System.out.printf("input arr: %s, sum: %d\n", Arrays.toString(arr), sum);
        List<Integer> list = find(arr, 0, sum, new ArrayList<>());
        // System.out.printf("result: %s\n", list);

        return listToArray(list);
    }

    /**
     * Find shortest combination add to given sum, start from given index.
     *
     * @param arr        input array,
     * @param start      start index, for further search,
     * @param sum        remain sum,
     * @param prefixList prefix list,
     * @return
     */
    private static List<Integer> find(int[] arr, int start, int sum, List<Integer> prefixList) {
        if (sum == 0) return prefixList; // base case,
        if (start >= arr.length || sum < 0) return null; // bad case,

        // exclude current index,
        List<Integer> shortestExcludeList = find(arr, start + 1, sum, prefixList);

        // include current index,
        List<Integer> includePrefixList = new ArrayList<>(prefixList);
        includePrefixList.add(arr[start]);
        List<Integer> shortestIncludeList = find(arr, start, sum - arr[start], includePrefixList);

        if (shortestIncludeList == null && shortestExcludeList == null) return null; // both null,
        if (shortestIncludeList != null && shortestExcludeList != null) // both non-null,
            return shortestIncludeList.size() < shortestExcludeList.size() ? shortestIncludeList : shortestExcludeList; // prefer to include elements with larger index,
        else return shortestIncludeList == null ? shortestExcludeList : shortestIncludeList; // exactly one null,
    }

    /**
     * Find shortest combination add to given sum, with cache.
     *
     * @param arr input array,
     * @param sum target sum,
     * @return
     */
    public static int[] findWithCache(int[] arr, int sum) {
        // System.out.printf("input arr: %s, sum: %d\n", Arrays.toString(arr), sum);
        List<Integer> list = findWithCache(arr, 0, sum, new ArrayList<>(), new HashMap<>());
        // System.out.printf("result: %s\n", list);

        return listToArray(list);
    }

    /**
     * Find shortest combination add to given sum, start from given index, with cache.
     *
     * @param arr        input array,
     * @param start      start index, for further search,
     * @param sum        remain sum,
     * @param prefixList prefix list,
     * @return
     */
    private static List<Integer> findWithCache(int[] arr, int start, int sum, List<Integer> prefixList, Map<Integer, Map<Integer, List<Integer>>> cache) {
        if (sum == 0) return prefixList; // base case,
        if (start >= arr.length || sum < 0) return null; // bad case,

        // check cache,
        Map<Integer, List<Integer>> cacheAtStart;
        if ((cacheAtStart = cache.get(start)) != null && cacheAtStart.containsKey(sum)) { // cache hit, tips: the cashed list could be null, which indicate no result,
            // System.out.printf("hit cache: start = %d, sum = %d, cached list: %s\n", start, sum, cacheAtStart.get(sum));
            return cacheAtStart.get(sum);
        }

        // exclude current index, tips: should call this first,
        List<Integer> shortestExcludeList = findWithCache(arr, start + 1, sum, prefixList, cache);

        // include current index,
        List<Integer> includePrefixList = new ArrayList<>(prefixList);
        includePrefixList.add(arr[start]);
        List<Integer> shortestIncludeList = findWithCache(arr, start, sum - arr[start], includePrefixList, cache);

        List<Integer> resultList;

        if (shortestIncludeList == null && shortestExcludeList == null) resultList = null; // both null,
        else if (shortestIncludeList != null && shortestExcludeList != null) // both non-null,
            resultList = shortestIncludeList.size() < shortestExcludeList.size() ? shortestIncludeList : shortestExcludeList; // prefer to include elements with larger index,
        else
            resultList = (shortestIncludeList == null ? shortestExcludeList : shortestIncludeList); // exactly one null,

        // add to cache,
        if (cacheAtStart == null) { // init cache at given start,
            cacheAtStart = new HashMap<>();
            cache.put(start, cacheAtStart);
        }
        cacheAtStart.put(sum, resultList == null ? null : resultList); // add this result to cache,
        // System.out.printf("add cache: start = %d, sum = %d, list: %s\n", start, sum, resultList);

        return resultList;
    }

    /**
     * List to array.
     *
     * @param list
     * @return
     */
    private static int[] listToArray(List<Integer> list) {
        if (list == null) return null; // no solution,

        // list to array,
        int[] result = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            result[i] = list.get(i);
        }

        return result;
    }
}

SubSumDupCombTest.java:
(测试用例,通过TestNG

import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.Arrays;

public class SubSumDupCombTest {
    private int[] arr;
    private int sum;
    private int[] expectedResultArr;

    private int[] arr2;
    private int sum2;
    private int sum2NoSolution;
    private int[] expectedResultArr2;

    @BeforeClass
    public void setUp() {
        // init - arr,
        arr = new int[]{1, 4, 10, 20, 35};
        sum = 17;
        expectedResultArr = new int[]{1, 4, 4, 4, 4};
        Arrays.sort(expectedResultArr);

        // init - arr2,
        arr2 = new int[]{14, 6, 10};
        sum2 = 40;
        sum2NoSolution = 17;
        expectedResultArr2 = new int[]{10, 10, 10, 10};
        Arrays.sort(expectedResultArr2);
    }

    @Test
    public void test_find() {
        // arr
        int[] resultArr = SubSumDupComb.find(arr, sum);
        Arrays.sort(resultArr);
        Assert.assertTrue(Arrays.equals(resultArr, expectedResultArr));

        // arr2
        int[] resultArr2 = SubSumDupComb.find(arr2, sum2);
        Arrays.sort(resultArr2);
        Assert.assertTrue(Arrays.equals(resultArr2, expectedResultArr2));
    }

    @Test
    public void test_find_noSolution() {
        Assert.assertNull(SubSumDupComb.find(arr2, sum2NoSolution));
    }

    @Test
    public void test_findWithCache() {
        // arr
        int[] resultArr = SubSumDupComb.findWithCache(arr, sum);
        Arrays.sort(resultArr);
        Assert.assertTrue(Arrays.equals(resultArr, expectedResultArr));

        // arr2
        int[] resultArr2 = SubSumDupComb.findWithCache(arr2, sum2);
        Arrays.sort(resultArr2);
        Assert.assertTrue(Arrays.equals(resultArr2, expectedResultArr2));
    }

    @Test
    public void test_findWithCache_noSolution() {
        Assert.assertNull(SubSumDupComb.findWithCache(arr2, sum2NoSolution));
    }
}

说明:

  • find()
    这纯粹是递归。
    Compleixty:

    • 时间:: O(t^n) //在最坏的情况下,
    • 空格O(n) //由递归方法堆栈使用,

    哪里:

    • t包括元素的平均时间。
  • findWithCache()
    对每对(start, sum)使用缓存。
    Compleixty:

    • 时间: O(n * s)
    • 空间O(n * s) //由缓存和递归方法堆栈使用,

    哪里:

    • s ,是中间可能和的计数。

提示:

  • 当存在多个可能的最短结果时,结果更喜欢索引较大的数字。

广度优先(BFS)方法的复杂度为O( n*k ),其中n是数组中唯一元素的数量, k是最短答案的长度。 伪代码如下:

1. remove duplicates from the input array, A:
   can be done by copying it into a set in O(|A|)
2. build a queue of lists Q;
   store the sum of elements as its 0th element, 
   and add an initially-empty list with a sum of 0
3. while Q is not empty, 
     extract the first list of Q, L
     for each element e in A,
       if L[0] + e == sum, 
           you have found your answer: the elements of L with e
       if L[0] + e < sum, 
           insert a new list (L[0] + e, elements of L, e) at the end of Q
4. if you reach this point, there is no way to add up to the sum with elements of A

不使用列表的第0个元素作为总和将导致重新计算其元素的总和的成本。 从这个意义上讲,存储和是一种动态编程的形式(=重复使用先前的答案以避免重新计算它们)。

这样可以保证第一个累加到sum列表也是最短的长度(因为队列中的所有列表都是按长度的升序评估的)。 您可以通过添加启发式方法来选择首先评估哪个相同长度列表(例如,最接近总和的那个)来提高运行时间。 但是,这仅适用于特定的输入,最坏情况下的复杂度将保持不变。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM