差分(C/C++)

目录

1. 一维差分

 2.1 一维差分数组的构造(不重要)

2.2 一维差分数组的用途

 3. 二维差分

 3.1 二维差分的用途


 

1. 一维差分

差分与前缀和就可以理解成数学中微分与积分的关系,差分是前缀和的逆运算。

例如给定数组 A ,他的前缀和数组为 S,那么称 S 为 A 的前缀和,称 A 为 S 的差分。

ad696314c2724385835241b692efc9f4.png

 2.1 一维差分数组的构造(不重要)

有了前缀和数组我们就要想办法构造差分数组了,在数学上根据递推公式:An = Sn - S(n - 1),其中 A 为差分数组,S为前缀和数组。这个公式并不重要哈,因为知道了差分数组的用途我们就可以很轻松地利用前缀和数组推导出差分数组。

2.2 一维差分数组的用途

利用差分可以实现对前缀和数组中的一段区间的元素均加上常数 C 。例如:给定前缀和数组 S,S的差分数组 A ,现在我们要将前缀和数组 S 中 [ l, r ] 区间的所有元素加上常数 C,用循环显然需要O(N)的时间复杂度。但是如果我们求出来前缀和数组 S 的差分数组 A,那么我们就可以在差分数组中让 A[ l ] + C,同时让 A[ r + 1 ] - C,此时的差分数组对应的前缀和数组即是目标数组。这样我们就在 O(1) 的时间复杂度里面达到了想要的结果。

原理:根据前缀和数组的定义,当我们在差分数组的某一项加上一个常数 C 时,那么前缀和数组中的对应位置,及其以后位置的数均会加上常数 C。

da4f8c0736774e049ac805b32d4e8af3.png

当然想要前缀和数组中的某一个区间减去常数 C 也是一样的道理。现在我们就能够理解如何实现我们的要求了。

632c09ab1201436e8d46a7a49c59b0a8.png

前面的 2.1 说了那种差分数组的构造方法并不重要。这是因为我们弄清楚了利用差分数组能够在前缀和数组中使得 [ l, r ] 区间内的元素加上常数 C,我们就能够轻而易举地通过前缀和数组推出差分数组。

假设我们要求前缀和数组 S 的差分数组,我们构造一个前缀和数组 S1 假设每一个元素均为 0 ,那么前缀和数组 S1 对应的差分数组 A1 的每一个元素也是 0,这个时候我们用变量 i 访问前缀和数组 S 中的每一个元素(遍历),假设每次遍历得到的数为 C,那么我们可以把 C 看作是让前缀和数组 S1 区间为 [ i, i ] 内的元素加上 C,然后用上面介绍的方法即可推出 S 的差分数组。

cc049dfed58d4a97a669d88b328558e5.png

 我们现在就可以尝试写代码啦。

输入长度为n的整数序列

接下来输入m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [ l, r ] 之间的每个数加上c。

 

输入格式

第一行包含两个整数n和m。

第二行包含n个这个数,表示整数序列。

接下来m行,每行包括三个整数 l,r,c,表示一个操作

void test03()
{
	//定义数组长度
	const int N = 100;

	//定义两个数组
	int s[N] = { 0 };
	int a[N] = { 0 };

	//读入已知的前缀和数组,因为数组是一个一个读入的,所以不需要遍历啦
	int n, m;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &s[i]);
		//求出前缀和数组的差分数组
		insert(a, i, i, s[i]);
	}

	//m个询问
	while (m--)
	{
		int l, r, c;
		scanf("%d %d %d", &l, &r, &c);
		//前缀和数组的[l,r]区间加上c
		insert(a, l, r, c);

	}

	//利用新的差分数组打印前缀和
	for (int i = 1; i <= n; i++)
	{
		//递推求出新的差分数组的前缀和
		a[i] += a[i - 1];
		printf("%d ", a[i]);
	}
	printf("\n");
}

int main()
{

	//一维差分
	test03();

	system("pause");
	return 0;
}

5826f7aa648a47a183ee0fbf012de48f.png

 3. 二维差分

