这是我参与更文挑战的第8天,活动详情查看: 更文挑战
砖墙(题号554)
题目
你的面前有一堵矩形的、由 n
行砖块组成的砖墙。这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和应该相等。
你现在要画一条 自顶向下 的、穿过 最少 砖块的垂线。如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。
给你一个二维数组 wall
,该数组包含这堵墙的相关信息。其中,wall[i]
是一个代表从左至右每块砖的宽度的数组。你需要找出怎样画才能使这条线 穿过的砖块数量最少 ,并且返回 穿过的砖块数量 。
示例 1:
输入:wall = [[1,2,2,1],[3,1,2],[1,3,2],[2,4],[3,1,2],[1,3,1,1]]
输出:2
示例 2:
输入:wall = [[1],[1],[1]]
输出:3
提示:
n == wall.length
1 <= n <= 104
1 <= wall[i].length <= 104
1 <= sum(wall[i].length) <= 2 * 104
- 对于每一行
i
,sum(wall[i])
应当是相同的 1 <= wall[i][j] <= 231 - 1
链接
解释
这题啊,这题主要是思路。
一开始想的有点狗,先获取到最后的和,因为每一行的固定长度是一样的,所以最后要求的数必然在这个区间内,那就硬找呗。
同时因为线不能在墙的两边,所以要去掉两个边界条件,0和最大数就都得去掉。所以从1开始,一直找到倒数第二个数。
但这样一试就直接超时了,原因也很简单,因为有很多数字是没有意义的,比方说这样的一个用例👇:
[
[1,99999],
[2,99998],
[3,99997],
[4,99996],
[5,99995],
[6,99994],
[7,99993],
[8,99992],
[9,99991]
]
其实生效的就1~9和99991~99999之间的这18个数字,但我们却从1开始,一直找到了99999,多了近10W次无效查找,这纯粹是浪费了时间和性能。
那还有什么更好的办法么?笔者想到了另外一种暴力解法,就是稍微优化一下👆的解法,不在数字区间内一个个查找了,而是按照数组中的元素开始遍历,以上面的🌰来说,从之前的1~99999,缩减到了1~9和99991~99999之间的这18个数字,这样省了不少时间,并且不需要计算总数了。
那还有没有更简单的办法呢?有的,可以利用对象,也就是哈希表来存储每个值出现的次数,如此便可更方便的得出结论,具体就放到答案里面进行讲解了,请耐心的往下看。
自己的答案(强行暴力)
这就是👆说的第一种解法了,强行暴力,统计这面墙有多长,然后一块砖一块砖的统计,一直试到最后一块即可得出结论。
var leastBricks = function(wall) {
var sum = wall[0].reduce((total, item) => item + total, 0)
var min = wall.length
for (let i = 1; i < sum; i++) {
var tmp = 0
for (const item of wall) {
var res = 0
for (let j = 0; j < item.length; j++) {
res += item[j]
if (res === i) {
break
} else if (res < i) {
continue
} else {
tmp++
break
}
}
}
min = Math.min(tmp, min)
}
return min
};
很遗憾,在第79个用例时超时了,当时的用例是:
[
[1,99999],
[2,99998],
[3,99997],
...
[998, 99002],
[999, 99001],
[1000, 99000]
]
到10W止就GG了,非常合理。
自己的答案(正常暴力)
正常暴力就简单一些了,一整块砖的进行查找,找到从当前位置画线需要穿过的砖的个数,每次都统计即可得出最小值,同时也需要注意边界条件。
var leastBricks = function(wall) {
var min = wall.length
for (const item of wall) {
var res = 0
for (let i = 0; i < item.length; i++) {
var tmp = 0
if (i === item.length - 1) break
res += item[i]
for (const item2 of wall) {
var tmpRes = 0
for (let j = 0; j < item2.length; j++) {
tmpRes += item2[j]
if (tmpRes === res) {
break
} else if (tmpRes < res) {
continue
} else {
tmp++
break
}
}
}
min = Math.min(min, tmp)
}
}
return min
}
看着这4层for
循环就头大,费劲是费劲了点,代码量也很大,但胜在占用内存较少,因为没有额外新增变量:
其实从这里可以推断出一件事,当前的代码量很大,但内存占用一直都是100%,这说明了一件事,别的解法肯定新增了变量,而且不是一个小变量,会占用较多的内存,这就为下一种方法提供了方向,需要用一个变量来存储一些东西。
自己的答案(哈希表)
上文说到了需要用一个东西来存储数据,以方便后续调用,那么是不是可以这样操作:
-
首先依旧是遍历所有元素,拿到他们的缝隙。
比方说这样的一行砖:
[1, 2, 2, 4]
那他的缝隙就是1,3,5。这样看是不是明显了很多。
是的,只要统计这些缝隙就行了。
-
在统计的过程中,如果之前有这个缝隙,就在缝隙的基础上
+1
,如果没有就新建缝隙,初始值为1,也就是这条缝隙只出现了一次。 -
最后,拿到所有缝隙的值,找到最大的那个,然后用墙的整体高度,也就是有多少行。拿行数减去最大的缝隙值,就能得到最少需要穿过的砖块数。
如果没看懂的话可以看这里,举个🌰。
墙可以是这样的👇:
[
[1, 2, 2, 1],
[3, 1, 2],
[1, 3, 2],
[2, 4],
[3, 1, 2],
[1, 3, 1, 1]
]
那么我们所谓的缝隙数是这样的👇:
{
1: 3,
2: 1,
3: 3,
4: 4,
5: 2,
}
简单来说就是这样的,在长度为1的砖的缝隙在整面墙中有3个缝隙,也就是说如果从这里画线,需要穿过的砖就是6-3
,也就是3块砖,剩下的答案同理可得。
最后找到最大值4,用总高度一减,就可以得出需要穿过的最小砖数为2。
那如果用例是这样的呢?
[
[1],
[1],
[1]
]
由于题目限制,线不能在墙的左右边界,所以线必须在中砖的中间,所以这里的线只能穿过3块砖,如果按照上面的方法得出的缝隙数会是一个空数组,那明显是不合理的,所以需要在缝隙数的对象,也就是哈希表上加一个最大值,也就是需要穿过所有的砖,为0。
所以需要在缝隙表中额外添加一个数据,防止缝隙表为空的情况。
那么最后的代码就是这样的👇:
var leastBricks = function(wall) {
var obj = {
max: 0
}
for (const line of wall) {
var res = 0
for (let i = 0; i < line.length - 1; i++) {
res += line[i]
obj[res] = obj[res] ? obj[res] + 1 : 1
}
}
return wall.length - Math.max(...Object.values(obj))
}
这里采用的对象来存储哈希表,其实用Map
也是可以的,只是取哈希表的值就比较麻烦了,没法用Object.values()
,而是需要用Map
的values()
方法,但改方法返回的是一个可迭代值,只能在里面进行一次次的比较,笔者是觉得比较麻烦了,不像对象可以直接使用Math.max()
来进行所有值的比较。
附上运行效率:
更好的方法
无
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