2928.给小朋友们分糖果I

目标

给你两个正整数 n 和 limit 。

请你将 n 颗糖果分给 3 位小朋友,确保没有任何小朋友得到超过 limit 颗糖果,请你返回满足此条件下的 总方案数 。

示例 1:

输入:n = 5, limit = 2
输出:3
解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。

示例 2:

输入:n = 3, limit = 3
输出:10
解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。

说明:

  • 1 <= n <= 50
  • 1 <= limit <= 50

思路

n 颗糖果分给 3 位小朋友,每个小朋友分到的糖果数量不超过 limit,求分配的方案数。注意,糖果必须分完,比如示例 1,不存在分得糖果数量为 0 的情况。

  • 第一个小朋友分到的糖果数为 a ∈ [0, Math.min(n, limit)]
  • 第二个小朋友分到的糖果数为 b ∈ [Math.max(0, n - a - limit), Math.min(n - a, limit)]
  • 第三个小朋友分到的糖果数为 n - a - b

代码

/**
 * @date 2024-06-01 15:59
 */
public class DistributeCandies2928 {
    public int distributeCandies(int n, int limit) {
        int res = 0;
        // 为第一个小朋友分配有0~limit种可能
        for (int i = 0; i <= limit; i++) {
            // 这时为第二个小朋友可以有n - i种可能,这里不要忘了不能超过limit
            for (int j = 0; j <= Math.min(limit, n - i); j++) {
                // 第三个小朋友,获取剩下的糖果,但不能超过limit
                if(n - i - j <= limit){
                    res++;
                }
            }
        }
        return res;
    }
}

性能

2965.找出缺失和重复的数字

目标

给你一个下标从 0 开始的二维整数矩阵 grid,大小为 n * n ,其中的值在 [1, n2] 范围内。除了 a 出现 两次,b 缺失 之外,每个整数都 恰好出现一次 。

任务是找出重复的数字a 和缺失的数字 b 。

返回一个下标从 0 开始、长度为 2 的整数数组 ans ,其中 ans[0] 等于 a ,ans[1] 等于 b 。

示例 1:

输入:grid = [[1,3],[2,2]]
输出:[2,4]
解释:数字 2 重复,数字 4 缺失,所以答案是 [2,4] 。

示例 2:

输入:grid = [[9,1,7],[8,9,2],[3,4,6]]
输出:[9,5]
解释:数字 9 重复,数字 5 缺失,所以答案是 [9,5] 。

说明:

  • 2 <= n == grid.length == grid[i].length <= 50
  • 1 <= grid[i][j] <= n * n
  • 对于所有满足 1 <= x <= n * n 的 x ,恰好存在一个 x 与矩阵中的任何成员都不相等。
  • 对于所有满足 1 <= x <= n * n 的 x ,恰好存在一个 x 与矩阵中的两个成员相等。
  • 除上述的两个之外,对于所有满足 1 <= x <= n * n 的 x ,都恰好存在一对 i, j 满足 0 <= i, j <= n - 1grid[i][j] == x

思路

记录元素的出现次数,选出其中出现次数为2和0的值即可。

网友还给出了一些位运算与数学公式求解的方法,可以降低空间复杂度,不用对每个元素计次,而是使用异或和与数学公式求解。

异或和求解的关键是如何区分得到的 a ^ b,由于a 与 b 不同,异或的结果至少有一个非0位,可以根据该bit位分组,这样每一组中就只含有a或b。

至于数学公式求解的关键是,通过各项和的差可以得到 b - a,各项平方和的差可以得到 b^2 - a^2,进而求得 b + a,解方程组可得a、b。

代码

/**
 * @date 2024-05-31 0:12
 */
