3186.施咒的最大总伤害

目标

一个魔法师有许多不同的咒语。

给你一个数组 power ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。

已知魔法师使用伤害值为 power[i] 的咒语时,他们就 不能 使用伤害为 power[i] - 2 ,power[i] - 1 ,power[i] + 1 或者 power[i] + 2 的咒语。

每个咒语最多只能被使用 一次 。

请你返回这个魔法师可以达到的伤害值之和的 最大值 。

示例 1:

输入:power = [1,1,3,4]
输出:6
解释:
可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。

示例 2:

输入:power = [7,1,6,6]
输出:13
解释:
可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。

说明:

  • 1 <= power.length <= 10^5
  • 1 <= power[i] <= 10^9

思路

有一个数组 power 表示咒语的伤害值,魔法师使用其中任意咒语后,就无法再使用伤害值 +1 +2 -1 -2 的咒语,每个咒语只能使用一次,求伤害值的最大值。

使用哈希表记录伤害值相同的和,创建新数组(不包含重复伤害值),根据伤害值排序。如果不选当前伤害值,状态可以从后面转移而来,如果选则从后面大于当前伤害值 + 2 的转移而来。

代码


/**
 * @date 2025-10-11 8:57
 */
public class MaximumTotalDamage3186 {

    public long maximumTotalDamage(int[] power) {
        Map<Integer, Long> sum = new HashMap<>();
        for (int i : power) {
            sum.merge(i, (long) i, Long::sum);
        }
        int n = sum.size();
        int[] powers = new int[n];
        int i = 0;
        for (Integer damage : sum.keySet()) {
            powers[i++] = damage;
        }
        Arrays.sort(powers);
        long[] mem = new long[n + 1];
        mem[n - 1] = sum.get(powers[n - 1]);
        for (int j = n - 2; j >= 0; j--) {
            int k = j;
            while (k < n && powers[k] - powers[j] <= 2) {
                k++;
            }
            mem[j] = Math.max(mem[j + 1], mem[k] + sum.get(powers[j]));
        }
        return mem[0];
    }

}

性能

166.分数到小数

目标

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。

如果小数部分为循环小数,则将循环的部分括在括号内。

如果存在多个答案,只需返回 任意一个 。

对于所有给定的输入,保证 答案字符串的长度小于 10^4 。

示例 1:

输入:numerator = 1, denominator = 2
输出:"0.5"

示例 2:

输入:numerator = 2, denominator = 1
输出:"2"

示例 3:

输入:numerator = 4, denominator = 333
输出:"0.(012)"

说明:

  • -2^31 <= numerator, denominator <= 2^31 - 1
  • denominator != 0

思路

有一个分数,分子分母均为整数,以字符串的形式返回小数,如果是循环小数,将循环部分括在括号内。

关键是如何确定从哪里开始循环?记录余数对应的商的下标,如果余数重复出现说明进入了循环节,根据下标来找出循环节。

代码


/**
 * @date 2025-09-24 9:13
 */
public class FractionToDecimal166 {

    public String fractionToDecimal_v1(int numerator, int denominator) {
        long a = numerator;
        long b = denominator;
        if (a % b == 0) {
            return String.valueOf(a / b);
        }
        StringBuilder sb = new StringBuilder();
        if (a * b < 0) {
            sb.append("-");
        }
        a = Math.abs(a);
        b = Math.abs(b);
        long d = a / b;
        sb.append(d);
        long rem = a % b;
        sb.append(".");
        StringBuilder fraction = new StringBuilder();
        Map<Long, Integer> map = new HashMap<>();
        int i = 0;
        while (rem != 0) {
            if (map.get(rem) != null) {
                return sb.append(fraction.substring(0, map.get(rem)))
                        .append("(")
                        .append(fraction.substring(map.get(rem)))
                        .append(")").toString();
            }
            map.put(rem, i);
            rem *= 10;
            if (rem < b) {
                fraction.append(0);
            } else {
                d = rem / b;
                fraction.append(d);
                rem = rem % b;
            }
            i++;
        }
        return sb.append(fraction).toString();
    }

}

性能

3508.设计路由器

目标

请你设计一个数据结构来高效管理网络路由器中的数据包。每个数据包包含以下属性:

  • source:生成该数据包的机器的唯一标识符。
  • destination:目标机器的唯一标识符。
  • timestamp:该数据包到达路由器的时间戳。

实现 Router 类:

