modify code

master
algorithmzuo 3 years ago
parent 0dfd08357a
commit 404596db40

@ -0,0 +1,88 @@
package class_2023_01_2_week;
// 如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,
// 那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。
// 例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置)
// "rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars""rats",或 "arts" 相似。
// 总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}。
// 注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似。
// 形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。
// 给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。
// 请问 strs 中有多少个相似字符串组?
// 测试链接 : https://leetcode.cn/problems/similar-string-groups/
public class Code01_SimilarStringGroups {
public static int numSimilarGroups(String[] strs) {
int n = strs.length;
int m = strs[0].length();
UnionFind uf = new UnionFind(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// [i] [j]
if (uf.find(i) != uf.find(j)) {
int diff = 0;
for (int k = 0; k < m && diff < 3; k++) {
if (strs[i].charAt(k) != strs[j].charAt(k)) {
diff++;
}
}
if (diff == 0 || diff == 2) {
uf.union(i, j);
}
}
}
}
return uf.sets;
}
public static class UnionFind {
public int[] father;
public int[] size;
public int[] help;
public int sets;
public UnionFind(int n) {
father = new int[n];
size = new int[n];
help = new int[n];
for (int i = 0; i < n; i++) {
father[i] = i;
size[i] = 1;
}
sets = n;
}
public int find(int i) {
int hi = 0;
while (i != father[i]) {
help[hi++] = i;
i = father[i];
}
while (hi != 0) {
father[help[--hi]] = i;
}
return i;
}
public void union(int i, int j) {
int fi = find(i);
int fj = find(j);
if (fi != fj) {
if (size[fi] >= size[fj]) {
father[fj] = fi;
size[fi] += size[fj];
} else {
father[fi] = fj;
size[fj] += size[fi];
}
sets--;
}
}
public int sets() {
return sets;
}
}
}

@ -0,0 +1,54 @@
package class_2023_01_2_week;
import java.util.ArrayList;
import java.util.HashMap;
// 设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。
// 实现 FreqStack 类:
// FreqStack() 构造一个空的堆栈。
// void push(int val) 将一个整数 val 压入栈顶。
// int pop() 删除并返回堆栈中出现频率最高的元素。
// 如果出现频率最高的元素不只一个,则移除并返回最接近栈顶的元素。
// 测试链接 : https://leetcode.cn/problems/maximum-frequency-stack/
public class Code02_MaximumFrequencyStack {
class FreqStack {
// 出现的最大次数
private int topTimes;
// 每层节点
private HashMap<Integer, ArrayList<Integer>> cntValues = new HashMap<>();
// 每一个数出现了几次
private HashMap<Integer, Integer> valueTopTime = new HashMap<>();
public void push(int val) {
// 当前数词频+1
valueTopTime.put(val, valueTopTime.getOrDefault(val, 0) + 1);
// 当前数是什么词频 5 7次
int curTopTimes = valueTopTime.get(val);
if (!cntValues.containsKey(curTopTimes)) {
cntValues.put(curTopTimes, new ArrayList<>());
}
ArrayList<Integer> curTimeValues = cntValues.get(curTopTimes);
curTimeValues.add(val);
topTimes = Math.max(topTimes, curTopTimes);
}
public int pop() {
// 最大词频的那一层的链表(动态数组)
ArrayList<Integer> topTimeValues = cntValues.get(topTimes);
int ans = topTimeValues.remove(topTimeValues.size() - 1);
if (topTimeValues.size() == 0) {
cntValues.remove(topTimes--);
}
int times = valueTopTime.get(ans);
if (times == 1) {
valueTopTime.remove(ans);
} else {
valueTopTime.put(ans, times - 1);
}
return ans;
}
}
}

