繁体   English   中英

仅使用 3 个元素形成数组的方法有多少?

[英]Number of ways to form array using only 3 elements?

我们需要找出可以形成仅包含 3 个元素(1,2 和 3)的 N 长度数组(A)的方法数量。

数组的相邻元素如何放置在数组中几乎没有限制:

某种类型的相邻元素对(A[i], A[i + 1])的数量不能超过问题陈述中给出的数量。

example :

1, 2 : 2 (we can have at most 2 adjacent-pairs of value [1, 2] in the array)
1, 3 : 3
2, 1 : 1
2, 3 : 0 (we cannot have any adjacent-pairs of value [2, 3] in entire array)
3, 1 : 4
3, 2 : 3

对于A[i] == A[i + 1]类型的相邻元素,它们可以在数组中出现任意次数

1, 1 : inf
2, 2 : inf
3, 3 : inf

示例案例:

输入

N = 3

1, 2 : 1 
1, 3 : 1
2, 1 : 1
2, 3 : 0 
3, 1 : 0
3, 2 : 0   

Output:

12

解释:

[1, 2, 1] , here { (1,2) : 1, (2,1) : 1 }, so valid 
[1, 2, 2]
[1, 1, 2]
[2, 1, 2]

[1, 3, 3] , here { (1,3) : 1, (3,3) : 1 }, so valid 
[1, 1, 3]
[2, 1, 3] , here { (2,1) : 1, (1,3) : 1 }, so valid 

[2, 1, 1] , here { (2,1) : 1, (1,1) : 1 }, so valid 
[2, 2, 1]     

[1, 1, 1] , here { (1,1) : 2 }, so valid, as adj-pairs (x, x) can be any number of times.
[2, 2, 2]
[3, 3, 3]

All other combinations of 1,2,3 are invalid like :
[3, 1, 1], [2, 3, 1], etc.

约束

1 <= N <= 10^6

0 <= limit[i][j] <= 10^5

where N = array length and limit[i][j] = number of pairs of type (i, j)

伪代码:

main() :
   ways = 0;
   for(p = 1; p <= 3; p++) :
       ways += num_ways(p, 1, n, A, limit);
   return ways;


num_ways(prev, i, n, A[], limit[][]) :
  
  if(i == n) return 1;
  
  ways = 0;
  for(e = 1; e <= 3; e++):
      if(limit[prev][e] > 0) 
          limit[prev][e] -= 1;
          ways += num_ways(e, i + 1, A, limit);
          limit[prev][e] += 1;

  return ways;

, where limit[i][j] means max number of adjacent-pairs of value (i, j) that can be present in array

伪代码说明:

我尝试使用递归(蛮力)来解决这个问题,即在每个 function 调用中,在索引i处插入任何元素(1,2,3)并检查(A[i - 1], A[i])对是否没有t 超出问题陈述中给出的限制,如果,则return else继续调用func()i != n

这种方法很好,但它给出了 TLE(超出时间限制)错误,因此它不是找出形成数组的方法数量的最佳方法。

有没有其他有效的方法来解决这个问题?

我会采取的方法不是创建实际的数组。 相反,我会通过分析您的限制来解决它。

1, 2 : 2
1, 3 : 3
2, 1 : 1
2, 3 : 0
3, 1 : 4
3, 2 : 3

因此,您的系统中只允许进行有限数量的转换。

所以我的出发点是计算所有可能的转换组合,每个组合都有一个最小长度 n 。 这可以使用一些蛮力方法来执行,尽管可能有也可能没有更有效的方法。

由蛮力方法产生的 output 样本应如下...

  • 最小长度 2 个过渡:
1, 2
1, 3
2, 1
3, 1
3, 2
So, n-2 pattern count = 5.
  • 最小长度 3 个过渡:
1, 2, 1
1, 3, 1
1, 3, 2
2, 1, 2
2, 1, 3
3, 1, 2
3, 1, 3
3, 2, 1
So, n-3 pattern count = 8.

一旦我们计算了每个最小长度的所有可能组合计数,我们就根据实际输入 n 执行排列数学。 我们可以重用我们创建的原始结构来非常快速地执行 n 的多个输入的计算。

例如,当 n = 3 时,我们从 3 开始进行空转换。 然后我们添加 8 以获得不需要最小长度 n 的转换的排列。 然后我们计算最小长度 n - 1、n - 2 等的排列,直到 n - x = 2。排列是通过用多余空间移动每个转换的 position 来计算的。 即其中 n = 3 和 min-n = 2,多余的空间 = 1,所以我们可以将转换左/右移动 1,给我们 2 个模式而不是 1。所以因为有 5 个模式的长度为 2,并且每个都可以转换为 2 种模式,这将给我们 10 种模式。 所以我们有 10 + 8 + 3 = 21。