Router(int memoryLimit):初始化路由器对象,并设置固定的内存限制。

  • memoryLimit 是路由器在任意时间点可以存储的 最大 数据包数量。
  • 如果添加一个新数据包会超过这个限制,则必须移除 最旧的 数据包以腾出空间。

bool addPacket(int source, int destination, int timestamp):将具有给定属性的数据包添加到路由器。

  • 如果路由器中已经存在一个具有相同 source、destination 和 timestamp 的数据包,则视为重复数据包。
  • 如果数据包成功添加(即不是重复数据包),返回 true;否则返回 false。

int[] forwardPacket():以 FIFO(先进先出)顺序转发下一个数据包。

  • 从存储中移除该数据包。
  • 以数组 [source, destination, timestamp] 的形式返回该数据包。
  • 如果没有数据包可以转发,则返回空数组。

int getCount(int destination, int startTime, int endTime):

  • 返回当前存储在路由器中(即尚未转发)的,且目标地址为指定 destination 且时间戳在范围 [startTime, endTime](包括两端)内的数据包数量。

注意:对于 addPacket 的查询会按照 timestamp 的递增顺序进行。

示例 1:

输入:
["Router", "addPacket", "addPacket", "addPacket", "addPacket", "addPacket", "forwardPacket", "addPacket", "getCount"]
[[3], [1, 4, 90], [2, 5, 90], [1, 4, 90], [3, 5, 95], [4, 5, 105], [], [5, 2, 110], [5, 100, 110]]
输出:
[null, true, true, false, true, true, [2, 5, 90], true, 1]
解释:
Router router = new Router(3); // 初始化路由器,内存限制为 3。
router.addPacket(1, 4, 90); // 数据包被添加,返回 True。
router.addPacket(2, 5, 90); // 数据包被添加,返回 True。
router.addPacket(1, 4, 90); // 这是一个重复数据包,返回 False。
router.addPacket(3, 5, 95); // 数据包被添加,返回 True。
router.addPacket(4, 5, 105); // 数据包被添加,[1, 4, 90] 被移除,因为数据包数量超过限制,返回 True。
router.forwardPacket(); // 转发数据包 [2, 5, 90] 并将其从路由器中移除。
router.addPacket(5, 2, 110); // 数据包被添加,返回 True。
router.getCount(5, 100, 110); // 唯一目标地址为 5 且时间在 [100, 110] 范围内的数据包是 [4, 5, 105],返回 1。

示例 2:

输入:
["Router", "addPacket", "forwardPacket", "forwardPacket"]
[[2], [7, 4, 90], [], []]
输出:
[null, true, [7, 4, 90], []]
解释:
Router router = new Router(2); // 初始化路由器,内存限制为 2。
router.addPacket(7, 4, 90); // 返回 True。
router.forwardPacket(); // 返回 [7, 4, 90]。
router.forwardPacket(); // 没有数据包可以转发,返回 []。

说明:

  • 2 <= memoryLimit <= 10^5
  • 1 <= source, destination <= 2 * 10^5
  • 1 <= timestamp <= 10^9
  • 1 <= startTime <= endTime <= 10^9
  • addPacket、forwardPacket 和 getCount 方法的总调用次数最多为 10^5。
  • 对于 addPacket 的查询,timestamp 按递增顺序给出。

思路

//todo

代码

性能

3484.设计电子表格

目标

电子表格是一个网格,它有 26 列(从 'A' 到 'Z')和指定数量的 rows。每个单元格可以存储一个 0 到 10^5 之间的整数值。

请你实现一个 Spreadsheet 类:

  • Spreadsheet(int rows) 初始化一个具有 26 列(从 'A' 到 'Z')和指定行数的电子表格。所有单元格最初的值都为 0 。
  • void setCell(String cell, int value) 设置指定单元格的值。单元格引用以 "AX" 的格式提供(例如,"A1","B10"),其中字母表示列(从 'A' 到 'Z'),数字表示从 1 开始的行号。
  • void resetCell(String cell) 重置指定单元格的值为 0 。
  • int getValue(String formula) 计算一个公式的值,格式为 "=X+Y",其中 X 和 Y 要么 是单元格引用,要么非负整数,返回计算的和。

注意: 如果 getValue 引用一个未通过 setCell 明确设置的单元格,则该单元格的值默认为 0 。

示例 1:

