LeetCode 1240. Tiling a Rectangle with the Fewest Squares

先说说一个 cheat 程序

周赛的时候第一个思路就是 dp… 直到看到了 Example 3,发现不能用 dp。。但看了看 m 和 n 的边界条件,感觉 Example 3 应该是唯一的一个 special case, 就直接写了 dp, 最后竟然真的就 Accepted 了。。。
思路很简单,就直接贴代码了

class Solution {
    public int tilingRectangle(int n, int m) {
        if(n<m){
            return tilingRectangle(m,n);
        }
        if(n==m){
            return 1;
        }
        if(n==13 && m==11){
            return 6;
        }
        
        int[][] dp = new int[14][14];
        for(int i=1;i<=n;i++){
            dp[i][i]=1;
            dp[1][i]=i;
            dp[i][1]=i;
        }
        
        for(int i=2;i<=n;i++){
            for(int j=i+1;j<=n;j++){
                dp[i][j]=i*j;
                
                for(int t=1;t<i;t++){
                    dp[i][j] = Math.min(dp[i][j],dp[t][j]+dp[i-t][j]);
                }
                
                for(int t=1;t<j;t++){
                    dp[i][j] = Math.min(dp[i][j],dp[i][t]+dp[i][j-t]);
                }
                dp[j][i]=dp[i][j];
            }
        }
        return dp[n][m];
    }
}

注意这一段对 special case 的处理代码。。。

        if(n==13 && m==11){
            return 6;
        }

这样做肯定是不对的,周赛完看了看别人的解答,也基本是这样做的。后来看到了一个正确答案,是用 dfs 做的。在这里主要分析 dfs 的代码。

正确方法 dfs

参考的链接在这里

基本思路是从底往上填充整个方块,每次优先填充最底下的没填充的方块,并选择不同的可能的size的正方形把他填充了。我们在dfs的时候维护一个height数组(天际线)。这个天际线是状态的标识。我们最终要求的结果就是天际线是n个m的那个状态的最小方块数。当然纯暴力的话时间复杂度会很高,但是可以通过以下三点来剪枝,或者优化。
1、 当前这个天际线的cnt(也就是方块数)已经超过了当前的全局最优解的值,那么直接return
2、 当前的这个天际线已经遍历过了,而且之前的cnt比当前的cnt要小,那么直接return
3、 当我们找到左下角的空方块后,选取下一个填充方块的的时候优先从较大的方块开始选取,这样可以使得程序快速收敛。(这一点不是剪枝,但是是很重要的优化)

以上是原帖给出的解释,思路说的很详细了。我在这里具体分析一下代码,具体见代码注释。

class Solution {
	// 这个set是一个记录,下面的优化[2]要用到。
    Map<Long, Integer> set = new HashMap<>();
    int res = Integer.MAX_VALUE;
    public int tilingRectangle(int n, int m) {
        if (n == m) return 1;
        if (n > m) {
            return tilingRectangle(m, n);
        }
        dfs(n, m, new int[n + 1], 0);
        return res;
    }
    
    private void dfs(int n, int m, int[] h, int cnt) {
    	// 优化[1]
        if (cnt >= res) return;
        boolean isFull = true;
        int pos = -1, minH = Integer.MAX_VALUE;
        
        // 看看数组h是不是满了,没满的话顺便记录一下最低的位置minH和对应的索引pos
        for (int i = 1; i <= n; i++) {
            if (h[i] < m) isFull = false;
            if (h[i] < minH) {
                pos = i;
                minH = h[i];
            }
        }

		// 如果铺满了
        if (isFull) {
            res = Math.min(cnt, res);
            return;
        }
        
        // 没铺满的话,继续铺呗
        /* 这里有一个小trick,把整个数组h的状态映射到一个long的整数类型上。
        这个映射是一对一的映射!!!
        具体方法是把数组看成(m+1)进制的数,就可以转化成一个long类型的整数了。
        因为base的选取是(m+1),而数组中的每个数都不超过m,故肯定是一个一对一的映射!
        即不可能存在两个不一样的数组映射到同一个long类型的整数上。
        */
        long key = 0, base = m + 1;
        for (int i = 1; i <= n; i++) {
            key += h[i] * base;
            base *= m + 1;
        }
        if (set.containsKey(key) && set.get(key) <= cnt) return;
        set.put(key, cnt);
        
        // 找到和pos一样低的末尾位置
        int end = pos;
        while (end + 1 <= n && h[end + 1] == h[pos] && (end + 1 - pos + 1 + minH) <= m) end++;

		// 找到了pos~end这个最低位置的区间后,从end开始,遍历到pos位置,每次铺一块砖。
		// 再继续 dfs
		// 这里从end开始就是优化[3]了
        for (int j = end; j >= pos; j--) {
            int curH = j - pos + 1;
            
            int[] next  = Arrays.copyOf(h, n + 1);
            for (int k = pos; k <= j; k++) {
                next[k] += curH;
            }
            dfs(n, m, next, cnt + 1);
        }
    }
}

总结一下,这里 dfs 思路就是,把最低的区间找出来,然后从区间的最大长度遍历到1,每次加相应长度的砖,并进行下一次 dfs。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值