diff --git a/算法周更班/class_2022_08_2_week/Code01_ParenthesesDye.java b/算法周更班/class_2022_08_2_week/Code01_ParenthesesDye.java new file mode 100644 index 0000000..7239e03 --- /dev/null +++ b/算法周更班/class_2022_08_2_week/Code01_ParenthesesDye.java @@ -0,0 +1,199 @@ +package class_2022_08_2_week; + +// 来自猿辅导 +// 2022.8.7笔试第三道 +// 给定一个数组arr,和一个正数k +// 如果arr[i] == 0,表示i这里既可以是左括号也可以是右括号, +// 而且可以涂上1~k每一种颜色 +// 如果arr[i] != 0,表示i这里已经确定是左括号,颜色就是arr[i]的值 +// 那么arr整体就可以变成某个括号字符串,并且每个括号字符都带有颜色 +// 返回在括号字符串合法的前提下,有多少种不同的染色方案 +// 不管是排列、还是颜色,括号字符串任何一点不一样,就算不同的染色方案 +// 最后的结果%10001,为了方便,我们不处理mod,就管核心思路 +// 2 <= arr长度 <= 5000 +// 1 <= k <= 1000 +// 0 <= arr[i] <= k +public class Code01_ParenthesesDye { + + // 暴力方法 + // 为了验证 + public static int ways1(int[] arr, int k) { + if ((arr.length & 1) != 0) { + return 0; + } + return process1(arr, 0, k); + } + + public static int process1(int[] arr, int index, int k) { + if (index == arr.length) { + int n = arr.length; + int[] stack = new int[n]; + int size = 0; + for (int i = 0; i < n; i++) { + if (arr[i] > 0) { + stack[size++] = arr[i]; + } else { + if (size == 0 || stack[--size] != -arr[i]) { + return 0; + } + } + } + return size == 0 ? 1 : 0; + } else if (arr[index] != 0) { + return process1(arr, index + 1, k); + } else { + int ans = 0; + for (int color = 1; color <= k; color++) { + arr[index] = color; + ans += process1(arr, index + 1, k); + arr[index] = -color; + ans += process1(arr, index + 1, k); + arr[index] = 0; + } + return ans; + } + } + + // 正式方法 + // 时间复杂度O(N^2), N是数组长度 + // 首先求合法的括号组合数量(忽略染色这件事), + // 就是combines方法,看注释 + // 当括号数量求出来,再看染色能有几种 + // 比如忽略颜色,某个合法的括号结合 长度为n, + // 如果已经有b个涂上了颜色,而且是左括号 + // 那么,因为该结合是合法的, + // 所以这b个涂上了颜色的左括号,和哪些右括号结合, + // 其实是确定的,这些右括号颜色也是确定的 + // 那么还剩n-(b*2)个字符 + // 这n-(b*2)个字符,就是(n-(b*2))/2对括号 + // 每对括号都可以自由发挥,所以,任何一个合法的组合,涂色方案为k^((n-(b*2))/2) + // 最终答案 : 合法括号组合数量 * k^((n-(b*2))/2) + public static int ways2(int[] arr, int k) { + int n = arr.length; + if ((n & 1) != 0) { + return 0; + } + int a = combines(arr); + int b = 0; + for (int num : arr) { + if (num != 0) { + b++; + } + } + return a * ((int) Math.pow((double) k, (double) ((n - (b << 1)) >> 1))); + } + + // 忽略染色这件事,求合法的括号结合数量 + public static int combines(int[] arr) { + int n = arr.length; + int[][] dp = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + dp[i][j] = -1; + } + } + return f(arr, 0, 0, dp); + } + + // arr[i....]范围上,去做决定 + // j : arr[0..i-1]已经做完决定的部分,左括号比右括号,多几个 + // 返回: + // arr[i....]范围上,去做决定, + // 已经做完决定的部分,左括号比右括号多j个 + // 这样的情况下,最终合法的括号结合,多少个! + // process(arr, 0, 0) + public static int process(int[] arr, int i, int j) { + if (i == arr.length) { + return j == 0 ? 1 : 0; + } + if (j < 0) { + return 0; + } + // 这个不写也行 + // 锦上添花的剪枝条件 + if (arr.length - i < j) { + return 0; + } + // arr[i] != 0 + if (arr[i] != 0) { + // ( + return process(arr, i + 1, j + 1); + } else { + // arr[i] 0 ? ( ) + int p1 = process(arr, i + 1, j + 1); + int p2 = process(arr, i + 1, j - 1); + return p1 + p2; + } + } + + // 在arr[i...]范围上做决定 + // 之前在arr[0...i-1]上的决定,使得左括号比右括号多了j个 + // 最终合法的括号结合是多少 + public static int f(int[] arr, int i, int j, int[][] dp) { + int n = arr.length; + if (i == n) { + return j == 0 ? 1 : 0; + } + if (j < 0) { + return 0; + } + if (n - i < j) { + return 0; + } + // 如果缓存命中,直接返回答案 + if (dp[i][j] != -1) { + return dp[i][j]; + } + int ans = 0; + if (arr[i] > 0) { + ans = f(arr, i + 1, j + 1, dp); + } else { + ans = f(arr, i + 1, j + 1, dp) + f(arr, i + 1, j - 1, dp); + } + dp[i][j] = ans; + return ans; + } + + // 生成长度随机的数组 + // 值在0~K之间,但是50%的概率值是0,50%的概率值是1~k中的一个 + public static int[] randomArray(int n, int k) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = Math.random() < 0.5 ? 0 : ((int) (Math.random() * k) + 1); + } + return ans; + } + + public static void main(String[] args) { + int N = 5; + int K = 4; + int testTimes = 1000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = ((int) (Math.random() * N) + 1) << 1; + int k = (int) (Math.random() * K) + 1; + int[] arr = randomArray(n, k); + int ans1 = ways1(arr, k); + int ans2 = ways2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 5000; + int k = 1000; + System.out.println("数组长度 : " + n); + System.out.println("颜色数量 : " + k); + int[] arr = randomArray(n, k); + long start = System.currentTimeMillis(); + ways2(arr, k); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + "毫秒"); + System.out.println("性能测试结束"); + + System.out.println("注意 : 这个解答没有取mod,只展示了核心思路"); + } + +} diff --git a/算法周更班/class_2022_08_2_week/Code02_TreeDye.java b/算法周更班/class_2022_08_2_week/Code02_TreeDye.java new file mode 100644 index 0000000..c7342bd --- /dev/null +++ b/算法周更班/class_2022_08_2_week/Code02_TreeDye.java @@ -0,0 +1,161 @@ +package class_2022_08_2_week; + +import java.util.ArrayList; +import java.util.Arrays; + +// 来自米哈游 +// 给定一个正数n,表示有多少个节点 +// 给定一个二维数组edges,表示所有无向边 +// edges[i] = {a, b} 表示a到b有一条无向边 +// edges一定表示的是一个无环无向图,也就是树结构 +// 每个节点可以染1、2、3三种颜色 +// 要求 : 非叶节点的相邻点一定要至少有两种和自己不同颜色的点 +// 返回一种达标的染色方案,也就是一个数组,表示每个节点的染色状况 +// 1 <= 节点数量 <= 10的5次方 +public class Code02_TreeDye { + + // 1 2 3 1 2 3 1 2 3 + public static int[] rule1 = { 1, 2, 3 }; + + // 1 3 2 1 3 2 1 3 2 + public static int[] rule2 = { 1, 3, 2 }; + + public static int[] dye(int n, int[][] edges) { + ArrayList> graph = new ArrayList<>(); + // 0 : { 2, 1 } + // 1 : { 0 } + // 2 : { 0 } + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + } + for (int[] edge : edges) { + // 0 -> 2 + // 1 -> 0 + graph.get(edge[0]).add(edge[1]); + graph.get(edge[1]).add(edge[0]); + } + // 选一个头节点! + int head = -1; + for (int i = 0; i < n; i++) { + if (graph.get(i).size() >= 2) { + head = i; + break; + } + } + // graph + // head + int[] colors = new int[n]; + if (head == -1) { // 两个点,互相连一下 + // 把colors,所有位置,都设置成1 + Arrays.fill(colors, 1); + } else { + // dfs 染色了! + colors[head] = 1; + dfs(graph, graph.get(head).get(0), 1, rule1, colors); + for (int i = 1; i < graph.get(head).size(); i++) { + dfs(graph, graph.get(head).get(i), 1, rule2, colors); + } + } + return colors; + } + + // 整个图结构,都在graph + // 当前来到的节点,是head号节点 + // head号节点,在level层 + // 染色的规则,rule {1,2,3...} {1,3,2...} + // 做的事情:以head为头的整颗树,每个节点,都染上颜色 + // 填入到colors数组里去 + public static void dfs( + ArrayList> graph, + int head, + int level, + int[] rule, int[] colors) { + + colors[head] = rule[level % 3]; + for (int next : graph.get(head)) { + if (colors[next] == 0) { + dfs(graph, next, level + 1, rule, colors); + } + } + } + + // 为了测试 + // 生成无环无向图 + public static int[][] randomEdges(int n) { + int[] order = new int[n]; + for (int i = 0; i < n; i++) { + order[i] = i; + } + for (int i = n - 1; i >= 0; i--) { + swap(order, i, (int) (Math.random() * (i + 1))); + } + int[][] edges = new int[n - 1][2]; + for (int i = 1; i < n; i++) { + edges[i - 1][0] = order[i]; + edges[i - 1][1] = order[(int) (Math.random() * i)]; + } + return edges; + } + + // 为了测试 + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 为了测试 + public static boolean rightAnswer(int n, int[][] edges, int[] colors) { + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + } + for (int[] edge : edges) { + graph.get(edge[0]).add(edge[1]); + graph.get(edge[1]).add(edge[0]); + } + boolean[] hasColors = new boolean[4]; + for (int i = 0, colorCnt = 1; i < n; i++, colorCnt = 1) { + if (colors[i] == 0) { + return false; + } + if (graph.get(i).size() <= 1) { // i号点是叶节点 + continue; + } + hasColors[colors[i]] = true; + for (int near : graph.get(i)) { + if (!hasColors[colors[near]]) { + hasColors[colors[near]] = true; + colorCnt++; + } + } + if (colorCnt != 3) { + return false; + } + Arrays.fill(hasColors, false); + } + return true; + } + + public static void main(String[] args) { +// int n = 7; +// int[][] edges = randomEdges(n); +// for (int[] edge : edges) { +// System.out.println(edge[0] + " , " + edge[1]); +// } + + int N = 100; + int testTimes = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[][] edges = randomEdges(n); + int[] ans = dye(n, edges); + if (!rightAnswer(n, edges, ans)) { + System.out.println("出错了"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_08_2_week/Code03_ReversePolishNotation.java b/算法周更班/class_2022_08_2_week/Code03_ReversePolishNotation.java new file mode 100644 index 0000000..94f209c --- /dev/null +++ b/算法周更班/class_2022_08_2_week/Code03_ReversePolishNotation.java @@ -0,0 +1,89 @@ +package class_2022_08_2_week; + +import java.util.Stack; + +// 给定一个逆波兰式 +// 转化成正确的中序表达式 +// 要求只有必要加括号的地方才加括号 +public class Code03_ReversePolishNotation { + + // 请保证给定的逆波兰式是正确的! + public static int getAns(String rpn) { + if (rpn == null || rpn.equals("")) { + return 0; + } + String[] parts = rpn.split(" "); + Stack stack = new Stack<>(); + for (String part : parts) { + if (part.equals("+") || part.equals("-") || part.equals("*") || part.equals("/")) { + int right = stack.pop(); + int left = stack.pop(); + int ans = 0; + if (part.equals("+")) { + ans = left + right; + } else if (part.equals("-")) { + ans = left - right; + } else if (part.equals("*")) { + ans = left * right; + } else { + ans = left / right; + } + stack.push(ans); + } else { + stack.push(Integer.valueOf(part)); + } + } + // stack 只有一个数,最终的结果 + return stack.pop(); + } + + enum Operation { + SingleNumber, AddOrMinus, MultiplyOrDivide; + } + + // 请保证输入的逆波兰式是正确的 + // 否则该函数不保证正确性 + // 逆波兰式仅支持+、-、*、/ + // 想支持更多算术运算符自己改 + public static String convert(String rpn) { + if (rpn == null || rpn.equals("")) { + return rpn; + } + String[] parts = rpn.split(" "); + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + for (String cur : parts) { + // cur 当前遇到的字符串 + // +- */ 单数 + if (cur.equals("+") || cur.equals("-")) { + String b = stack1.pop(); + String a = stack1.pop(); + stack2.pop(); + stack2.pop(); + stack1.push(a + cur + b); + stack2.push(Operation.AddOrMinus); + } else if (cur.equals("*") || cur.equals("/")) { + String b = stack1.pop(); + String a = stack1.pop(); + Operation bOp = stack2.pop(); + Operation aOp = stack2.pop(); + String left = aOp == Operation.AddOrMinus ? ("(" + a + ")") : (a); + String right = bOp == Operation.AddOrMinus ? ("(" + b + ")") : (b); + stack1.push(left + cur + right); + stack2.push(Operation.MultiplyOrDivide); + } else { + stack1.push(cur); + stack2.push(Operation.SingleNumber); + } + } + return stack1.pop(); + } + + public static void main(String[] args) { + // 3*(-5+13)+6/(2-3+2)-4*5*3 + String rpn = "3 -5 13 + * 6 2 3 - 2 + / + 4 5 3 * * -"; + System.out.println(getAns(rpn)); + System.out.println(convert(rpn)); + } + +} diff --git a/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints1.java b/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints1.java new file mode 100644 index 0000000..08132fd --- /dev/null +++ b/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints1.java @@ -0,0 +1,92 @@ +package class_2022_08_2_week; + +// 给定平面上n个点,x和y坐标都是整数 +// 找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的 +// 返回最短距离,精确到小数点后面4位 +// 测试链接 : https://www.luogu.com.cn/problem/P1429 +// 提交如下代码,把主类名改成Main,可以直接通过 +// T(N) = 2*T(N/2) + O(N*logN) +// 这个表达式的时间复杂度是O(N*(logN的平方)) +// 复杂度证明 : https://math.stackexchange.com/questions/159720/ +// 网上大部分的帖子,答案都是这个复杂度 +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 Code04_ClosestTwoPoints1 { + + public static int N = 200001; + + public static Point[] points = new Point[N]; + + public static Point[] deals = new Point[N]; + + 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) { + int n = (int) in.nval; + for (int i = 0; i < n; i++) { + in.nextToken(); + double x = (double) in.nval; + in.nextToken(); + double y = (double) in.nval; + points[i] = new Point(x, y); + } + Arrays.sort(points, 0, n, (a, b) -> a.x <= b.x ? -1 : 1); + double ans = nearest(0, n - 1); + out.println(String.format("%.4f", ans)); + out.flush(); + } + } + + public static class Point { + public double x; + public double y; + + public Point(double a, double b) { + x = a; + y = b; + } + } + + public static double nearest(int left, int right) { + double ans = Double.MAX_VALUE; + if (left == right) { + return ans; + } + int mid = (right + left) / 2; + ans = Math.min(nearest(left, mid), nearest(mid + 1, right)); + int l = mid; + int r = mid + 1; + int size = 0; + while (l >= left && points[mid].x - points[l].x <= ans) { + deals[size++] = points[l--]; + } + while (r <= right && points[r].x - points[mid].x <= ans) { + deals[size++] = points[r++]; + } + Arrays.sort(deals, 0, size, (a, b) -> a.y <= b.y ? -1 : 1); + for (int i = 0; i < size; i++) { + for (int j = i + 1; j < size; j++) { + if (deals[j].y - deals[i].y >= ans) { + break; + } + ans = Math.min(ans, distance(deals[i], deals[j])); + } + } + return ans; + } + + public static double distance(Point a, Point b) { + double x = a.x - b.x; + double y = a.y - b.y; + return Math.sqrt(x * x + y * y); + } + +} \ No newline at end of file diff --git a/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints2.java b/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints2.java new file mode 100644 index 0000000..8af6ccf --- /dev/null +++ b/算法周更班/class_2022_08_2_week/Code04_ClosestTwoPoints2.java @@ -0,0 +1,114 @@ +package class_2022_08_2_week; + +// 给定平面上n个点,x和y坐标都是整数 +// 找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的 +// 返回最短距离,精确到小数点后面4位 +// 测试链接 : https://www.luogu.com.cn/problem/P1429 +// 提交如下代码,把主类名改成Main,可以直接通过 +// T(N) = 2*T(N/2) + O(N) +// 这个表达式我们很熟悉,和归并排序一样的表达式 +// 时间复杂度是O(N*logN) +// 需要用到归并排序的技巧才能做到 +// 我们课上的独家 +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 Code04_ClosestTwoPoints2 { + + public static int N = 200001; + + public static Point[] points = new Point[N]; + + public static Point[] merge = new Point[N]; + + public static Point[] deals = new Point[N]; + + 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) { + int n = (int) in.nval; + for (int i = 0; i < n; i++) { + in.nextToken(); + double x = (double) in.nval; + in.nextToken(); + double y = (double) in.nval; + points[i] = new Point(x, y); + } + Arrays.sort(points, 0, n, (a, b) -> a.x <= b.x ? -1 : 1); + double ans = nearest(0, n - 1); + out.println(String.format("%.4f", ans)); + out.flush(); + } + } + + public static class Point { + public double x; + public double y; + + public Point(double a, double b) { + x = a; + y = b; + } + } + + public static double nearest(int left, int right) { + double ans = Double.MAX_VALUE; + if (left == right) { + return ans; + } + int mid = (right + left) / 2; + double midX = points[mid].x; + ans = Math.min(nearest(left, mid), nearest(mid + 1, right)); + int p1 = left; + int p2 = mid + 1; + int mergeSize = left; + int dealSize = 0; + while (p1 <= mid && p2 <= right) { + merge[mergeSize] = points[p1].y <= points[p2].y ? points[p1++] : points[p2++]; + if (Math.abs(merge[mergeSize].x - midX) <= ans) { + deals[dealSize++] = merge[mergeSize]; + } + mergeSize++; + } + while (p1 <= mid) { + merge[mergeSize] = points[p1++]; + if (Math.abs(merge[mergeSize].x - midX) <= ans) { + deals[dealSize++] = merge[mergeSize]; + } + mergeSize++; + } + while (p2 <= right) { + merge[mergeSize] = points[p2++]; + if (Math.abs(merge[mergeSize].x - midX) <= ans) { + deals[dealSize++] = merge[mergeSize]; + } + mergeSize++; + } + for (int i = left; i <= right; i++) { + points[i] = merge[i]; + } + for (int i = 0; i < dealSize; i++) { + for (int j = i + 1; j < dealSize; j++) { + if (deals[j].y - deals[i].y >= ans) { + break; + } + ans = Math.min(ans, distance(deals[i], deals[j])); + } + } + return ans; + } + + public static double distance(Point a, Point b) { + double x = a.x - b.x; + double y = a.y - b.y; + return Math.sqrt(x * x + y * y); + } + +} \ No newline at end of file diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index ac634bc..f2fa558 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -1536,6 +1536,41 @@ int query(int left, int right, int threshold)  +第035节 2022年8月第2周流行算法题目解析 + +来自猿辅导 +2022.8.7笔试第三道 +给定一个数组arr,和一个正数k +如果arr[i] == 0,表示i这里既可以是左括号也可以是右括号, +而且可以涂上1~k每一种颜色 +如果arr[i] != 0,表示i这里已经确定是左括号,颜色就是arr[i]的值 +那么arr整体就可以变成某个括号字符串,并且每个括号字符都带有颜色 +返回在括号字符串合法的前提下,有多少种不同的染色方案 +不管是排列、还是颜色,括号字符串任何一点不一样,就算不同的染色方案 +最后的结果%10001,为了方便,我们不处理mod,就管核心思路 +2 <= arr长度 <= 5000 +1 <= k <= 1000 +0 <= arr[i] <= k + +来自米哈游 +给定一个正数n,表示有多少个节点 +给定一个二维数组edges,表示所有无向边 +edges[i] = {a, b} 表示a到b有一条无向边 +edges一定表示的是一个无环无向图,也就是树结构 +每个节点可以染1、2、3三种颜色 +要求 : 非叶节点的相邻点一定要至少有两种和自己不同颜色的点 +返回一种达标的染色方案,也就是一个数组,表示每个节点的染色状况 +1 <= 节点数量 <= 10的5次方 + +给定一个逆波兰式 +转化成正确的中序表达式 +要求只有必要加括号的地方才加括号 + +给定平面上n个点,x和y坐标都是整数 +找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的 +返回最短距离,精确到小数点后面4位 +测试链接 : https://www.luogu.com.cn/problem/P1429 +