卡特兰数

news2024/11/14 0:34:00

文章目录

  • 1、简介
    • 1.1 何为卡特兰数
    • 1.2 卡特兰数的通项公式
  • 2、应用
    • 2.1 题目1:括号合法
      • 题目描述
      • 思路分析
    • 2.2 题目2:进出栈的方式
      • 2.2.1 题目描述
      • 2.2.2 思路分析
    • 2.3 题目3:合法的序列
      • 2.3.1 题目描述
      • 2.3.2 思路分析
      • 2.3.3 代码实现
    • 2.4 题目4:不同二叉树的数量
      • 2.4.1 题目描述
      • 2.4.2 思路分析
      • 2.4.3 代码实现
  • 3、总结

1、简介

1.1 何为卡特兰数

卡特兰数又称卡塔兰数,英文名Catalan number,是组合数学 中一个常在各种计数问题中出现的数列。

前几项为:1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, …

1.2 卡特兰数的通项公式

k ( 0 ) = 1 , k ( 1 ) = 1 k(0) = 1, k(1) = 1 k(0)=1,k(1)=1 时,如果接下来的项满足:

k ( n ) = k ( 0 ) ∗ k ( n − 1 ) + k ( 1 ) ∗ k ( n − 2 ) + . . . + k ( n − 2 ) ∗ k ( 1 ) + k ( n − 1 ) ∗ k ( 0 ) k(n) = k(0) * k(n - 1) + k(1) * k(n - 2) + ... + k(n - 2) * k(1) + k(n - 1) * k(0) k(n)=k(0)k(n1)+k(1)k(n2)+...+k(n2)k(1)+k(n1)k(0)

或者

k ( n ) = C 2 n n − C 2 n n − 1 k(n) = C_{2n}^n- C_{2n}^{n-1} k(n)=C2nnC2nn1

或者

k ( n ) = C 2 n n / ( n + 1 ) k(n) = C_{2n}^n / (n + 1) k(n)=C2nn/(n+1)

就说这个表达式,满足卡特兰数,常用的是范式1和2,3几乎不会使用到

2、应用

2.1 题目1:括号合法

题目描述

给定 N N N 个左括号, N N N 个右括号,它们自由组合,必须全部使用,能得到多少个合法的组合?

思路分析

“合法”的定义是对于自由排列的组合,只要保证任意的前缀,右括号数量 ≤ \le 左括号数量,那么它最终一定是合法的。原理就是左括号比右括号多的时候,一旦出现右括号就能将多的左括号配对,最终一定会全部配对成功。反之则不行。

先讨论一个数学思想:如何确定两个集合相等

【集合A 和 集合B 可以是完全不相干的两个可数集合,只要能找到一个映射 f f f,使得A集合中的一个对应B集合中的一个,又能找到一个和 f f f 毫不相干的映射 g g g 使得 B集合中的一个对应 A集合中的一个,如果存在这样一组映射,那么A集合数量和B集合数量一定相等。】

借此,来讨论括号组合的合法问题。合法的组合数量不好算,那就可以先选出不合法的组合数量,然后使用总的排列方法 减去 不合法的,就是合法的。

总的排列方法数为 C 2 n n C_{2n}^n C2nn,意思是一共 2 n 2n 2n 个位置,选择其中的 n n n 个放左括号,剩下的 n n n 个位置放右括号。

