2894.分类求和并作差

目标

给你两个正整数 n 和 m 。

现定义两个整数 num1 和 num2 ,如下所示:

  • num1:范围 [1, n] 内所有 无法被 m 整除 的整数之和。
  • num2:范围 [1, n] 内所有 能够被 m 整除 的整数之和。

返回整数 num1 - num2 。

示例 1:

输入:n = 10, m = 3
输出:19
解释:在这个示例中:
- 范围 [1, 10] 内无法被 3 整除的整数为 [1,2,4,5,7,8,10] ,num1 = 这些整数之和 = 37 。
- 范围 [1, 10] 内能够被 3 整除的整数为 [3,6,9] ,num2 = 这些整数之和 = 18 。
返回 37 - 18 = 19 作为答案。

示例 2:

输入:n = 5, m = 6
输出:15
解释:在这个示例中:
- 范围 [1, 5] 内无法被 6 整除的整数为 [1,2,3,4,5] ,num1 = 这些整数之和 =  15 。
- 范围 [1, 5] 内能够被 6 整除的整数为 [] ,num2 = 这些整数之和 = 0 。
返回 15 - 0 = 15 作为答案。

示例 3:

输入:n = 5, m = 1
输出:-15
解释:在这个示例中:
- 范围 [1, 5] 内无法被 1 整除的整数为 [] ,num1 = 这些整数之和 = 0 。 
- 范围 [1, 5] 内能够被 1 整除的整数为 [1,2,3,4,5] ,num2 = 这些整数之和 = 15 。
返回 0 - 15 = -15 作为答案。

说明:

  • 1 <= n, m <= 1000

思路

1 ~ n 中无法整除 m 的所有整数和 num1 与 能够整除 m 的所有整数和 num2 之差。

依题意模拟即可。

或者使用等差数列求和公式计算。

根据等差数列求和公式得到所有元素和为 total = (n + 1) * n / 2,能够被 m 整除的所有整数和为 首项为 m,公差为 m,个数为 cnt = n / m 的等差数列和,num2 = m * (cnt + 1) * cnt / 2。结果为 total - 2 * num2

代码


/**
 * @date 2025-05-27 1:00
 */
public class DifferenceOfSums2894 {

    public int differenceOfSums(int n, int m) {
        int total = (n + 1) * n / 2;
        int cnt = n / m;
        return total - m * (1 + cnt) * cnt;
    }

}

性能

1857.有向图中最大颜色值

目标

给你一个 有向图 ,它含有 n 个节点和 m 条边。节点编号从 0 到 n - 1 。

给你一个字符串 colors ,其中 colors[i] 是小写英文字母,表示图中第 i 个节点的 颜色 (下标从 0 开始)。同时给你一个二维数组 edges ,其中 edges[j] = [aj, bj] 表示从节点 aj 到节点 bj 有一条 有向边 。

图中一条有效 路径 是一个点序列 x1 -> x2 -> x3 -> ... -> xk ,对于所有 1 <= i < k ,从 xi 到 xi+1 在图中有一条有向边。路径的 颜色值 是路径中 出现次数最多 颜色的节点数目。

请你返回给定图中有效路径里面的 最大颜色值 。如果图中含有环,请返回 -1 。

示例 1:

输入:colors = "abaca", edges = [[0,1],[0,2],[2,3],[3,4]]
输出:3
解释:路径 0 -> 2 -> 3 -> 4 含有 3 个颜色为 "a" 的节点(上图中的红色节点)。

示例 2:

输入:colors = "a", edges = [[0,0]]
输出:-1
解释:从 0 到 0 有一个环。

说明:

  • n == colors.length
  • m == edges.length
  • 1 <= n <= 10^5
  • 0 <= m <= 10^5
  • colors 只含有小写英文字母。
  • 0 <= aj, bj < n

思路

// 今天没空做了 todo

代码

性能

2131.连接两字母单词得到的最长回文串

目标

给你一个字符串数组 words 。words 中每个元素都是一个包含 两个 小写英文字母的单词。

请你从 words 中选择一些元素并按 任意顺序 连接它们,并得到一个 尽可能长的回文串 。每个元素 至多 只能使用一次。

请你返回你能得到的最长回文串的 长度 。如果没办法得到任何一个回文串,请你返回 0 。

回文串 指的是从前往后和从后往前读一样的字符串。

示例 1:

输入:words = ["lc","cl","gg"]
输出:6
解释:一个最长的回文串为 "lc" + "gg" + "cl" = "lcggcl" ,长度为 6 。
"clgglc" 是另一个可以得到的最长回文串。

示例 2:

输入:words = ["ab","ty","yt","lc","cl","ab"]
输出:8
解释:最长回文串是 "ty" + "lc" + "cl" + "yt" = "tylcclyt" ,长度为 8 。
"lcyttycl" 是另一个可以得到的最长回文串。

示例 3:

输入:words = ["cc","ll","xx"]
输出:2
解释:最长回文串是 "cc" ,长度为 2 。
"ll" 是另一个可以得到的最长回文串。"xx" 也是。

说明:

  • 1 <= words.length <= 10^5
  • words[i].length == 2
  • words[i] 仅包含小写英文字母。

思路

有一个字符串数组 words,其元素字符个数为 2,求从中选择任意元素组成回文串的最大长度。

统计每个元素的出现次数,如果数组元素的两个字符不同,要组成回文只能左右对称,计算对称的元素对数 cnt,长度为 cnt * 4。如果元素的两个字符相同,它可以全部放到中间,为了使回文最长,当出现更长的相同字符元素时,可以将原来放中间的个数 centerCnt,放到对称的两边,centerCnt / 2 * 4

代码


/**
 * @date 2025-05-25 1:03
 */
public class LongestPalindrome2131 {

    public int longestPalindrome(String[] words) {
        Map<String, Integer> map = new HashMap<>();
        for (String word : words) {
            char a = word.charAt(0);
            char b = word.charAt(1);
            map.merge(a + String.valueOf(b), 1, Integer::sum);
        }
        int res = 0;
        int centerCnt = 0;
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            char a = key.charAt(0);
            char b = key.charAt(1);
            if (a == b) {
                Integer cnt = entry.getValue();
                if (cnt % 2 == 1) {
                    res += centerCnt / 2 * 4;
                    centerCnt = cnt;
                } else {
                    res += cnt / 2 * 4;
                }
            } else {
                int cnt = Math.min(entry.getValue(), map.getOrDefault(b + String.valueOf(a), 0));
                res += cnt * 4;
            }
            entry.setValue(0);
        }
        return res + centerCnt * 2;
    }

}

性能

2942.查找包含给定字符的单词

目标

给你一个下标从 0 开始的字符串数组 words 和一个字符 x 。

请你返回一个 下标数组,表示下标在数组中对应的单词包含字符 x 。

注意,返回的数组可以是 任意 顺序。

示例 1:

输入:words = ["leet","code"], x = "e"
输出:[0,1]
解释:"e" 在两个单词中都出现了:"leet" 和 "code" 。所以我们返回下标 0 和 1 。

示例 2:

输入:words = ["abc","bcd","aaaa","cbc"], x = "a"
输出:[0,2]
解释:"a" 在 "abc" 和 "aaaa" 中出现了,所以我们返回下标 0 和 2 。

示例 3:

输入:words = ["abc","bcd","aaaa","cbc"], x = "z"
输出:[]
解释:"z" 没有在任何单词中出现。所以我们返回空数组。

提示:

  • 1 <= words.length <= 50
  • 1 <= words[i].length <= 50
  • x 是一个小写英文字母。
  • words[i] 只包含小写英文字母。

思路

返回字符串数组中包含给定字符的下标。

代码


/**
 * @date 2025-05-24 10:09
 */
public class FindWordsContaining2942 {

    public List<Integer> findWordsContaining(String[] words, char x) {
        List<Integer> res = new ArrayList<>(words.length);
        for (int i = 0; i < words.length; i++) {
            String word = words[i];
            for (int j = 0; j < word.length(); j++) {
                if (word.charAt(j) == x) {
                    res.add(i);
                    break;
                }
            }
        }
        return res;
    }

}

性能

3068.最大节点价值之和

目标

给你一棵 n 个节点的 无向 树,节点从 0 到 n - 1 编号。树以长度为 n - 1 下标从 0 开始的二维整数数组 edges 的形式给你,其中 edges[i] = [ui, vi] 表示树中节点 ui 和 vi 之间有一条边。同时给你一个 正 整数 k 和一个长度为 n 下标从 0 开始的 非负 整数数组 nums ,其中 nums[i] 表示节点 i 的 价值 。

