【问题标题】:Finding two non-subsequent elements in array which sum is minimal在数组中找到两个总和最小的非后续元素
【发布时间】:2016-05-14 13:36:20
【问题描述】:

简介:据我所知,SO 中还没有提出这个问题。
这是一道面试题。
我什至没有专门寻找代码解决方案,任何算法/伪代码都可以。


问题:给定一个整数数组int[] A及其大小N,找到2个非后续(数组中不能相邻)元素以最少的金额。答案也不能包含第一个或最后一个元素(索引0n-1)。解决方案也应该是O(n)时间和空间复杂度。

例如当A = [5, 2, 4, 6, 3, 7] 时,答案是5,因为2+3=5
A = [1, 2, 3, 3, 2, 1] 时,答案是4,因为2+2=4 并且您不能选择1 中的任何一个,因为它们位于数组的末尾。


尝试:起初我认为解决方案中的一个必须是数组中最小的一个(除了第一个最后),但这很快就被反例驳斥了
A = [4, 2, 1, 2, 4]-> 4 (2+2)

然后我想如果我在数组中找到 2 个最小的数字(除了第一个和最后一个),那么解决方案就是这两个。这显然很快就失败了,因为我不能选择 2 个相邻的数字,如果我必须选择不相邻的数字,那么这就是问题的定义:)。

最后我想,好吧,我会在数组中找到 3 个最小的数字(除了第一个和最后一个),并且解决方案必须是两个其中,因为其中两个必须彼此不相邻。 这由于A = [2, 2, 1, 2, 4, 2, 6]-> 2+1=3而失败,这似乎有效,因为我会找到2, 1, 2,但假设我在索引1, 2, 3中找到2, 1, 2这不会必须工作(如果我在索引5 中专门找到了2 就可以了,但不幸的是我不能保证)。


问题: 现在我很困惑,谁能想出一个可行的解决方案/想法?

【问题讨论】:

  • 你确定它可以在 O(n) 时间内解决吗?
  • 如何修改三个最小数字的方法以包含索引。您将需要 6 个位置来存储 - 3 个索引、3 个值。如果您看到重复值,则只需更新索引,以便在您的示例中看到索引“5”处的“2”时,第二个索引可以更新为 5,从而将“2”和“5”处的元素作为解决方案在同一个过程中。只要确保仅在被跟踪的索引已经相邻时更新重复索引即可。
  • 只是为了确保我理解这个问题,a) A = {1,2,2,2,1} 的答案是 4 ? b) 数组的最小长度是 5?
  • 您可能可以使用Kadane's algorithm 的变体来解决这个问题。
  • @higuaro,我认为它需要很多变化,因为该算法严重依赖于相邻的选定元素,这里的要求是相反的。

标签: java arrays algorithm time-complexity minimum


【解决方案1】:

这是O(N)时间复杂度的python实现

import math

def minSum(array):
    _min = array[1]
    result = math.inf

    for i in range(3, len(array) - 1):
        _min = min(_min, array[i-2])
        if (_min + array[i]) < result:
            result = _min + array[i]
    return result
【解决方案2】:

由于我们只需要跟踪两个不相邻值的最小和,我们可以通过迭代数组来完成,不包括第一个和最后一个元素,并跟踪最小值和最小和。当前最小值将是当前值之前的两个索引。例如,如果我们正在检查当前索引 i,那么 minValue 是从索引 1i-2 的最小值。 代码:

    int minSum(int[] A){
        int minSum=Integer.MAX_VALUE;
        int min= Integer.MAX_VALUE;
        for(int i=3; i<A.length-1; i++){
            min= Math.min(A[i-2], min);
            minSum = Math.min(min+A[i], minSum);
        }
        return minSum;
    }

【讨论】:

【解决方案3】:

这个问题用大约 10 行 Java 代码就可以解决。

您可以从一个明显但效率低的 (O(N^2)) 解决方案开始:

public class Main {

    int solve(int[] array) {
        int answer = Integer.MAX_VALUE;
        for (int i = 3; i < array.length - 1; i++) {
            for (int j = 1; j < i - 1; j++) {
                if (array[i] + array[j] < answer) {
                    answer = array[i] + array[j];
                }
            }
        }
        return answer;
    }
}