而不合法的特征一定存在一个最初的前缀:右括号数量 = 左括号数量 + 1,如 ())((),最初前缀就是 ())。那么在该前缀后的就是 右括号数量 + 1 = 左括号数量 (因为左右括号的数量相等)。以这个最初前缀为分界线,后面的所有括号进行反转,即右括号变成左括号,左括号变成右括号,就变成了())))(,那么分界线后的左括号数量 + 1 = 右括号数量。如此一来,整体的 右括号数量 = 左括号数量 + 2。

定义两个集合,A集合放所有不合法的情况,B集合是 n + 1 n + 1 n+1 个右括号, n − 1 n-1 n1个左括号 组合得到的所有情况 (该集合的来源不深究),也就是说通过上面的括号反转,A集合中任意一个不合法的元素通过该映射都能变出一个B集合的某一个元素;而B集合中的每个元素都能变成A集合其中的一个,如))))((,最初的不合法前缀为),以该前缀为分界线后面的所有括号反转。最终得到)((())

所以 “ n n n 个左括号 和 n n n 个右括号组合不合法的数量” = “ n + 1 n+1 n+1个右括号 和 n − 1 n-1 n1个左括号组合的所有数量”,即 C 2 n n + 1 C_{2n}^{n+1} C2nn+1

所以, n n n 个左括号和 n n n 个右括号组合的合法数量 = C 2 n n − C 2 n n + 1 C_{2n}^{n} - C_{2n}^{n+1} C2nnC2nn+1,而 C 2 n n + 1 = C 2 n n − 1 C_{2n}^{n+1} = C_{2n}^{n-1} C2nn+1=C2nn1,因此合法的组合数量也可以是 C 2 n n − C 2 n n − 1 C_{2n}^n - C_{2n}^{n-1} C2nnC2nn1

也就是说,括号类型的违规可以转换为卡特兰数进行计算,因为 C 2 n n − C 2 n n − 1 C_{2n}^n - C_{2n}^{n-1} C2nnC2nn1 是卡特兰数的通项公式之一。

2.2 题目2:进出栈的方式

2.2.1 题目描述

n n n 个数字要进出栈,一共有多少种进出栈的方式?

2.2.2 思路分析

例如,给定数字[1, 2],进出栈的方式:

合法的情况:

1. 1进(↓),1出(↑),2进(↓),2出(↑)
2. 1进(↓),2进(↓),2出(↑),1出(↑)
只用记录箭头

不合法的情况:

1. ↑ ↑ ↓ ↓ (没有数字进栈是无法出栈的)

考察箭头组合的合法数量,这其实就是括号问题。进栈是左括号,出栈是右括号。合法的条件就是在任何时候,右括号数量不能大于左括号数量,也就是任意时刻出栈次数 ≤ \le 进栈次数就是合法的进出栈方式,就是卡特兰数。

再举个例子:某个公司的股票有上涨和下跌,问有多少种交易方式可以使得股票不会跌到X轴以下?
在这里插入图片描述
这也是一个卡特兰数问题,往上就是左括号,往下就是右括号,就是在问左右括号合理的结合方式。

2.3 题目3:合法的序列

2.3.1 题目描述

假设给你 N N N 个 0,和 N N N 个 1,你必须用全部数字拼序列。

返回有多少个序列满足:任何前缀串,1的数量都不少于0的数量

2.3.2 思路分析

这个就是 “括号合法” 模型问题,1是左括号,0是右括号,任意时刻满足左括号数量 ≥ \ge 右括号数量。

直接使用卡特兰数的通项公式解决即可。

2.3.3 代码实现

import java.util.LinkedList;

public class 10Ways {
	//方法1:暴力递归
	public static long ways1(int N) {
		int zero = N;
		int one = N;
		LinkedList<Integer> path = new LinkedList<>();
		LinkedList<LinkedList<Integer>> ans = new LinkedList<>();
		process(zero, one, path, ans);
		long count = 0;
		for (LinkedList<Integer> cur : ans) {
			int status = 0;
			for (Integer num : cur) {
				if (num == 0) {
					status++;
				} else {
					status--;
				}
				if (status < 0) {
					break;
				}
			}
			if (status == 0) {
				count++;
			}
		}
		return count;
	}

	public static void process(int zero, int one, LinkedList<Integer> path, LinkedList<LinkedList<Integer>> ans) {
		if (zero == 0 && one == 0) {
			LinkedList<Integer> cur = new LinkedList<>();
			for (Integer num : path) {
				cur.add(num);
			}
			ans.add(cur);
		} else {
			if (zero == 0) {
				path.addLast(1);
				process(zero, one - 1, path, ans);
				path.removeLast();
			} else if (one == 0) {
				path.addLast(0);
				process(zero - 1, one, path, ans);
				path.removeLast();
			} else {
				path.addLast(1);
				process(zero, one - 1, path, ans);
				path.removeLast();
				path.addLast(0);
				process(zero - 1, one, path, ans);
				path.removeLast();
			}
		}
	}
	
	//方法2:卡特兰数的通项公式解决
	public static long ways2(int N) {
		if (N < 0) {
			return 0;
		}
		if (N < 2) {
			return 1;
		}
		long a = 1;
		long b = 1;
		long limit = N << 1;
		for (long i = 1; i <= limit; i++) {
			if (i <= N) {
				a *= i;
			} else {
				b *= i;
			}
		}
		return (b / a) / (N + 1);
	}

	public static void main(String[] args) {
		System.out.println("test begin");
		for (int i = 0; i < 10; i++) {
			long ans1 = ways1(i);
			long ans2 = ways2(i);
			if (ans1 != ans2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("test finish");
	}
}

2.4 题目4:不同二叉树的数量

2.4.1 题目描述

N N N 个二叉树节点,每个节点彼此之间无任何差别。返回由 N N N 个二叉树节点,组成的不同结构数量是多少?

2.4.2 思路分析

如果 0 个节点,只能组成空树,1种;
如果 1 个节点,只能组成1个节点的树,1种;
如果 2 个节点,只有2种结构。

n 个节点组成二叉树的情况:

1)第 1 种划分:头结点1个,左子树0个,右子树 n − 1 n-1 n1
在这里插入图片描述
在这种划分下,不同结构的数量为:[0个节点组成的方法数] × \times × [ n − 1 n-1 n1个节点组成的方法数]

2)第 2 种划分:头结点左边1个节点,右边 n − 2 n-2 n2 个节点
在这里插入图片描述
在这种划分下,不同结构的数量为:[1个节点组成的方法数] × \times × [ n − 2 n-2 n2个节点组成的方法数]

这个规律就是卡特兰数的第1个通项公式,所以这就是卡特兰数。 那么直接用卡特兰数的第2个通项公式计算即可,因为卡特兰数的三个通项公式是等效的。

2.4.3 代码实现

public class DifferentBTNum {

//	k(0) = 1, k(1) = 1
//
//	k(n) = k(0) * k(n - 1) + k(1) * k(n - 2) + ... + k(n - 2) * k(1) + k(n - 1) * k(0)
//	或者
//	k(n) = c(2n, n) / (n + 1)
//	或者
//	k(n) = c(2n, n) - c(2n, n-1)
	
	//方法1
	public static long num1(int N) {
		if (N < 0) {
			return 0;
		}
		if (N < 2) {
			return 1;
		}
		long[] dp = new long[N + 1];
		dp[0] = 1;
		dp[1] = 1;
		for (int i = 2; i <= N; i++) {
			for (int leftSize = 0; leftSize < i; leftSize++) {
				dp[i] += dp[leftSize] * dp[i - 1 - leftSize];
			}
		}
		return dp[N];
	}

	//方法2:卡特兰数
	public static long num2(int N) {
		if (N < 0) {
			return 0;
		}
		if (N < 2) {
			return 1;
		}
		long a = 1;
		long b = 1;
		for (int i = 1, j = N + 1; i <= N; i++, j++) {
			a *= i;
			b *= j;
			long gcd = gcd(a, b);
			a /= gcd;
			b /= gcd;
		}
		return (b / a) / (N + 1);
	}

	public static long gcd(long m, long n) {
		return n == 0 ? m : gcd(n, m % n);
	}

	public static void main(String[] args) {
		System.out.println("test begin");
		for (int i = 0; i < 15; i++) {
			long ans1 = num1(i);
			long ans2 = num2(i);
			if (ans1 != ans2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("test finish");
	}
}

3、总结

  1. 要对卡特兰数的通项公式1和公式2烂熟于心;
  2. 对“括号合法”模型(左右括号数量相等的合法性问题)要有敏感度;
  3. 加强对卡特兰数通项公式1的敏感度的训练,如题目4,符合公式1等同于公式2,而通常可能是写出了暴力递归才会发现公式1,就要求对暴力解法有了解。

补充:

数学结论:所有整数和所有偶数数量相等。因为任何整数乘以2后等于某个偶数,而任意偶数除以2后等于某个整数,二者建立了一一映射关系,所以整数数量和偶数数量就是一样多的。在数学上,它叫作“等势”。

5米的线和10米的线上的点数量一样多。在两条线之外找某一个定点,5米线上找任意一个点和定点连线能对应到10米中的一个点,10米的任意一个点和定点连线能对应到5米中的一个点,所以5米的线和10米的线点一样多。
在这里插入图片描述
点是无长度的,无长度的东西有可能有无限个点。更多可以查看希尔伯特旅馆悖论。

总而言之,牵扯到一个非常重要的思想,两个可数集合A和B,能互相建立一种映射关系,那它们的数量就是一样多的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/377356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式ID生成系统

目录背景常用分布式ID生成系统介绍UUIDSnowflake背景 在大多数复杂的分布式系统中&#xff0c;往往需要对大量的数据和消息进行唯一标识。而对分布式系统后台数据库的分库分表后需要有一个唯一的ID来表示一条数据或者是消息。那么我们分布式系统ID一般都有哪些需求呢&#xff1…

IP地址、主机名、域名解析(DNS)

1.什么是IP地址 每一台联网的电脑都会有一个地址&#xff0c;用于和其他计算机进行通讯 IP地址主要有两个版本&#xff1a;v4 v6 IPV4版本的地址格式名为&#xff1a;a.b.c.d&#xff0c;其中abcd表示0-225的数字&#xff0c;如192.168.88.10为一个标准地址 查看IP地址&#x…

Android 蓝牙开发——HCI log 分析(二十)

HCI log 是用来分析蓝牙设备之间的交互行为是否符合预期,是否符合蓝牙规范。对于蓝牙开发者来说,通过 HCI log 可以帮助我们更好地分析问题,理解蓝牙协议。 一、抓取HCI log 1、手机抓取HCI log 在开发者选项中打开启用蓝牙HCI信息收集日志开关,Android系统就开始自动地收…

计算机SCI论文选题和投稿需要注意什么? - 易智编译EaseEditing

科研创新与选题 科研创新是至关重要的&#xff0c;往往关系到论文是否顺利发表。 摆在我们面前的&#xff0c;往往是别人挑剩下的资料&#xff0c;似乎毫无写作价值&#xff0c;很多人便知难而退&#xff0c;干脆不写论文了。 其实&#xff0c;应该问问自已“我有什么&#xf…

Flask应用的基本组成部分、模板引擎Jinja2的使用、Flask-WTF、SQLAlchemy

目录标题1. Flask应用的基本组成部分1.1 路由&#xff08;Routing&#xff09;1.2 视图函数&#xff08;View Function&#xff09;1.3 请求&#xff08;Request&#xff09;1.4 响应&#xff08;Response&#xff09;2. 模板引擎Jinja2的使用2.1 入门案例2.2 条件判断2.3 循环…

Python采集m3u8格式做个小姐姐动态壁纸~

人生苦短&#xff0c;我用python 首先&#xff0c;我和大家一样喜欢看小姐姐~ 其次&#xff0c;看美丽的事物会让人更加有动力去… 我编不下去了哈哈哈&#xff0c;我就是爱看充满美感的人儿~ 更多python好看的:点击此处跳转文末名片获取 环境 Pythonpycharm 模块使用 第…

使用virtualenv和pip构建项目所需的独立Python环境

1、为什么需要独立的Python环境&#xff1f;在讲技术前&#xff0c;想先讲讲目的。为什么我们需要独立的Python环境&#xff1f;这里就借用virtualenv的文档来解释吧。virtualenv is a tool to create isolated Python environments.The basic problem being addressed is one …

51-Jenkins-Periodic Backup插件实现Jenkins备份

Periodic Backup插件实现Jenkins备份前言目录结构插件备份安装插件使用插件前言 本篇来学习下使用Periodic Backup插件实现Jenkins备份 目录结构 Jenkins的所有数据都是存放在文件中的&#xff0c;所以&#xff0c;Jenins备份其实就是备份Jenkins_HOME目录。 Jenkins_Home目…

taobao.user.buyer.get( 查询买家信息API )

&#xffe5;开放平台基础API必须用户授权 查询买家信息API&#xff0c;只能买家类应用调用。 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new…

现在00后都是这么卷了吗?

现在的00后小年轻真的卷得过分了。前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪20K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家…

我应该把毕业设计做到什么程度才能过关?

本篇博客包含了狗哥多年职业生涯对于软件项目的一丢丢理解&#xff0c;也讲述了对于大学生毕业设计的一些理解。如果你还是懵懵懂懂就要离开学校了&#xff0c;被老师告知不得不做出一套毕业设计的时候&#xff0c;希望你可以看到这篇博客&#xff0c;让你有点头绪&#xff0c;…

PingCAP 唐刘:一个咨询顾问对 TiDB Chat2Query Demo 提出的脑洞

导读 近日&#xff0c;TiDB Cloud 发布了 Chat2Query 功能&#xff0c;在 TiDB Cloud 上通过自然语言提问&#xff0c;即可生成相应的 SQL&#xff0c;通过 TiDB Cloud 对上传的任意数据集进行分析。Gartner 也在一份有关 ChatGPT 对数据分析影响研究的报告中提及了 PingCAP 的…

Gateway集成Netty服务

Gateway和Netty都有盲区的感觉&#xff1b; 一、Netty简介 Netty是一个异步的&#xff0c;事件驱动的网络应用框架&#xff0c;用以快速开发高可靠、高性能的网络应用程序。 传输服务&#xff1a;提供网络传输能力的管理&#xff1b; 协议支持&#xff1a;支持常见的数据传输…

Python调用百度AI实现文字识别

目录标题 前沿实战演示重中之重(开玩笑)前沿 今天我们也来高大上一下,玩一把人工智能。那就是免费调用百度AI实现图片上面的文字识别。相对于Python的第三方库,百度人工智能要更强大,毕竟人工智能不是那么容易搞的。要调用,其实很简单,关键的代码只需要三行。但需要先注…

使用cmake在win10编译yolov5+tensorRT+cuda+cudnn+protobuf代码进行混合编译

这里进行之前需要把protobuf在win10下编译&#xff0c;可以参考这篇文章从Linux下载下来的工程代码&#xff0c;这里建议直接使用vs系列打开不要用vscode打开&#xff0c;vscode对win下的cmake不友好&#xff0c;主要体现在报错机制无法直接定位&#xff0c;题主的环境是vs2022…

Kubernetes07:Service

Kubernetes07:Service 1、service存在的意义 因为Pod的IP是不断变化的&#xff0c;所以需要注册service防止pod失联 1&#xff09;为了防止Pod失联&#xff08;服务发现&#xff09; 2、定义一组Pod访问策略&#xff08;负载均衡&#xff09; 2、Pod和Service的关系-------通…

为什么要学习C++软件调试技术?掌握这类技术都有哪些好处?

目录 1、为什么要学习C软件调试技术&#xff1f; 1.1、IDE调试手段虽必不可少&#xff0c;但还不够 1.2、通过查看日志和代码去排查异常崩溃问题&#xff0c;费时费力&#xff0c;很难定位问题 1.3、有的问题很难复现&#xff0c;可能只在客户的环境才能复现 1.4、为了应对…

主打的就是I/O流,顺便把File复习了

文章目录1. File类1.1 预备知识1.2 创建文件1.3 文件的常用方法2. IO流2.1 InputStream2.2 OutputStream2.3 Reader2.4 Writer2.5 缓冲流2.6 转换流2.7 对象流2.8 打印流1. File类 1.1 预备知识 文件分隔符 Windows&#xff1a;D:\Soft\QQ\PluginLinux&#xff1a;D:/Soft/QQ…

SpringMVC使用 redis 实现缓存

简介 SpringMVC 中也可以将缓存标签和 redis 结合起来使用&#xff0c;其实此时缓存没有起作用&#xff0c;只是通过缓存的那几个注解来操作 redis 而已&#xff1b;SpringMVC 中整合 redis 比较麻烦的是注意版本冲突的问题&#xff0c;如下是官网有关于版本的要求 https://d…

【人脸识别】DDL:数据分布知识蒸馏思想,提升困难样本(遮挡、低分辨率等)识别效果

论文题目&#xff1a;《Improving Face Recognition from Hard Samples via Distribution Distillation Loss》 论文地址&#xff1a;https://arxiv.org/pdf/2002.03662v3.pdf 代码地址&#xff1a;https://github.com/HuangYG123/DDL 1.前言及相关工作 Large facial variatio…