前端刷题路-Day44:砖墙(题号554)

1,042 阅读1分钟

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

砖墙(题号554)

题目

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

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

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

示例 1:

img

输入: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
  • 对于每一行 isum(wall[i]) 应当是相同的
  • 1 <= wall[i][j] <= 231 - 1

链接

leetcode-cn.com/problems/br…

解释

这题啊,这题主要是思路。

一开始想的有点狗,先获取到最后的和,因为每一行的固定长度是一样的,所以最后要求的数必然在这个区间内,那就硬找呗。

同时因为线不能在墙的两边,所以要去掉两个边界条件,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循环就头大,费劲是费劲了点,代码量也很大,但胜在占用内存较少,因为没有额外新增变量:

img

其实从这里可以推断出一件事,当前的代码量很大,但内存占用一直都是100%,这说明了一件事,别的解法肯定新增了变量,而且不是一个小变量,会占用较多的内存,这就为下一种方法提供了方向,需要用一个变量来存储一些东西。

自己的答案(哈希表)

上文说到了需要用一个东西来存储数据,以方便后续调用,那么是不是可以这样操作:

  1. 首先依旧是遍历所有元素,拿到他们的缝隙。

    比方说这样的一行砖:

    [1, 2, 2, 4]
    

    那他的缝隙就是1,3,5。这样看是不是明显了很多。

    是的,只要统计这些缝隙就行了。

  2. 在统计的过程中,如果之前有这个缝隙,就在缝隙的基础上+1,如果没有就新建缝隙,初始值为1,也就是这条缝隙只出现了一次。

  3. 最后,拿到所有缝隙的值,找到最大的那个,然后用墙的整体高度,也就是有多少行。拿行数减去最大的缝隙值,就能得到最少需要穿过的砖块数。

如果没看懂的话可以看这里,举个🌰。

墙可以是这样的👇:

[
  [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(),而是需要用Mapvalues()方法,但改方法返回的是一个可迭代值,只能在里面进行一次次的比较,笔者是觉得比较麻烦了,不像对象可以直接使用Math.max()来进行所有值的比较。

附上运行效率:

img

更好的方法



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