算法题——立方体的体对角线穿过多少个正方体?

这道题是笔者当年参加竞赛的题目,多年来一直未得其解,久久不能释怀。近日,重新拿起该题细细研究,终于将其解出,著文以记之。

问题描述:

长方体长X,宽Y,高Z。X、Y、Z都是正整数。长方体由长1、宽1、高1的正方体堆积而成。那么长方体的体对角线穿过多少个正方体?

这个题考量三维空间的想象。近日研究的时候,尝试先考量二维的情况,在求解出二维的情况下,在推广到三维里。下面是二维情况下的问题描述

长方形长X,宽Y。X、Y都是正整数。长方形由长1、宽1的正方形组成。那么长方形的对角线穿过多少个正方形?

以实例说明。长方形长6,宽4。长方形由长1、宽1的正方形组成。那么长方形的对角线穿过多少个正方形?

这个还是比较简单的,直接用图表示即可,如下图所示:

如上图所示,对角线一共穿过8个正方形(灰色部分)。但是,我们不可能每个问题都画图表示,比如长777,宽581的长方形的解就很难画图表示(数字太大,不容易精确表示)。

仔细看看,这8个正方形实际上把对角线分成了8段。线段的端点是对角线和水平线(或竖直线)的交点。

于是,问题似乎可以转化成

要求穿过多少个正方形,实际上相当于求有多少个线段

要求有多少个线段,实际上相当于求对角线和水平线和垂直线的交点的个数

把上图放在平面直角坐标系中,左下角坐标为(0,0),右上角坐标为(6,4)

则对角线的直线方程为

和我们一般想象中的直线方程不太一样。没关系,首先这个是正确的直线方程,其次是为了和后面的三维中的直线方程的表现形式统一。

我们把对角线和水平线(或竖直线)的交点在图上标示出来(为了后文的描述方便,我用不同颜色标示点)

左下角的起点用灰色标示,红色的点标示对角线和竖直线的交点(交点的横坐标是整数),绿色的点标示对角线和水平线的交点(交点的纵坐标是整数)

起点不算,则穿过的方块数和线段数和点的个数一致(都是8个)。

红色点的坐标(横坐标是整数)分别是:

个数和长方形的长的数值是一致的(是6)

绿色点的坐标(纵坐标是整数)分别是:

个数和长方形的宽的数值是一致的(是4)

可以看出,红色点和绿色点有2个点是重合的(图上用半红半绿的点标示),因此这些点合在一起就是如下(按照和起点的远近来进行排序)

于是该问题的求解过程可以如下表示:

1、求出横坐标是整数的点的个数,就是长方形长的数值。本题是6

2、求出纵坐标是整数的点的个数,就是长方体宽的数值。本题是4

3、求出步骤1和步骤2中重合的点的个数,也就是横纵坐标都是整数的点的个数。本题是2

4、问题的答案:步骤1的答案+步骤2的答案-步骤3的答案。本题是6+4-2=8

步骤1、2、3、4中,关键是步骤3,如何求出步骤1和步骤2中重合的点的个数,也就是横纵坐标都是整数的点的个数。

最大公约数:正整数a和b,若a能被b整除,则a是b的倍数,b是a的约数。正整数a和b中约数最大的那个称为a和b的最大公约数,记作gcd(a,b)

本题中,(4,6)=2,正好是步骤3的答数,是巧合么?不是,接下来我们来证明。

证明:长X、宽Y的长方形,对角线经过双整数点(横纵坐标都是整数)的个数为gcd(X,Y)(注:不算起点)

证:令x1=X/gcd(X,Y),y1=Y/gcd(X,Y)。则x1和y1都是整数,且x1和y1互质(除1以外,没有公约数)。

对角线所在的直线方程为

当x取整数时(1≤x≤X)时,要使y也是整数,则x必须取x1的倍数(这样才能把分母完全约掉)

而在1到X之间,x1的倍数一共有gcd(X,Y)个

证明完毕

综上所述:长方形长X,宽Y。X、Y都是正整数。长方形由长1、宽1的正方形组成。那么长方形的对角线穿过多少个正方形?

其解为:Ans=X+Y-gcd(X,Y),可以用下图表示

例如:

长6,宽4的长方形的对角线穿过6+4-gcd(6,4)=6+4-2=8个正方形

长5,宽3的长方形的对角线穿过5+3-gcd(5,3)=5+3-1=7个正方形

长12,宽8的长方形的对角线穿过12+8-gcd(12,8)=12+8-4=16个正方形

扩展到三维。长方体长X,宽Y,高Z。X、Y、Z都是正整数。长方体由长1、宽1、高1的正方体堆积而成。那么长方体的体对角线穿过多少个正方体?

长X、宽Y、高Z的立方体的体对角线的直线方程是

这个方程虽然有点怪,但是学过空间解析几何的都明白这个方程的正确性

求解的过程和二维的类似,也是找寻坐标是整数的点。可以用下图表示:

其解为:Ans=X+Y+Z-gcd(X,Y)-gcd(X,Z)-gcd(Y,Z)+gcd(X,Y,Z)

例如:

长5,宽3,高4的长方体的体对角线穿过5+3+4-gcd(5,3)-gcd(5,4)-gcd(3,4)+gcd(5,3,4)=5+3+4-1-1-1+1=10个正方体

长8,宽6,高3的长方体的体对角线穿过8+6+3-gcd(8,6)-gcd(8,3)-gcd(6,3)+gcd(8,6,3)=8+6+3-2-1-3+1=12个正方体

长12,宽8,高6的长方体的体对角线穿过12+8+6-gcd(12,8)-gcd(12,6)-gcd(8,6)+gcd(12,8,6)=12+8+6-4-6-2+2=16个正方体