二维差分:给定一个二维前缀和数组S,存在一个二维数组(矩阵)A,如果二维前缀和数组(矩阵)中 S[ i ][ j ] (i >= 1,i <= n)位置的元素满足:( A[1][1] + A[1][2] + ··· + A[1][j] ) + ( A[2][1] +

A[2][2] + ··· + A[2][j] ) + ··· + ( A[i][1] + A[i][2] + ··· + A[i][j] )。则称二维数组A为二维前缀和数组的差分数组。

9d3611e3be194fbd86c54fb20d7d71bb.png

 3.1 二维差分的用途

和一维差分的作用类似,只不过是使前缀和矩阵中某个子矩阵的所有元素加上常数C。方法如下:

在求出前缀和矩阵的差分矩阵后,要求我们让前缀和矩阵S中左上角坐标为 x1,y1;右下角坐标为x2,y2所围成的矩形里面的元素加上常数C。则只需要在差分矩阵A中,令A[x1][y1] + C,再令

A[x2+1][y1] - C,再令 A[x1][y2+1] - C,最后令 A[x2+1][y2+1] + C。这样我们就能够使前缀和矩阵中的某一子矩阵加上常数C啦。

4c3fd016f43e4aabad43b43f70d807e2.png

 现在的问题就是怎么根据前缀和矩阵求出差分矩阵,原理和怎么求一维差分的数组是一样的。给定一个前缀和矩阵S,我们要求S的差分矩阵,首先构造一个前缀和矩阵S1,全部初始化为0,构造一个差分矩阵A全部初始化为0,此时A当然是S1的差分矩阵。然后我们利用 i,j 遍历给定的前缀和矩阵S,将遍历得到的结果 C 看作是在左上角坐标为 (i, j) ,右下角坐标为 (i, j) 的矩阵中加上常数C。通过上面的方法,以此类推,就可以得到前缀和矩阵S的差分矩阵啦。这里就不画图了。图与一维差分数组的求解差不多的。

下面我们就可以尝试写代码啦。

输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1,y1,x2,y2,c,其中 (x1,y1),(x2,y2) 分别表示子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素加上常数C。最后输出操作后的矩阵。

 

输入格式

第一行包含整数n,m,q。

接下来n行,每行包含m个整数,表示整数矩阵。

接下来q行,每行包含5个整数x1,y1,x2,y2,c,表示一个操作。

//定义数组的大小
const int N = 10;

//将左上角坐标为(x1,y1),右下角坐标为(x2,y2)围成的矩阵中的元素加上c
void insert01(int(*a)[N], int x1, int y1, int x2, int y2, int c)
{
	a[x1][y1] += c;
	a[x1][y2 + 1] -= c;
	a[x2 + 1][y1] -= c;
	a[x2 + 1][y2 + 1] += c;
}

void test04()
{

	//差分矩阵,前缀和矩阵均需要初始化为0
	int s[N][N] = { 0 };
	int a[N][N] = { 0 };

	int n, m, q;
	scanf("%d %d %d", &n, &m, &q);

	//读入前缀和矩阵
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			scanf("%d", &s[i][j]);
			//读入前缀和矩阵的同时求出对应的差分矩阵
			insert01(a, i, j, i, j, s[i][j]);
		}
	}

	//q个询问
	while (q--)
	{
		int x1, x2, y1, y2, c;
		scanf("%d %d %d %d %d", &x1, &y1, &x2, &y2, &c);

		//对前缀和矩阵中的子矩阵加上常数c
		insert01(a, x1, y1, x2, y2, c);
	}
	
	//利用递推关系求出新的差分矩阵对应的前缀和矩阵
	//这里是直接在差分矩阵上修改得到新的前缀和矩阵
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

int main()
{

	//二维差分
	test04();

	return 0;
}