Alice 想 最大化 树中所有节点价值之和。为了实现这一目标,Alice 可以执行以下操作 任意 次(包括 0 次):

  • 选择连接节点 u 和 v 的边 [u, v] ,并将它们的值更新为:
    • nums[u] = nums[u] XOR k
    • nums[v] = nums[v] XOR k

请你返回 Alice 通过执行以上操作 任意次 后,可以得到所有节点 价值之和 的 最大值 。

示例 1:

输入:nums = [1,2,1], k = 3, edges = [[0,1],[0,2]]
输出:6
解释:Alice 可以通过一次操作得到最大价值和 6 :
- 选择边 [0,2] 。nums[0] 和 nums[2] 都变为:1 XOR 3 = 2 ,数组 nums 变为:[1,2,1] -> [2,2,2] 。
所有节点价值之和为 2 + 2 + 2 = 6 。
6 是可以得到最大的价值之和。

示例 2:

输入:nums = [2,3], k = 7, edges = [[0,1]]
输出:9
解释:Alice 可以通过一次操作得到最大和 9 :
- 选择边 [0,1] 。nums[0] 变为:2 XOR 7 = 5 ,nums[1] 变为:3 XOR 7 = 4 ,数组 nums 变为:[2,3] -> [5,4] 。
所有节点价值之和为 5 + 4 = 9 。
9 是可以得到最大的价值之和。

示例 3:

输入:nums = [7,7,7,7,7,7], k = 3, edges = [[0,1],[0,2],[0,3],[0,4],[0,5]]
输出:42
解释:Alice 不需要执行任何操作,就可以得到最大价值之和 42 。

说明:

  • 2 <= n == nums.length <= 2 * 10^4
  • 1 <= k <= 10^9
  • 0 <= nums[i] <= 10^9
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= edges[i][0], edges[i][1] <= n - 1
  • 输入保证 edges 构成一棵合法的树。

思路

// todo

代码

性能

3362.零数组变换III

目标

给你一个长度为 n 的整数数组 nums 和一个二维数组 queries ,其中 queries[i] = [li, ri] 。

每一个 queries[i] 表示对于 nums 的以下操作:

  • 将 nums 中下标在范围 [li, ri] 之间的每一个元素 最多 减少 1 。
  • 坐标范围内每一个元素减少的值相互 独立 。

零数组 指的是一个数组里所有元素都等于 0 。

请你返回 最多 可以从 queries 中删除多少个元素,使得 queries 中剩下的元素仍然能将 nums 变为一个 零数组 。如果无法将 nums 变为一个 零数组 ,返回 -1 。

示例 1:

输入:nums = [2,0,2], queries = [[0,2],[0,2],[1,1]]
输出:1
解释:
删除 queries[2] 后,nums 仍然可以变为零数组。
对于 queries[0] ,将 nums[0] 和 nums[2] 减少 1 ,将 nums[1] 减少 0 。
对于 queries[1] ,将 nums[0] 和 nums[2] 减少 1 ,将 nums[1] 减少 0 。

示例 2:

输入:nums = [1,1,1,1], queries = [[1,3],[0,2],[1,3],[1,2]]
输出:2
解释:
可以删除 queries[2] 和 queries[3] 。

示例 3:

输入:nums = [1,2,3,4], queries = [[0,3]]
输出:-1
解释:
nums 无法通过 queries 变成零数组。

说明:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i].length == 2
  • 0 <= li <= ri < nums.length

思路

有一个长度为 n 的整数数组,每一次操作可以将给定范围内的任意元素减 1,返回最多可以删掉多少个操作,使得剩下的操作能够将数组中的所有元素变为 0

对于元素 nums[0] 要将其变为 0 必须要操作 nums[0] 次,且操作范围必须包括 0,因此可以按照操作范围的左端点排序,每次选择右端点最远即覆盖最广的操作区间。可以维护一个由大到小的优先队列,从操作中选出左端点小于等于当前下标的操作,将其右端点放入队列。从队列中取出右端点大于等于当前下标的操作,使用差分数组进行区间修改。

代码


/**
 * @date 2025-05-22 23:02
 */
public class MaxRemoval3362 {

