diff --git a/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph1.java b/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph1.java new file mode 100644 index 0000000..33076e0 --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph1.java @@ -0,0 +1,106 @@ +package class_2022_08_3_week; + +import java.util.ArrayList; + +// 给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。 +// 图用一个大小为 n 下标从 0 开始的数组 edges 表示, +// 节点 i 到节点 edges[i] 之间有一条有向边。如果节点 i 没有出边,那么 edges[i] == -1 。 +// 请你返回图中的 最长 环,如果没有任何环,请返回 -1 。 +// 一个环指的是起点和终点是 同一个 节点的路径。 +// 用强联通分量 +// 测试链接 : https://leetcode.cn/problems/longest-cycle-in-a-graph/ +public class Code01_LongestCycleInGraph1 { + + public static int longestCycle(int[] edges) { + int n = edges.length; + ArrayList> graph = new ArrayList>(); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList()); + } + for (int i = 0; i < n; i++) { + if (edges[i] != -1) { + graph.get(i).add(edges[i]); + } + } + StronglyConnectedComponents connectedComponents = new StronglyConnectedComponents(graph); + int m = connectedComponents.getSccn() + 1; + int[] cnt = new int[m]; + int[] scc = connectedComponents.getScc(); + for (int i = 0; i < n; i++) { + cnt[scc[i]]++; + } + int ans = -1; + for (int count : cnt) { + ans = Math.max(ans, count > 1 ? count : -1); + } + return ans; + } + + public static class StronglyConnectedComponents { + public ArrayList> nexts; + public int n; + public int stackSize; + public int cnt; + public int sccn; + public int[] stack; + public int[] dfn; + public int[] low; + public int[] scc; + + public StronglyConnectedComponents(ArrayList> edges) { + nexts = edges; + init(); + scc(); + } + + private void init() { + n = nexts.size(); + stackSize = 0; + cnt = 0; + sccn = 0; + stack = new int[n]; + dfn = new int[n]; + low = new int[n]; + scc = new int[n]; + } + + private void scc() { + for (int i = 0; i < n; i++) { + if (dfn[i] == 0) { + tarjan(i); + } + } + } + + private void tarjan(int p) { + low[p] = dfn[p] = ++cnt; + stack[stackSize++] = p; + for (int q : nexts.get(p)) { + if (dfn[q] == 0) { + tarjan(q); + } + if (scc[q] == 0) { + low[p] = Math.min(low[p], low[q]); + } + } + if (low[p] == dfn[p]) { + sccn++; + int top = 0; + do { + top = stack[--stackSize]; + scc[top] = sccn; + } while (top != p); + } + } + + public int[] getScc() { + return scc; + } + + public int getSccn() { + return sccn; + } + + } + +} diff --git a/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph2.java b/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph2.java new file mode 100644 index 0000000..7198972 --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code01_LongestCycleInGraph2.java @@ -0,0 +1,40 @@ +package class_2022_08_3_week; + +// 给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 , +// 其中每个节点 至多 有一条出边。 +// 图用一个大小为 n 下标从 0 开始的数组 edges 表示, +// 节点 i 到节点 edges[i] 之间有一条有向边。 +// 如果节点 i 没有出边,那么 edges[i] == -1 。 +// 请你返回图中的 最长 环,如果没有任何环,请返回 -1 。 +// 一个环指的是起点和终点是 同一个 节点的路径。 +// 测试链接 : https://leetcode.cn/problems/longest-cycle-in-a-graph/ +public class Code01_LongestCycleInGraph2 { + + public int longestCycle(int[] edges) { + // edges[i] = j i -> j + // edges[i] = -1 i X + int n = edges.length; + int[] ids = new int[n]; + // 发现的最大环,有几个点! + int ans = -1; + for (int from = 0, cnt = 1; from < n; from++) { + if (ids[from] == 0) { + for (int cur = from, fromId = cnt; cur != -1; + cur = edges[cur]) { + // from -> -> cur -> + if (ids[cur] > 0) { + // 访问过,此时的环,之前遍历过的点 + if (ids[cur] >= fromId) { // 新的环 + ans = Math.max(ans, cnt - ids[cur]); + } + // 如果上面的if不成立,老的点,直接跳出 + break; + } + ids[cur] = cnt++; + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_08_3_week/Code02_CutOrPoison.java b/算法周更班/class_2022_08_3_week/Code02_CutOrPoison.java new file mode 100644 index 0000000..8754491 --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code02_CutOrPoison.java @@ -0,0 +1,119 @@ +package class_2022_08_3_week; + +// 来自学员问题 +// 给定怪兽的血量为hp +// 第i回合如果用刀砍,怪兽在这回合会直接掉血,没有后续效果 +// 第i回合如果用毒,怪兽在这回合不会掉血, +// 但是之后每回合都会掉血,并且所有中毒的后续效果会叠加 +// 给定的两个数组cuts、poisons,两个数组等长,长度都是n +// 表示你在n回合内的行动, +// 每一回合的刀砍的效果由cuts[i]表示 +// 每一回合的中毒的效果由poisons[i]表示 +// 如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了 +// 但是怪兽如果有中毒效果的话,那么怪兽依然会在hp耗尽的那回合死掉 +// 返回你最快能在多少回合内将怪兽杀死 +// 数据范围 : +// 1 <= n <= 10的5次方 +// 1 <= hp <= 10的9次方 +// 1 <= cuts[i]、poisons[i] <= 10的9次方 +public class Code02_CutOrPoison { + + // 不算好的方法 + // 为了验证 + public static int fast1(int[] cuts, int[] poisons, int hp) { + int sum = 0; + for (int num : poisons) { + sum += num; + } + int[][][] dp = new int[cuts.length][hp + 1][sum + 1]; + return process1(cuts, poisons, 0, hp, 0, dp); + } + + public static int process1(int[] cuts, int[] poisons, int index, int restHp, int poisonEffect, int[][][] dp) { + restHp -= poisonEffect; + if (restHp <= 0) { + return index + 1; + } + // restHp > 0 + if (index == cuts.length) { + if (poisonEffect == 0) { + return Integer.MAX_VALUE; + } else { + return cuts.length + 1 + (restHp + poisonEffect - 1) / poisonEffect; + } + } + if (dp[index][restHp][poisonEffect] != 0) { + return dp[index][restHp][poisonEffect]; + } + int p1 = restHp <= cuts[index] ? (index + 1) + : process1(cuts, poisons, index + 1, restHp - cuts[index], poisonEffect, dp); + int p2 = process1(cuts, poisons, index + 1, restHp, poisonEffect + poisons[index], dp); + int ans = Math.min(p1, p2); + dp[index][restHp][poisonEffect] = ans; + return ans; + } + + // 真正想实现的方法 + // O(N * log(hp)) + public static int fast2(int[] cuts, int[] poisons, int hp) { + // 怪兽可能的最快死亡回合 + int l = 1; + // 怪兽可能的最晚死亡回合 + int r = hp + 1; + int m = 0; + int ans = Integer.MAX_VALUE; + while (l <= r) { + m = l + ((r - l) >> 1); + if (ok(cuts, poisons, hp, m)) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static boolean ok(int[] cuts, int[] posions, long hp, int limit) { + int n = Math.min(cuts.length, limit); + for (int i = 0, j = 1; i < n; i++, j++) { + hp -= Math.max((long) cuts[i], (long) (limit - j) * (long) posions[i]); + if (hp <= 0) { + return true; + } + } + return false; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 30; + int cutV = 20; + int posionV = 10; + int hpV = 200; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] cuts = randomArray(n, cutV); + int[] posions = randomArray(n, posionV); + int hp = (int) (Math.random() * hpV) + 1; + int ans1 = fast1(cuts, posions, hp); + int ans2 = fast2(cuts, posions, hp); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_08_3_week/Code03_MaxStack.java b/算法周更班/class_2022_08_3_week/Code03_MaxStack.java new file mode 100644 index 0000000..f9c277a --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code03_MaxStack.java @@ -0,0 +1,191 @@ +package class_2022_08_3_week; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; + +// 设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。 +// 实现 MaxStack 类: +// MaxStack() 初始化栈对象 +// void push(int x) 将元素 x 压入栈中。 +// int pop() 移除栈顶元素并返回这个元素。 +// int top() 返回栈顶元素,无需移除。 +// int peekMax() 检索并返回栈中最大元素,无需移除。 +// int popMax() 检索并返回栈中最大元素,并将其移除。 +// 如果有多个最大元素,只要移除 最靠近栈顶 的那个。 +// 测试链接 : https://leetcode.cn/problems/max-stack/ +public class Code03_MaxStack { + + class MaxStack { + + public int cnt; + + public HeapGreater heap; + + public Node top; + + public MaxStack() { + cnt = 0; + heap = new HeapGreater<>(new NodeComparator()); + top = null; + } + + public void push(int x) { + Node cur = new Node(x, ++cnt); + heap.push(cur); + if (top == null) { + top = cur; + } else { + top.last = cur; + cur.next = top; + top = cur; + } + } + + public int pop() { + Node ans = top; + if (top.next == null) { + top = null; + } else { + top = top.next; + top.last = null; + } + heap.remove(ans); + return ans.val; + } + + public int top() { + return top.val; + } + + public int peekMax() { + return heap.peek().val; + } + + public int popMax() { + Node ans = heap.pop(); + if (ans == top) { + if (top.next == null) { + top = null; + } else { + top = top.next; + top.last = null; + } + } else { + if (ans.next != null) { + ans.next.last = ans.last; + } + if (ans.last != null) { + ans.last.next = ans.next; + } + } + return ans.val; + } + + class Node { + public int val; + public int cnt; + public Node next; + public Node last; + + public Node(int v, int c) { + val = v; + cnt = c; + } + } + + class NodeComparator implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.val != o2.val ? (o2.val - o1.val) : (o2.cnt - o1.cnt); + } + + } + + class HeapGreater { + + private ArrayList heap; + private HashMap indexMap; + private int heapSize; + private Comparator comp; + + public HeapGreater(Comparator c) { + heap = new ArrayList<>(); + indexMap = new HashMap<>(); + heapSize = 0; + comp = c; + } + + public T peek() { + return heap.get(0); + } + + public void push(T obj) { + heap.add(obj); + indexMap.put(obj, heapSize); + heapInsert(heapSize++); + } + + public T pop() { + T ans = heap.get(0); + swap(0, heapSize - 1); + indexMap.remove(ans); + heap.remove(--heapSize); + heapify(0); + return ans; + } + + public void remove(T obj) { + T replace = heap.get(heapSize - 1); + int index = indexMap.get(obj); + indexMap.remove(obj); + heap.remove(--heapSize); + if (obj != replace) { + heap.set(index, replace); + indexMap.put(replace, index); + resign(replace); + } + } + + private void resign(T obj) { + heapInsert(indexMap.get(obj)); + heapify(indexMap.get(obj)); + } + + private void heapInsert(int index) { + while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) { + swap(index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + private void heapify(int index) { + int left = index * 2 + 1; + while (left < heapSize) { + int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) + : left; + best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index; + if (best == index) { + break; + } + swap(best, index); + index = best; + left = index * 2 + 1; + } + } + + private void swap(int i, int j) { + T o1 = heap.get(i); + T o2 = heap.get(j); + heap.set(i, o2); + heap.set(j, o1); + indexMap.put(o2, i); + indexMap.put(o1, j); + } + + } + + } + +} diff --git a/算法周更班/class_2022_08_3_week/Code04_CorporateFlightBookings.java b/算法周更班/class_2022_08_3_week/Code04_CorporateFlightBookings.java new file mode 100644 index 0000000..17c010a --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code04_CorporateFlightBookings.java @@ -0,0 +1,33 @@ +package class_2022_08_3_week; + +// 这里有 n 个航班,它们分别从 1 到 n 进行编号。 +// 有一份航班预订表 bookings , +// 表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] +// 意味着在从 firsti 到 lasti +//(包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。 +// 请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。 +// 测试链接 : https://leetcode.cn/problems/corporate-flight-bookings/ +public class Code04_CorporateFlightBookings { + + public static int[] corpFlightBookings(int[][] bookings, int n) { + // 1 2 3 4 n + // 0 1 2 3 .. n n+1 + int[] cnt = new int[n + 2]; + for (int[] book : bookings) { + // start book[0] + // end book[1] + // 票 book[2] + cnt[book[0]] += book[2]; + cnt[book[1] + 1] -= book[2]; + } + for (int i = 1; i < cnt.length; i++) { + cnt[i] += cnt[i - 1]; + } + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = cnt[i + 1]; + } + return ans; + } + +} diff --git a/算法周更班/class_2022_08_3_week/Code05_SmallestRotationWithHighestScore.java b/算法周更班/class_2022_08_3_week/Code05_SmallestRotationWithHighestScore.java new file mode 100644 index 0000000..e6c5233 --- /dev/null +++ b/算法周更班/class_2022_08_3_week/Code05_SmallestRotationWithHighestScore.java @@ -0,0 +1,57 @@ +package class_2022_08_3_week; + +// 给你一个数组 nums,我们可以将它按一个非负整数 k 进行轮调, +// 例如,数组为 nums = [2,4,1,3,0], +// 我们按 k = 2 进行轮调后,它将变成 [1,3,0,2,4]。 +// 这将记为 3 分, +// 因为 1 > 0 [不计分]、3 > 1 [不计分]、0 <= 2 [计 1 分]、 +// 2 <= 3 [计 1 分],4 <= 4 [计 1 分]。 +// 在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调下标 k 。 +// 如果有多个答案,返回满足条件的最小的下标 k 。 +// 测试链接 : +// https://leetcode.cn/problems/smallest-rotation-with-highest-score/ +public class Code05_SmallestRotationWithHighestScore { + + public static int bestRotation(int[] nums) { + int n = nums.length; + + // cnt : 差分数组 + // cnt最后进行前缀和的加工! + // 加工完了的cnt[0] : 整体向右移动0的距离, 一共能得多少分 + // 加工完了的cnt[i] : 整体向右移动i的距离, 一共能得多少分 + int[] cnt = new int[n + 1]; + for (int i = 0; i < n; i++) { + // 遍历每个数! + // 看看每个数,对差分数组哪些范围,会产生影响! + if (nums[i] < n) { + if (i <= nums[i]) { + add(cnt, nums[i] - i, n - i - 1); + } else { + add(cnt, 0, n - i - 1); + add(cnt, n - i + nums[i], n - 1); + } + } + } + for (int i = 1; i <= n; i++) { + cnt[i] += cnt[i - 1]; + } + // 最大得分是啥!已经求出来了 + int max = cnt[0]; + int ans = 0; + for (int i = n - 1; i >= 1; i--) { + // 整体移动的i 0 n-1 n-2 n-3 1 + // k 0 1 2 3 n-1 + if (cnt[i] > max) { + max = cnt[i]; + ans = i; + } + } + return ans == 0 ? 0 : (n - ans); + } + + public static void add(int[] cnt, int l, int r) { + cnt[l]++; + cnt[r + 1]--; + } + +} diff --git a/算法课堂笔记/算法和数据结构课程全梳理.xmind b/算法课堂笔记/算法和数据结构课程全梳理.xmind new file mode 100644 index 0000000..b9d32c2 Binary files /dev/null and b/算法课堂笔记/算法和数据结构课程全梳理.xmind differ diff --git a/算法课堂笔记/算法数据结构内容脑图大纲.xmind b/算法课堂笔记/算法数据结构内容脑图大纲.xmind deleted file mode 100644 index b707e7f..0000000 Binary files a/算法课堂笔记/算法数据结构内容脑图大纲.xmind and /dev/null differ diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index f2fa558..6a95a84 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -1573,5 +1573,64 @@ edges一定表示的是一个无环无向图,也就是树结构 +第036节 2022年8月第3周流行算法题目解析 + +给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 , +其中每个节点 至多 有一条出边。 +图用一个大小为 n 下标从 0 开始的数组 edges 表示, +节点 i 到节点 edges[i] 之间有一条有向边。 +如果节点 i 没有出边,那么 edges[i] == -1 。 +请你返回图中的 最长 环,如果没有任何环,请返回 -1 。 +一个环指的是起点和终点是 同一个 节点的路径。 +测试链接 : https://leetcode.cn/problems/longest-cycle-in-a-graph/ + +来自学员问题 +给定怪兽的血量为hp +第i回合如果用刀砍,怪兽在这回合会直接掉血,没有后续效果 +第i回合如果用毒,怪兽在这回合不会掉血, +但是之后每回合都会掉血,并且所有中毒的后续效果会叠加 +给定的两个数组cuts、poisons,两个数组等长,长度都是n +表示你在n回合内的行动, +每一回合的刀砍的效果由cuts[i]表示 +每一回合的中毒的效果由poisons[i]表示 +如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了 +但是怪兽如果有中毒效果的话,那么怪兽依然会在hp耗尽的那回合死掉 +返回你最快能在多少回合内将怪兽杀死 +数据范围 : +1 <= n <= 10的5次方 +1 <= hp <= 10的9次方 +1 <= cuts[i]、poisons[i] <= 10的9次方 + +设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。 +实现 MaxStack 类: +MaxStack() 初始化栈对象 +void push(int x) 将元素 x 压入栈中。 +int pop() 移除栈顶元素并返回这个元素。 +int top() 返回栈顶元素,无需移除。 +int peekMax() 检索并返回栈中最大元素,无需移除。 +int popMax() 检索并返回栈中最大元素,并将其移除。 +如果有多个最大元素,只要移除 最靠近栈顶 的那个。 +测试链接 : https://leetcode.cn/problems/max-stack/ + +这里有 n 个航班,它们分别从 1 到 n 进行编号。 +有一份航班预订表 bookings , +表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] +意味着在从 firsti 到 lasti +(包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。 +请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。 +测试链接 : https://leetcode.cn/problems/corporate-flight-bookings/ + +给你一个数组 nums,我们可以将它按一个非负整数 k 进行轮调, +例如,数组为 nums = [2,4,1,3,0], +我们按 k = 2 进行轮调后,它将变成 [1,3,0,2,4]。 +这将记为 3 分, +因为 1 > 0 [不计分]、3 > 1 [不计分]、0 <= 2 [计 1 分]、 +2 <= 3 [计 1 分],4 <= 4 [计 1 分]。 +在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调下标 k 。 +如果有多个答案,返回满足条件的最小的下标 k 。 +测试链接 : +https://leetcode.cn/problems/smallest-rotation-with-highest-score/ + +