查找数组中值的组合,其中每个值都大于另一个值

时间:2018-03-18 08:45:47

标签: algorithm big-o

我在代码访谈中收到一个问题,遗憾的是我无法以有效的方式解决。我把它解决为O(n ^ 2)并且我相信它可以在O(n log n)中解决。

这是我的尝试,是解决问题的正确方法还是可以改进?

问题

您的数组ABC都包含n个整数值。如果B中的值必须高于A,并且C必须高于B,我们可以在A,B和C之间找到多少个值组合。

E.g。

A = [29, 49, 65]
B = [31, 55, 78]
C = [45, 98, 100]

# Combinations
29, 31, 45
29, 31, 98
29, 31, 100
29, 55, 98
29, 55, 100
49, 55, 98
49, 55, 100
65, 78, 98
65, 78, 100

解决方案

我通过对列表进行排序然后对最接近的索引进行二进制搜索来解决它,并且高于之前的值。

def getClosest(arr, left, right, val, closest=None):
  mid = right-int(abs(left-right)/2)
  if left >= right:
    if arr[mid] == val:
      return val
    return closest

  if arr[mid] == val:
    return mid
  elif val > arr[mid]:
    return getClosest(arr, mid+1, right, val, closest)
  elif val < arr[mid]:
    if closest is None or mid < closest:
      closest = mid
    return getClosest(arr, left, mid-1, val, closest)

  return closest

def getLocationGTE(arr, limit):
  index = getClosest(arr, 0, len(arr)-1, limit)
  if index is None:
    return []
  else:
    return arr[index:]

def countStops(A, B, C):
  A.sort()
  B.sort()
  C.sort()
  total = 0
  for i in range(len(A)):
    a = A[i]
    b_locations = getLocationGTE(B, a+1)
    for b in b_locations:
      c_locations = getLocationGTE(C, b+1)
      total += len(c_locations)

  return total

3 个答案:

答案 0 :(得分:2)

到目前为止给出的答案看起来有点复杂,所以让我添加另一种方法。

主要思想是使用给定的B元素计算组合的数量。它是较小的A元素的数量乘以较大的C元素的数量。

假设所有三个数组都按升序排序。您的示例数组已经排序,但事实并未在文本中明确提及,因此我们可能需要初始排序,计算O(nlogn)。

我们需要在所有B元素上循环,并且在循环内部我们需要将两个索引维护到A和C数组中,A索引ia标识最后一个A元素低于B元素,以及C索引ic标识大于B元素的第一个C元素。该循环为O(n),因为A和C索引的增量不能超过n次。

实现算法的Java类:

public class Combinations {

    // array access guarded against index-out-of-range.
    private static int arrayAccess(int[] array, int index) {
        if (index < 0)                  return Integer.MIN_VALUE;
        else if (index >= array.length) return Integer.MAX_VALUE;
        else                            return array[index];
    }

    public static int combinations(int[] a, int[] b, int[] c) {
        int ia = -1;
        int ic = 0;
        int nCombinations = 0;
        for (int ib=0; ib<b.length; ib++) {
            int bElement = b[ib];
            while (arrayAccess(a, ia+1) < bElement) {
                ia++;
            }
            while (arrayAccess(c, ic) <= bElement) {
                ic++;
            }
            nCombinations += (ia+1) * (c.length-ic);
        }
        return nCombinations;
    }

    public static void main(String[] args) {
        int[] a = {29, 49, 65};
        int[] b = {31, 55, 78};
        int[] c = {45, 98, 100};
        int result = combinations(a, b, c);
        System.out.println(result);
    }
}

答案 1 :(得分:0)

是的,你是对的,它可以在O(nlogn)中解决。

  1. 对所有数组进行排序

  2. 对于B中的每个索引b,计算C中有多少元素大于B [b]

  3.     int[] count = new int[n];
        int c = 0;
        for (int b = 0; b < n; b++) {
            while (c < n && C[c] <= B[b])
                c++;
            count[b] = n - c;
        }
    
    1. 以反向建立累计计数总和
    2.     int[] cumSum = new int[n];
          cumSum[n - 1] = count[n - 1];
          for (int i = n - 2; i >= 0; i--)
              cumSum[i] = cumSum[i + 1] + count[i];
      
      1. 对于A中的每个元素,找到B中的第一个索引,并将相应的累积总和添加到总计数
      2.     int total = 0, b = 0;
            for (int a = 0; a < n; a++) {
                while (b < n && B[b] <= A[a])
                    b++;
                if (b == n)
                    break;
                total += cumSum[b];
            }
        

        步骤1.取O(nlogn),步骤2.到4.是O(n),整体给出O(nlogn)。

        以下是javascript中的完整实现:​​

        function countCombos(A, B, C) {
          A.sort(function (a, b) {return a - b})
          B.sort(function (a, b) {return a - b})
          C.sort(function (a, b) {return a - b})
          let count = [], c = 0, n = A.length
          for (let b = 0; b < n; b++) {
            while (c < n && C[c] <= B[b])
              c++
            count[b] = n - c
          }
          for (let i = n - 2; i >= 0; i--) // building cumSum directly in count
            count[i] = count[i] + count[i + 1]
          let total = 0, b = 0
          for (let a = 0; a < n; a++) {
            while (b < n && B[b] <= A[a])
              b++
            if (b == n)
              break
            total += count[b]
          }
          return total
        }
        
        console.log(countCombos([29, 49, 65], [31, 55, 78], [45, 98, 100]))

        顺便说一下。您在列表中缺少4个组合:[29,78,98],[29,78,100],[49,78,98]和[49,78,100],因此13是正确答案。