@ -0,0 +1,114 @@
package class_2023_01_2_week;
import java.util.HashMap;
// 给定一个正整数 x我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式
// 其中每个运算符 op1op2… 可以是加、减、乘、除之一
// 例如,对于 x = 3我们可以写出表达式 3 * 3 / 3 + 3 - 3该式的值为3
// 在写这样的表达式时,我们需要遵守下面的惯例:
// 除运算符(/)返回有理数
// 任何地方都没有括号
// 我们使用通常的操作顺序:乘法和除法发生在加法和减法之前
// 不允许使用一元否定运算符(-。例如“x - x” 是一个有效的表达
// 因为它只使用减法,但是 “-x + x” 不是,因为它使用了否定运算符
// 我们希望编写一个能使表达式等于给定的目标值 target 且运算符最少的表达式
// 返回所用运算符的最少数量
// 测试链接 : https://leetcode.cn/problems/least-operators-to-express-number/
public class Code03_LeastOperatorsToExpressNumber {
public static int leastOpsExpressTarget(int x, int target) {
return dp(0, target, x, new HashMap<>()) - 1;
}
// i : 当前来到了x的i次方
// target : 认为target只能由x的i次方或者更高次方来解决不能使用更低次方
// 返回在这样的情况下target最少能由几个运算符搞定
// (3, 1001231) -> 返回值!
// dp.get(3) -> {1001231 对应的value}
public static int dp(int i, int target, int x, HashMap<Integer, HashMap<Integer, Integer>> dp) {
if (dp.containsKey(i) && dp.get(i).containsKey(target)) {
return dp.get(i).get(target);
}
int ans = 0;
if (target > 0 && i < 39) {
if (target == 1) {
ans = cost(i);
} else {
// 我们把课上没有讲清楚的地方这里写一下
// 比如 x = 5, target = 73
// 首先来到5的0次方要搞定73
// 73 % 5 = 3知道余数是3
// 这个余数3 需要 / 1得到3因为此时的余数只能由若干个5的0次方解决
// 所以73要么是70 + 3 * 1解决的
// 或者73是75 - (5 - 3) * 1解决的
// 前者代价是搞定3个5的0次方的代价 + 后续搞定70的代价
// 后者代价是搞定2个5的0次方的代价 + 后续搞定75的代价
// 如果选择70 + 3 * 1的路线3 * 1的代价3 * 搞定5的0次方的代价
// 后续是70的代价继续
// 此时来到5的1次方要搞定70
// 70 % 25 = 20知道余数是20
// 这个余数20 需要 / 5得到4
// 因为此时的余数只能由若干个5的1次方解决
// 所以70要么是50 + 4 * 5的1次方解决的
// 或者70是75 - (5 - 4) * 5的1次方解决的
// 前者代价是搞定4个5的1次方的代价 + 后续搞定50的代价
// 后者代价是搞定1个5的1次方的代价 + 后续搞定75的代价
// 如果选择50 + 4 * 5的1次方 的路线后续是50的代价继续
// 此时来到5的2次方要搞定50
// 50 % 125 = 50知道余数是50
// 这个余数50 需要 / 5的2次方得到2
// 因为此时的余数只能由若干个5的2次方解决
// 所以50要么是0 + 2 * 5的2次方解决的
// 或者50是125 - (5 - 2) * 5的2次方解决的
// 我们课上讲的是这个思路
// 上面的思路怎么方便的实现就是下面的code
// 我们来解释一下,大家可以把下面的过程,和上面的分析过程对比一下
// 会发现等效
// 比如i = 0, x = 5, target = 73
// 表示当前来到5的0次方x是5(固定的), target是73
// 73 % 5 = 3知道余数是3
// 所以73要么是70 + 3 * 5的0次方解决的
// 或者73是75 - (5 - 3) * 5的0次方解决的
// 3 * 5的0次方 代价就是 : mod * cost(0)
// (5 - 3) * 5的0次方 代价就是 : (x - mod) * cost(0)
// 假设我们选择70 + 3 * 5的0次方的路线
// 73 / 5 = 14也就是代码中的div解决70的后续过程就是
// i = 1, x = 5, target = 14
// 表示当前来到5的1次方x是5(固定的), target是14(其实是原来的70除过5了)
// 14 % 5 = 4我们得到了4这和上面的过程一样
// 所以原来的70要么是50 + 4 * 5的1次方解决的
// 或者原来的70是75 - (5 - 4) * 5的1次方解决的
// 这里变成:
// 现在的14要么是10(因为除了5其实代表原来的70) + 4 * 5的1次方
// 现在的14要么是15(因为除了5其实代表原来的75) - 1 * 5的1次方
// 14 / 5 = 2也就是代码中的div
// 14已经是除以5之后的结果了再除以5得到了2
// 其实这个2代表了原来的50因为除了两次5
// 所以i = 2, x = 5, target = 2
// 其实代表来到5的2次方搞定50的代价
// 所以i = 2, x = 5, target = 3
// 其实代表来到5的2次方搞定75的代价(因为3同样是2次除以5之后的结果)
// 也就是说,每一步的代价,其实都是算对了的
// 后续依然如此,但是代码这样处理可以写的非常少
int div = target / x;
int mod = target % x;
int p1 = mod * cost(i) + dp(i + 1, div, x, dp);
int p2 = (x - mod) * cost(i) + dp(i + 1, div + 1, x, dp);
ans = Math.min(p1, p2);
}
}
if (!dp.containsKey(i)) {
dp.put(i, new HashMap<>());
}
dp.get(i).put(target, ans);
return ans;
}
// 得到x的i次方这个数字
// 需要几个运算符,默认前面再加个'+'或'-'
public static int cost(int i) {
return i == 0 ? 2 : i;
}
}

