<数据结构与算法>堆的应用二叉树的链式实现

news2024/9/25 11:20:13

目录

前言

一、堆的应用

1. 堆排序

1.1 排升序,建大堆

1.2  时间复杂度计算

2. Top k问题

二、 二叉树的链式实现

1. 二叉树的遍历

2. 二叉树基础OJ

3.DFS && BFS 

总结


前言

        学习完堆的数据结构,我们要清楚,它虽然实现了排序功能,但是真正的排序函数应当是在给定的数组内,将数组排序,如果我们要用堆排序,那么我们不可能手写堆的数据结构,在堆内排序后再复制给给定的数组,这样不仅很麻烦,而且还要开辟另外的空间。

        所以,我们又有了堆排序的方法。


一、堆的应用

1. 堆排序

        我们可以将给定的数组看为一个完全二叉树,但它此刻还不是堆,因为堆是有顺序的,所以我们可以使用向上或向下调整函数进行堆排序,模拟堆插入,这就是一个建堆的过程。

 堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1.建堆

  • 升序:建大堆
  • 降序:建小堆

2.利用堆删除思想进行排序

1.1 排升序,建大堆

        方法一:先利用向上调整建大堆(在给定的数组内从下标为1的数据开始进行向上调整),然后再进行向下调整,完成升序排序

        解释: 如果采用小堆,那么堆顶就是最小值,取走堆顶数据后,次小数上至堆顶,此时堆所表示的二叉树内父子兄弟关系就全乱了,不能再按顺序提出堆顶数据。

        所以,排升序还是需要用大堆

 //堆排升序 -- 建大堆
void HeapSort(int* a, int n)
{
 // 1.建堆 -- 向上调整建堆--模拟插入的过程

	for (int i = 1; i < n; ++i)
	{
		AdjustUp(a, i);
	}

 // 2.利用堆删除思想进行排序
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end - 1, 0);

		end--;//end下标前移
	}

}

int main()
{
	int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 }; // 对数组排序
	HeapSort(a, 10);

	return 0;
}

        方法二:先向下调整建大堆,再用向下调整完成升序排序

        注意: 在向下调整时,不能从堆顶开始,因为数组最初是无序的,而向下调整的前提是其左右子树是大堆或小堆,才可以向下调整,        

        所以我们先将最后一个叶子节点的父节点开始向下调整,完成调整后,从父节点开始向前向下调整,直到堆顶完成向下调整后结束。

综上分析,方法二效率比方法一高,只用写一个向下调整函数即可

 //堆排升序 -- 建大堆
void HeapSort(int* a, int n)
{

	//1.建堆 -- 向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	// 2.利用堆删除思想进行排序
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end - 1, 0);

		end--;//end下标前移
	}

}

int main()
{
	int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 }; // 对数组排序
	HeapSort(a, 10);

	return 0;
}

1.2  时间复杂度计算

        向下调整建堆的时间复杂度计算 O(N)

         向上调整建堆的时间复杂度计算 O(N*logN)

         最简单的解释:向下调整,节点最多的一层最坏情况只调整一次,节点最少的一层最坏情况调整h-1次,而向上调整相反,节点最少的一层最坏情况只调整一次,节点最多的一层最坏情况调整h-1次,显而易见,向上调整的时间复杂度大于向下调整.

2. Top k问题

         TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

        对于Top-K问题,能想到的最简单直接的方式就是排序,但是,如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

        最佳的方式就是用堆来解决,基本思路下:

1. 用数据集合中前K个元素来建堆

  • 前k个最大的元素,则建小堆(取要排序的前k个数据先建一个小堆,之后依次遍历数据,与堆顶数据比较大小,如果比堆顶数据大,就替代它进堆,然后向下调整)
  • 前k个最小的元素,则建大堆()

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素

