3184.构成整天的下标对数目I

目标

给你一个整数数组 hours,表示以 小时 为单位的时间,返回一个整数,表示满足 i < j 且 hours[i] + hours[j] 构成 整天 的下标对 i, j 的数目。

整天 定义为时间持续时间是 24 小时的 整数倍 。

例如,1 天是 24 小时,2 天是 48 小时,3 天是 72 小时,以此类推。

示例 1:

输入: hours = [12,12,30,24,24]
输出: 2
解释:
构成整天的下标对分别是 (0, 1) 和 (3, 4)。

示例 2:

输入: hours = [72,48,24,3]
输出: 3
解释:
构成整天的下标对分别是 (0, 1)、(0, 2) 和 (1, 2)。

说明:

  • 1 <= hours.length <= 100
  • 1 <= hours[i] <= 10^9

思路

有一个整数数组 hours,返回 hours[i] + hours[j] % 24 == 0i < j 的下标对的个数。

n 个元素中枚举两个元素可能组合的时间复杂度为 O(C(n,2)),即 O(n^2),数据规模不大,直接依题意枚举判断并计数即可。

代码


/**
 * @date 2024-10-22 8:46
 */
public class CountCompleteDayPairs3184 {

    public int countCompleteDayPairs(int[] hours) {
        int n = hours.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if ((hours[i] + hours[j]) % 24 == 0) {
                    res++;
                }
            }
        }
        return res;
    }

}

性能

910.最小差值II

目标

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

对于每个下标 i(0 <= i < nums.length),将 nums[i] 变成 nums[i] + k 或 nums[i] - k 。

nums 的 分数 是 nums 中最大元素和最小元素的差值。

在更改每个下标对应的值之后,返回 nums 的最小 分数 。

示例 1:

输入:nums = [1], k = 0
输出:0
解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。

示例 2:

输入:nums = [0,10], k = 2
输出:6
解释:将数组变为 [2, 8] 。分数 = max(nums) - min(nums) = 8 - 2 = 6 。

示例 3:

输入:nums = [1,3,6], k = 3
输出:3
解释:将数组变为 [4, 6, 3] 。分数 = max(nums) - min(nums) = 6 - 3 = 3 。

说明:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 10^4
  • 0 <= k <= 10^4

思路

这道题与 908.最小差值I 的区别是对于每个元素操作是 必选的,增加或减少的值 固定k 而不是区间 [-k, k]

每个元素都可以取 nums[i] + knums[i] - k,如果枚举所有可能的数组,然后再取最大最小值差的最小值显然是不可能的。不考虑重复元素的情况下,可能的数组有 2^n 种。

可以先排序,增大小的值,减少大的值,枚举二者的边界,比如前 i 个元素 [0, i - 1]k,剩余元素减 k,这时上界为 Math.max(nums[i - 1] + k, nums[n - 1] - k) ,下界为 Math.min(nums[0] + k, nums[i] - k),取上下界的最小值即可。

代码


/**
 * @date 2024-10-21 9:57
 */
public class SmallestRangeII910 {

    public int smallestRangeII(int[] nums, int k) {
        Arrays.sort(nums);
        int n = nums.length;
        int res = nums[n - 1] - nums[0];
        for (int i = 1; i < n; i++) {
            int max = Math.max(nums[i - 1] + k, nums[n - 1] - k);
            int min = Math.min(nums[0] + k, nums[i] - k);
            res = Math.min(res, max - min);
        }
        return res;
    }

}

性能

908.最小差值I

目标

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

在一个操作中,您可以选择 0 <= i < nums.length 的任何索引 i 。将 nums[i] 改为 nums[i] + x ,其中 x 是一个范围为 [-k, k] 的任意整数。对于每个索引 i ,最多 只能 应用 一次 此操作。

nums 的 分数 是 nums 中最大和最小元素的差值。

在对 nums 中的每个索引最多应用一次上述操作后,返回 nums 的最低 分数 。

示例 1:

输入:nums = [1], k = 0
输出:0
解释:分数是 max(nums) - min(nums) = 1 - 1 = 0。

示例 2:

输入:nums = [0,10], k = 2
输出:6
解释:将 nums 改为 [2,8]。分数是 max(nums) - min(nums) = 8 - 2 = 6。

示例 3:

输入:nums = [1,3,6], k = 3
输出:0
解释:将 nums 改为 [4,4,4]。分数是 max(nums) - min(nums) = 4 - 4 = 0。

说明:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 10^4
  • 0 <= k <= 10^4

思路

有一个整数数组,可以对其中的每一个元素执行操作 加上 [-k, k] 范围内的任意整数。针对同一元素只能执行一次操作,求最大值与最小值差值的最小值。

先找出最大值 max 与最小值 min,返回 min + k >= max - k ? 0 : max - min - 2*k

代码


/**
 * @date 2024-10-20 16:36
 */
public class SmallestRangeI908 {

    public int smallestRangeI_v1(int[] nums, int k) {
        int min = Integer.MAX_VALUE;
        int max = 0;
        for (int num : nums) {
            if (num < min) {
                min = num;
            }
            if (num > max) {
                max = num;
            }
        }
        return Math.max(max - min - 2 * k, 0);
    }

}

性能

3192.使二进制数组全部等于1的最少操作次数II

目标

给你一个二进制数组 nums 。

你可以对数组执行以下操作 任意 次(也可以 0 次):

  • 选择数组中 任意 一个下标 i ,并将从下标 i 开始一直到数组末尾 所有 元素 反转 。

反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。

请你返回将 nums 中所有元素变为 1 的 最少 操作次数。

示例 1:

输入:nums = [0,1,1,0,1]
输出:4
解释:
我们可以执行以下操作:
选择下标 i = 1 执行操作,得到 nums = [0,0,0,1,0] 。
选择下标 i = 0 执行操作,得到 nums = [1,1,1,0,1] 。
选择下标 i = 4 执行操作,得到 nums = [1,1,1,0,0] 。
选择下标 i = 3 执行操作,得到 nums = [1,1,1,1,1] 。

示例 2:

输入:nums = [1,0,0,0]
输出:1
解释:
我们可以执行以下操作:
选择下标 i = 1 执行操作,得到 nums = [1,1,1,1] 。

说明:

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

思路

有一个二进制数组 nums,每一次操作可以将当前元素以及之后的元素反转,问将所有元素变为 1 的最少操作次数。

这与昨天的题目 3191.使二进制数组全部等于1的最少操作次数I 类似,那个是将当前元素及后面两个元素反转。

还沿用昨天的思路,遇到 0 就进行操作,每一次操作需要对大量元素进行取模运算,因此考虑使用差分数组。这里差分数组初始均为 0,每次操作是针对当前元素及以后的所有元素,无需考虑后续的减法操作,因此只需要一个变量计数即可。

网友最快的算法并非使用变量计数然后判断奇偶性,考虑到最终目标是将所有元素都变为 1,每一次操作会将自身与其后的元素反转,对于初始数组任意位置 i,如果它为 0,该位置一定需要执行奇数次操作,因为执行偶数次反转最后还是 0。同理,如果为 1,需要执行 偶数 次操作。实际上每个位置是否需要执行操作是 确定的

只要有一个初始条件,向后遍历的时候直接根据前一个元素与当前元素的关系判断是否需要累加操作即可,具体来说,如果它们值相同则不需要操作,如果不同则需要操作,直接累加这两个元素的异或值即可。

代码


/**
 * @date 2024-10-19 17:30
 */
public class MinOperations3192 {

    public int minOperations_v2(int[] nums) {
        int n = nums.length;
        int prev = nums[0];
        int res = prev ^ 1;
        for (int i = 1; i < n; i++) {
            res += prev ^ nums[i];
            prev = nums[i];
        }
        return res;
    }

    public int minOperations_v1(int[] nums) {
        int n = nums.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (((res % 2 ^ nums[i]) == 0)) {
                res++;
            }
        }
        return res;
    }

}

性能

3191.使二进制数组全部等于1的最少操作次数I

目标

给你一个二进制数组 nums 。

你可以对数组执行以下操作 任意 次(也可以 0 次):

  • 选择数组中 任意连续 3 个元素,并将它们 全部反转 。

反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。

请你返回将 nums 中所有元素变为 1 的 最少 操作次数。如果无法全部变成 1 ,返回 -1 。

示例 1:

输入:nums = [0,1,1,1,0,0]
输出:3
解释:
我们可以执行以下操作:
选择下标为 0 ,1 和 2 的元素并反转,得到 nums = [1,0,0,1,0,0] 。
选择下标为 1 ,2 和 3 的元素并反转,得到 nums = [1,1,1,0,0,0] 。
选择下标为 3 ,4 和 5 的元素并反转,得到 nums = [1,1,1,1,1,1] 。

示例 2:

输入:nums = [0,1,1,1]
输出:-1
解释:
无法将所有元素都变为 1 。

说明:

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

思路

有一个二进制数组 nums,每一次操作可以将其中连续的三个元素取反,求将其中的所有元素置为 1 的最少操作次数,如果无法将所有元素都变为 1,返回 -1

直觉告诉我 遇到 0 就执行一次操作 就是最少的操作次数。

如果 nums[0] == 0,将它变成 1唯一 方法是对其进行操作,即反转 nums[0] nums[1] nums[2]。将同样的逻辑应用于接下来的 1 ~ n - 1,换句话说 结果是注定的最少操作方式也是唯一的

代码


/**
 * @date 2024-10-18 8:53
 */
public class MinOperations3191 {

    public int minOperations_v1(int[] nums) {
        int res = 0;
        int n = nums.length;
        for (int i = 0; i < n - 2; i++) {
            if (nums[i] == 0) {
                nums[i + 1] ^= 1;
                nums[i + 2] ^= 1;
                res++;
            }
        }
        return (nums[n - 1] == 0 || nums[n - 2] == 0) ? -1 : res;
    }

}

性能

3193.统计逆序对的数目

目标

给你一个整数 n 和一个二维数组 requirements ,其中 requirements[i] = [endi, cnti] 表示这个要求中的末尾下标和 逆序对 的数目。

整数数组 nums 中一个下标对 (i, j) 如果满足以下条件,那么它们被称为一个 逆序对 :

  • i < j 且 nums[i] > nums[j]

请你返回 [0, 1, 2, ..., n - 1] 的 排列 perm 的数目,满足对 所有 的 requirements[i] 都有 perm[0..endi] 恰好有 cnti 个逆序对。

由于答案可能会很大,将它对 10^9 + 7 取余 后返回。

示例 1:

输入:n = 3, requirements = [[2,2],[0,0]]
输出:2
解释:
两个排列为:
[2, 0, 1]
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2] 的逆序对数目为 0 个。
[1, 2, 0]
前缀 [1, 2, 0] 的逆序对为 (0, 2) 和 (1, 2) 。
前缀 [1] 的逆序对数目为 0 个。

示例 2:

输入:n = 3, requirements = [[2,2],[1,1],[0,0]]
输出:1
解释:
唯一满足要求的排列是 [2, 0, 1] :
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2, 0] 的逆序对为 (0, 1) 。
前缀 [2] 的逆序对数目为 0 。

示例 3:

输入:n = 2, requirements = [[0,0],[1,0]]
输出:1
解释:
唯一满足要求的排列为 [0, 1] :
前缀 [0] 的逆序对数目为 0 。
前缀 [0, 1] 的逆序对为 (0, 1) 。

说明:

  • 2 <= n <= 300
  • 1 <= requirements.length <= n
  • requirements[i] = [endi, cnti]
  • 0 <= endi <= n - 1
  • 0 <= cnti <= 400
  • 输入保证至少有一个 i 满足 endi == n - 1 。
  • 输入保证所有的 endi 互不相同。

思路

数组 nums 的每一个子数组 [0, endi] 有一个 cnti,求这些子数组的排列中逆序对个数恰好为对应的 cnti 的个数。注意同一个排列需要满足所有的要求。

// todo

代码

性能

3194.最小元素和最大元素的最小平均值

目标

你有一个初始为空的浮点数数组 averages。另给你一个包含 n 个整数的数组 nums,其中 n 为偶数。

你需要重复以下步骤 n / 2 次:

  • 从 nums 中移除 最小 的元素 minElement 和 最大 的元素 maxElement。
  • 将 (minElement + maxElement) / 2 加入到 averages 中。

返回 averages 中的 最小 元素。

示例 1:

输入: nums = [7,8,3,4,15,13,4,1]
输出: 5.5
解释:
步骤 nums               averages
0   [7,8,3,4,15,13,4,1] []
1   [7,8,3,4,13,4]      [8]
2   [7,8,4,4]           [8,8]
3   [7,4]               [8,8,6]
4   []                  [8,8,6,5.5]
返回 averages 中最小的元素,即 5.5。