@ -0,0 +1,116 @@
package class_2023_01_2_week;
import java.util.ArrayList;
// 给你一个只包含小写英文字母的字符串 s 。
// 每一次 操作 ,你可以选择 s 中两个 相邻 的字符,并将它们交换。
// 请你返回将 s 变成回文串的 最少操作次数 。
// 注意 ,输入数据会确保 s 一定能变成一个回文串。
// 测试链接 : https://leetcode.cn/problems/minimum-number-of-moves-to-make-palindrome/
public class Code04_MinimumNumberOfMovesToMakePalindrome {
public static int minMovesToMakePalindrome(String s) {
int n = s.length();
ArrayList<ArrayList<Integer>> indies = new ArrayList<>();
// a -> 0 -> 含有a的所有位置{...}
// b -> 1 -> 含有b的所有位置{...}
for (int i = 0; i < 26; i++) {
indies.add(new ArrayList<>());
}
// AABAA...
// 12345
// A -> 0 : {1 2 4 5}
// B -> 1 : {3... }
for (int i = 0, j = 1; i < n; i++, j++) {
int c = s.charAt(i) - 'a';
indies.get(c).add(j);
}
// 原始下标 -> 该去往的下标 存在arr中
int[] arr = new int[n + 1];
// 建立好indexTree初始时下标1~n上认为全是1
IndexTree it = new IndexTree(n);
for (int i = 0, l = 1; i < n; i++, l++) {
// i -> 拿字符 从下标0开始
// l -> 从下标1开始
// arr[l] != 0
// 当前的l曾经作为姘头之一的右侧之前填过了
if (arr[l] == 0) {
int c = s.charAt(i) - 'a';
// l......r
int r = indies.get(c).remove(indies.get(c).size() - 1);
if (l == r) {
arr[l] = (1 + n) / 2;
it.add(l, -1);
} else {
// l != r
// l -> 左边的序号! 0...l累加和
int kth = it.sum(l);
arr[l] = kth;
arr[r] = n - kth + 1;
it.add(r, -1);
}
}
}
return number(arr, new int[n + 1], 1, n);
}
public static class IndexTree {
public int[] tree;
public int n;
public IndexTree(int size) {
tree = new int[size + 1];
n = size;
for (int i = 1; i <= n; i++) {
add(i, 1);
}
}
public int sum(int i) {
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= i & -i;
}
return ans;
}
public void add(int i, int v) {
while (i < tree.length) {
tree[i] += v;
i += i & -i;
}
}
}
public static int number(int[] arr, int[] help, int l, int r) {
if (l >= r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return number(arr, help, l, mid) + number(arr, help, mid + 1, r) + merge(arr, help, l, mid, r);
}
public static int merge(int[] arr, int[] help, int l, int m, int r) {
int i = r;
int p1 = m;
int p2 = r;
int ans = 0;
while (p1 >= l && p2 > m) {
ans += arr[p1] > arr[p2] ? (p2 - m) : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= l) {
help[i--] = arr[p1--];
}
while (p2 > m) {
help[i--] = arr[p2--];
}
for (i = l; i <= r; i++) {
arr[i] = help[i];
}
return ans;
}
}