void PrintTopK(const char* file, int k)
{
	// 1. 建堆--用a中前k个元素建小堆
	int* topk = (int*)malloc(sizeof(int) * k);
	assert(topk);

	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读出前k个数据建小堆
	for(int i = 0; i < k; ++i)
	{
		fscanf(fout, "%d", &topk[i]);
	}

	for (int i = (k-2)/2; i >= 0; --i)
	{
		AdjustDown(topk, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int val = 0;
	int ret = fscanf(fout, "%d", &val);
	while (ret != EOF)
	{
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}

		ret = fscanf(fout, "%d", &val);
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", topk[i]);
	}
	printf("\n");

	free(topk);
	fclose(fout);
}

void CreateNDate()
{
	// 造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 10000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

int main()
{
	//int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3}; // 对数组排序
	//HeapSort(a, 10);

	CreateNDate();
	PrintTopK("data.txt", 10);

	return 0;
}

二、 二叉树的链式实现

        二叉树本身增删查改没有什么实际意义,但当加上了一个特性例如:左子树小于根,右子树大于根后,即搜索二叉树,它就有了实际意义。

在看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

1. 空树

2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

1. 二叉树的遍历

        把每一个二叉树都分为三部分:根、左子树、右子树

                                        理解函数栈帧,画图就可理解递归调用过程 

 二叉树结构体:

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

1.1 前序遍历根、左子树、右子树  (先访问根,遇到每一个都作为根)

1  2  3  NULL  NULL  NULL  4  5  NULL  NULL  6  NULL  NULL

//前序遍历
void PreOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

1.2 中序遍历:左子树、根、右子树    (遇到每一个都先访问左子树,直到NULL,所以访问的第一个一定为NULL)

NULL  3  NULL  2  NULL  1  NULL  5 NULL  4  NULL  6  NULL

//中序遍历
void InOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

1.3 后序遍历:左子树、右子树、根     (左、右、根)

NULL  NULL  3  NULL  2  NULL  NULL 5  NULL  NULL 6  4  1

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

1.4 层序遍历:一层从左往右

1  2  4  3  5  6

用队列实现,每出队一个根节点就把它的孩子入队,实现出上一层带入下一层

#include"Queue.h"
//队列的实现,并把data的类型改为树结点指针类型

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);//用树节点的指针类型接收队头数据(因为队头数据类型为树节点指针类型)
		QueuePop(&q);
		printf("%d ", front->data);

        //如果左孩子不为空就入队
		if(front->left)
			QueuePush(&q, front->left);
        //如果右孩子不为空就入队
		if (front->right)
			QueuePush(&q, front->right);
	}

	QueueDestroy(&q);
}

1.计算二叉树内结点个数

注意:

  1. 如果想要计算二叉树内节点个数,可以在传参时加上一个参数psize指针
  2. 不要在函数内使用static静态局部变量,因为它指挥在第一次调用时才执行,之后调用都不会再执行,也就是说第一次计算二叉树内节点个数是正确的,而再次计算时,size不能初始化为0,所以计算结果就会出错。
  3. 也尽量不使用全局变量,这样的话我们要在每一次调用计算函数前,自己手动初始化size全局变量为0,并不是很方便。

        最简形式:

int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : 
			TreeSize(root->left) 
			+ TreeSize(root->right) 
			+ 1;
}

2.计算树的高度

        看山不是山,不考虑太多递归过程,看结果

注意:

  1. 一定要记录递归结果,因为如果不记录结果,那么程序就会递归两大遍次数
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

3.计算第k层节点数

        k--直到k为1,就找到了要计算的第k层,如果此时root不为NULL,则return 1,如果为NULL,则返回0。这样就计算出了左右子树的第k-1层节点数,最后相加。

int TreeKLevel(BTNode* root, int k)
{
	assert(k > 0);

	if (root == NULL)
		return 0;
	
	if (k == 1)
		return 1;

	return TreeKLevel(root->left, k - 1)
		+ TreeKLevel(root->right, k - 1);
}

4.二叉树查找值为x的结点

注意:

        在函数多层调用时,返回root是不能直接返回到最外面的,只能返回上一个函数调用处

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;
	
	BTNode* lret = BinaryTreeFind(root->left, x);
	if (lret)
		return lret;

	BTNode* rret = BinaryTreeFind(root->right, x);
	if (rret)
		return rret;

	return NULL;
}

