最小唯一数组总和

Posted

技术标签:

【中文标题】最小唯一数组总和【英文标题】:Minimum unique array sum 【发布时间】:2016-07-14 21:26:24 【问题描述】:

我有一个面试问题,我的算法只通过给定的示例测试用例,并没有通过所有测试用例。

问题:给定一个排序的整数数组,返回数组的总和,使每个元素都是唯一的,方法是向重复元素添加一些数字,使唯一元素的总和最小。

即,如果数组中的所有元素都是唯一的,则返回总和。 如果某些元素是重复的,则将它们递增以确保所有元素都是唯一的,从而使这些唯一元素的总和最小。

一些例子:

input1[] = 2, 3, 4, 5 => return 19 = 2+3+4+5(所有元素都是唯一的,所以只需将它们相加即可) input2[] = 1, 2, 2 => return 6 = 1+2+3(索引 2 是重复的,所以增加它) input3[] = 2, 2, 4, 5 => return 14 = 2+3+4+5(索引 1 是重复的,所以增加它)

这三个是问题中的示例,我的简单算法如下并通过了给定的三个示例,但没有通过其他看不到输入的情况。

static int minUniqueSum(int[] A) 
    int n = A.length;


    int sum = A[0];
    int prev = A[0];

    for( int i = 1; i < n; i++ ) 
        int curr = A[i];

        if( prev == curr ) 
            curr = curr+1;
            sum += curr;
        
        else 
            sum += curr;
        
        prev = curr;
    

    return sum;

我看不到该算法失败的其他输入。 我能想到的其他输入示例是

1, 1, 1, 1  --> 1, 2, 3, 4
1, 1, 2, 2, 3, 3, 3 --> 1, 2, 3, 4, 5, 6, 7

1, 2, 4, 4, 7, 7, 8 --> I think this should be 1, 2, 3, 4, 6, 7, 8  and my algorithm fails in this example because my algorithm has 1, 2, 4, 5, 7, 8, 9 whose sum is not minimum 

还有哪些其他测试用例和可以通过所有用例的算法?

有些人抱怨问题不清楚。我想让你知道这个问题。如果只允许正数或正数和负数,则没有关于添加的数字的明确描述。给定三个带有输入和输出的示例,以及其他一些您不允许看到的输入和输出案例,编写一个程序来传递所有其他看不见的输入/输出案例。这就是问题所在。

【问题讨论】:

在您的最后一个测试用例中,您正在从第二个索引中删除 1。但是,在您的问题中,您说您只能通过“如果重复添加最小数量”来使其唯一。 如果只允许加数字,1, 2, 4, 4, 7, 7, 8 应该如何变成1, 2, 3, 4, 6, 7, 8 所以只要数组保持排序,就可以添加负数? 一个测试用例可能是一个 int,它在 Integer 的最大范围内。导致溢出。您可能需要在递增之前向上转换很久以避免这种情况。您的总和可能还需要是一个大整数或双精度数。 【参考方案1】:

在重复值较多的情况下,您的算法将失败,例如

2、2、2

你会得到 7 而不是 9。

使用您的算法的最小修复是:

static int minUniqueSum(int[] A) 
    int n = A.length;

    int sum = A[0];
    int prev = A[0];

    for( int i = 1; i < n; i++ ) 
        int curr = A[i];

        if( prev >= curr ) 
            curr = prev+1;
        
        sum += curr;
        prev = curr;
    

    return sum;

*正如 cmets 中所指出的,无需对已排序的数组进行排序。

【讨论】:

@kremzeek 您的示例未排序【参考方案2】:

javascript

var list = [1, 1, 1, 10, 3, 2];

function minUniqueSum(arr) 
  const temp = arr.reduce((acc, cur) => 
    while (acc.includes(cur)) cur++;
    acc.push(cur);
    return acc;
  , []);
  console.log(temp); // [1, 2, 3, 10, 4, 5]
  return temp.reduce((acc, cur) => acc + cur, 0);


var result = minUniqueSum(list);
console.log(result); // 25

【讨论】:

【参考方案3】:

我确实喜欢这个,没有排序。

    // Complete the getMinimumUniqueSum function below.