public class FindMissingAndRepeatedValues2965 {

/**
     * 公式求和 1,2,……,n^2 以及 1^2,2^2,……,n^2
     * 然后遍历求和,二者之差为b - a 和 b^2 - a^2
     * 进而可以求得b + a,解方程可得a、b
     */
    public int[] findMissingAndRepeatedValues_v2(int[][] grid) {
        int n = grid.length;
        int n2 = n * n;
        long sum = 0;
        long sum2 = 0;
        for (int[] row : grid) {
            for (int i : row) {
                sum += i;
                sum2 += i * i;
            }
        }
        long bsuba = n2 * (n2 + 1L) / 2 - sum;
        long b2suba2 = n2 * (n2 + 1L) * (2 * n2 + 1) / 6 - sum2;
        long badda = b2suba2 / bsuba;
        long b = (badda + bsuba) / 2L;
        long a = badda - b;
        return new int[]{(int) a, (int) b};
    }

    /**
     * 异或和
     */
    public int[] findMissingAndRepeatedValues_v1(int[][] grid) {
        int n = grid.length;
        int n2 = n * n;
        int prefix = 0;
        for (int i = 1; i <= n2; i++) {
            prefix ^= i;
        }
        int tmp = 0;
        for (int[] row : grid) {
            for (int element : row) {
                tmp ^= element;
            }
        }
        int axorb = prefix ^ tmp;
        int shift = Integer.numberOfTrailingZeros(axorb);
        int[] res = new int[2];
        // 由于axorb至少有1bit是不同的,那么根据该bit分组,这样每一组里面a b是分开的
        for (int i = 1; i <= n2; i++) {
            res[i >> shift & 1] ^= i;
        }
        for (int[] row : grid) {
            for (int element : row) {
                res[element >> shift & 1] ^= element;
            }
        }
        // 由于顺序是不确定的,判断出现的为a
        for (int[] row : grid) {
            for (int element : row) {
                if (element == res[0]){
                    return res;
                }
            }
        }
        // 调换位置
        return new int[]{res[1], res[0]};
    }

    public int[] findMissingAndRepeatedValues(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int l = m * n + 1;
        int[] occurrence = new int[l];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                occurrence[grid[i][j]]++;
            }
        }
        int[] res = new int[2];
        for (int i = 1; i < l; i++) {
            if (occurrence[i] == 0) {
                res[1] = i;
            } else if (occurrence[i] == 2) {
                res[0] = i;
            }
        }
        return res;
    }
}

性能

2951.找出峰值

目标

给你一个下标从 0 开始的数组 mountain 。你的任务是找出数组 mountain 中的所有 峰值。

以数组形式返回给定数组中 峰值 的下标,顺序不限 。

注意:

  • 峰值 是指一个严格大于其相邻元素的元素。
  • 数组的第一个和最后一个元素 不 是峰值。

示例 1:

输入:mountain = [2,4,4]
输出:[]
解释:mountain[0] 和 mountain[2] 不可能是峰值,因为它们是数组的第一个和最后一个元素。
mountain[1] 也不可能是峰值,因为它不严格大于 mountain[2] 。
因此,答案为 [] 。

示例 2:

输入:mountain = [1,4,3,8,5]
输出:[1,3]
解释:mountain[0] 和 mountain[4] 不可能是峰值,因为它们是数组的第一个和最后一个元素。
mountain[2] 也不可能是峰值,因为它不严格大于 mountain[3] 和 mountain[1] 。
但是 mountain[1] 和 mountain[3] 严格大于它们的相邻元素。
因此,答案是 [1,3] 。

说明:

  • 3 <= mountain.length <= 100
  • 1 <= mountain[i] <= 100

思路

找到大于左右两边元素值的下标。

代码

/**
 * @date 2024-05-28 8:39
 */
public class FindPeaks2951 {
    public List<Integer> findPeaks(int[] mountain) {
        List<Integer> res = new ArrayList<>();
        int n = mountain.length;
        int l = 0;
        int r = 2;
        while (r < n) {
            int i = l + 1;
            if (mountain[i] > mountain[l] && mountain[i] > mountain[r]) {
                res.add(i);
                l = r;
                r += 2;
            } else {
                l++;
                r++;
            }
        }
        return res;
    }
}

性能

2903.找出满足差值条件的下标I

目标

给你一个下标从 0 开始、长度为 n 的整数数组 nums ,以及整数 indexDifference 和整数 valueDifference 。