2. 二叉树基础OJ

2.1      965. 单值二叉树

bool isUnivalTree(struct TreeNode* root)
{
    if(root == NULL)
        return true;
    if(root->left && root->val != root->left->val)
        return false;
    if(root->right && root->val != root->right->val)
        return false;

    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

        在写递归时,要写递归的出口!顾名思义,是在递归过程中特殊的结果,写能阻断递归的逻辑表达式。例如此题:第二三个if,只有在值不相等的时候返回false,终止递归,不能在判断中写相等的情况,因为那样的判断没有任何用处 。


2.2     100. 相同的树

   bool isSameTree(TreeNode* p, TreeNode* q)
    {
        if(p == NULL && q == NULL)
        return true;

        if(p == NULL || q == NULL)
        return false;

        if(p->val != q->val)
        return false;

        return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    }

  

2.3      144. 二叉树的前序遍历

        由于要返回数组,所以要在函数内开辟一个数组空间,而开辟多少字节空间就要我们计算二叉树的结点数 

int TreeSize(struct TreeNode* root)
{
    return root == NULL? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

void preorder(struct TreeNode* root, int*arr, int* pi)
{
    if(root == NULL)
        return;
    a[(*pi)++] = root->val;//错误,因为i的值不会及时更新
    preorder(root->left,arr,pi);
    preorder(root->right,arr,pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
    *returnSize = TreeSize(root);
    int* arr = (int*)malloc(sizeof(int)*(*returnSize)));
    int i = 0;
    preorder(root,arr,&i);
}

注意前序遍历函数内数组下标是*pi,因为如果只传整形 i 是无法在递归中及时更新i的值的!

2.4      572. 另一棵树的子树

         使左边的每一颗子树与右边的树比较,可以利用上相同的树函数

bool isSameTree(TreeNode* p, TreeNode* q)
    {
        //两个都为空
        if(p == NULL && q == NULL)
        return true;
        //其中一个为空
        if(p == NULL || q == NULL)
        return false;
        //可以退出的情况
        if(p->val != q->val)
        return false;

        return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    }

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
    if(root == NULL)
        return false;

    if(isSameTree(root,subRoot))
        return true;
    
    //递归一定要记录,不然就白递归了
    //比如:
    //isSubtree(root->left,subRoot);
    //isSubtree(root->right,subRoot);
    //return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
    //递归了两大遍,是错误写法
    
    return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}

 

2.5       KY11 二叉树遍历

struct TreeNode
{
	struct TreeNode* left;
	struct TreeNode* right;
	char val;
};

void InOrder(struct TreeNode* root)
{
	if (root == NULL)
		return;

	InOeder(root->left);
	printf("%c ", root->val);
	InOeder(root->right);
}

struct TreeNode* CreateTree(char* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
	root->val = a[(*pi)++];
	root->left = CreateTree(a, pi);
	root->right = CreateTree(a, pi);
	return root;
}

int main()
{
	char a[100];
	scanf("%s", a);
	int i = 0;
	InOrder(CreateTree(a, &i));
	return 0;
}

2.6  判断二叉树是否是完全二叉树

思路:

  1. 采用层序遍历的方式,如果访问到NULL,那么后面全是NULL,因为完全二叉树的性质,非空结点都是连续的,空结点是最后一个节点。
  2. 遇到NULL,就把剩下的数据都取出来,判断是否为非空,如果是非空数据,那么就返回false,如果都是NULL,那么返回true
bool TreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		else
		{
			QueuePush(root->left);
			QueuePush(root->right);
		}
	}
    //把剩下的数据都取出来,看是否右非空数据
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

 2.7  销毁二叉树

思路:采用后序遍历,递归式销毁二叉树