输入:
["Spreadsheet", "getValue", "setCell", "getValue", "setCell", "getValue", "resetCell", "getValue"]
[[3], ["=5+7"], ["A1", 10], ["=A1+6"], ["B2", 15], ["=A1+B2"], ["A1"], ["=A1+B2"]]
输出:
[null, 12, null, 16, null, 25, null, 15]
解释
Spreadsheet spreadsheet = new Spreadsheet(3); // 初始化一个具有 3 行和 26 列的电子表格
spreadsheet.getValue("=5+7"); // 返回 12 (5+7)
spreadsheet.setCell("A1", 10); // 设置 A1 为 10
spreadsheet.getValue("=A1+6"); // 返回 16 (10+6)
spreadsheet.setCell("B2", 15); // 设置 B2 为 15
spreadsheet.getValue("=A1+B2"); // 返回 25 (10+15)
spreadsheet.resetCell("A1"); // 重置 A1 为 0
spreadsheet.getValue("=A1+B2"); // 返回 15 (0+15)

说明:

  • 1 <= rows <= 10^3
  • 0 <= value <= 10^5
  • 公式保证采用 "=X+Y" 格式,其中 X 和 Y 要么是有效的单元格引用,要么是小于等于 10^5 的 非负 整数。
  • 每个单元格引用由一个大写字母 'A' 到 'Z' 和一个介于 1 和 rows 之间的行号组成。
  • 总共 最多会对 setCell、resetCell 和 getValue 调用 10^4 次。

思路

依题意模拟即可。

代码


/**
 * @date 2025-09-19 8:38
 */
public class Spreadsheet {

    private final int[][] data;

    public Spreadsheet(int rows) {
        data = new int[26][rows + 1];
    }

    public void setCell(String cell, int value) {
        int col = cell.charAt(0) - 'A';
        int row = Integer.parseInt(cell.substring(1));
        data[col][row] = value;
    }

    public void resetCell(String cell) {
        int col = cell.charAt(0) - 'A';
        int row = Integer.parseInt(cell.substring(1));
        data[col][row] = 0;
    }

    public int getValue(String formula) {
        String[] params = formula.substring(1).split("\\+");
        int res = 0;
        for (String param : params) {
            if (param.charAt(0) < 'A' || param.charAt(0) > 'Z') {
                res += Integer.parseInt(param);
            } else {
                int col = param.charAt(0) - 'A';
                int row = Integer.parseInt(param.substring(1));
                res += data[col][row];
            }
        }
        return res;
    }
}

性能

3408.设计任务管理器

目标

一个任务管理器系统可以让用户管理他们的任务,每个任务有一个优先级。这个系统需要高效地处理添加、修改、执行和删除任务的操作。

请你设计一个 TaskManager 类:

TaskManager(vector<vector>& tasks) 初始化任务管理器,初始化的数组格式为 [userId, taskId, priority] ,表示给 userId 添加一个优先级为 priority 的任务 taskId 。

void add(int userId, int taskId, int priority) 表示给用户 userId 添加一个优先级为 priority 的任务 taskId ,输入 保证 taskId 不在系统中。

void edit(int taskId, int newPriority) 更新已经存在的任务 taskId 的优先级为 newPriority 。输入 保证 taskId 存在于系统中。

void rmv(int taskId) 从系统中删除任务 taskId 。输入 保证 taskId 存在于系统中。

int execTop() 执行所有用户的任务中优先级 最高 的任务,如果有多个任务优先级相同且都为 最高 ,执行 taskId 最大的一个任务。执行完任务后,taskId 从系统中 删除 。同时请你返回这个任务所属的用户 userId 。如果不存在任何任务,返回 -1 。

注意 ,一个用户可能被安排多个任务。

示例 1:

输入:
["TaskManager", "add", "edit", "execTop", "rmv", "add", "execTop"]
[[[[1, 101, 10], [2, 102, 20], [3, 103, 15]]], [4, 104, 5], [102, 8], [], [101], [5, 105, 15], []]
输出:
[null, null, null, 3, null, null, 5]
解释:
TaskManager taskManager = new TaskManager([[1, 101, 10], [2, 102, 20], [3, 103, 15]]); // 分别给用户 1 ,2 和 3 初始化一个任务。
taskManager.add(4, 104, 5); // 给用户 4 添加优先级为 5 的任务 104 。
taskManager.edit(102, 8); // 更新任务 102 的优先级为 8 。
taskManager.execTop(); // 返回 3 。执行用户 3 的任务 103 。
taskManager.rmv(101); // 将系统中的任务 101 删除。
taskManager.add(5, 105, 15); // 给用户 5 添加优先级为 15 的任务 105 。
taskManager.execTop(); // 返回 5 。执行用户 5 的任务 105 。

