【问题标题】: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 的最大范围内。导致溢出。您可能需要在递增之前向上转换很久以避免这种情况。您的总和可能还需要是一个大整数或双精度数。

标签: arrays algorithm


【解决方案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);
    }
    

    【讨论】:

    【解决方案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));
                  
                  
                      }
                  
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-06-26
                    • 2019-01-19
                    • 2020-02-22
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多