答案 2 :(得分:0)

你的直觉是正确的,我们可以击败O(n^2),因为排序和二进制搜索解决方案会产生额外的工作(数组元素BC被多次读取)我们会喜欢避免。

解决方案A - O(n.log2(n))

可以使用二叉搜索树作为辅助数据结构来构造O(nlogn)解决方案。我们需要用颜色装饰每个节点,标识从中提取节点值的数组。

让我们使用以下颜色约定:

  • Amber代表数组A
  • Blue代表数组B
  • Cyan代表数组C

然后,我们执行树的有序深度优先搜索,计算Cyan个节点的数量,Cyan -> ... -> BlueCyan -> ... -> Blue -> ... -> Amber转换。

BST tree = new BST(AscendingOrder);

for (a: A) { tree.add(new Node(Amber, a)); }
for (b: B) { tree.add(new Node(Blue, b));  }
for (c: C) { tree.add(new Node(Cyan, c));  }

int cyans, cyanThenBlues, solutions = 0;
DFS(tree.root(), cyans, cyanThenBlues, solutions)

DFS(Node node, int& cyans, int& cyanThenBlues, int& solutions) {
   if (node == null) { return; }

   DFS(node.left(), cyans, cyanThenBlues, solutions);            

   cyans += node.color() == Cyan ? 1 : 0;
   cyanThenBlues += node.color() == Blue ? cyans : 0;
   solutions += node.color() == Amber ? cyanThenBlues : 0;

   DFS(node.right(), cyans, cyanThenBlues, solutions);
}

如果ABC的值是任意排序的,则BST的构建费用为O(n.log2(n)),而DFS O(n)。< / p>

但是,根据输入数组ABC的接近排序距离,树可能变得不平衡,O(n.log2(n))也变得不均衡乐观。

  • 平均时间复杂度:O(n.log2(n))
  • 最糟糕的案例复杂性:O(n^2)
  • 空间复杂度:O(n)

解决方案B - O(n.log2(n))

它与以前的方法基本相同,不同之处在于我们首先对数组ABC进行排序,创建一个长度为3.n的辅助数组哪些元素使用与前一解决方案类似的颜色进行修饰,使用ABC的值填充此数组,然后遍历数组并传递计算颜色的过渡数我们感兴趣的是。

sort(A);
sort(B);
sort(C);

// Constructing guards, loose syntax
int[] A' = A + { Integer.MIN_VALUE };
int[] B' = B + { Integer.MIN_VALUE };
int[] C' = C + { Integer.MIN_VALUE };

int[] D = new int[3 * n];
int i, j, k = 0;

for (int t = 0; t < 3 * n; ++t) {
   if (A'[i] >= B'[j] && A'[i] >= C'[k]) {
      D[t] = new Node(A'[i++], Amber);
   }
   else if (B'[j] >= A'[i] && B'[j] >= C'[k]) {
      D[t] = new Node(B'[j++], Blue);
   }
   else if (C'[k] >= A'[i] && C'[k] >= B'[j]) {
      D[t] = new Node(C'[k++], Cyan);
   }
}

int cyans, cyanThenBlues, solutions = 0;
for (int t = 0; t < 3 * n; ++t) {
   cyans += D[t].color() == Cyan ? 1 : 0;
   blues += D[t].color() == cyanThenBlues ? cyans : 0;
   solutions += D[t].color() == Amber ? cyanThenBlues : 0;
}

假设我们使用快速排序:

  • 平均时间复杂度:O(n.log2(n))
  • 最糟糕的案例复杂性:O(n^2)
  • 空间复杂度:O(n)

请注意,第二个解决方案的内存占用量将小于第一个解决方案的内存占用量(BST的成本)。

后续评论

  • 假设数组已排序,Theta(n)是可实现的最佳时间复杂度(因为每个元素至少需要读取一次)。这些解决方案的总体时间复杂性主要取决于BST的分类或构建。
  • 在这两种情况下,关键是展示跨越所有三个阵列的排序关系。
  • 我们可以为整数数组想出另一个sorting algorithm吗?