为了进一步阐明数学,我将在 n-3 模式上使用 n = 10。 我们有 9 个转换槽和 2 个转换,并且可以应用置换数学:

  1. 第一个转换可能发生在 9 个转换槽的前 8 个中的任何位置。 选择哪个插槽决定了第二个转换可能在 go 的位置,但现在让我们忽略它。 然后这变成 9./7,, 然而。 这包括所有乱序组合,所以我们想进一步将其除以 2。所以我们有 9 * 4 = 36 个组合,* n-3 模式的模式计数 = 36 * 8。将其添加到 n-2 模式. n-4 个模式。 ETC...

这可以概括为:

  • sum(i: n... 1) { patternCount(i) * ((n - 1)!)/(n - i - 1)!/i! }

从计算的角度来看,您面临两个问题:Memory 空间(见下文)和性能。

候选数的个数为m=3^N (mmax=3^Nmax=3^(10^6))。

你必须建立一个约束列表,ic=1...nc。 显然,最大约束数为 ncmax=3^2=9。 如果对 (i,i) 将始终不受约束,则 ncmax=3*(3-1)=6。 进一步不受限制的每一对都计入该总数。 您的“标准”订单 (ic,pair) 似乎是 {(1,(1,2)), (2,(1,3)), (3,(2,1)), (4,(2, 3)), (5,(3,1)), (6,(3,2))}。

每个约束由以下部分组成:

  1. 约束 function,它接受一个数字 i 并返回要验证的条件/对的出现次数 pc(i,ic)(或 pc(i,pair))。
  2. 允许出现的最大次数 pcmax(ic)(或 pcmax(pair))。

在您的第一个“示例案例”中,数字 i=121, pc(121,(1,2))=1, pcmax((1,2))=1 -> ok。

一种非常蛮力的方法(没有递归):

nways = 0
for i in 1:m   # (you have to find how to loop on the very large number of numbers)
    for ic in 1:nc
        c = pc(i,ic)
        cmax = pcmax(i)
        if (c <= cmax)
            nways += 1

该算法的许多(可能是重要的)优化是可能的,也许是通过使用递归。

  1. 在计算 pc(i,ic) 时,如果该方法遍历第 i 个中的每一对进行计数,您也可以通过 pcmax(ic) 以“失败”提前退出,而不完全检查 i 中的所有对。
  2. 如果您的 pcmax(ic) 值与 N 相比总是很小。您提到 1<=pcmax(pair)<=P,其中 P=10^5。 P<<N 的情况可能需要采用不同的方法,但是您 state P 和 N 之间没有关系,所以原则上不是这种情况
    请注意,您的“示例案例”使用 pcmax((2,3))=pcmax((3,1))=pcmax((3,2))=0,这与您最后的一般条件 1<=pcmax(pair) <=P。
  3. 别的?

如果没有对问题的任何进一步规范/限制,我不确定你能做得更好。


Memory 空间

您的候选号码的 1 位 -> 2 位 (1,2,3)。 (这是 25% 的空间浪费)
10^6 位 -> 10^6*2 位 = 10^6/4 字节 ~ 256 kB,如果在一个字节中打包 4 位。 或者,您可以使用每个数字一个字节,最多占用约 1MB。

您的问题是我们如何才能更有效地做到这一点。 我假设您指的是运行时,而不是 memory。 我将建议一个解决方案,它可能会很快,但代价是 memory 消耗非常高。 一般来说,他们通常是以牺牲另一个为代价的。

我会迭代地构建它。 首先让我们准备我们需要的数据。 我们需要什么? 我们将构造一个map,其中key是左边的数字,value是map本身。 第二个 map 将任何可能的正确值映射到可能的数量。 让我们从您的示例中生成这样的 map:

1, 2 : 2 (we can have at most 2 adjacent-pairs of value [1, 2] in the array)
1, 3 : 3
2, 1 : 1
2, 3 : 0 (we cannot have any adjacent-pairs of value [2, 3] in entire array)
3, 1 : 4
3, 2 : 3

我们将生成 map:

Map(
  1 -> Map(2 -> 2, 3 -> 3),
  2 -> Map(1 -> 1),
  3-> Map(1 -> 4, 2 -> 3)
)

鉴于 map,现在我们可以开始构建解决方案了。

我们将启动一个元组序列。 我们将其称为 allPossibleSolutions。 这些元组将是什么? 第一个元素将是最后添加到结果序列中的数字。 第二个值将是剩余的可能对。 这意味着在每次迭代中,我们需要从 map 中减少使用的对。

