2-13 标准二维表问题
问题描述
设n是一个正整数。2*n的标准二维表是由正整数1,2,…,2n组成的2*n数组,该数组的每行从左到右递增,每列从上到下递增。2*n的标准二维表全体记为Tab(n)。例如,当n=3时,tab(3)二维表如下图所示。
给定正整数n,试计算Tab(n)中2*n的标准二维表的个数。
分析
一维数组递归遍历法
将上面标准二维表从左到右从上往下看成一维数组,递归遍历,记录符合条件的情况。
进栈出栈法
如上图,先把2*n个数字排成一行来看(1~2*n 有序)。之前上表中放到第一行的数字1,2,3在下表中标记为0,放到第二行的数字4,5,6在下表中标记为1,这样就可以有一个0,1的序列,如下图(按照上面方式,将前面n=3时的所有5种标准二维表转变为一维表):
当n=3时,5种标准二维表转化成的总表如下
1 |
2 |
3 |
4 |
5 |
6 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
可见,题目要求前面的数字比后面、上面的数字比下面的数字小的问题,可以转化为,序列中每个数字前面0的个数要大于等于1的个数问题。
该问题又可以转换成进栈出栈问题。2*n个数字,进栈是0,出栈是1,且进栈出栈次数均为n,这样就可以保证输出的进栈出栈序列中每个数字前面0的个数大于等于1的个数,符合题目要求。
Catalan数法
上面方法中涉及到的 进栈出栈问题 本质上属于 Catalan数问题。因此,又可以转化为 Catalan数问题。
令h(0)=1,h(1)=1,Catalan数满足递推式[1] :
h(n)=h(0)∗h(n−1)+h(1)∗h(n−2)+...+h(n−1)h(0)(n>=2)
例如:h(2)=h(0)*h(1)+h(1)*h(0)=1*1+1*1=2
h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=1*2+1*1+2*1=5
另类递推式[2] :
h(n)=h(n−1)∗(4∗n−2)/(n+1);
递推关系的解为:
h(n)=C(2n,n)/(n+1)(n=0,1,2,...)
递推关系的另类解为:
h(n)=c(2n,n)−c(2n,n−1)(n=0,1,2,...)
Catalan数法(大数乘法与除法)
当输入的数n较大时,可以用 大数乘法与除法(二维数组实现) 来解决,计算Catalan数。
一维数组递归遍历法
Java
import java.util.Scanner;
public class Main {
private static long count = 0L;
private static int[] numbers;
private static int n;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (true) {
count = 0;
System.out.println("Input n: ");
n = input.nextInt();
System.out.println("-----------------");
numbers = new int[2*n];
for(int i=0;i<2*n;i++)
numbers[i]=i+1;
if(n==1)
print();
else
perm(1,2*n-2);
System.out.println("Tables: "+count);
System.out.println("------------------------");
}
}
private static void perm(int start, int end){
if(start==end && isOk())
print();
else{
for(int i=start; i<=end; i++)
if(start==i || (start!=i && numbers[start]!=numbers[i])){
swap(start,i);
perm(start+1,end);
swap(start,i);
}
}
}
private static void swap(int i, int j){
int temp=numbers[i];
numbers[i]=numbers[j];
numbers[j]=temp;
}
private static boolean isOk(){
int i,j;
for(i=1;i<n;i++)
if(numbers[i]<numbers[i-1])
return false;
for(++i;i<2*n;i++)
if(numbers[i]<numbers[i-1])
return false;
for(j=0; j<n; j++)
if(numbers[j]>numbers[j+n])
return false;
return true;
}
private static void print(){
int m;
count++;
for(m=0; m<n; m++)
System.out.print(String.format("%3d", numbers[m]));
System.out.println();
for(; m<2*n; m++)
System.out.print(String.format("%3d", numbers[m]));
System.out.println();
System.out.println("-----------------");
}
}
Input n:
1
Input n:
Input n:
1 2
1 3
Input n:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
Input n:
1 2 3 4
1 2 3 5
1 2 3 6
1 2 3 7
1 2 4 5
1 2 4 6
1 2 4 7
1 2 5 6
1 2 5 7
1 3 4 5
1 3 4 6
1 3 4 7
1 3 5 6
1 3 5 7
Input n:
进栈出栈法
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int i, j;
int count0, count1;
long count;
int num;
int n;
while (true) {
count = 0;
n = input.nextInt();
for (i = 0; i < Math.pow(2, 2 * n); i++) {
num = i;
count0 = count1 = 0;
for (j = 0; j < 2 * n; j++) {
if (num % 2 == 0) {
count0++;
} else {
count1++;
}
num = num / 2;
if (count0 > count1)
break;
}
if (count1 == count0 && count0 == n)
count++;
}
System.out.println("Tables: " + count);
System.out.println("-----------------");
}
}
}
0
1
2
3
4
5
6
7
8
9
10
Catalan数法
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n;
long catalan;
while (true){
n = input.nextInt();
catalan = Catalan(n);
System.out.println(catalan);
System.out.println("--------------");
}
}
private static long Catalan(int n){
if(n <= 1)
return 1;
long[] h = new long[n+1];
h[0] = h[1] = 1;
for(int i = 2 ; i <= n ; i++){
h[i] = 0;
for(int j = 0 ; j < i ; j++)
h[i] += h[j]*h[i-j-1];
}
return h[n];
}
private static long Catalan2(int n){
if(n <= 1)
return 1;
long[] h = new long[n+1];
h[0] = h[1] = 1;
for(int i=2; i<=n; i++)
h[i] = h[i-1]*(4*i-2)/(i+1);
return h[n];
}
}
0
1
2
3
4
5
7
27
Catalan数法(大数乘法与除法)
Java
import java.util.Scanner;
public class Main {
private static int MAX = 100;
private static int BASE = 10000;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int i, j, n;
int[][] a = new int[105][MAX];
for (i=0; i<MAX; i++) {
a[1][i] = 0;
}
for (i=2, a[1][MAX-1]=1; i<=100; i++) {
for (j=0; j<MAX; j++)
a[i][j] = a[i-1][j];
multiply(a[i], MAX, 4*i-2);
divide(a[i], MAX, i+1);
}
while (true) {
n = input.nextInt();
for (i = 0; i<MAX && a[n][i]==0; i++);
System.out.print(a[n][i++]);
for (; i<MAX; i++)
System.out.print(String.format("%04d", a[n][i]));
System.out.println();
System.out.println("------------------");
}
}
private static void multiply(int[] a, int Max, int b) {
int i, array = 0;
for (i=Max-1; i>=0; i--) {
array += b*a[i];
a[i] = array%BASE;
array /= BASE;
}
}
private static void divide(int[] a, int Max, int b) {
int i, div=0;
for (i=0; i<Max; i++) {
div = div*BASE+a[i];
a[i] = div/b;
div %= b;
}
}
}
1
2
3
4
35
100
57
24
68
79
46
Reference
王晓东《计算机算法设计与分析》(第3版)P47
https://blog.csdn.net/llwwlql/article/details/52920198