modify code

master
algorithmzuo 2 years ago
parent 5fb9b050d3
commit 1d3c3ab572

@ -0,0 +1,113 @@
package class_2022_11_3_week;
import java.util.HashMap;
// 来自亚马逊
// 给定一个字符串数组strs其中每个字符串都是小写字母组成的
// 如果i < j并且strs[i]和strs[j]所有的字符随意去排列能组成回文串
// 那么说(i,j)叫做一个互补对(complementary)
// 求strs中有多少个互补对
// strs长度 <= 3 * 10^5
// 单个字符串长度 <= 10^5
// strs里所有字符串总长度 <= 10^6
public class Code01_ComplementaryPairsInStringArray {
// 暴力方法
// 为了测试
public static int num1(String[] strs) {
int ans = 0;
for (int i = 0; i < strs.length; i++) {
for (int j = i + 1; j < strs.length; j++) {
if (complementary(strs[i], strs[j])) {
ans++;
}
}
}
return ans;
}
public static boolean complementary(String a, String b) {
int[] cnt = new int[26];
for (int i = 0; i < a.length(); i++) {
cnt[a.charAt(i) - 'a']++;
}
for (int i = 0; i < b.length(); i++) {
cnt[b.charAt(i) - 'a']++;
}
int odd = 0;
for (int num : cnt) {
if ((num & 1) != 0) {
odd++;
}
}
return odd < 2;
}
// 正式方法
// O(N*M)N字符串长M字符串平均长度
// 时间复杂度O(N) + O(M)一共有多少个字符串N一共有多少字符M
public static int num2(String[] strs) {
// key : 某一种状态(int类型状态)
// z..d c b a
// 3 2 1 0
// 1 0 1 1
// value : 这样状态的字符串,有几个
HashMap<Integer, Integer> status = new HashMap<>();
int ans = 0;
for (String str : strs) {
// 当前str这个字符串
// 它自己的状态,加工好
// d c b a
// 0 0 0 1
int cur = 0;
for (int i = 0; i < str.length(); i++) {
cur ^= 1 << (str.charAt(i) - 'a');
}
// 一点点都不捣乱curmap有几个状态也是cur的字符串
ans += status.getOrDefault(cur, 0);
for (int i = 0; i < 26; i++) {
// 每一位捣乱一下
// a
// b
// c
// z
ans += status.getOrDefault(cur ^ (1 << i), 0);
}
status.put(cur, status.getOrDefault(cur, 0) + 1);
}
return ans;
}
// 为了验证
public static String[] randomStringArray(int n, int m, int r) {
String[] ans = new String[n];
for (int i = 0; i < n; i++) {
int len = (int) (Math.random() * m) + 1;
char[] str = new char[len];
for (int j = 0; j < len; j++) {
str[j] = (char) ((int) (Math.random() * r) + 'a');
}
ans[i] = String.valueOf(str);
}
return ans;
}
public static void main(String[] args) {
int N = 100;
int M = 20;
int R = 5;
int testTime = 5000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int n = (int) (Math.random() * N) + 1;
String[] strs = randomStringArray(n, M, R);
int ans1 = num1(strs);
int ans2 = num2(strs);
if (ans1 != ans2) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}
}