但是您会注意到实际上不需要内部 for 循环,因为您可以保留最小值并在必要时使用每个新元素更新它,这比每次都重新找到最小值要快。因此最终的O(N) 解决方案如下所示:

public class Main {

    int solve(int[] array) {
        int answer = Integer.MAX_VALUE;
        int min = array[1];
        for (int i = 3; i < array.length - 1; i++) {
            min = Math.min(min, array[i - 2]);
            if (array[i] + min < answer) {
                answer = array[i] + min;
            }
        }
        return answer;
    }
}

【讨论】:

  • 最准确最优的解决方案。
【解决方案4】:

详细说明above 答案,您需要修改插入排序来跟踪最小的四个值和相应的索引(每个数组包含 4 个元素)。

一旦找到,解决方案将是第一对索引差异大于 1 且总和最小的对。

解决方案是 (0,1)(0,2)(0,3)(1,2)(1,3)(2,3) 之一,其中值指示数组的索引,该索引依次跟踪实际元素的位置在数组中。

您还需要处理数组长度 5 (arr\[1]+arr[3]) 的特殊情况以及小于 5 的数组的错误。

【讨论】:

    【解决方案5】:

    编辑:你说得对,我完全忽略了邻接约束。 幸运的是,我想到了一个解决方案。 算法是这样的:

    1. 您在数组上运行一次以找到最小的(O(n))
    2. 你第二次运行找到第二小的(O(n))
    3. 如果第二个最小的不与最小的相邻,我们就完成了(O(1) - 只是一个索引检查)
    4. 否则第三次运行找到第三小的(仍然是O(n)
    5. 如果不与最小的返回最小的和第三小的 否则返回第二和第三小的

    【讨论】:

    • 仅链接的答案在 StackOverflow 中没有帮助 - 更不用说由于限制,这并不能解决我的问题。请重新阅读问题
    • OP 已经尝试过这种方法,但不一定适用于数组A = [2, 2, 1, 2, 4, 2, 6]
    【解决方案6】:

    怎么样:你会发现k 最小的数字(或更准确地说是它们的索引)(k 足够大,比如说10)。可以肯定的是,通缉的一对就在他们之间。现在您只需检查可能的 50 对并选择满足约束条件的最佳对。

    你不需要10,更少会做 - 但比3更多:)

    编辑:找到k 最小的数字是O(n),因为您只需将最好的10 保留在例如堆中(添加新元素,删除最大O(k*logk)=O(1) 操作)。

    然后会有一对满足约束(不是彼此相邻)。同样清楚的是,如果您使用不是来自 k 的元素来构建总和,那么它会大于从那些 k 元素中选择的最佳对。

    最多检查k*k对也是O(1),因此整个运行时间是O(n)

    【讨论】:

    • 这也不保证。
    • 不涉及置换,要从 n 个元素中找到 k 个最小的数,您需要 10*n 次操作。而且只有 k*(k-1)/2 对。
    【解决方案7】:

    我已经用动态规划来解决了。

    想法是首先创建一个数组来跟踪到目前为止找到的最小值,如下所示: 输入数组 = [1, 3, 0, 5, 6] 最小数组 = [1, 1, 0, 0, 0]

    现在使用最小数组和我们可以在下面使用的输入数组:

    DP[i] = min(DP[i-1], min(first_data, second_data))
    

    其中DP[i] 表示到目前为止找到的最小值,它是之前两个替代元素的总和。

    first_data = 输入数组中current 元素的总和 + 最小数组中current-2 元素的总和

    second_data = 输入数组中current-1 元素的总和 + 最小数组中current-3 元素的总和

        import random
        def get_min(numbers):
                #disregard the first and last element
                numbers = numbers[1:len(numbers)-1]
                #for remembering the past results
                DP = [0]*len(numbers)
                #for keeping track of minimum till now found
                table = [0]*len(numbers)
                high_number = 1 << 30
    
                min_number = numbers[0]
                table[0] = min_number
                for i in range(0, len(numbers)):
                        DP[i] = high_number
                for i in range(1, len(numbers)):
                        if numbers[i] < min_number:
                                min_number = numbers[i]
                                table[i] = numbers[i]
                        else:
                                table[i] = min_number
                for i in range(0, len(numbers)):
                        min_first, min_second = high_number, high_number
                        if i >= 2:
                                min_first = numbers[i] + table[i-2]
                        if i >= 3:
                                min_second = numbers[i-1] + table[i-3]
                        if i >= 1:
                                DP[i] = min(min(DP[i-1], min_first), min_second)
                return DP[len(numbers)-1]
    
        input = random.sample(range(100), 10)
        print(input)
        print(get_min(input))
    

    【讨论】:

      【解决方案8】:
      1. 找出第一个和最后一个旁边的最小数字。
      2. 找到第二个最小的,它不是第一个的邻居,也不是数组中的第一个或最后一个。然后建立总和。

        • 如果第一个元素是第二个或倒数第二个元素,您已经有了解决方案。
      3. 否则计算第一个数字的两个邻居的总和。检查它是否小于第一个总和

        • 如果不是:取第一个总和
        • 否则拿第二个

      这将始终有效,因为如果第一个总和不是答案,则意味着第一个数字不能成为解决方案的一部分。另一方面意味着,解决方案可以是第二个总和。

      【讨论】:

      • 这缺少从步骤 2 和 3 中排除末端的关键元素。但是,如果您只是将两端作为步骤 0 删除(并考虑步骤 1 中的所有元素),那么一切都应该可以正常工作。
      • 我在答案中添加了你的建议
      【解决方案9】:

      这是一个实时的 javascript 算法实现:

      • 查找最小的 4 个元素(不包括搜索中的第一个/最后一个元素)
      • 找出这4个元素在原始数组中不相邻的对
      • 从这些对中找出总和最小的一对

      function findMinNonAdjacentPair(a) {
          var mins = [];
          
          // quick exits:
          if (a.length < 5) return {error: "no solution, too few elements."};
          if (a.some(isNaN)) return {error: "non-numeric values given."};
          
          // collect 4 smallest values by their indexes    
          for (var i = 1; i < a.length - 1; i++) { // O(n)
              if (mins.length < 4 || a[i] < a[mins[3]]) {
                  // need to keep record of this element in sorted list of 4 elements
                  for (var j = Math.min(mins.length - 1, 2); j >= 0; j--) { // O(1)
                      if (a[i] >= a[mins[j]]) break;
                      mins[j+1] = mins[j];
                  }
                  mins[j+1] = i;
              }
          }
          // mins now has the indexes to the 4 smallest values
      
          // Find the smallest sum
          var result = {
              sum: a[mins[mins.length-1]]*2+1 // large enough
          }
          
          for (var j = 0; j < mins.length-1; j++) { // O(1)
              for (var k = j + 1; k < mins.length; k++) {
                  if (Math.abs(mins[j] - mins[k]) > 1) { // not adjacent
                      if (result.sum    > a[mins[j]]+a[mins[k]]) {
                          result.sum    = a[mins[j]]+a[mins[k]];
                          result.index1 = mins[j];
                          result.index2 = mins[k];
                      };
                      if (k < j + 3) return result; // cannot be improved
                      break; // exit inner loop: it cannot bring improvement
                  }
              }
          }
          return result;
      }
      
      // Get I/O elements
      var input = document.getElementById('in');
      var output = document.getElementById('out');
      var select = document.getElementById('pre');
      
      function process() {
          // translate input to array of numbers
          var a = input.value.split(',').map(Number);
          // call main function and display returned value
          output.textContent = JSON.stringify(findMinNonAdjacentPair(a), null, 4);
      }
      
      // respond to selection from list
      select.onchange = function() {
          input.value = select.value;
          process();
      }
      
      // respond to change in input box
      input.oninput = process;
      
      // and produce result upon load:
      process();
      Type comma-separated list of values (or select one):</br>
      <input id="in" value="2, 2, 1, 2, 4, 2, 6"> &lt;=
      <select id="pre">
          <option value="5, 2, 4, 6, 3, 7">5, 2, 4, 6, 3, 7</option>
          <option value="1, 2, 3, 3, 2, 1">1, 2, 3, 3, 2, 1</option>
          <option value="4, 2, 1, 2, 4">4, 2, 1, 2, 4</option>
          <option value="2, 2, 1, 2, 4, 2, 6" selected>2, 2, 1, 2, 4, 2, 6</option>
      </select>
      </br>
      Output:</br>
      <pre id="out"></pre>

      该算法有几个循环,具有以下大 O 复杂性:

      • 找到4个最小值:O(n),因为内循环最多运行3次,即O(1)
      • 找到最小的非相邻对的总和有一个双循环:总共身体最多运行 4 次 = O(1)。注意:可能的对数为 6,但可以保证执行更快地跳出循环。

      所以算法在O(n)中运行。

      【讨论】:

        【解决方案10】:

        使用动态规划。

        1. 删除或忽略数组的第一个和最后一个元素。由于它们不能参与解决方案,因此它们并不重要。完成此操作后,您还可以忽略“不能是第一个或最后一个元素”约束,因为我们已经考虑过了。
        2. 找到数组(剩下的)前三个元素的解决方案(并且不考虑“没有第一个/最后一个元素”规则)。在这种情况下只有一个解决方案 (array[0] + array[2]),所以这是一个微不足道的步骤。
        3. 记住不是最后一个元素的最小元素(即min(array[0], array[1]))。
        4. 找到前四个元素的解决方案。我们不必重做整个问题;相反,我们只需要问引入第四个元素是否允许我们产生更小的解决方案。我们可以通过将第四个元素添加到我们在上一步中记住的最小元素,并将总和与我们在第二步中找到的解进行比较来做到这一点。
        5. 更新记忆的最小元素,使其成为前三个元素中的最小值。
        6. 以这种方式继续扩大和更新,直到我们考虑了整个数组。

        整个算法是 O(n),因为扩展和更新都是常数时间操作。该算法可以通过简单的归纳证明是正确的。 O(n) 也是一个下界,因为我们必须考虑数组的每个元素,所以这个算法是最优的。

        【讨论】:

        • 遵守 OP 约束的最小数组的长度为 5。但这不应该影响您的一般方法。
        • 这会将meriton's approach 变成文字,还有一些流行语。
        【解决方案11】:

        我认为这不需要任何深层次的推理,一次就可以解决,保持目前处理的数组元素的最优解:

        public static int[] minimumSumOfNonAcjacentElements(int[] a) {
            // the result for the sequence a[1:i]
            int minSum = Integer.MAX_VALUE;
            int minSumElement1 = Integer.MAX_VALUE;
            int minSumElement2 = Integer.MAX_VALUE;
        
            // the minimum element eligible for joining with a[i], i.e. from a[1 : i-2]
            int minElement = a[1];
        
            int prevElement = a[2]; // a[i - 1]
            for (int i = 3; i + 1 < a.length; i++) {
                int sum = minElement + a[i];
                if (sum < minSum) {
                    minSum = sum;
                    minSumElement1 = minElement;
                    minSumElement2 = a[i];
                }
        
                if (prevElement < minElement) {
                    minElement = prevElement;
                }
                prevElement = a[i];
            }
        
            return new int[] {minSumElement1, minSumElement2};
        }
        

        这是一些测试代码,以及 OP 问题中的极端案例:

        private static void test(int minSumIndex1, int minSumIndex2, int... input) {
            int[] result = minimumSumOfNonAcjacentElements(input);
            if (result[0] == minSumIndex1 && result[1] == minSumIndex2) {
                // ok
            } else {
                throw new AssertionError("Expected: " + minSumIndex1 + ", " + minSumIndex2 + ". Actual=" + Arrays.toString(result));
            }
        }
        
        public static void main(String[] args) throws Exception {
            test(2, 2, 4, 2, 1, 2, 4);
            test(1, 2, 2, 2, 1, 2, 4, 2, 6);
            test(1, 2, 0, 2, 1, 2, 4, 2, 0);
            System.out.println("All tests passed.");
        }
        

        【讨论】:

        • 您好,请您解释一下该解决方案如何满足索引不相邻的要求?我不知道您在代码中的哪个位置跟踪此内容。谢谢
        【解决方案12】:

        我不知道我的解决方案是否正确,因为我只是用 OP 中的数据对其进行了测试,我什至不知道这是否比其他想法更好或更差,但我想尝试一下。

        static void printMinimalSum(int[] A) {  
            // Looking for mins so we init this with max value
            int[] mins = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
            // Indices, used just to print the solution
            int[] indices = new int[]{-1, -1, -1};
            // If the array has length 5 then there's only one solution with the 2nd and 4th elements
            if (A.length == 5) {
                mins[0] = A[1];
                indices[0] = 1;
                mins[1] = A[3];
                indices[1] = 3;
            } else {        
                // Loop on the array without considering the first and the last element
                for (int i = 1; i < A.length - 1; i++) {
                    // We consider each element which is smaller than its neighbours
                    if ((i == 1 && A[i] < A[i + 1]) // 1: first element, compare it with the second one
                            || (i == A.length - 2 && A[i] < A[i - 1]) // 2: last element, compare it with the previous one
                            || (A[i] < A[i + 1] && A[i] < A[i - 1])) { // 3: mid element, compare it with both neighbors
                        // If the element is "legal" then we see if it's smaller than the 3 already saved
                        if (A[i] < mins[0]) {
                            mins[0] = A[i];
                            indices[0] = i;
                        } else if (A[i] < mins[1]) {
                            mins[1] = A[i];
                            indices[1] = i;
                        } else if (A[i] < mins[2]) {
                            mins[2] = A[i];
                            indices[2] = i;
                        }
                    }
                }
            }     
            // Compute the 3 sums between those 3 elements
            int[] sums = new int[]{Math.abs(mins[0]+mins[1]), Math.abs(mins[0]+mins[2]), Math.abs(mins[1]+mins[2])};
            // Find the smaller sum and print it
            if (sums[0] < sums[1] || sums[0] < sums[2]){
                System.out.println("Sum = " + sums[0] + " (elements = {" + mins[0] + "," + mins[1] + "}, indices = {" + indices[0] + "," + indices[1] + "}");
            } else if (sums[1] < sums[0] || sums[1] < sums[2]){
                System.out.println("Sum = " + sums[1] + " (elements = {" + mins[0] + "," + mins[2] + "}, indices = {" + indices[0] + "," + indices[2] + "}");
            } else {
                System.out.println("Sum = " + sums[2] + " (elements = {" + mins[1] + "," + mins[2] + "}, indices = {" + indices[1] + "," + indices[2] + "}");
            }
        }
        
        public static void main(String[] args) {
            printMinimalSum(new int[]{5, 2, 4, 6, 3, 7});
            printMinimalSum(new int[]{1, 2, 3, 3, 2, 1});
            printMinimalSum(new int[]{4, 2, 1, 2, 4});
            printMinimalSum(new int[]{2, 2, 1, 2, 4, 2, 6});
        }
        

        输出是:

        Sum = 5 (elements = {2,3}, indices = {1,4}
        Sum = 4 (elements = {2,2}, indices = {1,4}
        Sum = 4 (elements = {2,2}, indices = {1,3}
        Sum = 3 (elements = {1,2}, indices = {2,5}
        

        看起来不错。

        【讨论】:

          【解决方案13】:

          我认为这应该可行:

          找出最小的 3 个元素及其索引。由于它们都不能相邻,因此选择其中的 2 个。

          如果它们都是相邻的并且最小的数字在它们中间,则遍历所有元素,找到第四个最小的元素,选择min1+min4min2+min3中的最小值,以较小者为准。

          您也可以在一次迭代中完成此操作。

          【讨论】:

          • 这实际上是我的尝试之一...请阅读问题:/
          【解决方案14】:

          算法:

          1. 找到最小值,避免结束索引。 (1 O(n) 次通过)
          2. 找到最小值,避免结束索引和 (1) 的索引和相邻索引。 (1 O(n) 次通过)
          3. 找到最小值,避免结束索引和 (1) 的索引(1 O(n) pass)
          4. 找到最小值,避免结束索引和 (3) 的索引和相邻索引。 (1 O(n) 次通过)
          5. 返回最小值 (1) + (2), (3) + (4),如果它们存在的话。

          通过 3 和 4 旨在通过找到两个 2 来通过案例 [4, 2, 1, 2, 4] = 4。

          public static int minSumNonAdjNonEnd(int[] array)
          {
              // 1. Find minimum
              int minIdx1 = -1;
              int minValue1 = Integer.MAX_VALUE;
              for (int i = 1; i < array.length - 1; i++)
              {
                  if (array[i] < minValue1)
                  {
                      minIdx1 = i;
                      minValue1 = array[i];
                  }
              }
              // 2. Find minimum not among (1) or adjacents.
              int minIdx2 = -1;
              int minValue2 = Integer.MAX_VALUE;
              for (int i = 1; i < array.length - 1; i++)
              {
                  if ((i < minIdx1 - 1 || i > minIdx1 + 1) && (array[i] < minValue2))
                  {
                      minIdx2 = i;
                      minValue2 = array[i];
                  }
              }
              boolean sum1Exists = (minIdx1 > -1 && minIdx2 > -1);
              int sum1 = minValue1 + minValue2;
          
              // 3. Find minimum not among (1).
              int minIdx3 = -1;
              int minValue3 = Integer.MAX_VALUE;
              for (int i = 1; i < array.length - 1; i++)
              {
                  if ((i != minIdx1) && (array[i] < minValue3))
                  {
                      minIdx3 = i;
                      minValue3 = array[i];
                  }
              }
          
              // 4. Find minimum not among(3) or adjacents.
              int minIdx4 = -1;
              int minValue4 = Integer.MAX_VALUE;
              for (int i = 1; i < array.length - 1; i++)
              {
                  if ((i < minIdx3 - 1 || i > minIdx3 + 1) && (array[i] < minValue4))
                  {
                      minIdx4 = i;
                      minValue4 = array[i];
                  }
              }
              boolean sum2Exists = (minIdx3 > -1 && minIdx4 > -1);
              int sum2 = minValue3 + minValue4;
          
              if (sum1Exists)
              {
                  if (sum2Exists)
                      return Math.min(sum1, sum2);
                  else
                      return sum1;
              }
              else
              {
                  if (sum2Exists)
                      return sum2;
                  else
                      throw new IllegalArgumentException("impossible");
              }
          }
          

          这会执行 4 次线性搜索,复杂度为 O(n)。

          测试用例:

          System.out.println(minSumNonAdjNonEnd(new int[] {5, 2, 4, 6, 3, 7}));
          System.out.println(minSumNonAdjNonEnd(new int[] {1, 2, 3, 3, 2, 1}));
          System.out.println(minSumNonAdjNonEnd(new int[] {4, 2, 1, 2, 4}));
          System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 1, 2, 4, 2, 6}));
          System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 3, 2}));
          
          5
          4
          4
          3
          Exception in thread "main" java.lang.IllegalArgumentException: impossible
          

          【讨论】:

            【解决方案15】:

            找出四个最小的并考虑这四个中的所有可能性。最小的与第二、第三或第四最小中的至少一个不相邻;唯一可能更好的其他可能性是第二和第三小的可能性(假设它们不相邻)。

            【讨论】:

            • 我正在编写一些代码来测试它。如果是对的,那就赞一个! :)
            • 我们要么需要跟踪索引以避免再次通过,不是吗?
            • @RavindraHV 是的,这也隐含在提问者尝试的解决方案中。
            • 如果你称这些数字为 1, 2, 3, 4 那么最小的和是 1+2 如果不相邻,否则 1+3 如果不相邻,否则 2+3 和 1+4不相邻和最佳结果都是较小的总和。
            猜你喜欢
            • 1970-01-01
            • 2016-03-24
            • 2019-09-11
            • 1970-01-01
            • 2011-05-28
            • 1970-01-01
            • 2011-12-09
            • 1970-01-01
            • 2015-11-08
            相关资源
            最近更新 更多