static int getMinimumUniqueSum(int[] arr) 

 int sum = 0;

 ArrayList < Integer > arrayList = new ArrayList < Integer > (arr.length);

 arrayList.add(arr[0]);


 for (int i = 1; i < arr.length; i++) 

  int val = arr[i];

  while (arrayList.contains(val)) 

   val++;
  

  arrayList.add(val);

 



 for (int i = 0; i < arrayList.size(); i++) 
  sum += arrayList.get(i);
 

 return sum;

它通过了所有 (13) 个测试用例。

【讨论】:

Given a sorted integer array,你不需要排序并且使用 Collection.contains() 是矫枉过正的。 Arraylist.contains() 对性能有害。【参考方案4】:

虽然这个解决方案是基于 java 的,但思维过程可以应用到任何地方。

您的解决方案几乎是正确和优化的。使用多个 for 循环会减慢很多速度,因此应尽可能避免!由于您的数组已经预先排序,因此您有足够的 1 个 for 循环。

您认为最后一个测试用例错误的假设似乎并不正确,因为增量意味着您只能做 +1(实际上大多数问题将此分配限制为仅增量。)

您错过的是整数的最大范围。

如果他们传递一个 Integer.MAX_VALUE,那么您的总和将溢出并变为负数。所以你的 sum 变量需要是一个更大的类型。 double 或 BigInteger 应该可以工作(最好是 BigInteger)。

此外,当它们两次通过 MAX_VALUE 时,您的 curr+1 也会溢出变为负数。所以你希望你的 curr 和 prev 也是一个更大的类型。 long 应该这样做。

 public static double calculateMinSumSorted(int[] input)
    double sum = input[0];

    long prev = input[0];
    long cur;

    for(int i = 1 ; i < input.length ; i++)
        cur = input[i];
        if(cur <= prev)
            cur = ++prev;
        
        prev = cur;
        sum += cur;
    
    return sum;

这是我使用的一些测试用例:

@Test
public void testSimpleArray()
    double test1 = muas.calculateMinSumSorted(new int[]1,2,3,4);
    Assert.assertEquals(10, test1, 0.1);


@Test
public void testBeginningSameValues()
    double test1 = muas.calculateMinSumSorted(new int[]2,2,3,4);
    Assert.assertEquals(14, test1, 0.1);

@Test
public void testEndingSameValues()
    double test1 = muas.calculateMinSumSorted(new int[]1,2,4,4);
    Assert.assertEquals(12, test1, 0.1);

@Test
public void testAllSameValues()
    double test1 = muas.calculateMinSumSorted(new int[]1,1,1,1);
    Assert.assertEquals(10, test1, 0.1);


@Test
public void testOverMaxIntResult()
    double test1 = muas.calculateMinSumSorted(new int[]1,2,3,3,4,4,4,4,4,Integer.MAX_VALUE);
    System.out.println(test1);
    Assert.assertEquals(2147483692.0, test1, 0.1);


@Test
public void testDoubleMaxIntArray()
    double test1 = muas.calculateMinSumSorted(new int[]2,2,3,4,5,6,7,8,9, Integer.MAX_VALUE, Integer.MAX_VALUE);
    Assert.assertEquals(4294967349.0, test1, 0.1);


@Test
public void testDoubleMinIntArray()
    double test1 = muas.calculateMinSumSorted(new int[]Integer.MIN_VALUE, Integer.MIN_VALUE,2,2,3,4,5,6,7,8,9);
    Assert.assertEquals(-4294967241.0, test1, 0.1);

【讨论】:

在考虑边界条件和提供单元测试方面非常好。缺少(doc-)cmets,有点冗长。我会坚持使用long sum - An array with length n can be indexed by the integers 0 to n-1An attempt to access an array component with a long index value results in a compile-time error,所以最多有Integer.MAX_VALUE 的价值成分Integer.MAX_VALUE 导致总和约为 3/2 Integer.MAX_VALUE² - 在范围内 完全准确.【参考方案5】:

如果您可以向任何输入添加负值,那么最小值就是第 N 个三角形数,其中 N 是数组中的元素数。 (我假设我们只处理调整后数组的正数,因为否则我们可以使其任意小(负)。

所以你的算法只是寻找一对相同的连续值。如果未找到,则返回总和,否则返回 N * (N + 1) / 2


如果确实只能调整重复的元素,那么方法是在连续元素之间找到空洞,并用以前“排队”的值填充它们。 “排队”元素的实际值无关紧要,只需要一个计数器。下面是一个 C# 解决方案,我假设对元素的调整必须是正值。所以这意味着我们不能倒退并填补未使用的漏洞,从而简化问题。

int F()

    int[] a = 2, 2, 2, 3, 8, 9; // sorted list

    int n = 0; /* num */   int p = 0; /* prev; zero is an invalid value in the list */
    int q = 0; /* queue */ int s = 0; /* sum */

    for (int i = 1; i < a.Length; i++)
    
        n = a[i];
        if (n == p)
            q++; // increment queue on duplicate number
        else
        
            // for every hole between array values, decrement queue and add to the sum
            for (int j = 1; q > 0 && j < n - p; j++, q--)
                s += p + j;
            s += (p = n);
        
    
    // flush the queue
    for (; q > 0; q--)
        s += ++n;

    return s;


您的示例1, 2, 4, 4, 7, 7, 8 表明先前的假设无效。所以我继续写了一个版本,它使用队列来存储跳过的孔以供以后填充。它并没有那么痛苦,而且在结构上也非常相似,但对于大多数面试来说可能还是太过分了。

using System.Collections.Generic;
int F2()

    int[] a = 1, 1, 8, 8, 8, 8, 8; // sorted list

    int n = 0; /* num */   int p = 0; // prev; zero is an invalid value in the list
    int q = 0; /* queue */ int s = 0; // sum
    Queue<int> h = new Queue<int>(); // holes

    for (int i = 1; i < a.Length; i++)
    
        n = a[i];
        if (n == p)
            q++; // increment queue on duplicate number
        else
        
            for (int j = 1; j < n - p; j++)
                if (h.Count <= q + a.Length - i) // optimization
                    h.Enqueue(p + j);
            s += (p = n);
        
    
    // flush the queue
    for (; q > 0; q--)
        s += h.Count > 0 ? h.Dequeue() : ++n;

    return s;

在这里尝试它们:http://rextester.com/APO79723

【讨论】:

你是对的,这个问题没有具体说明“数字”是什么,所以我们可以例如假设它是负面的(你做了什么)并添加例如IEEE 754“负无穷大”或最小的实数双精度数,或者-如果限制为正值-将差值添加到下一个最小浮点数等。但是,如果存在例如,您的答案是有缺陷的。只有一个重复的数字[1,5,5,9] 您只能修改其中一个5 元素,这将不允许填充其他“洞”并且总和不可能是N * (N + 1) / 2 @le_m 该问题未指定允许修改哪些值。 “添加一些数字”有点不清楚,我确实在我的回答中陈述了这个假设 恕我直言,这里的问题很准确:“通过向重复元素添加一些数字” - 好吧,有人可能会争辩说只能修改一个或两个重复元素,但我们仍然不能保证您的以上关于最小值的假设。总和。 @le_m 你是对的。我专注于下面的重述。但如前所述,我对此没有信心,我陈述了我的假设。 对,整个问题不是很清楚,需要拆开——可能不是面试时最好的做法,但谁愿意为一个连工资都拿不到的雇主工作问题对吗? :)【参考方案6】:
int a[] = 1,2,2,3,5,6,6,6,6 ; So what would be elements in array for sum
As per above problem statement it would be 1,2,3,4,5,6,7,8,9 

Solution
public static void uniqueSum()
        int a[] = 1,2,2,3,5,6,6,6,6 ;
        int n = a.length;
        int sum = a[0];
        int prv=a[0];
        for(int i=1; i<n;i++)
            int cur = a[i];
            if(cur==prv)
                cur = cur+1;
                sum+= cur;
                System.out.print("--"+cur);
            else
                if(cur<prv)
                    cur = prv +1;
                
                sum += cur;
            
            prv = cur;
        
        System.out.println("===================== "+sum);
    

【讨论】:

【参考方案7】:

你可以试试下面的代码。

int a[] = 1, 1 , 1;
ArrayList<Integer> arr = new ArrayList<Integer>();
HashMap hash = new HashMap();
for(int i=0;i<a.length;i++)
    arr.add(a[i]);