@ -0,0 +1,173 @@
package class_2023_01_2_week;
// 一条单向的铁路线上火车站编号为1~n
// 每个火车站都有一个级别,最低为 1 级。
// 现有若干趟车次在这条线路上行驶,
// 每一趟都满足如下要求:
// 如果这趟车次停靠了火车站 x则始发站、终点站之间所有级别大于等于火车站x的都必须停靠。
//(注意:起始站和终点站自然也算作事先已知需要停靠的站点)
// 现有 m 趟车次的运行情况(全部满足要求),
// 试推算这n个火车站至少分为几个不同的级别。
// 测试链接 : https://www.luogu.com.cn/problem/P1983
// 线段树建边
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交如下方法把主类名改成Main可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
public class Code05_BusStationsMinLevelNumbers {
public static final int maxn = 100001;
// 1 500 600 1000
// stops[1,500,600,1000]
// 停靠车站
public static int[] stops = new int[maxn];
// 一段线段树范围的id编号
// id[rt] = xrt背后的范围这一段给它的点编号是x
// rt -> 线段树的某个范围的固有属性l~r,rt
public static int[] id = new int[maxn << 2];
// id点是否为单点
// a 单点 范围 虚拟点?
// 70~90 rt = 60 -> 17 single[17] ?
public static boolean[] single = new boolean[maxn << 3];
// id点的入度
public static int[] inDegree = new int[maxn << 3];
// id点拓扑排序统计的最大深度(只算路径上的单点数量)
public static int[] singleDeep = new int[maxn << 3];
// 链式前向星建图用
public static int[] head = new int[maxn << 3];
public static int[] to = new int[maxn << 3];
public static int[] next = new int[maxn << 3];
// 拓扑排序用
public static int[] queue = new int[maxn << 3];
// n为车站个数、nth为线段树上范围的编号计数、eth为边的计数
public static int n, nth, eth;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
nth = 0;
eth = 0;
Arrays.fill(single, 0, (n << 2) + m + 1, false);
Arrays.fill(inDegree, 0, (n << 2) + m + 1, 0);
Arrays.fill(singleDeep, 0, (n << 2) + m + 1, 0);
build(1, n, 1);
for (int i = 0; i < m; i++) {
in.nextToken();
int k = (int) in.nval;
for (int j = 0; j < k; j++) {
in.nextToken();
stops[j] = (int) in.nval;
}
int curVirtual = ++nth;
// 虚点向停靠车站连边
for (int j = 0; j < k; j++) {
vLinkStop(curVirtual, stops[j], 1, n, 1);
}
// 不停靠的连续车站向虚点连边
for (int j = 1; j < k; j++) {
if (stops[j] > stops[j - 1] + 1) {
rangeLinkV(stops[j - 1] + 1, stops[j] - 1, curVirtual, 1, n, 1);
}
}
}
out.println(topoSort());
out.flush();
}
}
public static void build(int l, int r, int rt) {
id[rt] = ++nth;
if (l == r) {
single[id[rt]] = true;
} else {
int m = (l + r) / 2;
build(l, m, rt << 1);
build(m + 1, r, rt << 1 | 1);
addEdge(id[rt << 1], id[rt]);
addEdge(id[rt << 1 | 1], id[rt]);
}
}
public static void rangeLinkV(int L, int R, int vid, int l, int r, int rt) {
if (L <= l && r <= R) {
addEdge(id[rt], vid);
} else {
int m = (l + r) / 2;
if (L <= m) {
rangeLinkV(L, R, vid, l, m, rt << 1);
}
if (R > m) {
rangeLinkV(L, R, vid, m + 1, r, rt << 1 | 1);
}
}
}
// 17 17~17
public static void vLinkStop(int vid, int stop, int l, int r, int rt) {
if (l == r) {
addEdge(vid, id[rt]);
} else {
int m = (l + r) / 2;
// 1~100
// 想去的车站是70 70~70
// 1~50 51~100
if (stop <= m) {
vLinkStop(vid, stop, l, m, rt << 1);
} else {
vLinkStop(vid, stop, m + 1, r, rt << 1 | 1);
}
}
}
public static void addEdge(int fid, int tid) {
inDegree[tid]++;
to[++eth] = tid;
next[eth] = head[fid];
head[fid] = eth;
}
public static int topoSort() {
int l = 0;
int r = 0;
for (int i = 1; i <= nth; i++) {
if (inDegree[i] == 0) {
queue[r++] = i;
if (single[i]) {
singleDeep[i] = 1;
}
}
}
int ans = 0;
while (l < r) {
int curNode = queue[l++];
ans = Math.max(ans, singleDeep[curNode]);
for (int edgeIndex = head[curNode]; edgeIndex != 0; edgeIndex = next[edgeIndex]) {
int child = to[edgeIndex];
singleDeep[child] = Math.max(singleDeep[child], singleDeep[curNode] + (single[child] ? 1 : 0));
if (--inDegree[child] == 0) {
queue[r++] = child;
}
}
}
return ans;
}
}

