[20210502]LeetCode每日一题 - 554. 砖墙

你的面前有一堵矩形的、由 n 行砖块组成的砖墙。这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和应该相等。

你现在要画一条自顶向下的、穿过最少砖块的垂线。如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。

给你一个二维数组 wall,该数组包含这堵墙的相关信息。其中,wall[i]
是一个代表从左至右每块砖的宽度的数组。你需要找出怎样画才能使这条线 穿过的砖块数量最少 ,并且返回穿过的砖块数量 。

原题地址:地址

实例1:
示例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的图片:
示例1图片
我们发现黄色线在03行穿过了砖块,但也发现1245就是缝隙所在的行数,这两者数量之和就是墙的高度
被穿过的砖块所在的列并不好计算,但缝隙所在indexwall数组中简单求和即可得出的
因此对于数组,处理后结果如下:

原始:
[
	[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% 的用户

综上所述

  1. 暴力法是初学者第一个想到的方法,有些时候暴力法可以解决一部分问题,幸运的话可以AC,但暴力法不是解决问题最好的途径,但却是初学者走上算法之路的必经途径;
  2. 暴力法无法解决的问题,则一般情况下需要反向思考,如何动用最少的资源与复杂度去解决问题,如何根据题目中给予的数据,找出作者的暗示,也是很重要的一点;
  3. 当有一定的算法经验后,暴力法则不是优先使用的方法了,所以还是需要多多练习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值