说明:

  • 1 <= tasks.length <= 10^5
  • 0 <= userId <= 10^5
  • 0 <= taskId <= 10^5
  • 0 <= priority <= 10^9
  • 0 <= newPriority <= 10^9
  • add ,edit ,rmv 和 execTop 的总操作次数 加起来 不超过 2 * 10^5 次。
  • 输入保证 taskId 是合法的。

思路

2349.设计数字容器系统 类似,同样是懒删除。

代码


/**
 * @date 2025-09-18 8:42
 */
public class TaskManager3408 {

    static class TaskManager {

        private final Map<Integer, int[]> taskIdMap = new HashMap<>();
        private final PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> {
            int compare = b[2] - a[2];
            if (compare != 0) {
                return compare;
            }
            return b[1] - a[1];
        });

        public TaskManager(List<List<Integer>> tasks) {
            for (List<Integer> task : tasks) {
                int[] t = {task.get(0), task.get(1), task.get(2)};
                taskIdMap.put(t[1], t);
                q.offer(t);
            }
        }

        public void add(int userId, int taskId, int priority) {
            int[] t = {userId, taskId, priority};
            taskIdMap.put(taskId, t);
            q.offer(t);
        }

        public void edit(int taskId, int newPriority) {
            int[] t = taskIdMap.get(taskId);
            t = new int[]{t[0], t[1], newPriority};
            taskIdMap.put(taskId, t);
            q.offer(t);
        }

        public void rmv(int taskId) {
            taskIdMap.remove(taskId);
        }

        public int execTop() {
            while (!q.isEmpty() && (taskIdMap.get(q.peek()[1]) == null || taskIdMap.get(q.peek()[1])[2] != q.peek()[2]|| taskIdMap.get(q.peek()[1])[0] != q.peek()[0])) {
                q.poll();
            }
            if (q.isEmpty()) {
                return -1;
            }
            int[] task = q.poll();
            taskIdMap.remove(task[1]);
            return task[0];
        }
    }

}

性能

2349.设计数字容器系统

目标

设计一个数字容器系统,可以实现以下功能:

  • 在系统中给定下标处 插入 或者 替换 一个数字。
  • 返回 系统中给定数字的最小下标。

请你实现一个 NumberContainers 类:

  • NumberContainers() 初始化数字容器系统。
  • void change(int index, int number) 在下标 index 处填入 number 。如果该下标 index 处已经有数字了,那么用 number 替换该数字。
  • int find(int number) 返回给定数字 number 在系统中的最小下标。如果系统中没有 number ,那么返回 -1 。

示例:

输入:
["NumberContainers", "find", "change", "change", "change", "change", "find", "change", "find"]
[[], [10], [2, 10], [1, 10], [3, 10], [5, 10], [10], [1, 20], [10]]
输出:
[null, -1, null, null, null, null, 1, null, 2]
解释:
NumberContainers nc = new NumberContainers();
nc.find(10); // 没有数字 10 ,所以返回 -1 。
nc.change(2, 10); // 容器中下标为 2 处填入数字 10 。
nc.change(1, 10); // 容器中下标为 1 处填入数字 10 。
nc.change(3, 10); // 容器中下标为 3 处填入数字 10 。
nc.change(5, 10); // 容器中下标为 5 处填入数字 10 。
nc.find(10); // 数字 10 所在的下标为 1 ,2 ,3 和 5 。因为最小下标为 1 ,所以返回 1 。
nc.change(1, 20); // 容器中下标为 1 处填入数字 20 。注意,下标 1 处之前为 10 ,现在被替换为 20 。
nc.find(10); // 数字 10 所在下标为 2 ,3 和 5 。最小下标为 2 ,所以返回 2 。

说明:

  • 1 <= index, number <= 10^9
  • 调用 change 和 find 的 总次数 不超过 10^5 次。

思路

设计一个数字容器,能够将数字更新到指定下标,并且查询数字的最小下标。

使用最小堆维护相同数字的最小下标,同时使用哈希表记录下标对应的数字。查询最小下标时,如果下标上的数字不是查询的数字则从堆中删除。

代码


/**
 * @date 2025-09-17 8:48
 */
public class NumberContainers {