//销毁二叉树
//思路:采用后序遍历的的方式,递归销毁二叉树
void TreeDestory(BTNode* root)
{
	if (root == NULL)
		return;
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
    //如果想要在里面置空,要么传二级指针,要么使用C++中的引用
    //如果不在里面置空,那么要在外面手动或置空
	root = NULL;
}

3.DFS && BFS 

3.1 DFS 深度优先遍历

        二叉树的前序遍历就是严格的深度优先遍历一般使用递归实现如果不考虑数据访问顺序,那么中序、后序遍历也可算为深度优先遍历。

3.2 BFS 广度优先遍历

        二叉树的层序遍历就是广度优先遍历的一种,一般使用队列实现


总结

    二叉树这一数据结构包含了诸多的递归函数,本节学习了二叉树的遍历、计算二叉树的高度、第k层结点数、总结点数、如何在二叉树内查找数以及一些二叉树的OJ

    如何正确书写递归函数,如何正确使用递归函数来计算二叉树的各项属性是学习二叉树的关键。

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

Wlan——STA上线流程与802.11MAC帧讲解

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标准属于以太网的一种帧格式…

Redis 分布式锁存在什么问题 ?如何解决 ?

目录 1. 如何实现分布式锁 2. Redis 分布式锁存在什么问题 2.1 解决死锁问题 2.2 解决锁误删问题 1. 如何实现分布式锁 Redis 天生就可以作为一个分布式系统来使用&#xff0c;所以它实现的锁都是分布式锁。 Redis 可以通过 setnx&#xff08;set if not exists&#xff09…

vant4 自定义垂直步骤条时间线组件几行css代码改造完成(附效果图)

直接上效果图片 <template><!-- 审批流程 --><div><van-steps direction="vertical" active="-1"><van-step><template #inactive-icon><div class="relative"><img :src="girlIcon" /&…

JavaScript-console:JavaScript控制台(Console)常用方法

一、理解 console JavaScript 控制台&#xff08;console&#xff09;是一个开发人员在编写 JavaScript 代码时常用的工具。它是浏览器提供的一种界面&#xff0c;让开发人员能够追踪代码执行的状态和结果。JavaScript 控制台可以记录代码输出的信息、警告和错误&#xff0c;并…

opencv 进阶13-Fisherfaces 人脸识别-函数cv2.face.FisherFaceRecognizer_create()

Fisherfaces 人脸识别 PCA 方法是 EigenFaces 方法的核心&#xff0c;它找到了最大化数据总方差特征的线性组合。不可否认&#xff0c;EigenFaces 是一种非常有效的方法&#xff0c;但是它的缺点在于在操作过程中会损失许多特征信息。 因此&#xff0c;在一些情况下&#xff0c…

外贸网站怎么做推广优化

外贸网站的推广和优化是关键&#xff0c;因为它可以帮助您扩展国际市场并吸引更多的客户。以下是e6zz seo多年经验总结出一些有效的方法&#xff0c;可以帮助您推广和优化外贸网站&#xff1a; 优化网站内容&#xff1a; 确保您的网站内容对国际客户有吸引力&#xff0c;包括产…

lab4 traps

在开始做lab之前务必弄清楚 所谓的系统调用&#xff0c;本质上就是内核态和用户态之间的切换内核态和用户态的区别本质上就是一些关键属性的区别&#xff0c;比如页表 而他们的运行方式都一样&#xff0c;就是取指执行&#xff0c;没有魔法 只不过内核态和用户态的执行不在一个…

自动化客户服务必备神器——salesmartly

自动化客户服务必备神器——salesmartly 您是否正在寻找节省时间和简化客户服务流程的方法&#xff1f;客户服务自动化就是答案&#xff01;自动化使企业能够提供更快、更高效的客户服务&#xff0c;尤其是在电子商务领域。在本文中&#xff0c;我们将讨论自动化客户服务流程的…

没有高学历就没有高薪工作?

大家好&#xff0c;我是郑州软件测试4期的小路同学&#xff0c;是一个只有普通高中学历的河南人。由于自己上学的时候对学习不感兴趣&#xff0c;于是很早就辍学了。干过服务员&#xff0c;也卖过房子&#xff0c;但是干啥工作都没有挺过半年的&#xff0c;因为薪资很低&#x…