在每次迭代中,我们都会将队列的长度添加到结果中,因为我们总是可以通过重复最后一个元素来完成序列,直到我们得到一个 N 长度的序列。

我假设我们已经有了 map,其余的对,我将其称为剩余对。 现在,让我们编写一些代码:

intermediateQueue = Queue((1 to N).map(i => remainingPairs))
numWays = N
for (int i = 0; i < n - 1; i++) { // The first iteration was the line above
  for (int j = 0; j < intermediateQueue.length; j++) {
    (lastInSeries, currentRemainingPairs) = intermediateQueue.pull()
    toContinueWith = currentRemainingPairs.get(lastInSeries) // that is a map from the next element to the count it can still be used
    for ((value, repeatCount) <- toContinueWith) {
      if (repeatCount > 0) {
        newToContinueWith = toContinueWith.copy // This is very important!! otherwise you'll override yourself between iterations.
        newToContinueWith[value] = newToContinueWith[value] - 1
        newRemainingPairs = remainingPairs.copy
        newRemainingPairs[lastInSeries] = newToContinueWith
        intermediateQueue.add((value, newRemainingPairs))
      }
    }
    numWays += iterlength
  }
}

让我们尝试使用给定的示例来跟进该代码。 我们已经构建了初始 map。

我们使用 (1 -> map, 2 -> map, 3 -> map) 启动队列

i=0
numWays = 3 // That represents the series (1,1,1) (2,2,2) (3,3,3) 
  j = 0
  (lastInSeries, currentRemainingPairs) = (1, Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 1), 3-> Map(1 -> 4, 2 -> 3)))
  toContinueWith = Map(2 -> 2, 3 -> 3)
    (value, repeatCount) = (2, 2)
      newToContinueWith = Map(2 -> 1, 3 -> 3)
      newRemainingPairs = Map(1 -> Map(2 -> 1, 3 -> 3), 2 -> Map(1 -> 1), 3-> Map(1 -> 4, 2 -> 3))
      intermediateQueue.add(2, newRemainingPairs)
    (value, repeatCount) = (3, 3)
      newToContinueWith = Map(2 -> 2, 3 -> 2)
      newRemainingPairs = Map(1 -> Map(2 -> 2, 3 -> 2), 2 -> Map(1 -> 1), 3-> Map(1 -> 4, 2 -> 3))
      intermediateQueue.add(3, newRemainingPairs)
  j = 1
    (lastInSeries, currentRemainingPairs) = (2, Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 1), 3-> Map(1 -> 4, 2 -> 3)))
  toContinueWith = Map(1 -> 1)
    (value, repeatCount) = (1, 1)
      newToContinueWith = Map(1, 0)
      newRemainingPairs = Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 0), 3-> Map(1 -> 4, 2 -> 3))
      intermediateQueue.add(1, newRemainingPairs)
  j = 2
    (lastInSeries, currentRemainingPairs) = (3, Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 1), 3-> Map(2 -> 3)))
  toContinueWith = Map(1 -> 4)
    (value, repeatCount) = (1, 4)
      newToContinueWith = Map(1, 3)
      newRemainingPairs = Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 0), 3-> Map(1 -> 3, 2 -> 2))
      intermediateQueue.add(1, newRemainingPairs)
  toContinueWith = Map(2 -> 3)
    (value, repeatCount) = (2, 3)
      newToContinueWith = Map(2, 2)
      newRemainingPairs = Map(1 -> Map(2 -> 2, 3 -> 3), 2 -> Map(1 -> 0), 3-> Map(1 -> 4, 2 -> 2))
      intermediateQueue.add(2, newRemainingPairs)
  numWays += 5 // That represents the following: (1,2,2) (1,3,3) (2,1,1) (3,1,1) (3,2,2)
i = 1

我们继续这样的另一个循环,并得到以下有效序列:

(1,2,1) (1,3,1) (1,3,2) (2,1,2) (2,1,3) (3,1,2) (3,1,3) (3,2,1)

附言

我们甚至可以通过删除空映射来稍微改进该算法。 它将节省大量计算,以及 memory。

由于您要的是计数而不是排列列表,因此如果将其分为两个步骤,则很容易解决:

  1. 预先计算所有可能的组合,直到没有任何组合可以组合。 这会为每个长度 = 2,3,... 生成一组解决方案
  2. 要获得给定 len 的计数,请将解决方案使用到所需长度,并使用较小的 len 扩展解决方案。 幸运的是,这可以通过一个简单的公式来完成,即将 k 个球分配到 n 个桶中。 x = (( n over k )) = ( k+n+1 over n-1) 将该值 x 乘以变体的数量并将其相加。