    private final Map<Integer, Integer> indexToValue;
    private final Map<Integer, PriorityQueue<Integer>> valueToIndex;

    public NumberContainers() {
        indexToValue = new HashMap<>();
        valueToIndex = new HashMap<>();
    }

    public void change(int index, int number) {
        indexToValue.put(index, number);
        valueToIndex.putIfAbsent(number, new PriorityQueue<>());
        valueToIndex.get(number).offer(index);
    }

    public int find(int number) {
        PriorityQueue<Integer> q = valueToIndex.get(number);
        if (q == null) {
            return -1;
        }
        while (!q.isEmpty() && indexToValue.get(q.peek()) != number) {
            q.poll();
        }
        return q.isEmpty() ? -1 : q.peek();
    }
}

性能

1935.可以输入的最大单词数

目标

键盘出现了一些故障,有些字母键无法正常工作。而键盘上所有其他键都能够正常工作。

给你一个由若干单词组成的字符串 text ,单词间由单个空格组成(不含前导和尾随空格);另有一个字符串 brokenLetters ,由所有已损坏的不同字母键组成,返回你可以使用此键盘完全输入的 text 中单词的数目。

示例 1:

输入:text = "hello world", brokenLetters = "ad"
输出:1
解释:无法输入 "world" ,因为字母键 'd' 已损坏。

示例 2:

输入:text = "leet code", brokenLetters = "lt"
输出:1
解释:无法输入 "leet" ,因为字母键 'l' 和 't' 已损坏。

示例 3:

输入:text = "leet code", brokenLetters = "e"
输出:0
解释:无法输入任何单词,因为字母键 'e' 已损坏。

说明:

  • 1 <= text.length <= 10^4
  • 0 <= brokenLetters.length <= 26
  • text 由若干用单个空格分隔的单词组成,且不含任何前导和尾随空格
  • 每个单词仅由小写英文字母组成
  • brokenLetters 由 互不相同 的小写英文字母组成

思路

有一个字符串,其中的单词由空格分隔,现在键盘有一些按键不能正常工作,返回能够正常输入的单词个数。

使用哈希表记录不正常的按键,分割单词,判断单词是否包含不正常字符。

代码


/**
 * @date 2025-09-15 8:46
 */
public class CanBeTypedWords1935 {

    public int canBeTypedWords(String text, String brokenLetters) {
        Set<Character> brokenSet = new HashSet<>();
        for (char c : brokenLetters.toCharArray()) {
            brokenSet.add(c);
        }
        String[] words = text.split(" ");
        int res = words.length;
        for (String word : words) {
            for (char c : word.toCharArray()) {
                if (brokenSet.contains(c)){
                    res--;
                    break;
                }
            }
        }
        return res;
    }
}

性能

966.元音拼写检查器

目标

在给定单词列表 wordlist 的情况下,我们希望实现一个拼写检查器,将查询单词转换为正确的单词。

对于给定的查询单词 query,拼写检查器将会处理两类拼写错误:

  • 大小写:如果查询匹配单词列表中的某个单词(不区分大小写),则返回的正确单词与单词列表中的大小写相同。
    • 例如:wordlist = ["yellow"], query = "YellOw": correct = "yellow"
    • 例如:wordlist = ["Yellow"], query = "yellow": correct = "Yellow"
    • 例如:wordlist = ["yellow"], query = "yellow": correct = "yellow"
  • 元音错误:如果在将查询单词中的元音 ('a', 'e', 'i', 'o', 'u') 分别替换为任何元音后,能与单词列表中的单词匹配(不区分大小写),则返回的正确单词与单词列表中的匹配项大小写相同。
    • 例如:wordlist = ["YellOw"], query = "yollow": correct = "YellOw"
    • 例如:wordlist = ["YellOw"], query = "yeellow": correct = "" (无匹配项)
    • 例如:wordlist = ["YellOw"], query = "yllw": correct = "" (无匹配项)

此外,拼写检查器还按照以下优先级规则操作:

  • 当查询完全匹配单词列表中的某个单词(区分大小写)时,应返回相同的单词。
  • 当查询匹配到大小写问题的单词时,您应该返回单词列表中的第一个这样的匹配项。
  • 当查询匹配到元音错误的单词时,您应该返回单词列表中的第一个这样的匹配项。
  • 如果该查询在单词列表中没有匹配项,则应返回空字符串。