示例 2:

输入: nums = [1,9,8,3,10,5]
输出: 5.5
解释:
步骤 nums           averages
0   [1,9,8,3,10,5]  []
1   [9,8,3,5]       [5.5]
2   [8,5]           [5.5,6]
3   []              [5.5,6,6.5]

示例 3:

输入: nums = [1,2,3,7,8,9]
输出: 5.0
解释:
步骤 nums           averages
0   [1,2,3,7,8,9]   []
1   [2,3,7,8]       [5]
2   [3,7]           [5,5]
3   []              [5,5,5]

说明:

  • 2 <= n == nums.length <= 50
  • n 为偶数。
  • 1 <= nums[i] <= 50

思路

有一个数组 nums,元素个数为偶数,取其最大值与最小值的平均数加入 averages 浮点数数组,并将其从 nums 中删除。求 averages 数组的最小值。

先将数组 nums 排序,然后取前后两个元素计算平均数,取最小值即可。

代码


/**
 * @date 2024-10-16 8:49
 */
public class MinimumAverage3194 {

    public double minimumAverage(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int end = n / 2;
        n = n - 1;
        double res = Double.MAX_VALUE;
        for (int i = 0; i < end; i++) {
            res = Math.min(res, (nums[i] + nums[n - i]) / 2.0);
        }
        return res;
    }
}

性能

3200.三角形的最大高度

目标

给你两个整数 red 和 blue,分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形,满足第 1 行有 1 个球,第 2 行有 2 个球,第 3 行有 3 个球,依此类推。

每一行的球必须是 相同 颜色,且相邻行的颜色必须 不同。

返回可以实现的三角形的 最大 高度。

示例 1:

输入: red = 2, blue = 4
输出: 3
解释:
上图显示了唯一可能的排列方式。

示例 2:

输入: red = 2, blue = 1
输出: 2
解释:
上图显示了唯一可能的排列方式。

示例 3:

输入: red = 1, blue = 1
输出: 1

示例 4:

输入: red = 10, blue = 1
输出: 2
解释:
上图显示了唯一可能的排列方式。

说明:

  • 1 <= red, blue <= 100

思路

有红球 red 个,蓝球 blue 个,使用这两种球组成三角形,要求每一行只能由同一种颜色的球组成,且相邻行球的颜色不同,问三角形的最大高度。

三角形第 i 行球的个数为 i,奇数行的总个数为 1 + 3 + 5 + …… 偶数行的总个数为 2 + 4 + 6 + ……,根据等差数列求和公式,奇数行所需球的总个数为 oddRowCnt^2,偶数行所需球的总个数为 evenRowCnt^2 + evenRowCnt

假设 red 放在奇数行,代入可得 redOddRowCnt = sqrt(red),如果放在偶数行,则 redEvenRowCnt = (sqrt(1 + 4 * red) - 1) / 2。同理可求出 blueOddRowCnt blueEvenRowCnt

接下来分两种情况讨论:

  1. 红色球放第一行:如果 |redOddRowCnt - blueEvenRowCnt| > 1,取 Math.min(redOddRowCnt, blueEvenRowCnt) * 2 + 1,否则取 Math.min(redOddRowCnt, blueEvenRowCnt) * 2
  2. 蓝色球放第一行:如果 |blueOddRowCnt - redEvenRowCnt| > 1,取 Math.min(blueOddRowCnt, redEvenRowCnt) * 2 + 1,否则取 Math.min(blueOddRowCnt, redEvenRowCnt) * 2

取上面两种情况的最大值即可。

代码


/**
 * @date 2024-10-15 9:25
 */
public class MaxHeightOfTriangle3200 {

    public int maxHeightOfTriangle(int red, int blue) {
        int redOddRowCnt = (int) Math.sqrt(red);
        int redEvenRowCnt = (int) ((Math.sqrt(1 + 4 * red) - 1) / 2);
        int blueOddRowCnt = (int) Math.sqrt(blue);
        int blueEvenRowCnt = (int) ((Math.sqrt(1 + 4 * blue) - 1) / 2);
        int r, b;
        if (redOddRowCnt - blueEvenRowCnt >= 1) {
            r = 2 * blueEvenRowCnt + 1;
        } else {
            r = 2 * redOddRowCnt;
        }
        if (blueOddRowCnt - redEvenRowCnt >= 1) {
            b = 2 * redEvenRowCnt + 1;
        } else {
            b = 2 * blueOddRowCnt;
        }
        return Math.max(r, b);
    }

}

