微信上的“程序员的那些事”想必是很多码农们关注的公众账号之一,我也是其粉丝,每天都会看看里面有没有什么趣事,前段时间“程序员的那些事”分享了一篇博文《我的Twitter技术面试失败了》挺有意思,链接如下http://mp.weixin.qq.com/mp/appmsg/show?__biz=MjM5OTA1MDUyMA==&appmsgid=10000710&itemidx=1&sign=fab77147279ef685c50e39cc06623e5d&uin=MjM3Mjc1NTIwMA%3D%3D&key=38b17fed399880fb7129f69083fd038240b4873f89a22a1bf82803ef35479ff9dda602589570716d1a73ae7c3e9f739d&devicetype=android-17&version=25000105&lang=zh_CN。
里面说到了一道算法题,给出一个数组,将其转化为二维坐标系的点,并且连接每个点形成一个“容器”状的图形,问“容器”能装多少水(详情参照上面的链接)。这道题吸引我的原因是,每当说到算法题,许多公司总是喜欢考一些查找、排序、大数据处理等等,没什么多新意,也考不了程序员的思维能力(面试前复习下《数据结构》即可);而这道题很有新意,引发了我做一做的欲望,顺便也开拓一下思维,毕竟在实际工作中还是很少用到算法的。
第一次解题:思路很自然地想到从左到右遍历数组,确定左右底部的位置(即一个“蓄水区域”),进行一次容积的计算,然后将指针移动到此“蓄水区域”的右侧,继续确定下一个“蓄水区域” 的左右底部位置,再进行一次容积的计算...直到数组最右端,代码如下:
1 /* 2 * waterHolder.c 3 * 4 * Created on: 2013-11-5 5 * Author: pengyiming 6 * Description: 1,输入如下的非负数组(2, 5, 1, 2, 4),将其转化到二维坐标系中的点((2, 0), (2, 1), (5, 1), (5, 2), (1, 2), (1, 3), (2, 3), (2, 4), (4, 4), (4, 5)) 7 * 2,连接上述个点形成一个"容器" 8 * 试问"容器"可装多少水,以1x1方格为单位 9 * Answer: 第一次遍历,从左向右遍历数组,找出左右侧最高点 10 * 第二次遍历,遍历左右侧最高点之间的数组,计算容积 11 */ 12 13 #include <stdio.h> 14 15 /* 宏begin */ 16 #define TRUE 1 17 #define FALSE 0 18 /* 宏end */ 19 20 /* 输入数据begin */ 21 static const unsigned int WATER_BUCKET[] = { 6, 1, 4, 6, 7, 5, 1, 6, 4 }; 22 /* 输入数据end */ 23 24 int main(int argc, char *argv[]) 25 { 26 int volume = 0; 27 28 // "容器"左右侧高度,底部高度 29 int leftHeight = 0; 30 int bottomHeight = 0; 31 int rightHeight = 0; 32 33 // "容器"左右侧高度,底部高度位置 34 int leftPos = 0; 35 int bottomPos = 0; 36 int rightPos = 0; 37 38 // "容器"左侧高度,底部高度确定状态 39 int leftPosOK = FALSE; 40 int bottomPosOK = FALSE; 41 42 // 遍历数组以获取"容器"中的每个蓄水区域 43 int length = sizeof(WATER_BUCKET) / sizeof(unsigned int); 44 while (TRUE) 45 { 46 int i; 47 for (i = rightPos; i < length; i++) 48 { 49 // 寻找左侧位置 50 if (!leftPosOK && WATER_BUCKET[i] >= leftHeight) 51 { 52 leftHeight = WATER_BUCKET[i]; 53 leftPos = i; 54 55 bottomHeight = WATER_BUCKET[i]; 56 } 57 58 // 寻找底部位置,确定左侧位置 59 if (!bottomPosOK && (WATER_BUCKET[i] < bottomHeight)) 60 { 61 bottomHeight = WATER_BUCKET[i]; 62 bottomPos = i; 63 64 leftPosOK = TRUE; 65 rightHeight = WATER_BUCKET[i]; 66 } 67 68 // 寻找右侧位置,确定底部位置 69 if (leftPosOK && (WATER_BUCKET[i] > rightHeight)) 70 { 71 rightHeight = WATER_BUCKET[i]; 72 rightPos = i; 73 74 bottomPosOK = TRUE; 75 76 // 优化:若右侧高度已经大于等于左侧高度,则已确定右侧位置,无需遍历完数组 77 if (rightHeight >= leftHeight) 78 { 79 break; 80 } 81 } 82 83 printf("loop : left = %d, right = %d, bottom = %d ", leftPos, rightPos, bottomPos); 84 } 85 86 if (bottomPos > leftPos 87 && rightPos > bottomPos) 88 { 89 volume += countVolume(leftPos, rightPos, bottomPos); 90 91 // 重置并计算下一个"蓄水区域" 92 leftHeight = 0; 93 bottomHeight = 0; 94 rightHeight = 0; 95 96 leftPos = rightPos; 97 bottomPos = rightPos; 98 99 leftPosOK = FALSE; 100 bottomPosOK = FALSE; 101 } 102 else 103 { 104 // 可能在"容器"中有多个蓄水区域,遍历可能需要多次,设定一个变量标识是否找到蓄水区域,当找不到时退出 105 break; 106 } 107 } 108 109 printf("total volume = %d ", volume); 110 111 return 0; 112 } 113 114 int countVolume(int leftPos, int rightPos, int bottomPos) 115 { 116 printf("count volume : left = %d, right = %d, bottom = %d ", leftPos, rightPos, bottomPos); 117 118 if (WATER_BUCKET[leftPos] == WATER_BUCKET[bottomPos] 119 || WATER_BUCKET[rightPos] == WATER_BUCKET[bottomPos]) 120 { 121 return 0; 122 } 123 124 // "容器"能装多少水取决于最短一侧的高度 125 int minHeight = WATER_BUCKET[leftPos]; 126 if (minHeight > WATER_BUCKET[rightPos]) 127 { 128 minHeight = WATER_BUCKET[rightPos]; 129 } 130 131 int volume = 0; 132 int i; 133 for (i = leftPos + 1; i < rightPos; i++) 134 { 135 volume += minHeight - WATER_BUCKET[i]; 136 } 137 138 return volume; 139 }
上述算法通过了链接中及我自己列举的测试用例,说明上述代码确实能工作,但是其中掺杂了太多奇怪的boolean变量来帮助确定左右底部的位置,这使得代码可读性不好,且逻辑不够清晰,最近发完版后又闲下来了,重新理解了一下链接中的解题思路,确实比这段算法的思路清晰很多。
第二次解题:第一次遍历确定“容器”最高点,从而将“容器”一分为二,接下来只要分别确定“容器”左右侧最高点,并计算左右侧容积即可(“容器”左右侧最高点必定小于“容器”最高点,根据“木桶原理”,用左右侧最高点计算容积)。
多插一句,给出的数组是线性的,但是不见得非要从左到右遍历去解决问题,可以试一试上面这种分治的思路,线性的数组一分为二,分别通过左右两侧的子遍历来解决问题,代码如下:
1 /* 2 * waterHolder2.c 3 * 4 * Created on: 2013-11-19 5 * Author: pengyiming 6 * Description: 1,输入如下的非负数组(2, 5, 1, 2, 4),将其转化到二维坐标系中的点((2, 0), (2, 1), (5, 1), (5, 2), (1, 2), (1, 3), (2, 3), (2, 4), (4, 4), (4, 5)) 7 * 2,连接上述个点形成一个"容器" 8 * 试问"容器"可装多少水,以1x1方格为单位 9 * Answer: 第一次遍历,遍历整个数组,找出最高点 10 * 第二次遍历,分为两个子遍历最高点左右侧的数组,计算容积 11 */ 12 13 #include <stdio.h> 14 15 /* 宏begin */ 16 /* 宏end */ 17 18 /* 输入数据begin */ 19 static const unsigned int WATER_BUCKET[] = { 6, 1, 4, 6, 7, 5, 1, 6, 4 }; 20 /* 输入数据end */ 21 22 int main(int argc, char *argv[]) 23 { 24 int length = sizeof(WATER_BUCKET) / sizeof(unsigned int); 25 int volume = 0; 26 27 int maxPos = findMaxPos(length); 28 29 volume += countLeftVolume(maxPos); 30 volume += countRightVolume(length, maxPos); 31 printf("total volume = %d ", volume); 32 } 33 34 int findMaxPos(int length) 35 { 36 int max = 0; 37 int maxPos = 0; 38 int i; 39 for (i = 0; i < length; i++) 40 { 41 if (WATER_BUCKET[i] > max) 42 { 43 max = WATER_BUCKET[i]; 44 maxPos = i; 45 } 46 } 47 48 return maxPos; 49 } 50 51 int countLeftVolume(int maxPos) 52 { 53 int volume = 0; 54 int leftMax = 0; 55 int i; 56 for (i = 0;i < maxPos; i++) 57 { 58 if (leftMax >= WATER_BUCKET[i]) 59 { 60 volume += leftMax - WATER_BUCKET[i]; 61 } 62 else 63 { 64 leftMax = WATER_BUCKET[i]; 65 } 66 } 67 68 printf("left volume = %d ", volume); 69 return volume; 70 } 71 72 int countRightVolume(int length, int maxPos) 73 { 74 int volume = 0; 75 int rightMax = 0; 76 int i; 77 for (i = length - 1; i > maxPos; i--) 78 { 79 if (rightMax >= WATER_BUCKET[i]) 80 { 81 volume += rightMax - WATER_BUCKET[i]; 82 } 83 else 84 { 85 rightMax = WATER_BUCKET[i]; 86 } 87 } 88 89 printf("right volume = %d ", volume); 90 return volume; 91 }
上述算法也通过了链接中及我自己列举的测试用例,如果大家还有什么更好的解法,欢迎不啬赐教!