2-13 标准二维表问题
问题描述
设n是一个正整数。2*n的标准二维表是由正整数1,2,…,2n组成的2*n数组,该数组的每行从左到右递增,每列从上到下递增。2*n的标准二维表全体记为Tab(n)。例如,当n=3时,tab(3)二维表如下图所示。
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 1 | 2 | 4 |
| 3 | 5 | 6 |
| 1 | 2 | 5 |
| 3 | 4 | 6 |
| 1 | 3 | 4 |
| 2 | 5 | 6 |
| 1 | 3 | 5 |
| 2 | 4 | 6 |
给定正整数n,试计算Tab(n)中2*n的标准二维表的个数。
分析
一维数组递归遍历法
将上面标准二维表从左到右从上往下看成一维数组,递归遍历,记录符合条件的情况。
进栈出栈法
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 0 | 0 | 1 | 1 | 1 |
如上图,先把2*n个数字排成一行来看(1~2*n 有序)。之前上表中放到第一行的数字1,2,3在下表中标记为0,放到第二行的数字4,5,6在下表中标记为1,这样就可以有一个0,1的序列,如下图(按照上面方式,将前面n=3时的所有5种标准二维表转变为一维表):
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 0 | 0 | 1 | 1 | 1 |
| 1 | 2 | 4 |
| 3 | 5 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 0 | 1 | 0 | 1 | 1 |
| 1 | 2 | 5 |
| 3 | 4 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 0 | 1 | 1 | 0 | 1 |
| 1 | 3 | 4 |
| 2 | 5 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 1 | 0 | 0 | 1 | 1 |
| 1 | 3 | 5 |
| 2 | 4 | 6 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 0 | 1 | 0 | 1 | 0 | 1 |
当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
(
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
)
=
h
(
n
−
1
)
∗
(
4
∗
n
−
2
)
/
(
n
+
1
)
;
递推关系的解为:
h(n)=C(2n,n)/(n+1)(n=0,1,2,...)
h
(
n
)
=
C
(
2
n
,
n
)
/
(
n
+
1
)
(
n
=
0
,
1
,
2
,
.
.
.
)
递推关系的另类解为:
h(n)=c(2n,n)−c(2n,n−1)(n=0,1,2,...)
h
(
n
)
=
c
(
2
n
,
n
)
−
c
(
2
n
,
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(arr[i]<arr[i-1]||arr[i]>arr[i+n])
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 & Output
Input n:
1
-----------------
1
2
-----------------
Tables: 1
------------------------
Input n:
0
-----------------
Tables: 0
------------------------
Input n:
2
-----------------
1 2
3 4
-----------------
1 3
2 4
-----------------
Tables: 2
------------------------
Input n:
3
-----------------
1 2 3
4 5 6
-----------------
1 2 4
3 5 6
-----------------
1 2 5
3 4 6
-----------------
1 3 4
2 5 6
-----------------
1 3 5
2 4 6
-----------------
Tables: 5
------------------------
Input n:
4
-----------------
1 2 3 4
5 6 7 8
-----------------
1 2 3 5
4 6 7 8
-----------------
1 2 3 6
4 5 7 8
-----------------
1 2 3 7
4 5 6 8
-----------------
1 2 4 5
3 6 7 8
-----------------
1 2 4 6
3 5 7 8
-----------------
1 2 4 7
3 5 6 8
-----------------
1 2 5 6
3 4 7 8
-----------------
1 2 5 7
3 4 6 8
-----------------
1 3 4 5
2 6 7 8
-----------------
1 3 4 6
2 5 7 8
-----------------
1 3 4 7
2 5 6 8
-----------------
1 3 5 6
2 4 7 8
-----------------
1 3 5 7
2 4 6 8
-----------------
Tables: 14
------------------------
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();
//转化为 进栈出栈的问题:2*n个数字,进栈是0,出栈是1,且进栈与出栈次数相等,均为n
//B:二进制 D:十进制
//当n=3时,有6个数字,最大01排列为:111111B < 1000000B = 2^(2*3)D = 2^6D
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) //二进制最后为0,表示进栈,当进栈与出栈次数相等时,前面出栈必然比进栈多一次,无进栈如何出栈,不可能。
// if(count1 > count0)
break;
}
if (count1 == count0 && count0 == n)
count++;
}
System.out.println("Tables: " + count);
System.out.println("-----------------");
}
}
}
Input & Output
0
Tables: 1
-----------------
1
Tables: 1
-----------------
2
Tables: 2
-----------------
3
Tables: 5
-----------------
4
Tables: 14
-----------------
5
Tables: 42
-----------------
6
Tables: 132
-----------------
7
Tables: 429
-----------------
8
Tables: 1430
-----------------
9
Tables: 4862
-----------------
10
Tables: 16796
-----------------
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("--------------");
}
}
//第一种Catalan数计算方法
//h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)
private static long Catalan(int n){ //0<=n<=35
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];
}
//第二种Catalan数计算方法
//h(n)=h(n-1)*(4*n-2)/(n+1)
private static long Catalan2(int n){ //0<=n<=33
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];
}
}
Input & Output
0
1
--------------
1
1
--------------
2
2
--------------
3
5
--------------
4
14
--------------
5
42
--------------
7
429
--------------
27
69533550916004
--------------
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; //1<=n<=100
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;
}
}
}
Input & Output
1
1
------------------
2
2
------------------
3
5
------------------
4
14
------------------
35
3116285494907301262
------------------
100
896519947090131496687170070074100632420837521538745909320
------------------
57
26700952856774851904245220912664
------------------
24
1289904147324
------------------
68
86218923998960285726185640663701108500
------------------
79
289450081175264899454283846029490767264392230
------------------
46
8740328711533173390046320
------------------
Reference
王晓东《计算机算法设计与分析》(第3版)P47
https://blog.youkuaiyun.com/llwwlql/article/details/52920198

本文探讨了2*n的标准二维表问题,通过一维数组递归遍历、进栈出栈法以及Catalan数法进行求解。特别是,将问题转化为进栈出栈问题,利用Catalan数的递推关系进行计算,对于大数情况,使用大数乘法与除法来解决。文章提供了相关算法的Java实现及输入输出示例。
251

被折叠的 条评论
为什么被折叠?