go_并发编程

go并发编程 一、 并发介绍1&#xff0c;进程和线程2&#xff0c;并发和并行3&#xff0c;协程和线程4&#xff0c;goroutine 二、 Goroutine1&#xff0c;使用goroutine1&#xff09;启动单个goroutine2&#xff09;启动多个goroutine 2&#xff0c;goroutine与线程3&#xff0…

docker compose的用法

目录 一、Docker-Compose介绍 1.1 Docker-Compose的概述 1.2 Docker-Compose 用来实现Docker容器快速编排 1.3 Docker-compose模板文件简介 二、YAML简介 2.1 YAML的概述 2.2 YAML的基本语法规则 2.3 YAML支持的数据架构 三、配置内部常用字段 四、Docker-compose 常…

常见的软件测试用例设计方法有哪些?

常见的软件测试用例设计方法&#xff0c;个人认为主要是下面这6种&#xff1a; 1)流程图法&#xff08;也叫场景法&#xff09; 2)等价类划分法 3)边界值分析 4)判定表 5)正交法 6)错误推测法 这6种常见方法中&#xff0c;我分别按照定义、应用场景、使用步骤、案例讲解这4个部…

Vue-13.创建完整的Vue项目(vue+vue-cli+js)-1

前言 之前写了命令创建Vue项目&#xff0c;但是事实上我们可以直接用编译器直接创建项目&#xff0c;这里我使用webstorm&#xff08;因为我是前后端兼修的所以我习惯使用Idea家族的编译器&#xff09; 只写前端的推荐用VsCode前后端都写的推荐用webstorm 新建项目 项目初始…

记录node 版本对应的 node-sass 版本号

当我们在项目中使用 node-sass 时&#xff0c;电脑的 node版本号一定要与 node-sass 版本号对应&#xff0c;不对应时下载就会报错 一、官方文档地址 https://www.npmjs.com/package/node-sass 二、对应版本表格

一张图看懂 USDT三种类型地址 Omni、ERC20、TRC20的区别

USDT是当前实用最广泛&#xff0c;市值最高的稳定币&#xff0c;它是中心化的公司Tether发行的。在今年的4月17日之前&#xff0c;市场上存在着2种不同类型的USDT。4月17日又多了一种波场TRC20协议发行的USDT&#xff0c;它们各自有什么区别呢?哪个转账最快到账&#xff1f;哪…

Android kotlin 跳转手机热点开关页面和判断热点是否打开

Android kotlin 跳转手机热点开关页面和判断热点是否打开 判断热点是否打开跳转手机热点开关页面顺带介绍一些其他常用的设置页面跳转 其他热点的一些相关知识Local-only hotspot 参考 判断热点是否打开 网上方法比较多&#xff0c;我这边使用了通过WifiManager 拿反射的getWi…

【C#学习笔记】委托和事件

文章目录 委托委托的定义委托实例化委托的调用多播委托 为什么使用委托&#xff1f;官方委托泛型方法和泛型委托 事件为什么要有事件&#xff1f;事件和委托的区别&#xff1a; 题外话——委托与观察者模式 委托 在 .NET 中委托提供后期绑定机制。 后期绑定意味着调用方在你所…

问题解决:Failed to start sshd.service: Unit is masked.

centos7.6 ssh突然不能用了 也启动不了 错误如下&#xff1a; 解决方式&#xff1a; systemctl unmask sshd systemctl start sshd

day 34 | ● 62.不同路径 ● 63. 不同路径 II

62.不同路径 递归公式为上 左 func uniquePaths(m int, n int) int {dp : make([][]int, m)for i : 0; i < m; i{tmp : make([]int, n)dp[i] tmpdp[i][0] 1}for i : 0; i < n; i{dp[0][i] 1}for i : 1; i < m; i{for j : 1; j < n; j{dp[i][j] dp[i - 1][j] …