你的任务是从范围 [0, n - 1] 内找出 2 个满足下述所有条件的下标 i 和 j :

  • abs(i - j) >= indexDifference 且
  • abs(nums[i] - nums[j]) >= valueDifference

返回整数数组 answer。如果存在满足题目要求的两个下标,则 answer = [i, j] ;否则,answer = [-1, -1] 。如果存在多组可供选择的下标对,只需要返回其中任意一组即可。

注意:i 和 j 可能 相等 。

示例 1:

输入:nums = [5,1,4,1], indexDifference = 2, valueDifference = 4
输出:[0,3]
解释:在示例中,可以选择 i = 0 和 j = 3 。
abs(0 - 3) >= 2 且 abs(nums[0] - nums[3]) >= 4 。
因此,[0,3] 是一个符合题目要求的答案。
[3,0] 也是符合题目要求的答案。

示例 2:

输入:nums = [2,1], indexDifference = 0, valueDifference = 0
输出:[0,0]
解释:
在示例中,可以选择 i = 0 和 j = 0 。 
abs(0 - 0) >= 0 且 abs(nums[0] - nums[0]) >= 0 。 
因此,[0,0] 是一个符合题目要求的答案。 
[0,1]、[1,0] 和 [1,1] 也是符合题目要求的答案。 

示例 3:

输入:nums = [1,2,3], indexDifference = 2, valueDifference = 4
输出:[-1,-1]
解释:在示例中,可以证明无法找出 2 个满足所有条件的下标。
因此,返回 [-1,-1] 。

说明:

  • 1 <= n == nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= indexDifference <= 100
  • 0 <= valueDifference <= 50

思路

给我们一个数组,找出其中下标之差大于等于indexDifference,并且值值差大于等于valueDifference的下标,如果不存在返回[-1, -1]。

按照题意我们循环 [0, length),将 [i + indexDifference, length) 的元素分别与 i 进行比较。

题目给定的数据范围比较小,可以使用暴力解法。数据范围变大后这个方法可能会超时,参考 2905.找出满足差值条件的下标 II。

题解给出了一次遍历的题解,只需记录前面的最大与最小值。// todo

代码

/**
 * @date 2024-05-25 20:14
 */
public class FindIndices2903 {
    public int[] findIndices(int[] nums, int indexDifference, int valueDifference) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int r = i + indexDifference;
            while (r < n && Math.abs(nums[i] - nums[r]) < valueDifference) {
                r++;
            }
            if (r < n) {
                return new int[]{i, r};
            }
        }
        return new int[]{-1, -1};
    }
}

性能

2769.找出最大的可达成数字

目标

给你两个整数 num 和 t 。

如果整数 x 可以在执行下述操作不超过 t 次的情况下变为与 num 相等,则称其为 可达成数字 :

  • 每次操作将 x 的值增加或减少 1 ,同时可以选择将 num 的值增加或减少 1 。

返回所有可达成数字中的最大值。可以证明至少存在一个可达成数字。

示例 1:

输入:num = 4, t = 1
输出:6
解释:最大可达成数字是 x = 6 ,执行下述操作可以使其等于 num :
- x 减少 1 ,同时 num 增加 1 。此时,x = 5 且 num = 5 。 
可以证明不存在大于 6 的可达成数字。

示例 2:

输入:num = 3, t = 2
输出:7
解释:最大的可达成数字是 x = 7 ,执行下述操作可以使其等于 num :
- x 减少 1 ,同时 num 增加 1 。此时,x = 6 且 num = 4 。 
- x 减少 1 ,同时 num 增加 1 。此时,x = 5 且 num = 5 。 
可以证明不存在大于 7 的可达成数字。

说明:

  • 1 <= num, t <= 50

思路

给定一个正整数 num,以及操作次数 t,求一个数字 x 能够在 t 次操作后与 num 相等。每一次操作可以使 x 增大或减小1,同时可以选择使 num 增大或减少1。问 x 最大是多少。

要使 x 最大则应该选大于 num 的数,操作减1,为了使 x 尽可能大,应该同时增大 num,使 xnum 相向变化。很明显,x 最大为 num + 2 * t