性能

887.鸡蛋掉落

目标

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。

每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。

请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

示例 1:

输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。 
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。 
如果它没碎,那么肯定能得出 f = 2 。 
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 

示例 2:

输入:k = 2, n = 6
输出:3

示例 3:

输入:k = 3, n = 14
输出:4

提示:

  • 1 <= k <= 100
  • 1 <= n <= 10^4

思路

参考1884.鸡蛋掉落-两枚鸡蛋,这次是 k 枚鸡蛋。

//todo

代码

性能

1884.鸡蛋掉落-两枚鸡蛋

目标

给你 2 枚相同 的鸡蛋,和一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都 会碎 ,从 f 楼层或比它低 的楼层落下的鸡蛋都 不会碎 。

每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。

请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

示例 1:

输入:n = 2
输出:2
解释:我们可以将第一枚鸡蛋从 1 楼扔下,然后将第二枚从 2 楼扔下。
如果第一枚鸡蛋碎了,可知 f = 0;
如果第二枚鸡蛋碎了,但第一枚没碎,可知 f = 1;
否则,当两个鸡蛋都没碎时,可知 f = 2。

示例 2:

输入:n = 100
输出:14
解释:
一种最优的策略是:
- 将第一枚鸡蛋从 9 楼扔下。如果碎了,那么 f 在 0 和 8 之间。将第二枚从 1 楼扔下,然后每扔一次上一层楼,在 8 次内找到 f 。总操作次数 = 1 + 8 = 9 。
- 如果第一枚鸡蛋没有碎,那么再把第一枚鸡蛋从 22 层扔下。如果碎了,那么 f 在 9 和 21 之间。将第二枚鸡蛋从 10 楼扔下,然后每扔一次上一层楼,在 12 次内找到 f 。总操作次数 = 2 + 12 = 14 。
- 如果第一枚鸡蛋没有再次碎掉,则按照类似的方法从 34, 45, 55, 64, 72, 79, 85, 90, 94, 97, 99 和 100 楼分别扔下第一枚鸡蛋。
不管结果如何,最多需要扔 14 次来确定 f 。

说明:

  • 1 <= n <= 1000

思路

有一个 1 ~ n 层楼的建筑,存在一个楼层 f,任何大于 f 层落下的鸡蛋都会摔碎。现在有两个鸡蛋,每次操作可以从任意楼层向下扔鸡蛋,如果鸡蛋碎了则无法再使用,求确定 f 值的最小操作次数。

为了确保能够找到 f,如果第一个尝试的鸡蛋碎了,那么另一个鸡蛋只能从已知的安全楼层一层一层向上尝试。

观察示例2,可以从 n 开始 减 1 2 3 …… i 直到小于等于零,返回 i - 1即可。

看了题解,这样做可行的逻辑是这样的:

假设已知最小操作次数 k,我们扔第一枚鸡蛋选第几层?显然,应该选第 k 层,因为如果第一枚鸡蛋碎了,只需要从 1 ~ k - 1 枚举即可。

如果第一枚鸡蛋没碎,那么下一次选第几层?现在还剩下 k - 1 次尝试,所以应该选 k + 1 + (k - 2) = k + (k - 1) 层,因为如果在该层扔鸡蛋碎了,只需从 k + 1 ~ k + k - 2 枚举即可,共 k - 2 次,再加上前面尝试的 2 次,总次数为 k

以此类推,我们可以确定总层数 n = k + (k - 1) + (k - 2) + …… + 2 + 1 = k * (k + 1)/2,解方程得 k = (sqrt(1+8*n) - 1)/2,结果需要向上取整。

代码


/**
 * @date 2024-10-13 19:30
 */
public class TwoEggDrop1884 {

    public int twoEggDrop_v1(int n) {
        return (int) Math.ceil((Math.sqrt(1 + 8 * n) - 1) / 2);
    }

    public int twoEggDrop(int n) {
        int i = 1;
        while (n > 0){
            n -= i++;
        }
        return i - 1;
    }
}

性能