Leetcode 20250718到20250724每日一题的记录
2163. 删除元素后和的最小差值
20250718
题目链接
参考
思路
为了使前$n$个数减后$n$个数的差值最小
在前$n + i$个数中去掉$i$个最大值
维护一个数组代表当前$i$下前面数组和的最小值
用大顶堆将前$n$个元素入堆
用小顶堆将后$n$个元素入堆
Java 大顶堆(优先队列)
PriorityQueue<Integer> pq = new PriorityQueue<>(n, new Comparator<Integer>() {
@Override
public int compare(Integer a,Integer b) {
return b - a;
}
}); //优先队列
优先队列相关操作
pq.offer(i)元素入堆- 与
pq.add(i)的区别是 向满的队列加元素时: pq.offer(i)会返回false,pq.add(i)会抛出异常
- 与
pq.poll()元素出堆并返回当前最大 小( 值) - 与
pq.remove()的区别是 向空的队列删元素时: pq.poll()会返回null,pq.remove()会抛出异常
- 与
pq.peek()返回当前最大 小( 值) - 与
pq.element()的区别是 向空的队列删元素时: pq.peek()会返回null,pq.element()会抛出异常
- 与
其他
Math.max(a, b)求2个数的最大值Math.min(a, b)求2个数的最小值
复杂度
- 时间复杂度
堆操作每次$O(\log{(N)})$: 因此总体是$O(N\log{(N)})$, - 空间复杂度
堆: 数组$O(N)$、
代码
class Solution {
public long minimumDifference(int[] nums) {
int len = nums.length;
int n = len / 3;
long[] minv = new long[n + 1];
minv[0] = 0;
PriorityQueue<Integer> pq = new PriorityQueue<>(n, new Comparator<Integer>() {
@Override
public int compare(Integer a,Integer b) {
return b - a;
}
});
for (int i = 0; i < n; i++) {
pq.offer(nums[i]);
minv[0] += nums[i];
}
for (int i = n; i < 2 * n; i++) {
if (nums[i] >= pq.peek()) {
minv[i - n + 1] = minv[i - n];
} else {
minv[i - n + 1] = minv[i - n] + nums[i] - pq.peek();
pq.poll();
pq.offer(nums[i]);
}
}
pq = new PriorityQueue<Integer>(n);
long sum = 0;
for (int i = 2 * n; i < len; i++) {
pq.offer(nums[i]);
sum += nums[i];
}
long res = minv[n] - sum;
for (int i = 2 * n - 1; i >= n; i--) {
if (nums[i] > pq.peek()) {
sum = sum + nums[i] - pq.peek();
pq.poll();
pq.offer(nums[i]);
}
res = Math.min(res, minv[i - n] - sum);
}
return res;
}
}
1233. 删除子文件夹
20250719
题目链接
参考
用时前排的代码
解法1 排序
思路
将字符串数组按字典序排序后一定会按照$1$个文件夹后面跟着$n$个($n \geq 0$)该文件夹的子文件夹这种形式
例如
/a
/a/b
---
/c/d
/c/d/e
---
/c/f
字典序
$aa < ab < abc$
- 第一个不同的字母小的字典序小
- 如果一个字符串是另一个字符串的前缀
并且较短, 它的字典序较小, - $/ < 数字 < 大写字母 < 小写字母$
ascii码顺序
复杂度
- 时间复杂度
$l$为字符串平均长度: 排序$O(N \log(N))$, 整体$O(Nl \log(N))$, - 空间复杂度
存储结果$O(Nl)$:
其他
- 字符串长度
s.length() - 字符串子串
s.substring(a, b) 从a到b, 包括a不包括b, - 数组排序
Arrays.sort(array)
优化
按照字符串长度排序
用集合存储结果
自定义数组排序Comparator
Arrays.sort(folder, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
代码
class Solution {
public List<String> removeSubfolders(String[] folder) {
List<String> res = new LinkedList<>();
Arrays.sort(folder);
String pref = "*";
for (String s : folder) {
if (s.equals("/") || s.contains("//")) {
continue;
}
if (!(s.length() > pref.length() && pref.equals(s.substring(0, pref.length())) && s.charAt(pref.length()) == '/')) {
pref = s;
res.add(s);
}
}
return res;
}
}
优化后
class Solution {
public List<String> removeSubfolders(String[] folder) {
List<String> ans = new ArrayList<>();
Arrays.sort(folder, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
Set<String> set = new HashSet<>();
for (String f : folder) {
int n = f.length();
boolean flag = false;
for (int i = 1; i < n; i++) {
if (f.charAt(i) == '/') {
String sub = f.substring(0, i);
if (set.contains(sub)) {
flag = true;
break;
}
}
}
if (!flag) {
ans.add(f);
set.add(f);
}
}
return ans;
}
}
解法2 前缀树
思路
按照/分割每一个文件夹构造前缀树
复杂度
- 时间复杂度
构造前缀树: DFS需要$O(Nl)$、 - 空间复杂度
存储前缀树需要$O(Nl)$:
优化
- 前缀树的节点可以存储字符串的哈希以提升效率
数据范围规定文件夹只有小写字母, 将字符串当作数字, 选择一个26及以上的数字当作进制数构造哈希, , 虽然感觉碰撞率很高( 但是可以通过本题, ) 使用。 str.hashCode()时间上会慢一些。 - 在构造前缀树的时候
如果当前节点存在且有标记, 则表示当前文件夹必然是子文件夹, 可以直接舍弃当前文件夹, 。
其他
Map操作
map.putIfAbsent(key, value) 如果key不存在则执行, map.put(key, value) 如果key存在则不执行;
代码
class Solution {
class Trie {
int index;
Map<Integer, Trie> children;
public Trie() {
index = -1;
children = new HashMap<>();
}
}
public List<String> removeSubfolders(String[] folder) {
List<String> res = new ArrayList<>();
Trie root = new Trie();
Trie cur = root;
for (int i = 0; i < folder.length; i++) {
int hash = 0;
cur = root;
boolean flag = false;
for (int j = 1; j < folder[i].length(); j++) {
if (folder[i].charAt(j) == '/') {
if (cur.index != -1) {
flag = true;
break;
}
cur.children.putIfAbsent(hash, new Trie());
cur = cur.children.get(hash);
hash = 0;
} else {
hash = hash * 28 + folder[i].charAt(j);
}
}
if (flag) {
continue;
}
cur.children.putIfAbsent(hash, new Trie());
cur = cur.children.get(hash);
if (cur.index == -1) {
cur.index = i;
}
}
dfs(root, res, folder);
return res;
}
private void dfs(Trie cur, List<String> res, String[] folder) {
if (cur.index != -1) {
res.add(folder[cur.index]);
return;
}
for (Trie t : cur.children.values()) {
dfs(t, res, folder);
}
}
}
1948. 删除系统中的重复文件夹
20250720
题目链接
参考
执行用时前排代码
思路
后序遍历整棵树map
然后DFS整棵树
子树序列化方法
$serial(node) = child1(serial(child1))child2(serial(child2))...childn(serial(childn))$
$child$之间按照字典序排序
优化1
生成序列化子树时Map中查询到有节点存在Map中的节点和当前节点
在DFS时
优化2
本题的输入保证了如果有$n$个路径
如果按照长度对路径数组排序
从前向后遍历路径数组建树
从后向前遍历增加的节点Map中查询Map
对于子节点可以使用TreeMap存储以保证其按字典序排列
设置删除标记的时候要将它的子节点同时设置删除标记
最后从前向后遍历所有节点
代码
class Solution {
public List<List<String>> deleteDuplicateFolder(List<List<String>> paths) {
Trie root = new Trie();
for (List<String> list : paths) {
Trie cur = root;
for (String s : list) {
cur.children.putIfAbsent(s, new Trie());
cur = cur.children.get(s);
}
}
Map<String, Integer> freq = new HashMap<>();
getSerial(root, freq);
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
dfs(root, freq, res, path);
return res;
}
class Trie {
String serial;
Map<String, Trie> children;
public Trie() {
children = new HashMap<>();
}
}
void getSerial(Trie cur, Map<String, Integer> freq) {
if (cur.children.isEmpty()) {
return;
}
List<String> cl = new ArrayList<>();
for (Map.Entry<String, Trie> entry : cur.children.entrySet()) {
String s = entry.getKey();
Trie t = entry.getValue();
getSerial(t, freq);
cl.add(s + "(" + t.serial + ")");
}
Collections.sort(cl);
StringBuilder sb = new StringBuilder();
for (String s : cl) {
sb.append(s);
}
cur.serial = sb.toString();
freq.put(cur.serial, freq.getOrDefault(cur.serial, 0) + 1);
}
void dfs(Trie cur, Map<String, Integer> freq, List<List<String>> res, List<String> path) {
if (freq.getOrDefault(cur.serial, 0) > 1) {
return;
}
if (!path.isEmpty()) {
res.add(new ArrayList<>(path));
}
for (Map.Entry<String, Trie> entry : cur.children.entrySet()) {
String s = entry.getKey();
Trie t = entry.getValue();
path.add(s);
dfs(t, freq, res, path);
path.removeLast();
}
}
}
优化1
class Solution {
public List<List<String>> deleteDuplicateFolder(List<List<String>> paths) {
Trie root = new Trie();
for (List<String> list : paths) {
Trie cur = root;
for (String s : list) {
cur.children.putIfAbsent(s, new Trie());
cur = cur.children.get(s);
}
}
Map<String, Trie> map = new HashMap<>();
getSerial(root, map);
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
dfs(root, res, path);
return res;
}
class Trie {
String serial;
Map<String, Trie> children;
boolean deleted;
public Trie() {
children = new HashMap<>();
deleted = false;
}
public void setD() {
deleted = true;
}
}
void getSerial(Trie cur, Map<String, Trie> map) {
if (cur.children.isEmpty()) {
return;
}
List<String> cl = new ArrayList<>();
for (Map.Entry<String, Trie> entry : cur.children.entrySet()) {
String s = entry.getKey();
Trie t = entry.getValue();
getSerial(t, map);
cl.add(s + "(" + t.serial + ")");
}
Collections.sort(cl);
StringBuilder sb = new StringBuilder();
for (String s : cl) {
sb.append(s);
}
cur.serial = sb.toString();
Trie t = map.get(cur.serial);
if (t == null) {
map.put(cur.serial, cur);
} else {
t.setD();
cur.setD();
}
}
void dfs(Trie cur, List<List<String>> res, List<String> path) {
if (cur.deleted) {
return;
}
if (!path.isEmpty()) {
res.add(new ArrayList<>(path));
}
for (Map.Entry<String, Trie> entry : cur.children.entrySet()) {
String s = entry.getKey();
Trie t = entry.getValue();
path.add(s);
dfs(t, res, path);
path.removeLast();
}
}
}
优化2
class Solution {
public List<List<String>> deleteDuplicateFolder(List<List<String>> paths) {
Trie root = new Trie();
paths.sort(new Comparator<List<String>>() {
@Override
public int compare(List<String> l1, List<String> l2) {
return l1.size() - l2.size();
}
});
int n = paths.size();
List<Trie> tlist = new ArrayList<>(n);
for (List<String> list : paths) {
Trie cur = root;
for (int i = 0 ; i < list.size() - 1; i++) {
String s = list.get(i);
cur = cur.children.get(s);
}
Trie t = new Trie();
cur.children.put(list.getLast(), t);
tlist.add(t);
}
StringBuilder sb = new StringBuilder();
Map<String, Trie> map = new HashMap<>();
for (int i = n - 1; i >= 0; i--) {
Trie cur = tlist.get(i);
if (cur.children.isEmpty()) {
continue;
}
for (Map.Entry<String, Trie> entry : cur.children.entrySet()) {
sb.append(entry.getKey()).append("(").append(entry.getValue().serial).append(")");
}
cur.serial = sb.toString();
Trie t = map.get(cur.serial);
if (t == null) {
map.put(cur.serial, cur);
} else {
t.setD();
cur.setD();
}
sb.setLength(0);
}
List<List<String>> res = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (!tlist.get(i).deleted) {
res.add(paths.get(i));
}
}
return res;
}
class Trie {
String serial;
Map<String, Trie> children;
boolean deleted;
public Trie() {
children = new TreeMap<>();
deleted = false;
}
public void setD() {
if (deleted) {
return;
}
deleted = true;
for (Trie t : children.values()) {
t.setD();
}
}
}
}
1957. 删除字符使字符串变好
20250721
题目链接
参考
思路
字符串长度小于3时直接返回
用一个指针存新字符串的结束位置
默认将最后两个字符加入新字符串
实现上可以在同一个数组中操作
优化
使用byte数组存字符
byte[] sc = s.getBytes(java.nio.charset.StandartCharsets.ISO_8859_1);
最后用byte数组构建String
new String(sc, 0, p, java.nio.charset.StandardCharsets.ISO_8859_1)
代码
class Solution {
public String makeFancyString(String s) {
if (s.length() < 3) {
return s;
}
byte[] sc = s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
int p = 0;
for (int i = 0; i < s.length() - 2; i++) {
if (!(sc[i] == sc[i + 1] && sc[i] == sc[i + 2])) {
sc[p++] = sc[i];
}
}
sc[p++] = sc[s.length() - 2];
sc[p++] = sc[s.length() - 1];
return new String(sc, 0, p, java.nio.charset.StandardCharsets.ISO_8859_1);
}
}
1695. 删除子数组的最大得分
20250722
题目链接
参考
执行用时前排代码
思路
遍历数组
复杂度
- 时间复杂度
遍历数组时更新: HashSet 且次数不会超过$N$, 总体为$O(N)$, - 空间复杂度
需要一个: HashSet 为$O(N)$,
优化
题目数据范围$1 \leq nums[i] \leq 10^{4}$
初始化一个$10^4 + 1$大小的数组$pren$
遍历数组时
查询$pren[nums[i]]$
如果小于等于$starts$
代码
class Solution {
public int maximumUniqueSubarray(int[] nums) {
Set<Integer> s = new HashSet<>();
int ans = 0;
int tem = 0;
for (int i = 0, j = 0; i < nums.length; i++) {
tem += nums[i];
while (s.contains(nums[i])) {
s.remove(nums[j]);
tem -= nums[j];
j++;
}
s.add(nums[i]);
ans = Math.max(ans, tem);
}
return ans;
}
}
优化后
class Solution {
public int maximumUniqueSubarray(int[] nums) {
int[] pren = new int[10001];
int ans = 0;
int ends = 0, starts = 0;
for (int num : nums) {
if(pren[num] > starts) {
starts = pren[num];
}
ends += num;
pren[num] = ends;
ans = Math.max(ans, ends - starts);
}
return ans;
}
}
1717. 删除子字符串的最大得分
20250723
题目链接
参考
思路
假设$ab$得分大于$ba$
实现时用$cl$存储得分高的字符串的第一个字符
假设$ab$得分更高
遍历到非$a$或$b$的字符时该字串结束
复杂度
- 时间复杂度
遍历数组: $O(N)$, - 空间复杂度
不需要额外空间: $O(1)$,
其他
遍历字符串的字符
for (char c : s.toCharArray())
代码
class Solution {
public int maximumGain(String s, int x, int y) {
int ans = 0;
int cntl = 0, cntr= 0;
char cl = x > y ? 'a' : 'b';
char cr = x > y ? 'b' : 'a';
int t1 = Math.max(x, y);
int t2 = Math.min(x, y);
x = t1;
y = t2;
for (char c : s.toCharArray()) {
if (c == cl) {
cntl++;
} else if (c == cr) {
if (cntl == 0) {
cntr++;
} else {
ans += x;
cntl--;
}
} else {
ans += Math.min(cntl, cntr) * y;
cntl = 0;
cntr = 0;
}
}
ans += Math.min(cntl, cntr) * y;
return ans;
}
}
2322. 从树中删除边的最小分数
20250724
题目链接
参考
思路
前置知识
- 异或性质
如果$a \oplus b = c$: 则$b \oplus c = a$, - DFS时间戳
DFS遍历初始化: in和out数组 如果, i是j的子节点则$in[i] > in[j]$并且$out[i] \leq out[j]$
使用邻接表建(无向)图in和out数组,维护一个xor数组存储以$i$为根的子树的异或值
枚举除$0$以外的任意$2$个节点
假设$2$个节点分别为$i$和$j$
- $i$是$j$的子节点
以$i$为根的连通块的异或值为$xor[i]$, 以$j$为根的连通块要排除以$i$为根的连通块; 异或值为$xor[j] \oplus xor[i]$, 以$0$为根的连通块的要排除整个以$j$为根的子树; 异或值为$xor[0] \oplus xor[j]$, - $j$是$i$的子节点
与上一种类似, 三个连通块的异或值分别为$xor[j]$, $xor[i] \oplus xor[j]$和$xor[0] \oplus xor[i]$、 - $i$和$j$互不为子节点
以$i$为根的连通块的异或值为$xor[i]$, 以$j$为根的连通块的异或值为$xor[j]$; 以$0$为根的连通块要排除以$i$为根的连通块和以$j$为根的连通块; 异或值为$xor[0] \oplus xor[i] \oplus xor[j]$, ;
复杂度
- 时间复杂度
枚举$2$个任意节点: $O(N ^ {2})$, - 空间复杂度
: in, out, xor等数组长度与节点数量相等 $O(N)$,
其他
邻接表的定义及初始化
List<Integer>[] g;
g = new List[n];
for (int i = 0; i < n; i++) {
g[i] = new ArrayList<>();
}
代码
class Solution {
int clock = 0;
int in[];
int out[];
int s[];
List<Integer>[] g;
private int getres(int xo1, int xo2, int xo3) {
return Math.max(xo1, Math.max(xo2, xo3)) - Math.min(xo1, Math.min(xo2, xo3));
}
public int minimumScore(int[] nums, int[][] edges) {
g = new List[nums.length];
for (int i = 0; i < nums.length; i++) {
g[i] = new ArrayList<>();
}
in = new int[nums.length];
out = new int[nums.length];
s = new int[nums.length];
for (int i = 0; i < edges.length; i++) {
g[edges[i][0]].add(edges[i][1]);
g[edges[i][1]].add(edges[i][0]);
}
for (int i = 0; i < nums.length; i++) {
s[i] = nums[i];
}
dfs(0, -1);
int ans = Integer.MAX_VALUE;
for (int i = 1; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (in[i] > in[j] && out[i] <= out[j]) {
ans = Math.min(ans, getres(s[i], s[j] ^ s[i], s[0] ^ s[j]));
} else if (in[j] > in[i] && out[j] <= out[i]) {
ans = Math.min(ans, getres(s[j], s[i] ^ s[j], s[0] ^ s[i]));
} else {
ans = Math.min(ans, getres(s[i], s[j], s[0] ^ s[i] ^ s[j]));
}
}
}
return ans;
}
private int dfs(int node, int fa) {
in[node] = clock++;
for (int i = 0; i < g[node].size(); i++) {
if (g[node].get(i) == fa) {
continue;
}
s[node] ^= dfs(g[node].get(i), node);
}
out[node] = clock;
return s[node];
}
}
如有错误欢迎批评指正
由于vercel.app域名被墙,可能无法查看和发送评论