代码

/**
 * @date 2024-05-21 8:47
 */
public class TheMaximumAchievableX2769 {
    public int theMaximumAchievableX(int num, int t) {
        return num + 2 * t;
    }
}

性能

2644.找出可整除性得分最大的整数

目标

给你两个下标从 0 开始的整数数组 nums 和 divisors 。

divisors[i] 的 可整除性得分 等于满足 nums[j] 能被 divisors[i] 整除的下标 j 的数量。

返回 可整除性得分 最大的整数 divisors[i] 。如果有多个整数具有最大得分,则返回数值最小的一个。

示例 1:

输入:nums = [4,7,9,3,9], divisors = [5,2,3]
输出:3
解释:divisors 中每个元素的可整除性得分为:
divisors[0] 的可整除性得分为 0 ,因为 nums 中没有任何数字能被 5 整除。
divisors[1] 的可整除性得分为 1 ,因为 nums[0] 能被 2 整除。 
divisors[2] 的可整除性得分为 3 ,因为 nums[2]、nums[3] 和 nums[4] 都能被 3 整除。 
因此,返回 divisors[2] ,它的可整除性得分最大。

示例 2:

输入:nums = [20,14,21,10], divisors = [5,7,5]
输出:5
解释:divisors 中每个元素的可整除性得分为:
divisors[0] 的可整除性得分为 2 ,因为 nums[0] 和 nums[3] 都能被 5 整除。
divisors[1] 的可整除性得分为 2 ,因为 nums[1] 和 nums[2] 都能被 7 整除。
divisors[2] 的可整除性得分为 2 ,因为 nums[0] 和 nums[3] 都能被5整除。 
由于 divisors[0]、divisors[1] 和 divisors[2] 的可整除性得分都是最大的,因此,我们返回数值最小的一个,即 divisors[2] 。

示例 3:

输入:nums = [12], divisors = [10,16]
输出:10
解释:divisors 中每个元素的可整除性得分为:
divisors[0] 的可整除性得分为 0 ,因为 nums 中没有任何数字能被 10 整除。
divisors[1] 的可整除性得分为 0 ,因为 nums 中没有任何数字能被 16 整除。 
由于 divisors[0] 和 divisors[1] 的可整除性得分都是最大的,因此,我们返回数值最小的一个,即 divisors[0] 。

说明:

  • 1 <= nums.length, divisors.length <= 1000
  • 1 <= nums[i], divisors[i] <= 10^9

思路

给定一个被除数数组 nums 与除数数组 divisorsnums 中能够被 divisors 整除的元素个数称为相应除数的分数,求分数最大的除数 divisor,如果分数相同则取最小的。

通俗的讲就是找到能够被更多的元素整除的除数,如果有多个则取最小的。

简单题直接放入优先队列即可,但排序其实是没必要的,可以直接在循环中记录结果。

网友还提供了一种算法,不用对每一个 nums 中的元素进行mod运算,而是将 nums 记录到map中,value是其重复次数,同时求出 nums 的最大值。在循环除数的时候,只需要判断 i * divisor 是否在map中即可,结束条件是小于等于最大值。但是这种算法太有针对性,对于那种最大值很大而除数很小的情况可能会超时。

也有网友提供了排序加优化的算法,更通用一些。

代码

/**
 * @date 2024-05-18 10:10
 */
public class MaxDivScore2644 {

    /**
     * 网友的解法 排序加优化 70ms
     */
    public int maxDivScore_v3(int[] nums, int[] divisors) {
        Arrays.sort(nums);
        int n = nums.length;
        int dup = 0;
        for (int i = 1; i < n; i++) {
            if (nums[i] == nums[i - 1]) {
                dup++;
            }
        }
        Arrays.sort(divisors);

        int ans = 0;
        int maxCnt = -1;
        for (int d : divisors) {
            // 提前结束说明 d 的倍数 d,2d,3d,⋯ ,(maxCnt−dup+1)⋅d 中的最大值已经超出了 nums 的最大值,
            // 即使把 nums 中的重复元素也算上,我们也无法统计出比 maxCnt 还多的倍数。
            if (maxCnt - dup >= nums[n - 1] / d) {
                break;
            }
            int cnt = 0;
            for (int i = n - 1; i >= 0; i--) {
                int x = nums[i];
                if (x < d) {
                    break;
                }
                if (x % d == 0) {
                    cnt++;
                }
            }
            if (cnt > maxCnt) {
                maxCnt = cnt;
                ans = d;
            }
        }
        return ans;
    }