@ -2808,6 +2808,57 @@ I 意味着增加
第055节 2023年1月第2周流行算法题目解析
如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,
那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。
例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置)
"rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars""rats",或 "arts" 相似。
总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}。
注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似。
形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。
给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。
请问 strs 中有多少个相似字符串组?
测试链接 : https://leetcode.cn/problems/similar-string-groups/
设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。
实现 FreqStack 类:
FreqStack() 构造一个空的堆栈。
void push(int val) 将一个整数 val 压入栈顶。
int pop() 删除并返回堆栈中出现频率最高的元素。
如果出现频率最高的元素不只一个,则移除并返回最接近栈顶的元素。
测试链接 : https://leetcode.cn/problems/maximum-frequency-stack/
给定一个正整数 x我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式
其中每个运算符 op1op2… 可以是加、减、乘、除之一
例如,对于 x = 3我们可以写出表达式 3 * 3 / 3 + 3 - 3该式的值为3
在写这样的表达式时,我们需要遵守下面的惯例:
除运算符(/)返回有理数
任何地方都没有括号
我们使用通常的操作顺序:乘法和除法发生在加法和减法之前
不允许使用一元否定运算符(-。例如“x - x” 是一个有效的表达
因为它只使用减法,但是 “-x + x” 不是,因为它使用了否定运算符
我们希望编写一个能使表达式等于给定的目标值 target 且运算符最少的表达式
返回所用运算符的最少数量
测试链接 : https://leetcode.cn/problems/least-operators-to-express-number/
给你一个只包含小写英文字母的字符串 s 。
每一次 操作 ,你可以选择 s 中两个 相邻 的字符,并将它们交换。
请你返回将 s 变成回文串的 最少操作次数 。
注意 ,输入数据会确保 s 一定能变成一个回文串。
测试链接 : https://leetcode.cn/problems/minimum-number-of-moves-to-make-palindrome/
一条单向的铁路线上火车站编号为1~n
每个火车站都有一个级别,最低为 1 级。
现有若干趟车次在这条线路上行驶,
每一趟都满足如下要求:
如果这趟车次停靠了火车站 x则始发站、终点站之间所有级别大于等于火车站x的都必须停靠。
(注意:起始站和终点站自然也算作事先已知需要停靠的站点)
现有 m 趟车次的运行情况(全部满足要求),
试推算这n个火车站至少分为几个不同的级别。
测试链接 : https://www.luogu.com.cn/problem/P1983

Loading…
Cancel
Save