1920.基于排列构建数组

目标

给你一个 从 0 开始的排列 nums(下标也从 0 开始)。请你构建一个 同样长度 的数组 ans ,其中,对于每个 i(0 <= i < nums.length),都满足 ans[i] = nums[nums[i]] 。返回构建好的数组 ans 。

从 0 开始的排列 nums 是一个由 0 到 nums.length - 1(0 和 nums.length - 1 也包含在内)的不同整数组成的数组。

示例 1:

输入:nums = [0,2,1,5,3,4]
输出:[0,1,2,4,5,3]
解释:数组 ans 构建如下:
ans = [nums[nums[0]], nums[nums[1]], nums[nums[2]], nums[nums[3]], nums[nums[4]], nums[nums[5]]]
    = [nums[0], nums[2], nums[1], nums[5], nums[3], nums[4]]
    = [0,1,2,4,5,3]

示例 2:

输入:nums = [5,0,1,2,3,4]
输出:[4,5,0,1,2,3]
解释:数组 ans 构建如下:
ans = [nums[nums[0]], nums[nums[1]], nums[nums[2]], nums[nums[3]], nums[nums[4]], nums[nums[5]]]
    = [nums[5], nums[0], nums[1], nums[2], nums[3], nums[4]]
    = [4,5,0,1,2,3]

说明:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] < nums.length
  • nums 中的元素 互不相同

进阶:你能在不使用额外空间的情况下解决此问题吗(即 O(1) 内存)?

思路

数组 nums 是一个 0 ~ n - 1 的排列,构建一个新数组 res 满足 res[i] = nums[nums[i]]

一般的解法是创建一个新数组,依题意设置对应的值。

进阶解法需要满足空间复杂度为 O(1),即原地修改。可以将结果左移 10 位保存到原数组,最后将元素值右移 10 位还原结果。

代码


/**
 * @date 2025-05-06 8:43
 */
public class BuildArray1920 {

    public int[] buildArray(int[] nums) {
        int mask = 1023;
        for (int i = 0; i < nums.length; i++) {
            nums[i] = nums[i] | ((nums[nums[i]] & mask) << 10);
        }
        for (int i = 0; i < nums.length; i++) {
            nums[i] >>= 10;
        }
        return nums;
    }

}

性能

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

代码

性能

1295.统计位数为偶数的数字

目标

给你一个整数数组 nums,请你返回其中包含 偶数 个数位的数字的个数。

示例 1:

输入:nums = [12,345,2,6,7896]
输出:2
解释:
12 是 2 位数字(位数为偶数) 
345 是 3 位数字(位数为奇数)  
2 是 1 位数字(位数为奇数) 
6 是 1 位数字 位数为奇数) 
7896 是 4 位数字(位数为偶数)  
因此只有 12 和 7896 是位数为偶数的数字

示例 2:

输入:nums = [555,901,482,1771]
输出:1 
解释: 
只有 1771 是位数为偶数的数字。

说明:

  • 1 <= nums.length <= 500
  • 1 <= nums[i] <= 10^5

思路

有一个正整数数组,返回数组中数位长度为偶数的元素个数。

代码


/**
 * @date 2025-04-30 8:42
 */
public class FindNumbers1295 {

    public int findNumbers(int[] nums) {
        int res = nums.length;
        for (int num : nums) {
            int cnt = 0;
            while (num > 0) {
                cnt++;
                num /= 10;
            }
            res -= cnt % 2;
        }
        return res;
    }

}

性能

2962.统计最大元素出现至少K次的子数组

目标

给你一个整数数组 nums 和一个 正整数 k 。

请你统计有多少满足 「nums 中的 最大 元素」至少出现 k 次的子数组,并返回满足这一条件的子数组的数目。

子数组是数组中的一个连续元素序列。

示例 1:

输入:nums = [1,3,2,3,3], k = 2
输出:6
解释:包含元素 3 至少 2 次的子数组为:[1,3,2,3]、[1,3,2,3,3]、[3,2,3]、[3,2,3,3]、[2,3,3] 和 [3,3] 。

示例 2:

输入:nums = [1,4,2,1], k = 3
输出:0
解释:没有子数组包含元素 4 至少 3 次。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6
  • 1 <= k <= 10^5

思路

找出满足条件的子数组个数,子数组至少要包含 k 个最大元素。

代码


/**
 * @date 2025-04-29 8:50
 */
public class CountSubarrays2962 {

    public long countSubarrays(int[] nums, int k) {
        long res = 0L;
        int max = Arrays.stream(nums).max().orElse(1);
        int left = 0;
        for (int right = 0; right < nums.length; right++) {
            k -= nums[right] == max ? 1 : 0;
            while (k <= 0){
                k += nums[left++] == max ? 1 : 0;
            }
            res += left;
        }
        return res;
    }
}

性能

2302.统计得分小于K的子数组数目

目标

一个数组的 分数 定义为数组之和 乘以 数组的长度。

  • 比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75 。

给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k 的 非空整数子数组数目。

子数组 是数组中的一个连续元素序列。

示例 1:

输入:nums = [2,1,4,3,5], k = 10
输出:6
解释:
有 6 个子数组的分数小于 10 :
- [2] 分数为 2 * 1 = 2 。
- [1] 分数为 1 * 1 = 1 。
- [4] 分数为 4 * 1 = 4 。
- [3] 分数为 3 * 1 = 3 。 
- [5] 分数为 5 * 1 = 5 。
- [2,1] 分数为 (2 + 1) * 2 = 6 。
注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。

示例 2:

输入:nums = [1,1,1], k = 5
输出:5
解释:
除了 [1,1,1] 以外每个子数组分数都小于 5 。
[1,1,1] 分数为 (1 + 1 + 1) * 3 = 9 ,大于 5 。
所以总共有 5 个子数组得分小于 5 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5
  • 1 <= k <= 10^15

思路

统计 正整数 数组中 子数组元素和 * 子数组长度 < k 的子数组个数。

统计满足条件的子数组个数,首先考虑滑动窗口。枚举右端点 right,只要 [left, right] 的分数小于 k,那么任意以 i ∈ [left, right] 为左端点的子数组的分数都小于 k,共有 right - left + 1 个。如果窗口内的分数大于等于 k,需要移出左端点,直到窗口内的分数小于 k。

代码


/**
 * @date 2025-04-28 8:41
 */
public class CountSubarrays2302 {

    public long countSubarrays(int[] nums, long k) {
        long res = 0L;
        int n = nums.length;
        int left = 0;
        long sum = 0L;
        int len = 0;
        for (int right = 0; right < n; right++) {
            sum += nums[right];
            len++;
            while (left < n && sum * len >= k) {
                sum -= nums[left++];
                len--;
            }
            res += right - left + 1;
        }
        return res;
    }

}

性能

3392.统计符合条件长度为3的子数组数目

目标

给你一个整数数组 nums ,请你返回长度为 3 的 子数组,满足第一个数和第三个数的和恰好为第二个数的一半。

子数组 指的是一个数组中连续 非空 的元素序列。

示例 1:

输入:nums = [1,2,1,4,1]
输出:1
解释:
只有子数组 [1,4,1] 包含 3 个元素且第一个和第三个数字之和是中间数字的一半。number.

示例 2:

输入:nums = [1,1,1]
输出:0
解释:
[1,1,1] 是唯一长度为 3 的子数组,但第一个数和第三个数的和不是第二个数的一半。

说明:

  • 3 <= nums.length <= 100
  • -100 <= nums[i] <= 100

思路

求数组 nums 中长度为 3 且满足条件的子数组个数,子数组需满足两边元素和 是中间元素的一半。

依题意模拟即可。

代码


/**
 * @date 2025-04-27 0:14
 */
public class CountSubarrays3392 {

    public int countSubarrays(int[] nums) {
        int n = nums.length;
        int left = 0;
        int res = 0;
        for (int right = 2; right < n; right++) {
            if ((nums[left++] + nums[right]) * 2 == nums[right - 1]) {
                res++;
            }
        }
        return res;
    }

}

性能