你的面前有一堵矩形的、由
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
暴力法
首先这道题我的第一想法,是对这面砖墙的数组做优化处理
因为传入的wall
的数据,第二维中个数参差不齐,对于计算时会有影响
如果根据每个数字代表占多少个位置处理,那么这面墙就会是N x M
的规范数组了
对于示例1,数组会处理成:
原始:
[
[1,2,2,1],
[3,1,2],
[1,3,2],
[2,4],
[3,1,2],
[1,3,1,1]
]
处理后:
[
[1,2,2,2,2,1],
[3,3,3,1,2,2],
[1,3,3,3,2,2],
[2,2,4,4,4,4],
[3,3,3,1,2,2],
[1,3,3,3,1,1]
]
处理后的数据如何判断是两块砖的缝隙?那么就通过循环,判断前后是否是同一个数字即可。
但上面处理后的数据,则发现,如果相邻的两块砖的长度一致,则无法判断是否是否有缝隙。
于是,wall
数组处理方式更改一下,在循环wall
的时候,就判断前后砖头是否有空隙,以0
,1
来分割,即:
原始:
[
[1,2,2,1],
[3,1,2],
[1,3,2],
[2,4],
[3,1,2],
[1,3,1,1]
]
处理后:
[
[ 0, 1, 1, 0, 0, 1 ],
[ 0, 0, 0, 1, 0, 0 ],
[ 0, 1, 1, 1, 0, 0 ],
[ 0, 0, 1, 1, 1, 1 ],
[ 0, 0, 0, 1, 0, 0 ],
[ 0, 1, 1, 1, 0, 1 ]
]
这样就可以清楚知晓是否有缝隙了,此时再遍历每一个纵线(根据题目,除第0列的左侧,及最后一列的右侧属于非遍历列外,其余皆需要判断),判断前后数字是否一致,不一致+1
,用一个一维数组存储,然后用Math.min()
方法去判断所需要的划过最少的砖块数量。
代码:
function leastBricks(wall: number[][]): number {
const columns = wall[0].reduce((prev, curr) => prev + curr, 0)
if (columns < 2) return wall.length
const formatWall = new Array(wall.length).fill(0).map(item => new Array(columns).fill(0))
wall.forEach((row, rowIndex) => {
let index = 0
let value = 0
row.forEach((num, columnIndex) => {
for (let i = 0; i < num; i++) {
formatWall[rowIndex][index] = value
index++
}
value = value === 0 ? 1 : 0
})
})
const rowAmount = formatWall.length
const columnAmount = formatWall[0].length
const result = new Array(columnAmount - 1).fill(0)
for (let i = 0; i < columnAmount - 1; i++) {
for (let j = 0; j < rowAmount; j++) {
if (formatWall[j][i] === formatWall[j][i + 1]) {
result[i]++
}
}
}
return Math.min(...result)
};
当我通过本地的几个示例后,我提交了,但发现并没有AC
提示我:FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
而对应的传入数据为:[[100000000],[100000000],[100000000]]
再看下题目中提示:
n == wall.length
1 <= n <= 10^4
1 <= wall[i].length <= 10^4
1 <= sum(wall[i].length) <= 2 * 10^4
对于每一行 i ,sum(wall[i]) 应当是相同的
1 <= wall[i][j] <= 2^31 - 1
超出内存限制,因此暴力法不可取
哈希表
既然暴力法无法处理,那么作为程序员,第一个反应应该就是反向思考
还是看示例1的图片:
我们发现黄色线在0
、3
行穿过了砖块,但也发现1
、2
、4
、5
就是缝隙所在的行数,这两者数量之和就是墙的高度
而被穿过的砖块所在的列并不好计算,但缝隙所在index
是wall
数组中简单求和即可得出的
因此对于数组,处理后结果如下:
原始:
[
[1,2,2,1],
[3,1,2],
[1,3,2],
[2,4],
[3,1,2],
[1,3,1,1]
]
处理后:
[
[ 1, 3, 5, 6],
[ 3, 4, 6 ],
[ 1, 4, 6 ],
[ 2, 6 ],
[ 3, 4, 6],
[ 1, 4, 5, 6 ]
]
根据题目的说明,最后一列的缝隙index
忽略
此时,我们计算每个缝隙出现的次数,然后用墙的高度wall.length
相减其中最多的次数,即可得到可穿过最短砖块的数量
而存储每个缝隙出现的次数时,一个是使用数组,以下标记录缝隙的index
,但因为缝隙数字可能会非常大,所以使用哈希表
在JS
中,新的ES6
(其实也不新了) 规范中,有了一个Map
数据类型,就是哈希表
对于其功能阐述,在这里不做赘述,请参见阮一峰老师的《ECMAScript 6 入门教程》中对Map
的说明:地址
根据这个思路,代码改为:
function leastBricks(wall: number[][]): number {
const count = new Map()
for (const row of wall) {
let sum = 0
for (let i = 0; i < row.length - 1; i++) {
sum += row[i]
count.set(sum, (count.has(sum) ? count.get(sum) : 0) + 1)
}
}
let maxCount = 0
for (const [_, amount] of count.entries()) {
maxCount = Math.max(amount, maxCount)
}
return wall.length - maxCount
};
提交后AC,耗时92ms,在所有 TypeScript
提交中击败了 96.61%
的用户
综上所述
- 暴力法是初学者第一个想到的方法,有些时候暴力法可以解决一部分问题,幸运的话可以AC,但暴力法不是解决问题最好的途径,但却是初学者走上算法之路的必经途径;
- 暴力法无法解决的问题,则一般情况下需要反向思考,如何动用最少的资源与复杂度去解决问题,如何根据题目中给予的数据,找出作者的暗示,也是很重要的一点;
- 当有一定的算法经验后,暴力法则不是优先使用的方法了,所以还是需要多多练习。