diff --git a/算法周更班/class_2023_02_2_week/Code01_NumberOfMusicPlaylists.java b/算法周更班/class_2023_02_2_week/Code01_NumberOfMusicPlaylists.java new file mode 100644 index 0000000..303c56a --- /dev/null +++ b/算法周更班/class_2023_02_2_week/Code01_NumberOfMusicPlaylists.java @@ -0,0 +1,72 @@ +package class_2023_02_2_week; + +// 你的音乐播放器里有 N 首不同的歌 +// 在旅途中,你的旅伴想要听 L 首歌(不一定不同,即,允许歌曲重复 +// 请你为她按如下规则创建一个播放列表 +// 每首歌至少播放一次 +// 一首歌只有在其他 K 首歌播放完之后才能再次播放 +// 返回可以满足要求的播放列表的数量 +// 由于答案可能非常大,请返回它模 10^9 + 7 的结果 +// 测试链接 : https://leetcode.cn/problems/number-of-music-playlists/ +public class Code01_NumberOfMusicPlaylists { + + public static int mod = 1000000007; + + public static int limit = 100; + + // 阶乘表 + public static long[] fac = new long[limit + 1]; + + // 阶乘结果的乘法逆元表 + public static long[] inv = new long[limit + 1]; + + static { + fac[0] = inv[0] = 1; + for (int i = 1; i <= limit; i++) { + fac[i] = ((long) i * fac[i - 1]) % mod; + } + // 费马小定理计算乘法逆元 +// for (int i = 1; i <= limit; i++) { +// inv[i] = power(fac[i], mod - 2); +// } + // 费马小定理计算乘法逆元,优化如下 + // 这一块叫:阶乘的逆元倒推 + inv[limit] = power(fac[limit], mod - 2); + for (int i = limit; i > 1; i--) { + inv[i - 1] = ((long) i * inv[i]) % mod; + } + } + + // x的n次方,% mod之后,是多少? + public static long power(long x, int n) { + long ans = 1; + while (n > 0) { + if ((n & 1) == 1) { + ans = (ans * x) % mod; + } + x = (x * x) % mod; + n >>= 1; + } + return ans; + } + + // n * logn + public static int numMusicPlaylists(int n, int l, int k) { + long cur, ans = 0, sign = 1; + for (int i = 0; i <= n - k; i++, sign = sign == 1 ? (mod - 1) : 1) { + // cur -> + // fac[n] -> n! % mod 的结果! + // inv[i] -> i! 的逆元! + // inv[n - k - i] -> (n - k - i)! 的逆元 + // sign * 1 -> 1 + // * -1 -> mod - 1 + cur = (sign * power(n - k - i, l - k)) % mod; + cur = (cur * fac[n]) % mod; + cur = (cur * inv[i]) % mod; + cur = (cur * inv[n - k - i]) % mod; + ans = (ans + cur) % mod; + } + return (int) ans; + } + +} diff --git a/算法周更班/class_2023_02_2_week/Code02_VerticalOrderTraversalOfBinaryTree.java b/算法周更班/class_2023_02_2_week/Code02_VerticalOrderTraversalOfBinaryTree.java new file mode 100644 index 0000000..da8e899 --- /dev/null +++ b/算法周更班/class_2023_02_2_week/Code02_VerticalOrderTraversalOfBinaryTree.java @@ -0,0 +1,82 @@ +package class_2023_02_2_week; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +// 给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列。 +// 对位于 (row, col) 的每个结点而言, +// 其左右子结点分别位于 (row + 1, col - 1) 和 (row + 1, col + 1) +// 树的根结点位于 (0, 0) 。 +// 二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有结点, +// 形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个结点, +// 则按结点的值从小到大进行排序。 +// 返回二叉树的 垂序遍历 序列。 +// 测试链接 : https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/ +public class Code02_VerticalOrderTraversalOfBinaryTree { + + // 不提交这个类 + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + // 提交以下所有的code + public static class Info { + public int row; + public int col; + public int val; + + public Info(int r, int c, int v) { + row = r; + col = c; + val = v; + } + } + + public static class InfoComparator implements Comparator { + + @Override + public int compare(Info o1, Info o2) { + if (o1.col != o2.col) { + return o1.col - o2.col; + } + if (o1.row != o2.row) { + return o1.row - o2.row; + } + return o1.val - o2.val; + } + + } + + public static List> verticalTraversal(TreeNode root) { + ArrayList collects = new ArrayList<>(); + Info rootInfo = new Info(0, 0, root.val); + collects.add(rootInfo); + dfs(root, rootInfo, collects); + List> ans = new ArrayList<>(); + collects.sort(new InfoComparator()); + for (int i = 0; i < collects.size(); i++) { + if (i == 0 || collects.get(i - 1).col != collects.get(i).col) { + ans.add(new ArrayList<>()); + } + ans.get(ans.size() - 1).add(collects.get(i).val); + } + return ans; + } + + public static void dfs(TreeNode root, Info rootInfo, ArrayList collects) { + if (root.left != null) { + Info leftInfo = new Info(rootInfo.row + 1, rootInfo.col - 1, root.left.val); + collects.add(leftInfo); + dfs(root.left, leftInfo, collects); + } + if (root.right != null) { + Info rightInfo = new Info(rootInfo.row + 1, rootInfo.col + 1, root.right.val); + collects.add(rightInfo); + dfs(root.right, rightInfo, collects); + } + } + +} diff --git a/算法周更班/class_2023_02_2_week/Code03_WidthOfBinaryTree.java b/算法周更班/class_2023_02_2_week/Code03_WidthOfBinaryTree.java new file mode 100644 index 0000000..31cf6b0 --- /dev/null +++ b/算法周更班/class_2023_02_2_week/Code03_WidthOfBinaryTree.java @@ -0,0 +1,54 @@ +package class_2023_02_2_week; + +import java.util.LinkedList; + +// 给你一棵二叉树的根节点 root ,返回树的 最大宽度 。 +// 树的 最大宽度 是所有层中最大的 宽度 。 +// 每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。 +// 将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点, +// 这些 null 节点也计入长度。 +// 题目数据保证答案将会在 32 位 带符号整数范围内。 +// 测试链接 : https://leetcode.cn/problems/maximum-width-of-binary-tree/ +public class Code03_WidthOfBinaryTree { + + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + // 提交以下所有的方法 + public static class Info { + // 当前节点 + public TreeNode node; + // 编号! + public int index; + + public Info(TreeNode n, int i) { + node = n; + index = i; + } + } + + public static int widthOfBinaryTree(TreeNode root) { + int ans = 1; + LinkedList queue = new LinkedList<>(); + queue.add(new Info(root, 1)); + while (!queue.isEmpty()) { + ans = Math.max(ans, queue.peekLast().index - queue.peekFirst().index + 1); + int size = queue.size(); + for (int i = 0; i < size; i++) { + Info cur = queue.pollFirst(); + if (cur.node.left != null) { + queue.addLast(new Info(cur.node.left, cur.index * 2)); + } + if (cur.node.right != null) { + queue.addLast(new Info(cur.node.right, cur.index * 2 + 1)); + } + } + } + return ans; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_02_2_week/Code04_MinimizeMalwareSpreadII.java b/算法周更班/class_2023_02_2_week/Code04_MinimizeMalwareSpreadII.java new file mode 100644 index 0000000..0873c2e --- /dev/null +++ b/算法周更班/class_2023_02_2_week/Code04_MinimizeMalwareSpreadII.java @@ -0,0 +1,144 @@ +package class_2023_02_2_week; + +import java.util.Arrays; + +// 给定一个由 n 个节点组成的网络,用 n x n 个邻接矩阵 graph 表示 +// 在节点网络中,只有当 graph[i][j] = 1 时,节点 i 能够直接连接到另一个节点 j。 +// 一些节点 initial 最初被恶意软件感染。只要两个节点直接连接, +// 且其中至少一个节点受到恶意软件的感染,那么两个节点都将被恶意软件感染。 +// 这种恶意软件的传播将继续,直到没有更多的节点可以被这种方式感染。 +// 假设 M(initial) 是在恶意软件停止传播之后,整个网络中感染恶意软件的最终节点数。 +// 我们可以从 initial 中删除一个节点, +// 并完全移除该节点以及从该节点到任何其他节点的任何连接。 +// 请返回移除后能够使 M(initial) 最小化的节点。 +// 如果有多个节点满足条件,返回索引 最小的节点 。 +// initial 中每个整数都不同 +// 测试链接 : https://leetcode.cn/problems/minimize-malware-spread-ii/ +public class Code04_MinimizeMalwareSpreadII { + + public int minMalwareSpread(int[][] graph, int[] initial) { + // 节点个数 + int n = graph.length; + // 病毒数组, + // 病毒initial -> [3,6,9] + // virus[3] = true; + // virus[6] = true; + // virus[7] = false; + boolean[] virus = new boolean[n]; + for (int i : initial) { + virus[i] = true; + } + // 建立并查集 + // n大小 0~n-1 + UnionFind uf = new UnionFind(n); + // 忽略感染点及其链接! + // 合并集合! + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + // i, j ? + if (graph[i][j] == 1 && !virus[i] && !virus[j]) { + uf.union(i, j); + } + } + } + // 所有非感染点,该链接都链接了! + // 0,6,7,8 是一个集合 + // 6被选成了代表点,无法控制,只由并查集自己决定 + // infect[6] == -1 ,目前这个集合没有感染源 + // infect[6] == -2 ,目前这个集合已经发现了不只一个感染源 + // infect[6] == x x>=0,目前这个集合已经发现了一个感染源,是x点 + int[] infect = new int[n]; + // 目前所有集合都没有感染源 + Arrays.fill(infect, -1); + for (int v : initial) { // 枚举所有感染点! + // 当前感染点是v + // 跟v链接的所有点,next + // n = 7 编号0~6 + // n = 13 编号0~12 + for (int next = 0; next < n; next++) { + if (v != next && !virus[next] && graph[v][next] == 1) { + // next 无辜点 + // 找到next集合所在的代表点,f + int f = uf.find(next); + if (infect[f] == -1) { + infect[f] = v; + } else { // != -1 可能 == -2 或者 >= 0 + // 之前的感染点 -> infect[f] + if (infect[f] != -2 && infect[f] != v) { + infect[f] = -2; + } + // infect[f] == -2 + // infect[f] == v + // 这两种情况,一律不设置是符合预期的 + } + } + } + } + int[] cnt = new int[n]; + for (int i = 0; i < n; i++) { + if (infect[i] >= 0) { + cnt[infect[i]] += uf.size[i]; + } + } + // 4 -> 13 + // 0 -> 6 + // 1 -> 5 + // 13 -> 50 + // 9 -> 20 + // 0 1 4 9 13 14 15 + // 6 5 13 20 50 34 7 + Arrays.sort(initial); + int ans = initial[0]; + int count = cnt[ans]; + for (int i : initial) { + if (cnt[i] > count) { + ans = i; + count = cnt[i]; + } + } + return ans; + } + + public static class UnionFind { + public int[] father; + public int[] size; + public int[] help; + + 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; + } + } + + 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]; + } + } + } + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 8b87a4c..c63d9c8 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -2911,5 +2911,65 @@ k位翻转 就是从 nums 中选择一个长度为 k 的 子数组 +第057节 2023年2月第2周流行算法题目解析 + +你的音乐播放器里有 N 首不同的歌 +在旅途中,你的旅伴想要听 L 首歌(不一定不同,即,允许歌曲重复 +请你为她按如下规则创建一个播放列表 +每首歌至少播放一次 +一首歌只有在其他 K 首歌播放完之后才能再次播放 +返回可以满足要求的播放列表的数量 +由于答案可能非常大,请返回它模 10^9 + 7 的结果 +测试链接 : https://leetcode.cn/problems/number-of-music-playlists/ + +给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列。 +对位于 (row, col) 的每个结点而言, +其左右子结点分别位于 (row + 1, col - 1) 和 (row + 1, col + 1) +树的根结点位于 (0, 0) 。 +二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有结点, +形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个结点, +则按结点的值从小到大进行排序。 +返回二叉树的 垂序遍历 序列。 +测试链接 : https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/ + +给你一棵二叉树的根节点 root ,返回树的 最大宽度 。 +树的 最大宽度 是所有层中最大的 宽度 。 +每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。 +将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点, +这些 null 节点也计入长度。 +题目数据保证答案将会在 32 位 带符号整数范围内。 +测试链接 : https://leetcode.cn/problems/maximum-width-of-binary-tree/ + +给定一个由 n 个节点组成的网络,用 n x n 个邻接矩阵 graph 表示 +在节点网络中,只有当 graph[i][j] = 1 时,节点 i 能够直接连接到另一个节点 j。 +一些节点 initial 最初被恶意软件感染。只要两个节点直接连接, +且其中至少一个节点受到恶意软件的感染,那么两个节点都将被恶意软件感染。 +这种恶意软件的传播将继续,直到没有更多的节点可以被这种方式感染。 +假设 M(initial) 是在恶意软件停止传播之后,整个网络中感染恶意软件的最终节点数。 +我们可以从 initial 中删除一个节点, +并完全移除该节点以及从该节点到任何其他节点的任何连接。 +请返回移除后能够使 M(initial) 最小化的节点。 +如果有多个节点满足条件,返回索引 最小的节点 。 +initial 中每个整数都不同 +测试链接 : https://leetcode.cn/problems/minimize-malware-spread-ii/ + + + + + + + + + + + + + + + + + + +