下图是长5,宽3,高4的长方体的体对角线穿过正方体的示意图,一共10个正方体,你看清了么?

这个题历时若干年,总是百思不得其解。也是今朝灵光一闪,终于把该题解决了。也总算是一块石头落了地

算法题——立方体的体对角线穿过多少个正方体?

时间: 2024-08-01 22:45:35

算法题——立方体的体对角线穿过多少个正方体?的相关文章

【转】立方体的体对角线穿过多少个正方体?

问题描述: 长方体长X,宽Y,高Z.X.Y.Z都是正整数.长方体由长1.宽1.高1的正方体堆积而成.那么长方体的体对角线穿过多少个正方体? 这个题考量三维空间的想象.近日研究的时候,尝试先考量二维的情况,在求解出二维的情况下,在推广到三维里.下面是二维情况下的问题描述 长方形长X,宽Y.X.Y都是正整数.长方形由长1.宽1的正方形组成.那么长方形的对角线穿过多少个正方形? 以实例说明.长方形长6,宽4.长方形由长1.宽1的正方形组成.那么长方形的对角线穿过多少个正方形? 这个还是比较简单的,直接

笔试算法题(09):查找指定和值的两个数 & 构造BST镜像树

出题:输入一个已经升序排序的数组和一个数字:要求在数组中查找两个数,这两个数的和正好等于输入的那个数字,输出任意一对数字就可以,要求时间复杂度是O(n): 分析:对于升序排序的数组{-i-j-k-m--},只有可能是i+m=j+k(j和k可能是同一个数),所以可以从两边往中间收缩而忽视其他交叉相加的情况: 解题: 1 void FindSumFactor(int *array, int length, int sum) { 2 int left=0, right=length-1; 3 whil

笔试算法题(08):输出倒数第K个节点

出题:输入一个单向链表,要求输出链表中倒数第K个节点 分析:利用等差指针,指针A先行K步,然后指针B从链表头与A同步前进,当A到达链表尾时B指向的节点就是倒数第K个节点: 解题: 1 struct Node { 2 int v; 3 Node *next; 4 }; 5 Node* FindLastKth(Node *head, int k) { 6 if(head==NULL) { 7 printf("\nhead is NULL\n"); 8 exit(0); 9 } 10 Nod

笔试算法题(07):还原后序遍历数组 & 半翻转英文句段

出题:输入一个整数数组,判断该数组是否符合一个二元查找树的后序遍历(给定整数数组,判定其是否满足某二元查找树的后序遍历): 分析:利用后序遍历对应到二元查找树的性质(序列最后一个元素必定是根节点,从左向右第一个比根节点大的元素开始直到根节点之前的所有元素必定在右子树,之前的所有元素必定在左子树): 解题: 1 bool PostOrderCheck(int *array, int i, int j) { 2 /** 3 * 如快速排序一样,解决小子文件 4 * */ 5 if(j-i+1 ==

java基础算法题

为了提高自己的代码能力和算法能力,我决定每天学习一道算法题,吸收前辈思想. [程序1] TestRabbit.java 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 程序分析:兔子数量的规律为数列:1,1,2,3,5,8,13.....其实就是斐波那契数列  使用递归就可以实现 1 /** 2 * 兔子问题 3 * 2016/5/9 4 * 斐波那契数列求值 5 *题目:古典问题:有一对兔子,

ios 算法题

1兔子算法题 兔子可以跳一步2步或者3步,问跳到100有多少种跳法? // 兔子可以跳一步2步或者3步 // 问跳到100有几种跳法 /* 分析1 两个变量,X*2+Y*3=100. X最大为50,X最小为2 Y最大为32.最小为0 分析2 某个情景分析:假设X=35,Y为10时, 虽然知道了数量,但是兔子的35小步,和10大步的顺序是怎样的? 应为组合-> 45个节点中,选择10个放三步的.那就是简单的C(45 10).变成阶乘,就解得出来. */ double temp=0.0f; for

[solution]腾讯TEG_计算广告组_算法题

度娘笔试归来,题目实打实的,感觉真心不易,上百号人就抢那么几个坑......只恨自己平时积累太少啊~ 故曝一道鹅厂面试用的算法题(当时我就死在了这题上),来为度娘家攒一下RP~ 题目: 对于长度为N的一个无序的数组a[1..N],请将a进行排序,要求所有正数都排在0之前,所有负数都排在0之后(如果没有0,则所有正数排在负数前) 要求时间复杂度O(N),空间复杂度O(1) 题目不难,但给思考的时间很短,大约不到5分钟吧.当时脑子比较短路,于是只给出了O(n) O(n)复杂度的算法,然后就被面试官挂

算法题——翻转链表中的一段

题目:给出一个链表中的两个指针p1和p2,将其之间的结点翻转. 思路:可以通过交换结点内的值来实现结点的翻转,空间为O(N):如果要求不能交换值,那么仅凭p1和p2是无法翻转的,只能交换两个指针之间的链表. 代码: 交换值: 1 struct ListNode 2 { 3 int val; 4 ListNode *next; 5 }; 6 7 void reverseNodes(ListNode *p1, ListNode *p2) { 8 if ( p1 == NULL || p2 == NU

算法题:求数组中最小的k个数

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 题目:输入n个整数,找出其中最小的k个数. <剑指offer>给出了两种实现算法: 算法1:采用Partition+递归法,该算法可以说是快速排序和二分查找的有机结合.算法的时间复杂度为O(n),缺点在于在修改Partition的过程中会修改原数组的值. 算法2:采用top-k算法.如果要找最小的K个数,我们才用一个含有K个值的大顶堆:如果要找最大的K个数,我们采用小顶堆.该算法的时间复杂度为O(nlogK),是一种比较好的算法,启发于堆排序