    /**
     * 25ms
     */
    public int maxDivScore_v1(int[] nums, int[] divisors) {
        Map<Integer, Integer> cntMap = new HashMap<>();
        int maxNum = 0;
        for (int num : nums) {
            cntMap.put(num, cntMap.getOrDefault(num, 0) + 1);
            maxNum = Math.max(maxNum, num);
        }
        // 出错点:这里maxCnt的初值为-1,如果为0会跳过无法整除的情况,进入不到if条件里,res还是初值0,而非dividsor
        int res = 0, maxCnt = -1;
        for (int divisor : divisors) {
            int cnt = 0;
            int num = divisor;
            for (int i = 2; num <= maxNum; i++) {
                if (cntMap.containsKey(num)) {
                    cnt += cntMap.get(num);
                }
                num = i * divisor;
            }
            if (cnt > maxCnt || (cnt == maxCnt && res > divisor)) {
                maxCnt = cnt;
                res = divisor;
            }
        }
        return res;
    }

    /**
     * 使用优先队列 180ms
     */
    public int maxDivScore(int[] nums, int[] divisors) {
        PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> {
            int c = b[1] - a[1];
            return c == 0 ? a[0] - b[0] : c;
        });
        for (int divisor : divisors) {
            int cnt = 0;
            for (int num : nums) {
                if (num % divisor == 0) {
                    cnt++;
                }
            }
            q.offer(new int[]{divisor, cnt});
        }
        return q.poll()[0];
    }

}

性能

使用优先队列

不使用排序,使用hashmap保存nums,成倍增加除数,当除数很大的时候可以跳过一些循环。

但还是取决于测试用例,如果nums数组没有几个元素,而max又很大,循环次数并不会减少,反而会增加。

例如这个测试用例,上面的方法直接超出限制

排序加优化:

2960.统计已测试设备

目标

给你一个长度为 n 、下标从 0 开始的整数数组 batteryPercentages ,表示 n 个设备的电池百分比。

你的任务是按照顺序测试每个设备 i,执行以下测试操作:

  • 如果 batteryPercentages[i] 大于 0:
    • 增加 已测试设备的计数。
    • 将下标在 [i + 1, n - 1] 的所有设备的电池百分比减少 1,确保它们的电池百分比 不会低于 0 ,即 batteryPercentages[j] = max(0, batteryPercentages[j] - 1)。
    • 移动到下一个设备。
  • 否则,移动到下一个设备而不执行任何测试。

返回一个整数,表示按顺序执行测试操作后 已测试设备 的数量。

示例 1:

输入:batteryPercentages = [1,1,2,1,3]
输出:3
解释:按顺序从设备 0 开始执行测试操作:
在设备 0 上,batteryPercentages[0] > 0 ,现在有 1 个已测试设备,batteryPercentages 变为 [1,0,1,0,2] 。
在设备 1 上,batteryPercentages[1] == 0 ,移动到下一个设备而不进行测试。
在设备 2 上,batteryPercentages[2] > 0 ,现在有 2 个已测试设备,batteryPercentages 变为 [1,0,1,0,1] 。
在设备 3 上,batteryPercentages[3] == 0 ,移动到下一个设备而不进行测试。
在设备 4 上,batteryPercentages[4] > 0 ,现在有 3 个已测试设备,batteryPercentages 保持不变。
因此,答案是 3 。

示例 2:

输入:batteryPercentages = [0,1,2]
输出:2
解释:按顺序从设备 0 开始执行测试操作:
在设备 0 上,batteryPercentages[0] == 0 ,移动到下一个设备而不进行测试。
在设备 1 上,batteryPercentages[1] > 0 ,现在有 1 个已测试设备,batteryPercentages 变为 [0,1,1] 。
在设备 2 上,batteryPercentages[2] > 0 ,现在有 2 个已测试设备,batteryPercentages 保持不变。
因此,答案是 2 。