给出一些查询 queries,返回一个单词列表 answer,其中 answer[i] 是由查询 query = queries[i] 得到的正确单词。

示例 1:

输入:wordlist = ["KiTe","kite","hare","Hare"], queries = ["kite","Kite","KiTe","Hare","HARE","Hear","hear","keti","keet","keto"]
输出:["kite","KiTe","KiTe","Hare","hare","","","KiTe","","KiTe"]

示例 2:

输入:wordlist = ["yellow"], queries = ["YellOw"]
输出:["yellow"]

说明:

  • 1 <= wordlist.length, queries.length <= 5000
  • 1 <= wordlist[i].length, queries[i].length <= 7
  • wordlist[i] 和 queries[i] 只包含英文字母

思路

有一个单词列表 wordlist,将输入单词 query 按照下面规则转换为列表中的单词:

  • 如果 query 在 wordlist 中直接返回
  • 如果 query 忽略大小写后在 wordlist 中返回第一个匹配的单词
  • 如果 query 忽略大小并且其中的元音替换为任意元音后在 wordlist 中返回第一个匹配的单词
  • 如果以上都没有匹配返回空串

依题意模拟即可。创建三个哈希表,key 分别为 原单词、忽略大小写后的单词以及 将元音字母替换为通配符后的单词。

代码


/**
 * @date 2025-09-14 21:25
 */
public class Spellchecker966 {

    public String[] spellchecker(String[] wordlist, String[] queries) {
        Set<String> originSet = new HashSet<>();
        Map<String, List<String>> caseMap = new HashMap<>();
        Map<String, List<String>> vowelMap = new HashMap<>();
        for (String word : wordlist) {
            originSet.add(word);
            String key = word.toLowerCase();
            caseMap.putIfAbsent(key, new ArrayList<>());
            caseMap.get(key).add(word);
            String vowelKey = key.replaceAll("[aeiouAEIOU]", "*");
            vowelMap.putIfAbsent(vowelKey, new ArrayList<>());
            vowelMap.get(vowelKey).add(word);
        }
        for (int i = 0; i < queries.length; i++) {
            String query = queries[i];
            if (originSet.contains(query)) {
                continue;
            }
            String key = query.toLowerCase();
            List<String> caseList = caseMap.get(key);
            if (caseList != null && caseList.size() > 0) {
                queries[i] = caseList.get(0);
                continue;
            }
            List<String> vowelList = vowelMap.get(key.replaceAll("[aeiouAEIOU]", "*"));
            if (vowelList != null && vowelList.size() > 0) {
                queries[i] = vowelList.get(0);
                continue;
            }
            queries[i] = "";
        }
        return queries;
    }

}

性能

3487.删除后的最大子数组元素和

目标

给你一个整数数组 nums 。

你可以从数组 nums 中删除任意数量的元素,但不能将其变为 空 数组。执行删除操作后,选出 nums 中满足下述条件的一个子数组:

  • 子数组中的所有元素 互不相同 。
  • 最大化 子数组的元素和。

返回子数组的 最大元素和 。

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

示例 1:

输入:nums = [1,2,3,4,5]
输出:15
解释:
不删除任何元素,选中整个数组得到最大元素和。

示例 2:

输入:nums = [1,1,0,1,1]
输出:1
解释:
删除元素 nums[0] == 1、nums[1] == 1、nums[2] == 0 和 nums[3] == 1 。选中整个数组 [1] 得到最大元素和。

示例 3:

输入:nums = [1,2,-1,-2,1,0,-1]
输出:3
解释:
删除元素 nums[2] == -1 和 nums[3] == -2 ,从 [1, 2, 1, 0, -1] 中选中子数组 [2, 1] 以获得最大元素和。

说明:

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

思路

有一个数组 nums,允许删除其中任意元素得到一个 非空 数组,使得最终数组中 不包含重复元素 并且所有 元素和最大

为了使和最大,我们应该删掉所有的负数,同时对剩余元素去重累加求和。需要注意如果数组中全是负数,需要保留最大元素。

代码


/**
 * @date 2025-07-25 8:55
 */
public class MaxSum3487 {

    public int maxSum(int[] nums) {
        int sum = Arrays.stream(nums).distinct().filter(x -> x > 0).sum();
        if (sum == 0){
            OptionalInt max = Arrays.stream(nums).max();
            return max.isPresent() ? max.getAsInt() : sum;
        }
        return sum;
    }

}

性能

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;
    }

}

性能