@ -0,0 +1,74 @@
package class_2022_11_3_week;
// n对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手
// 人和座位由一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的ID
// 情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)
// 返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起
// 每次交换可选择任意两人,让他们站起来交换座位
// 测试链接 : https://leetcode.cn/problems/couples-holding-hands/
public class Code02_CouplesHoldingHands {
public int minSwapsCouples(int[] row) {
// n人数偶数
int n = row.length;
// n/2
// 0 1 -> 0 0
// 4 5 -> 2 2
UnionFind uf = new UnionFind(n / 2);
for (int i = 0; i < n; i += 2) {
uf.union(row[i] / 2, row[i + 1] / 2);
}
return n / 2 - 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;
}
private 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,79 @@
package class_2022_11_3_week;
import java.util.Arrays;
// 来自谷歌
// 给定一个长度为N的数组值一定在0~N-1范围且每个值不重复
// 比如arr = [4, 2, 0, 3, 1]
// 0 1 2 3 4
// 把0想象成洞任何非0数字都可以来到这个洞里然后在原本的位置留下洞
// 比如4这个数字来到0所代表的洞里那么数组变成 :
// arr = [0, 2, 4, 3, 1]
// 也就是原来的洞被4填满4走后留下了洞
// 任何数字只能搬家到洞里,并且走后留下洞
// 通过搬家的方式,想变成有序的,有序有两种形式
// 比如arr = [4, 2, 0, 3, 1],变成
// [0, 1, 2, 3, 4]或者[1, 2, 3, 4, 0]都叫有序
// 返回变成任何一种有序的情况都可以,最少的数字搬动次数
// 测试链接 : https://leetcode.cn/problems/sort-array-by-moving-items-to-empty-space/
public class Code03_SortArrayByMovingItemsToEmptySpace {
public static int sortArray(int[] nums) {
// 长度n
// ans1 : 0 1 2 3 4 .... 这种样子,至少交换几次
// ans2 : 1 2 3 4 .... 0 这种样子,至少交换几次
// m : 每个环里有几个数
// next : 往下跳的位置
int n = nums.length, ans1 = 0, ans2 = 0, m, next;
boolean[] touched = new boolean[n];
// 0 1 2 3 4...
for (int i = 0; i < n; i++) {
// i == 0找到的环必含有0mm-1
// i !=0 找到的环必不含有0mm+1
if (!touched[i]) {
// 12 17 9 6
// i(6) 9 12 17
// Y
touched[i] = true;
m = 1;
next = nums[i];
while (next != i) {
m++;
touched[next] = true;
next = nums[next];
}
// m 当前环,有几个数
// 6
// 6
if (m > 1) {
ans1 += i == 0 ? (m - 1) : (m + 1);
}
}
}
Arrays.fill(touched, false);
// 1 2 3 4 ... 0
// i == n-1
for (int i = n - 1; i >= 0; i--) {
if (!touched[i]) {
touched[i] = true;
m = 1;
// next
// 5
// i(8) -> 4
// 0
// i(8) -> n-1
next = nums[i] == 0 ? (n - 1) : (nums[i] - 1);
while (next != i) {
m++;
touched[next] = true;
next = nums[next] == 0 ? (n - 1) : (nums[next] - 1);
}
if (m > 1) {
ans2 += i == n - 1 ? (m - 1) : (m + 1);
}
}
}
return Math.min(ans1, ans2);
}
}

@ -0,0 +1,111 @@
package class_2022_11_3_week;
import java.util.ArrayList;
// 设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。
// 实现 WordFilter 类:
// WordFilter(string[] words) 使用词典中的单词 words 初始化对象
// f(string pref, string suff)
// 返回词典中具有前缀 prefix 和后缀 suff 的单词的下标
// 如果存在不止一个满足要求的下标,返回其中 最大的下标
// 如果不存在这样的单词,返回 -1 。
// 测试链接 : https://leetcode.cn/problems/prefix-and-suffix-search/
public class Code04_PrefixAndSuffixSearch {
// 提交以下这个类
class WordFilter {
class TrieNode {
TrieNode[] nexts;
ArrayList<Integer> indies;
public TrieNode() {
nexts = new TrieNode[26];
indies = new ArrayList<>();
}
}
TrieNode preHead;
TrieNode sufHead;
public WordFilter(String[] words) {
preHead = new TrieNode();
sufHead = new TrieNode();
for (int i = 0; i < words.length; i++) {
String word = words[i];
TrieNode cur = preHead;
for (int j = 0; j < word.length(); j++) {
int path = word.charAt(j) - 'a';
if (cur.nexts[path] == null) {
cur.nexts[path] = new TrieNode();
}
cur = cur.nexts[path];
cur.indies.add(i);
}
cur = sufHead;
for (int j = word.length() - 1; j >= 0; j--) {
int path = word.charAt(j) - 'a';
if (cur.nexts[path] == null) {
cur.nexts[path] = new TrieNode();
}
cur = cur.nexts[path];
cur.indies.add(i);
}
}
}
public int f(String pref, String suff) {
ArrayList<Integer> preList = null;
TrieNode cur = preHead;
for (int i = 0; i < pref.length() && cur != null; i++) {
cur = cur.nexts[pref.charAt(i) - 'a'];
}
if (cur != null) {
preList = cur.indies;
}
if (preList == null) {
return -1;
}
ArrayList<Integer> sufList = null;
cur = sufHead;
for (int i = suff.length() - 1; i >= 0 && cur != null; i--) {
cur = cur.nexts[suff.charAt(i) - 'a'];
}
if (cur != null) {
sufList = cur.indies;
}
if (sufList == null) {
return -1;
}
ArrayList<Integer> small = preList.size() <= sufList.size() ? preList : sufList;
ArrayList<Integer> big = small == preList ? sufList : preList;
for (int i = small.size() - 1; i >= 0; i--) {
if (bs(big, small.get(i))) {
return small.get(i);
}
}
return -1;
}
private boolean bs(ArrayList<Integer> sorted, int num) {
int l = 0;
int r = sorted.size() - 1;
int m = 0;
int midValue = 0;
while (l <= r) {
m = (l + r) / 2;
midValue = sorted.get(m);
if (midValue == num) {
return true;
} else if (midValue < num) {
l = m + 1;
} else {
r = m - 1;
}
}
return false;
}
}
}

@ -0,0 +1,225 @@
package class_2022_11_3_week;
import java.util.HashSet;
import java.util.TreeSet;
// 设计一个叫Bank的类并提供如下方法
// Bank(int n) 初始化的时候准备好0、1、2 ... n-1个座位
// int hello() :
// 如果此时所有座位都无人那么分配0号座位给当前用户
// 如果此时座位上有人,那么分配一个座位,这个座位保证是所有座位中离最近的人距离最远的座位
// 如果有多个座位都满足,分配座位编号最小的座位
// 返回座位编号
// 如果已经没有座位,返回-1表示无法分配
// void goodbye(int x) :
// 如果x号座位上无人什么也不用做
// 如果x号座位上有人现在这个人离开了该座位又能重新考虑分配
// 举例 :
// Bank b = new Bank(10) 0~9号座位被初始化出来
// b.hello()返回0表示给当前用户分配了0座位
// b.hello()返回9因为此时9座位离0座位的人最远此时
// 0 1 2 3 4 5 6 7 8 9
// X X
// b.hello()虽然座位4和座位5离最近人的距离都是4(最远)
// 这种情况根据描述分配座位编号最小的座位返回4此时
// 0 1 2 3 4 5 6 7 8 9
// X X X
// b.hello()座位2、座位6、座位7都是离最近人的距离最远的(2)
// 这种情况根据描述分配座位编号最小的座位返回2此时
// 0 1 2 3 4 5 6 7 8 9
// X X X X
// b.goodbye(4)4座位的人离开了此时
// 0 1 2 3 4 5 6 7 8 9
// X X X
// b.hello()座位5、座位6都是离最近人的距离最远的(3)
// 这种情况根据描述分配座位编号最小的座位返回5
// 测试连接 : https://leetcode.cn/problems/exam-room/
public class Code05_FarAwaySuggestion {
class ExamRoom {
// 空闲座位的类
// 比如一共8个空间
// 0 1 2 3 4 5 6 7
// 如果某个空闲座位区间是0~3
// 0 1 2 3 X
// 0是开头3是结尾提供4的距离因为0位置离X位置最远
// 如果某个空闲座位区间是4~7
// X 4 5 6 7
// 4是开头7是结尾提供4的距离因为7位置离X位置最远
// 如果某个空闲座位区间是2~5
// X 2 3 4 5 X
// 2是开头5是结尾提供2的距离因为3、4位置离X位置最远
// 根据start、end的具体情况可以算出far也可以算出该分配那个座位
public static class FreeSpace {
public int start;
public int end;
public int far;
public FreeSpace(int a, int b, int c) {
start = a;
end = b;
far = c;
}
}
// right是最右的座位在哪
public int right;
// 所有空闲区间都在seats里
// far越远越早使用
// far一样start越小越早使用
// 需要看体系学习班,有序表、比较器的内容
public TreeSet<FreeSpace> seats;
// 所有空闲区间,根据开头位置从小到大的一个有序表
// 方便定位,举个例子
// 比如所有的空闲区间为:
// 3~7
// 8~10
// 23~56
// 64~78
// 在heads中这些空闲区间是根据开头位置组织的
// 假设x = 60位置然后被释放了
// 你需要找到 <= 60位置最近的空闲区间
// 你需要找到 >= 60位置最近的空闲区间
// 因为你需要可能需要把x位置左边、x位置、x位置右边
// 三段合起来如果x可以联通的话
// 那么你就可以用这个有序表,快速定位
// 查找 <= 60且最近的区间就是有序表的floor方法速度很快
// 查找 >= 60且最近的区间就是有序表的ceiling方法速度很快
// 然后你就能查到:
// 23~56 60~60 64~78
// 不能合并因为这三段无法通过60~60连起来
// 但是如果能连接的话,这么做就很方便了
public TreeSet<FreeSpace> heads;
// 那些座位已经使用了,都在这个哈希表里
public HashSet<Integer> used;
public ExamRoom(int n) {
right = n - 1;
// far越远越早使用
// far一样start越小越早使用
// 看比较器的内容!
seats = new TreeSet<>((a, b) -> a.far != b.far ? (b.far - a.far) : (a.start - b.start));
// 根据开头位置从小到大的一个有序表
heads = new TreeSet<>((a, b) -> a.start - b.start);
used = new HashSet<>();
// 最开始时0~n-1整个范围都是空闲区间
add(0, right, Integer.MAX_VALUE);
}
public int seat() {
if (used.size() == right + 1) {
return -1;
}
FreeSpace cur = poll();
int ans;
// 比如有8个位置
// 0 1 2 3 4 5 6 7
// 如果cur.start == 0, cur.end == 7
// 此时肯定使用0位置
if (cur.start == 0 && cur.end == right) {
ans = 0;
add(1, right, right);
} else if (cur.start == 0) {
// 如果cur.start == 0, cur.end != 7
// 0(s) 1 2 3 4 5(e) 6 7
// 此时肯定使用0位置
// 剩下就是1(s)...e
// 提供的距离就是(e - s) / 2 + 1
ans = 0;
int start = 1;
int end = cur.end;
if (start <= end) {
add(start, end, (end - start) / 2 + 1);
}
} else if (cur.end == right) {
// 如果cur.start != 0, cur.end == 7
// 0 1 2(s) 3 4 5 6 7(e)
// 此时肯定使用7位置
// 剩下就是s...6(e)
// 提供的距离就是(e - s) / 2 + 1
ans = right;
int start = cur.start;
int end = cur.end - 1;
if (start <= end) {
add(start, end, (end - start) / 2 + 1);
}
} else {
// 如果cur.start != 0, cur.end != 7
// 0 1 2(s) 3 4 5 6(e) 7
// 此时肯定使用中点位置,(s + e) / 2
// 0 1 2(s) 3 4(中点) 5 6(e) 7
// 剩下就是s...中点-1
// 还剩下,中点+1...e
ans = (cur.start + cur.end) / 2;
int start1 = cur.start;
int end1 = ans - 1;
int start2 = ans + 1;
int end2 = cur.end;
if (start1 <= end1) {
add(start1, end1, (end1 - start1) / 2 + 1);
}
if (start2 <= end2) {
add(start2, end2, (end2 - start2) / 2 + 1);
}
}
used.add(ans);
return ans;
}
private void add(int start, int end, int distance) {
FreeSpace space = new FreeSpace(start, end, distance);
seats.add(space);
heads.add(space);
}
private FreeSpace poll() {
FreeSpace space = seats.pollFirst();
heads.remove(space);
return space;
}
public void leave(int x) {
if (used.contains(x)) {
used.remove(x);
FreeSpace m = new FreeSpace(x, x, 1);
FreeSpace l = heads.floor(m);
FreeSpace r = heads.ceiling(m);
merge(l, m, r);
}
}
// 左区间、中区间、右区间
// 能合并就合在一起
private void merge(FreeSpace l, FreeSpace m, FreeSpace r) {
int start = m.start;
int end = m.end;
if (l != null && l.end == m.start - 1) {
remove(l);
start = l.start;
}
if (r != null && m.end + 1 == r.start) {
remove(r);
end = r.end;
}
int far = 0;
if (start == 0 && end == right) {
far = Integer.MAX_VALUE;
} else if (start == 0) {
far = end + 1;
} else if (end == right) {
far = end - start + 1;
} else {
far = (end - start) / 2 + 1;
}
add(start, end, far);
}
private void remove(FreeSpace space) {
seats.remove(space);
heads.remove(space);
}
}
}

@ -2389,6 +2389,80 @@ num 包含数字 '1' 到 '9' ,其中每个数字 至多 使用一次。
第048节 2022年11月第3周流行算法题目解析
来自亚马逊
给定一个字符串数组strs其中每个字符串都是小写字母组成的
如果i < j并且strs[i]和strs[j]所有的字符随意去排列能组成回文串
那么说(i,j)叫做一个互补对(complementary)
求strs中有多少个互补对
strs长度 <= 3 * 10^5
单个字符串长度 <= 10^5
strs里所有字符串总长度 <= 10^6
n对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手
人和座位由一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的ID
情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)
返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起
每次交换可选择任意两人,让他们站起来交换座位
测试链接 : https://leetcode.cn/problems/couples-holding-hands/
来自谷歌
给定一个长度为N的数组值一定在0~N-1范围且每个值不重复
比如arr = [4, 2, 0, 3, 1]
把0想象成洞任何非0数字都可以来到这个洞里然后在原本的位置留下洞
比如4这个数字来到0所代表的洞里那么数组变成 :
arr = [0, 2, 4, 3, 1]
也就是原来的洞被4填满4走后留下了洞
任何数字只能搬家到洞里,并且走后留下洞
通过搬家的方式,想变成有序的,有序有两种形式
比如arr = [4, 2, 0, 3, 1],变成
[0, 1, 2, 3, 4]或者[1, 2, 3, 4, 0]都叫有序
返回变成任何一种有序的情况都可以,最少的数字搬动次数
测试链接 : https://leetcode.cn/problems/sort-array-by-moving-items-to-empty-space/
设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。
实现 WordFilter 类:
WordFilter(string[] words) 使用词典中的单词 words 初始化对象
f(string pref, string suff)
返回词典中具有前缀 prefix 和后缀 suff 的单词的下标
如果存在不止一个满足要求的下标,返回其中 最大的下标
如果不存在这样的单词,返回 -1 。
测试链接 : https://leetcode.cn/problems/prefix-and-suffix-search/
设计一个叫Bank的类并提供如下方法
Bank(int n) 初始化的时候准备好0、1、2 ... n-1个座位
int hello() :
如果此时所有座位都无人那么分配0号座位给当前用户
如果此时座位上有人,那么分配一个座位,这个座位保证是所有座位中离最近的人距离最远的座位
如果有多个座位都满足,分配座位编号最小的座位
返回座位编号
如果已经没有座位,返回-1表示无法分配
void goodbye(int x) :
如果x号座位上无人什么也不用做
如果x号座位上有人现在这个人离开了该座位又能重新考虑分配
举例 :
Bank b = new Bank(10) 0~9号座位被初始化出来
b.hello()返回0表示给当前用户分配了0座位
b.hello()返回9因为此时9座位离0座位的人最远此时
0 1 2 3 4 5 6 7 8 9
X X
b.hello()虽然座位4和座位5离最近人的距离都是4(最远)
这种情况根据描述分配座位编号最小的座位返回4此时
0 1 2 3 4 5 6 7 8 9
X X X
b.hello()座位2、座位6、座位7都是离最近人的距离最远的(2)
这种情况根据描述分配座位编号最小的座位返回2此时
0 1 2 3 4 5 6 7 8 9
X X X X
b.goodbye(4)4座位的人离开了此时
0 1 2 3 4 5 6 7 8 9
X X X
b.hello()座位5、座位6都是离最近人的距离最远的(3)
这种情况根据描述分配座位编号最小的座位返回5
测试连接 : https://leetcode.cn/problems/exam-room/

Loading…
Cancel
Save