    public int maxRemoval(int[] nums, int[][] queries) {
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> b - a);
        Arrays.sort(queries, (a, b) -> a[0] - b[0]);
        int n = nums.length;
        int[] diff = new int[n + 1];
        diff[0] = nums[0];
        for (int i = 1; i < n; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
        int num = 0;
        int k = 0;
        for (int i = 0; i < n; i++) {
            num += diff[i];
            while (k < queries.length && queries[k][0] <= i) {
                q.offer(queries[k++][1]);
            }
            while (!q.isEmpty() && q.peek() >= i && num > 0) {
                diff[q.poll() + 1]++;
                num--;
            }
            if (num > 0) {
                return -1;
            }
        }
        return q.size();
    }

}

性能

3356.零数组变换II

目标

给你一个长度为 n 的整数数组 nums 和一个二维数组 queries,其中 queries[i] = [li, ri, vali]。

每个 queries[i] 表示在 nums 上执行以下操作:

  • 将 nums 中 [li, ri] 范围内的每个下标对应元素的值 最多 减少 vali。
  • 每个下标的减少的数值可以独立选择。

零数组 是指所有元素都等于 0 的数组。

返回 k 可以取到的 最小非负 值,使得在 顺序 处理前 k 个查询后,nums 变成 零数组。如果不存在这样的 k,则返回 -1。

示例 1:

输入: nums = [2,0,2], queries = [[0,2,1],[0,2,1],[1,1,3]]
输出: 2
解释:
对于 i = 0(l = 0, r = 2, val = 1):
在下标 [0, 1, 2] 处分别减少 [1, 0, 1]。
数组将变为 [1, 0, 1]。
对于 i = 1(l = 0, r = 2, val = 1):
在下标 [0, 1, 2] 处分别减少 [1, 0, 1]。
数组将变为 [0, 0, 0],这是一个零数组。因此,k 的最小值为 2。

示例 2:

输入: nums = [4,3,2,1], queries = [[1,3,2],[0,2,1]]
输出: -1
解释:
对于 i = 0(l = 1, r = 3, val = 2):
在下标 [1, 2, 3] 处分别减少 [2, 2, 1]。
数组将变为 [4, 1, 0, 0]。
对于 i = 1(l = 0, r = 2, val = 1):
在下标 [0, 1, 2] 处分别减少 [1, 1, 0]。
数组将变为 [3, 0, 0, 0],这不是一个零数组。

提示:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 5 * 10^5
  • 1 <= queries.length <= 10^5
  • queries[i].length == 3
  • 0 <= li <= ri < nums.length
  • 1 <= vali <= 5

思路

有一个长度为 n 的整数数组,每一次操作可以将给定范围内的任意元素 最多 减去 vali = queries[2],计算将数组中的所有元素变为 0 最少需要按顺序操作几次。

3355.零数组变换I 相比,求的是最小操作次数,每一次操作都需要判断数组元素是否全为 0,涉及到区间修改与区间查询,可以使用线段树维护区间最大值,每次操作后判断最大值是否大于 0

官网给出了另一种思路,二分查找操作次数 k,针对每一个 k 问题变成 3355.零数组变换I

代码


/**
 * @date 2025-05-21 9:35
 */
public class MinZeroArray3356 {

    public int minZeroArray(int[] nums, int[][] queries) {
        int right = queries.length - 1;
        int left = -1;
        int mid = left + (right - left) / 2;
        while (left <= right) {
            if (check(nums, mid, queries)) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
            mid = left + (right - left) / 2;
        }
        return left == queries.length ? -1 : left + 1;
    }

    public boolean check(int[] nums, int k, int[][] queries) {
        int n = nums.length;
        int[] diff = new int[n + 1];
        diff[0] = nums[0];
        for (int i = 1; i < n; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
        for (int i = 0; i <= k; i++) {
            int[] query = queries[i];
            int val = query[2];
            diff[query[0]] -= val;
            diff[query[1] + 1] += val;
        }
        int num = 0;
        for (int i = 0; i < n; i++) {
            num += diff[i];
            if (num > 0) {
                return false;
            }
        }
        return true;
    }

}

性能

3355.零数组变换I

目标

给定一个长度为 n 的整数数组 nums 和一个二维数组 queries,其中 queries[i] = [li, ri]。

对于每个查询 queries[i]:

  • 在 nums 的下标范围 [li, ri] 内选择一个下标 子集。
  • 将选中的每个下标对应的元素值减 1。

零数组 是指所有元素都等于 0 的数组。

如果在按顺序处理所有查询后,可以将 nums 转换为 零数组 ,则返回 true,否则返回 false。

示例 1:

输入: nums = [1,0,1], queries = [[0,2]]
输出: true
解释:
对于 i = 0:
选择下标子集 [0, 2] 并将这些下标处的值减 1。
数组将变为 [0, 0, 0],这是一个零数组。

示例 2:

输入: nums = [4,3,2,1], queries = [[1,3],[0,2]]
输出: false
解释:
对于 i = 0: 
选择下标子集 [1, 2, 3] 并将这些下标处的值减 1。
数组将变为 [4, 2, 1, 0]。
对于 i = 1:
选择下标子集 [0, 1, 2] 并将这些下标处的值减 1。
数组将变为 [3, 1, 0, 0],这不是一个零数组。

说明:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i].length == 2
  • 0 <= li <= ri < nums.length

思路

有一个长度为 n 的整数数组,每一次操作可以将给定范围内的任意元素减 1,判断操作完成后能否将数组中的所有元素变为 0

使用差分数组批量修改元素,最后判断是否存在元素大于 0 即可。

代码


/**
 * @date 2025-05-20 9:37
 */
public class IsZeroArray3355 {

    public boolean isZeroArray(int[] nums, int[][] queries) {
        int n = nums.length;
        int[] diff = new int[n + 1];
        diff[0] = nums[0];
        for (int i = 1; i < n; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
        for (int[] query : queries) {
            diff[query[0]] -= 1;
            diff[query[1] + 1] += 1;
        }
        int num = 0;
        for (int i = 0; i < n; i++) {
            num += diff[i];
            if (num > 0) {
                return false;
            }
        }
        return true;
    }

}

性能

3024.三角形类型

目标

给你一个下标从 0 开始长度为 3 的整数数组 nums ,需要用它们来构造三角形。

  • 如果一个三角形的所有边长度相等,那么这个三角形称为 equilateral 。
  • 如果一个三角形恰好有两条边长度相等,那么这个三角形称为 isosceles 。
  • 如果一个三角形三条边的长度互不相同,那么这个三角形称为 scalene 。

如果这个数组无法构成一个三角形,请你返回字符串 "none" ,否则返回一个字符串表示这个三角形的类型。

示例 1:

输入:nums = [3,3,3]
输出:"equilateral"
解释:由于三条边长度相等,所以可以构成一个等边三角形,返回 "equilateral" 。

示例 2:

输入:nums = [3,4,5]
输出:"scalene"
解释:
nums[0] + nums[1] = 3 + 4 = 7 ,大于 nums[2] = 5 。
nums[0] + nums[2] = 3 + 5 = 8 ,大于 nums[1] = 4 。
nums[1] + nums[2] = 4 + 5 = 9 ,大于 nums[0] = 3 。
由于任意两边之和都大于第三边,所以可以构成一个三角形,因为三条边的长度互不相等,所以返回 "scalene"。

说明:

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

思路

已知三角形的三条边的长度,判断三角形的类型。

将数组 nums 排序,判断 nums[2] < nums[0] + nums[1] 如果不满足返回 none,如果 nums[0] == nums[2] 返回 equilateral,如果 nums[0] == nums[1] || nums[1] == nums[2] 返回 isosceles,否则返回 scalene

代码


/**
 * @date 2025-05-19 8:47
 */
public class TriangleType3024 {

    public String triangleType(int[] nums) {
        Arrays.sort(nums);
        if (nums[0] + nums[1] <= nums[2]) {
            return "none";
        }
        if (nums[0] == nums[2]) {
            return "equilateral";
        } else if (nums[0] == nums[1] || nums[1] == nums[2]) {
            return "isosceles";
        } else {
            return "scalene";
        }
    }

}

性能

1931.用三种不同颜色为网格涂色

目标

给你两个整数 m 和 n 。构造一个 m x n 的网格,其中每个单元格最开始是白色。请你用 红、绿、蓝 三种颜色为每个单元格涂色。所有单元格都需要被涂色。

涂色方案需要满足:不存在相邻两个单元格颜色相同的情况 。返回网格涂色的方法数。因为答案可能非常大, 返回 对 10^9 + 7 取余 的结果。

示例 1:

输入:m = 1, n = 1
输出:3
解释:如上图所示,存在三种可能的涂色方案。

示例 2:

输入:m = 1, n = 2
输出:6
解释:如上图所示,存在六种可能的涂色方案。

示例 3:

输入:m = 5, n = 5
输出:580986

说明:

  • 1 <= m <= 5
  • 1 <= n <= 1000

思路

// todo 状压DP

代码

性能