790.多米诺和托米诺平铺

目标

有两种形状的瓷砖:一种是 2 x 1 的多米诺形,另一种是形如 "L" 的托米诺形。两种形状都可以旋转。

给定整数 n ,返回可以平铺 2 x n 的面板的方法的数量。返回对 10^9 + 7 取模 的值。

平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同,当且仅当面板上有四个方向上的相邻单元中的两个,使得恰好有一个平铺有一个瓷砖占据两个正方形。

示例 1:

输入: n = 3
输出: 5
解释: 五种不同的方法如上所示。

示例 2:

输入: n = 1
输出: 1

说明:

  • 1 <= n <= 1000

思路

1 x 2L 型两种形状的瓷砖,求铺满 2 x n 的面版有多少种方法。

题目关于两个平铺是否不同的描述很不清晰,关键在于对 恰好有一个瓷砖 占据两个正方形的理解。

如何判断两种平铺方法是不同的:能找到两个上下相邻的或左右相邻的正方形区域,在其中一种平铺方法中属于同一块瓷砖,在另一种平铺方法中属于不同的瓷砖,则认为这两种平铺方法是不同的。

实际去解决这个问题时可以尝试找规律,通过观察发现:

  • 2 x 1 的面版,有 1 种铺法。
  • 2 x 2 的面版,有 2 种铺法。
  • 2 x 3 的面版,有 5 种铺法。
  • 2 x 4 的面版,有 11 种铺法。
  • 2 x 5 的面版,有 24 种铺法。

dp[n] = 2 * dp[n - 1] + dp[n - 3]

如果找不到规律可以参考官网题解的二维动态规划解法。

代码


/**
 * @date 2025-05-05 21:37
 */
public class NumTilings790 {

    public int numTilings_v1(int n) {
        if (n == 1) {
            return 1;
        }
        int mod = 1000000007;
        int[] dp = new int[n + 1];
        dp[0] = dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = ((2 * dp[i - 1]) % mod + dp[i - 3]) % mod;
        }
        return dp[n];
    }

}

性能

1128.等价多米诺骨牌对的数量

目标

给你一组多米诺骨牌 dominoes 。