int sum = 0;
hash.put(0, arr.get(0));
sum = (int) hash.get(0);
for(int i=1;i<arr.size();i++)
    for(int j=1;j<=a.length;j++)
        if(hash.containsValue((arr.get(i))))
            arr.set(i, arr.get(i)+1);
        else
            hash.put(i, arr.get(i));
            sum += (int) hash.get(i);
            break;
        
    


System.out.println(sum);

PS:即使我在面试中得到了这个问题,上面的代码也通过了所有的测试用例。

【讨论】:

介意用你的代码解释你的理由吗? 0 的预期结果是什么,这段代码会产生什么以及为什么?【参考方案8】:

公共静态 int minSum(int arr[])

    for(int i=0; i<arr.length-1;i++)

        if(arr[i]==arr[i+1])

            arr[i+1]= arr[i+1]+1;
        
    

    int sum=0;

    for(int i=0; i<arr.length;i++)

        sum=sum+arr[i];
    

    System.out.println("sum: "+sum);
    return sum;

【讨论】:

【参考方案9】:

根据您对隐藏 I/O 的描述,这可能是一道 HackerRank 测试题。说明问题的更好方法是“给定一个排序的数字数组,通过递增它们(一次 num++)使数字不同,从而使数组总和最小化。”

该问题只允许增量,即一次将数字增加 1。这也确保了数组始终保持排序。 所以 1, 2, 4, 4, 7, 7, 8 --> 1, 2, 4, 5, 7, 8, 9

这是解决方案的问题。 https://www.geeksforgeeks.org/making-elements-distinct-sorted-array-minimum-increments/

【讨论】:

【参考方案10】:

工作解决方案 (JAVA 7) :

public static int getMinimumUniqueSum(List <Integer> arr)
    int sum = 0, val = 0;
    ArrayList < Integer > arrayList = new ArrayList < Integer > (arr.size());
    arrayList.add(arr.get(0));
    for (int i = 1; i < arr.size(); i++) 
        val = arr.get(i);
        while (arrayList.contains(val)) 
            val++;
        
        arrayList.add(val);    
    
    for (int i = 0; i < arrayList.size(); i++) 
        sum += arrayList.get(i);
    
    return sum;

【讨论】:

【参考方案11】:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

/* No sorting required. Works even with random list */
public static int getMinUniqueSum(List<Integer> list)
    
        Set<Integer> set = new HashSet<Integer>();

        int sum = 0;

        for (Integer val : list) 
        
            if(!set.add(val))
            
                while(true)
                
                    Integer temp = val + 1;
                    if(set.add(temp))
                    
                        sum = sum + temp;
                        break;
                    
                
            
            else
            
                sum = sum + val;
            
        

        return sum;
    

    public static void main(String[] args) 
    
        Scanner s = new Scanner(System.in);

        System.out.println("Enter size of the list");

        int n = s.nextInt();

        List<Integer> list = new ArrayList<Integer>(n);

        System.out.println("Enter " + n + " elements of the list");

        for(int i = 0; i < n; i++)
            list.add(s.nextInt());

        s.close();

        System.out.println("MinUniqueSum = " + getMinUniqueSum(list));

    

【讨论】:

Given a sorted integer array,反正不需要排序。【参考方案12】:

在 C++ 中:

int64_t getMinimumUniqueSum(std::vector<int> arr)

    std::sort(arr.begin(), arr.end());

    int64_t sum = arr[0];
    size_t i = 0;
    size_t j = 1;
    size_t gap_i = j;
    int avail_val = arr[j] + 1;

    while (j < arr.size()) 
        // find next gap with available values
        if (j > gap_i) 
            gap_i = j;
            avail_val = arr[gap_i] + 1;
        
        while (gap_i < arr.size() && arr[gap_i] <= avail_val) 
            avail_val = arr[gap_i] + 1;
            gap_i++;
        

        if (arr[i] == arr[j]) 
            // update duplicated value
            arr[j] = avail_val;
            avail_val++;
         else 
            // move index of prev value - i
            i = j;
        

        sum += arr[j];
        j++;
    

    return sum;

使用哈希集的直接解决方案会更慢:

int64_t getMinimumUniqueSum_Slow(std::vector<int> arr)

    std::unordered_set<int> s;

    int64_t sum = 0;

    for (int a : arr) 
        while (s.find(a) != s.end()) 
            a++;
        
        s.insert(a);
        sum += a;
    

    return sum;