55c1e3781ff24fcfa3ca9600eb07d391.png

 

 

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
### 回答1: C/C++ 是应用广泛的编程语言,其在数据结构应用方面也十分重要。面试中相关的 C/C++ 数据结构问题主要围绕数组、链表、二叉树和图等方面。以下是一些常见问题及其解答: 1. 如何反转一个单向链表? 答:可以使用三个指针来实现:cur 代表当前节点,pre 代表上一个节点,next 代表下一个节点。每次遍历时,将 cur 的 next 指向 pre,然后将三个指针分别向后移动即可。 2. 如何判断两个链表是否相交,并找出相交的点? 答:可以分别遍历两个链表,得到各自的长度。然后让长的链表先走 n 步,使得两个链表剩余的长度相等。接下来同时遍历两个链表,比较节点是否相同即可找出相交的点。 3. 如何判断一个二叉树是否为平衡二叉树? 答:可以计算每个节点的左右子树深度差,如果任何一个节点的深度差大于1,则此树不是平衡二叉树。可以使用递归实现,每次计算当前节点的深度,然后递归判断其左右子树是否平衡。 4. 如何实现图的深度优先搜索(DFS)和广度优先搜索(BFS)算法? 答:DFS 可以使用递归实现。从某个节点开始,逐个访问其未被访问的邻接节点,并将其标记为已访问。然后对每个未被访问的邻接节点递归调用 DFS 函数。BFS 可以使用队列实现。从某个节点开始,将其加入队列,并标记为已访问。然后从队列中弹出节点,并访问其所有未被访问的邻接节点,并将其加入队列中。重复此过程直到队列为空。 以上是一些常见的 C/C++ 数据结构面试问题及其解答。在面试中,除了掌握相关算法和数据结构知识外,还需多做练习和积累经验,才能更好地应对各种面试问题。 ### 回答2: C语言是一种用于编写系统级程序的高级编程语言,具有简单、高效、灵活等特点,是许多操作系统、编译器等软件的首选语言,也是许多企业在进行面试时重点考察的技能。在C/C++数据结构面试题中,经常会涉及到各种数据结构相关的算法和应用,测试面试者的算法思维能力和实现能力。 其中,常见的数据结构包括链表、栈和队列、二叉树、搜索树、哈希表等。在面试时,会常常涉及代码设计和实现,比如实现链表的插入、删除、查找操作,实现二叉树的遍历、查找操作等。 此外,在数据结构面试中,还经常涉及排序和查找算法,如冒泡排序、快速排序、归并排序、二分查找、哈希查找等。同时,面试者还需要解决一些较为复杂的算法问题,如图的最短路径问题,最小生成树问题等。 总之,C/C++数据结构面试题涵盖了运用数据结构的各种算法和实现方法,需要面试者具备扎实的编程基础和算法思维能力。在备战面试时,可以多做练习,熟悉常用的数据结构和算法,提高理解和实现能力,从而更好地应对面试挑战。 ### 回答3: 面试过程中常见的C/C++数据结构面试题有很多。以下就介绍几个常见的题目并给出解答。 1. 求两个有序数组的中位数 题目描述:给定两个升序排列的整形数组,长度分别为m和n。实现一个函数,找出它们合并后的中位数。时间复杂度为log(m+n)。 解答:这个问题可以使用二分法求解。首先,我们可以在两个数组中分别选出所谓的中间位置,即(i+j)/2和(k+l+1)/2,其中i和j分别是数组A的起始和结束位置,k和l分别是数组B的起始和结束位置。判断A[i+(j-i)/2]和B[k+(l-k)/2]的大小,如果A的中间元素小于B的中间元素,则中位数必定出现在A的右半部分以及B的左半部分;反之,则必定出现在A的左半部分以及B的右半部分。以此类推,每一次都可以删去A或B的一半,从而达到对数级别的时间复杂度。 2. 堆排序 题目描述:对一个长度为n的数组进行排序,时间复杂度为O(nlogn)。 解答:堆排序是一种常用的排序算法,在面试中也经常被考察。堆排序的具体过程是首先将数组构建成一个最大堆或最小堆,然后不断将堆顶元素与最后一个元素交换,并将最后一个元素从堆中剔除。这样,每次剔除后,堆都会重新调整,使得剩下的元素仍然保持堆的性质,直到堆中只剩下一个元素为止。 3. 链表反转 题目描述:反转一个单向链表,例如给定一个链表: 1->2->3->4->5, 反转后的链表为: 5->4->3->2->1。 解答:链表反转题目也是非常常见,其思路也比较简单。遍历链表,将当前节点的next指针指向前一个节点,同时记录当前节点和前一个节点,直至遍历到链表末尾。 以上这三个问题分别从二分法、堆排序和链表三个方面介绍了常见的C/C++数据结构面试题,希望能帮助面试者更好地准备面试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姬如祎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值