形式上,dominoes[i] = [a, b] 与 dominoes[j] = [c, d] 等价 当且仅当 (a == c 且 b == d) 或者 (a == d 且 b == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。

在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。

示例 1:

输入:dominoes = [[1,2],[2,1],[3,4],[5,6]]
输出:1

示例 2:

输入:dominoes = [[1,2],[1,2],[1,1],[1,2],[2,2]]
输出:3

说明:

  • 1 <= dominoes.length <= 4 * 10^4
  • dominoes[i].length == 2
  • 1 <= dominoes[i][j] <= 9

思路

计算二维数组中相同元素组成的下标对 (i, j) 个数,这里相同元素指 (dominoes[i][0] == dominoes[j][0] && dominoes[i][1] == dominoes[j][1]) || (dominoes[i][0] == dominoes[j][1] && dominoes[i][1] == dominoes[j][0])

使用哈希表记录相同元素的出现次数,key 可以使用 dominoes[i][0] << 4 | dominoes[i][1]dominoes[i][1] << 4 | dominoes[i][0] 表示。

代码


/**
 * @date 2025-05-04 17:38
 */
public class NumEquivDominoPairs1128 {

    public int numEquivDominoPairs(int[][] dominoes) {
        Map<Integer, Integer> map = new HashMap<>();
        int res = 0;
        for (int[] dominoe : dominoes) {
            int key = dominoe[0] << 4 | dominoe[1];
            res += map.getOrDefault(key, 0);
            map.merge(key, 1, Integer::sum);
            if (dominoe[0] != dominoe[1]) {
                map.merge(dominoe[1] << 4 | dominoe[0], 1, Integer::sum);
            }
        }
        return res;
    }

}

性能

1007.行相等的最少多米诺旋转

目标

在一排多米诺骨牌中,tops[i] 和 bottoms[i] 分别代表第 i 个多米诺骨牌的上半部分和下半部分。(一个多米诺是两个从 1 到 6 的数字同列平铺形成的 —— 该平铺的每一半上都有一个数字。)

我们可以旋转第 i 张多米诺,使得 tops[i] 和 bottoms[i] 的值交换。

返回能使 tops 中所有值或者 bottoms 中所有值都相同的最小旋转次数。

如果无法做到,返回 -1.

示例 1:

输入:tops = [2,1,2,4,2,2], bottoms = [5,2,6,2,3,2]
输出:2
解释: 
图一表示:在我们旋转之前, tops 和 bottoms 给出的多米诺牌。 
如果我们旋转第二个和第四个多米诺骨牌,我们可以使上面一行中的每个值都等于 2,如图二所示。

示例 2:

输入:tops = [3,5,1,2,3], bottoms = [3,6,3,3,4]
输出:-1
解释: 在这种情况下,不可能旋转多米诺牌使一行的值相等。

说明:

  • 2 <= tops.length <= 2 * 10^4
  • bottoms.length == tops.length
  • 1 <= tops[i], bottoms[i] <= 6

思路

有两个长度相同的数组,数组元素 1 ~ 6,每次操作可以交换这两个数组相同下标的元素值,求使其中任意数组的元素值全部相同所需的最小操作数。

由于最终需要任一数组的元素值完全相同,不是 tops[0] 就是 bottoms[0]。分别计算将这两个数组变成这两个值的最小操作数即可。

代码


/**
 * @date 2025-05-03 20:46
 */
public class MinDominoRotations1007 {

    public int minDominoRotations_v2(int[] tops, int[] bottoms) {
        int res = Math.min(ops(tops, bottoms, tops[0]), ops(tops, bottoms, bottoms[0]));
        return res == Integer.MAX_VALUE ? -1 : res;
    }

    public int ops(int[] tops, int[] bottoms, int target) {
        int topCnt = 0;
        int bottomCnt = 0;
        for (int i = 0; i < tops.length; i++) {
            if (target != tops[i] && target != bottoms[i]) {
                return Integer.MAX_VALUE;
            }
            if (target != tops[i]) {
                topCnt++;
            } else if (target != bottoms[i]) {
                bottomCnt++;
            }
        }
        return Math.min(topCnt, bottomCnt);
    }

}

性能

838.推多米诺

目标

n 张多米诺骨牌排成一行,将每张多米诺骨牌垂直竖立。在开始时,同时把一些多米诺骨牌向左或向右推。

每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。

如果一张垂直竖立的多米诺骨牌的两侧同时有多米诺骨牌倒下时,由于受力平衡, 该骨牌仍然保持不变。

就这个问题而言,我们会认为一张正在倒下的多米诺骨牌不会对其它正在倒下或已经倒下的多米诺骨牌施加额外的力。

给你一个字符串 dominoes 表示这一行多米诺骨牌的初始状态,其中:

  • dominoes[i] = 'L',表示第 i 张多米诺骨牌被推向左侧,
  • dominoes[i] = 'R',表示第 i 张多米诺骨牌被推向右侧,
  • dominoes[i] = '.',表示没有推动第 i 张多米诺骨牌。

返回表示最终状态的字符串。

示例 1:

输入:dominoes = "RR.L"
输出:"RR.L"
解释:第一张多米诺骨牌没有给第二张施加额外的力。

示例 2:

输入:dominoes = ".L.R...LR..L.."
输出:"LL.RR.LLRRLL.."

说明:

  • n == dominoes.length
  • 1 <= n <= 10^5
  • dominoes[i] 为 'L'、'R' 或 '.'

思路

已知多米诺骨牌的初始状态,求最终状态。

分类讨论,使用变量 prevIndex 记录前一个非 '.' 的位置,根据 prev = chars[prevIndex]cur = chars[curIndex] 来填充 (prevIndex, curIndex)

  • prev = 'R',cur = 'R',填充为 'R'
  • prev = 'R',cur = 'L',前半部分填充 'R',后半部分填充 'L'
  • prev = 'L',cur = 'L',填充为 'L'
  • prev = 'L',cur = 'R',保持不变

代码


/**
 * @date 2025-05-02 22:06
 */
public class PushDominoes838 {

    public String pushDominoes(String dominoes) {
        char[] chars = dominoes.toCharArray();
        int n = chars.length;
        int prevIndex = -1;
        char prev = 'L';
        char[] res = dominoes.toCharArray();
        for (int curIndex = 0; curIndex <= n; curIndex++) {
            char cur;
            if (curIndex == n) {
                cur = 'R';
            } else {
                cur = chars[curIndex];
            }
            if (prev == 'R' && cur == 'L') {
                int cnt = (curIndex - 1 - prevIndex) / 2;
                Arrays.fill(res, prevIndex + 1, prevIndex + cnt + 1, 'R');
                Arrays.fill(res, curIndex - cnt, curIndex, 'L');
            } else if (prev == cur) {
                Arrays.fill(res, prevIndex + 1, curIndex, cur);
            }
            if (cur != '.') {
                prevIndex = curIndex;
                prev = cur;
            }
        }
        return new String(res);
    }

}

性能

2071.你可以安排的最多任务数目

目标

给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成,需要的力量值保存在下标从 0 开始的整数数组 tasks 中,第 i 个任务需要 tasks[i] 的力量才能完成。每个工人的力量值保存在下标从 0 开始的整数数组 workers 中,第 j 个工人的力量值为 workers[j] 。每个工人只能完成 一个 任务,且力量值需要 大于等于 该任务的力量要求值(即 workers[j] >= tasks[i] )。

除此以外,你还有 pills 个神奇药丸,可以给 一个工人的力量值 增加 strength 。你可以决定给哪些工人使用药丸,但每个工人 最多 只能使用 一片 药丸。

给你下标从 0 开始的整数数组tasks 和 workers 以及两个整数 pills 和 strength ,请你返回 最多 有多少个任务可以被完成。

示例 1:

输入:tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1
输出:3
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 2(0 + 1 >= 1)
- 1 号工人完成任务 1(3 >= 2)
- 2 号工人完成任务 0(3 >= 3)

示例 2:

输入:tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5
输出:1
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 0(0 + 5 >= 5)

示例 3:

输入:tasks = [10,15,30], workers = [0,10,10,10,10], pills = 3, strength = 10
输出:2
解释:
我们可以按照如下方案安排药丸:
- 给 0 号和 1 号工人药丸。
- 0 号工人完成任务 0(0 + 10 >= 10)
- 1 号工人完成任务 1(10 + 10 >= 15)

示例 4:

输入:tasks = [5,9,8,5,9], workers = [1,6,4,2,6], pills = 1, strength = 5
输出:3
解释:
我们可以按照如下方案安排药丸:
- 给 2 号工人药丸。
- 1 号工人完成任务 0(6 >= 5)
- 2 号工人完成任务 2(4 + 5 >= 8)
- 4 号工人完成任务 3(6 >= 5)

说明:

  • n == tasks.length
  • m == workers.length
  • 1 <= n, m <= 5 * 10^4
  • 0 <= pills <= m
  • 0 <= tasks[i], workers[j], strength <= 10^9

思路

//todo

代码

性能