这是一个工作示例代码:

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

public class A3 {

    static class P {
        List<Integer> list;
        Map<Integer, Integer> avail;

        public P(List<Integer> list, Map<Integer, Integer> pair2n) {
            this.list = list;
            avail = pair2n;
        }
        
        public String toString() {
            return list.toString();
        }
    }
    
    // sequences with count as array of {a, b, n}
    int limited[][] = {
            {1,2,1},
            {1,3,1},
            {2,1,1},
            {2,3,0},
            {3,1,0},
            {3,2,0}
    };

    // lookup   a*10 + b -> n
    private TreeMap<Integer, Integer> pair2n;

    // precalculated combinations
    private List<ArrayList<P>> precalc = new ArrayList<>();
    
    /**
     * Simple main stub
     * @param args unused
     */
    public static void main(String[] args) {
        A3 a3 = new A3();
        a3.init();
        a3.precalc();
        a3.solve();
    }

    /**
     * Fill map from array.
     */
    private void init() {
        pair2n = new TreeMap<Integer, Integer>();
        for (int []x : limited) {
            pair2n.put(x[0] * 10 + x[1], x[2]);
        }
    }

    /**
     * Calculate all valid combinations until all counters are 0.
     */
    private void precalc() {
        // pre-calculate solutions without 1,1 2,2 and 3,3 until all counters are 0
        int prelen = 0;
        for (Integer n : pair2n.values()) {
            prelen += n;
        }
        
        System.out.println("precalc 2.." + prelen);
        ArrayList<P> current = new ArrayList<>();
        current.add(new P(Arrays.asList(1), pair2n));
        current.add(new P(Arrays.asList(2), pair2n));
        current.add(new P(Arrays.asList(3), pair2n));
        precalc.add(current);
        
        for (int i = 2;; ++i) {
            ArrayList<P> next = new ArrayList<>();
            for (P p : current) {
                for (Entry<Integer, Integer> e : p.avail.entrySet()) {
                    // count > 0 and last number matches current sequence
                    if (e.getValue() > 0 && e.getKey() / 10 == p.list.get(p.list.size() - 1)) {
                        ArrayList<Integer> al = new ArrayList<Integer>(p.list);
                        al.add(e.getKey() % 10);
                        Map<Integer, Integer> map = new TreeMap<>(p.avail);
                        map.put(e.getKey(), e.getValue() - 1);
                        P np = new P(al, map);
                        next.add(np);
                    }
                }
            }
            if (next.isEmpty())
                break;

            precalc.add(next);
            System.out.println("len=" + i);
            System.out.println("with " + next.size() + " permutations");
            System.out.println(next);
            current = next;
        }
    }

    /**
     * precalc contains sequences of different numbers.
     * => any extension of n -> n,n will create an unique solution.
     * 
     * the difference to current length defines how many combinations are insertable.
     */
    private void solve() {
        for (int in = 1;in <= 99;++in) {
            BigInteger wantedLen = new BigInteger(Integer.toString(in));
            BigInteger count = BigInteger.ZERO;
            for (ArrayList<P> pc : precalc) {
                P p = pc.get(0);
                int ik = p.list.size();
                if (ik > in)
                    break;
                
                BigInteger currentLen = new BigInteger(Integer.toString(ik));
                
                BigInteger k = wantedLen.subtract(currentLen);
                BigInteger n = currentLen;
                BigInteger addend = nOverK(n.add(k).subtract(BigInteger.ONE), n.subtract(BigInteger.ONE));
                
                BigInteger len = new BigInteger(Integer.toString(pc.size()));
                System.out.println(currentLen + " -> " + wantedLen + ": " + addend + " * " + len);
                count = count.add(addend.multiply(len));
            }
            System.out.println("Total permutations for " + in + " = " + count);
        }
    }

    /**
     * Helper to calc the faculty.
     * @param k a value
     * @return the faculty.
     */
    private BigInteger faculty(BigInteger k) {
        BigInteger r = BigInteger.ONE;
        while (k.compareTo(BigInteger.ONE) > 0) {
            r = r.multiply(k);
            k = k.subtract(BigInteger.ONE);
        }
        return r;
    }

    /**
     * Calculate n over k.
     * @param n 
     * @param k
     * @return ( n
     *           k )
     */
    private BigInteger nOverK(BigInteger n, BigInteger k) {
        BigInteger fn = faculty(n);
        BigInteger fk = faculty(k);
        BigInteger fnk = faculty(n.subtract(k));
        BigInteger r = fn.divide(fnk.multiply(fk));
        return r;
    }
}

暂无
暂无

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

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