Slow 版本大约需要 10s 来处理具有 10^5 个数字的数组。

虽然已优化,但处理具有 10^7 个数字的数组大约需要 0.5 秒

虽然缓慢的解决方案显然是正确的 - 我们可以用它来测试优化的解决方案:

std::vector<int> random_vec(size_t size, int min_val, int max_val)

    std::random_device rnd_device;
    std::mt19937 mersenne_engine rnd_device();
    std::uniform_int_distribution<int> dist min_val, max_val;

    auto gen = [&dist, &mersenne_engine]()
                   return dist(mersenne_engine);
               ;

    std::vector<int> arr(size);
    generate(begin(arr), end(arr), gen);

    return arr;


int main()

    for (int i = 0; i < 1000; i++) 
        printf("%d\n", i);
        auto arr = random_vec(i*10+1, -5, 5);

        int64_t x = getMinimumUniqueSum(arr);
        int64_t y = getMinimumUniqueSum_Slow(arr);
        if (x != y) 
            printf("Results not match: fast -> %lld, slow -> %lld !!!\n\n", x, y);
            return 1;
        
    

    return 0;


【讨论】:

【参考方案13】:

在 Haskell 中:

countdups _ _  [] = []
countdups first prev (x:xs)             
        | (prev >= x) && (first /= True) = (prev+1) : countdups False (prev+1)  xs 
        | otherwise = x: countdups False x xs

minsum list =  sum $ countdups True 0 (sort list)

这是我使用的一些测试用例:

countdups True 0 [2, 3, 4, 5]

[2,3,4,5]

minsum = 14 

countdups True 0 [1, 2, 2]

[1,2,3]

minsum = 6

countdups True 0 [2, 2, 4, 5]

[2,3,4,5]

minsum = 14

countdups True 0 [1,2,2,3,7]

[1,2,3,4,7]

minsum = 17

countdups True 0 [1,1,1,2,3,10]

[1,2,3,4,5,10]

minsum = 25

countdups True 0 [1,1,1,1]

[1,2,3,4]    

minsum= 10

countdups True 0 [1,2,3,3,4,4,4,4,4,2147483647]

[1,2,3,4,5,6,7,8,9,2147483647]

minsum= 2147483692

【讨论】:

【参考方案14】:
// 1,1,2,3 -> 1,2,2,3 -> 1,2,3,3 -> 1,2,3,4 => 10
// 2,2,2 -> 2,3,2 -> 2,3,3 -> 2,3,4 => 9
public int calculateMinSumSorted(int[] input) 
    int sum = input[0];
    for (int i = 1, v = sum; i < input.length; v = input[i++]) 
        if (input[i] <= input[i - 1]) 
            input[i--] = ++v;
         else 
            sum += input[i];
        
    
    return sum;

【讨论】:

【参考方案15】:

在 java 中使用集合有很大帮助, 这里我使用 HashMap,因为它存储每个唯一键的值

我在 hashmap 中的 Key 是数组元素,value 是 no。出现在数组中的计数。

package uniquesum;
import java.util.*;
public class Uniquesum 
static HashMap<Integer, Integer> hp = new HashMap<Integer, Integer>();
    static int Sum(int arr[])
        int sum=0;
        Arrays.sort(arr);
        hp.put(arr[0], 1);
        for(int i=1; i<arr.length; i++)
            if(hp.containsKey(arr[i]))

                Integer val = hp.get(arr[i]);
                hp.put(arr[i], val+1);
                hp.put(arr[i]+val, 1);                
            
            else
                hp.put(arr[i], 1);
            
        

        for(Map.Entry m:hp.entrySet())
            sum = sum + (int)m.getKey();
        
        return sum;
    
    public static void main(String[] args) 

        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int arr[] = new int [n];
        for(int i=0; i<n;i++)

        arr[i] = scan.nextInt();
        

        System.out.println("Sum is " + Sum(arr));


    


【讨论】:

以上是关于最小唯一数组总和的主要内容,如果未能解决你的问题,请参考以下文章

寻找子集组合以实现给定总和同时保持成本最小的算法

什么样的图最小生成树唯一?

LeetCode~945.使数组唯一的最小增量

LeetCode——使数组唯一的最小增量

查找具有唯一列的数组中每一行的最小值

识别数组的最小唯一元素