说明:

  • 1 <= n == batteryPercentages.length <= 100
  • 0 <= batteryPercentages[i] <= 100

思路

如果设备的剩余电量大于0,已测试设备加1且后续设备的所有电量减1。

简而言之,后续设备需要减去的电量为已检测的设备数。

题解上说这是差分的思想。我们没必要检测一个设备就向后循环并将电量减1,当前设备需要减去的电量等于上一个设备需要减去的电量+1。

代码

/**
 * @date 2024-05-10 8:43
 */
public class CountTestedDevices2960 {
    public int countTestedDevices(int[] batteryPercentages) {
        int res = 0;
        for (int i = 0; i < batteryPercentages.length; i++) {
            if (batteryPercentages[i] > res) {
                res++;
            }
        }
        return res;
    }
}

性能

2639.查询网格图中每一列的宽度

目标

给你一个下标从 0 开始的 m * n 整数矩阵 grid 。矩阵中某一列的宽度是这一列数字的最大 字符串长度 。

  • 比方说,如果 grid = [[-10], [3], [12]] ,那么唯一一列的宽度是 3 ,因为 -10 的字符串长度为 3 。

请你返回一个大小为 n 的整数数组 ans ,其中 ans[i] 是第 i 列的宽度。

一个有 len 个数位的整数 x ,如果是非负数,那么 字符串长度 为 len ,否则为 len + 1 。

示例 1:

输入:grid = [[1],[22],[333]]
输出:[3]
解释:第 0 列中,333 字符串长度为 3 。

示例 2:

输入:grid = [[-15,1,3],[15,7,12],[5,6,-2]]
输出:[3,1,2]
解释:
第 0 列中,只有 -15 字符串长度为 3 。
第 1 列中,所有整数的字符串长度都是 1 。
第 2 列中,12 和 -2 的字符串长度都为 2 。

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100
  • -10^9 <= grid[r][c] <= 10^9

思路

这个题核心就是如何计算数字的长度。我们可以枚举 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE,分别与这些值比较,Integer.stringSize就是如此实现的。对于负数需要将长度加1 int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);

也可以循环除以10直到0来计算长度。一个优化的点是不必求每一个数的长度,只需要求出最大值 max 与 最小值 min的长度即可。网友还提供了一个小技巧可以减少对较小值的长度计算。选取 max/10-min 中的较大值来计算长度 l,取 l+1。解释如下:如果 min > 0 那么 -min < 0,我们取到 max/10 的长度,所以长度为 l+1。如果 min < 0,并且 -min > max/10,我们取到 -min,长度需要加上负号,即 l+1。这里需要解释一下 -min < max && -min > max/10 的情况,这时 max 的长度与 -min 的长度加1 是相同的。而如果 min < 0,且 -min < max/10,取 max/10 长度减小了1,所以取 l+1

代码

/**
 * @date 2024-04-27 18:39
 */
public class FindColumnWidth2639 {

    int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999,
            99999999, 999999999, Integer.MAX_VALUE};

    int stringSize(int x) {
        int j;
        if (x < 0) {
            x = -x;
            j = 2;
        } else {
            j = 1;
        }
        for (int i = 0; ; i++) {
            if (x <= sizeTable[i]) {
                return i + j;
            }
        }
    }

    public int[] findColumnWidth(int[][] grid) {
        int n = grid[0].length;
        int[] res = new int[n];
        int[] negative = new int[n];
        for (int[] ints : grid) {
            for (int i = 0; i < n; i++) {
                if (ints[i] < negative[i]) {
                    negative[i] = ints[i];
                } else if (ints[i] > res[i]) {
                    res[i] = ints[i];
                }
            }
        }
        for (int i = 0; i < n; i++) {
            res[i] = Math.max(stringSize(res[i]), stringSize(negative[i]));
        }
        return res;
    }

}

性能