diff --git a/公开课/class001/Code01_PosArrayToBST.java b/公开课/class001/Code01_PosArrayToBST.java new file mode 100644 index 0000000..f873fc0 --- /dev/null +++ b/公开课/class001/Code01_PosArrayToBST.java @@ -0,0 +1,224 @@ +package class001; + +import java.util.ArrayList; + +public class Code01_PosArrayToBST { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static Node posArrayToBST1(int[] posArr) { + // 0~N-1 + return process1(posArr, 0, posArr.length - 1); + } + + // 目前,我们在使用posArr[L..R]这些数字,来建树 + // 建出的每一个节点都连好,然后把整棵树的头节点返回 + public static Node process1(int[] posArr, int L, int R) { + if (L > R) { + return null; + } + Node head = new Node(posArr[R]); + if (L == R) { + return head; + } + int M = L - 1; + for (int i = L; i < R; i++) { // i -> L..R-1 + if (posArr[i] < posArr[R]) { + M = i; + } + } + head.left = process1(posArr, L, M); + head.right = process1(posArr, M + 1, R - 1); + return head; + } + + + public static Node process3(int[] posArr, int L, int R) { + Node head = new Node(posArr[R]); + if (L == R) { + return head; + } + int M = -1; + for (int i = L; i < R; i++) { // i -> L..R-1 + if (posArr[i] < posArr[R]) { + M = i; + } + } + // > + if(M==-1) { + head.right = process3(posArr, L, R - 1); + }else if(M == R-1) { + head.left = process3(posArr, L, R - 1); + }else { + head.left = process3(posArr, L, M); + head.right = process3(posArr, M + 1, R - 1); + } + return head; + } + + + + + + + + + + + public static Node posArrayToBST2(int[] posArr) { + return process2(posArr, 0, posArr.length - 1); + } + + public static Node process2(int[] posArr, int L, int R) { + if (L > R) { + return null; + } + Node head = new Node(posArr[R]); + if (L == R) { + return head; + } + + int M = L - 1; + int left = L; + int right = R - 1; + while (left <= right) { + int mid = left + ((right - left) >> 1); + if (posArr[mid] < posArr[R]) { + M = mid; + left = mid + 1; + } else { + right = mid - 1; + } + } + head.left = process2(posArr, L, M); + head.right = process2(posArr, M + 1, R - 1); + return head; + } + + // for test + public static int[] getBstPosArray(Node head) { + ArrayList posList = new ArrayList<>(); + pos(head, posList); + int[] ans = new int[posList.size()]; + for (int i = 0; i < ans.length; i++) { + ans[i] = posList.get(i); + } + return ans; + } + + // for test + public static void pos(Node head, ArrayList posList) { + if (head != null) { + pos(head.left, posList); + pos(head.right, posList); + posList.add(head.value); + } + } + + // for test [0~1000] + // N + public static Node generateRandomBST(int min, int max, int N) { + if (min > max) { + return null; + } + return createTree(min, max, 1, N); + } + + // for test + public static Node createTree(int min, int max, int level, int N) { + if (min > max || level > N) { + return null; + } + Node head = new Node(random(min, max)); + head.left = createTree(min, head.value - 1, level + 1, N); + head.right = createTree(head.value + 1, max, level + 1, N); + return head; + } + + // for test + public static int random(int min, int max) { + return min + (int) (Math.random() * (max - min + 1)); + } + + // for test + public static boolean isSameValueStructure(Node head1, Node head2) { + if (head1 == null && head2 != null) { + return false; + } + if (head1 != null && head2 == null) { + return false; + } + if (head1 == null && head2 == null) { + return true; + } + return head1.value == head2.value && isSameValueStructure(head1.left, head2.left) + && isSameValueStructure(head1.right, head2.right); + } + + // for test + public static void printTree(Node head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + // for test + public static void printInOrder(Node head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.right, height + 1, "v", len); + String val = to + head.value + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.left, height + 1, "^", len); + } + + // for test + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + // for test + public static void main(String[] args) { + int min = 0; + int max = 12; + int level = 4; + Node head = generateRandomBST(min, max, level); + int[] pos = getBstPosArray(head); + printTree(head); + printTree(posArrayToBST1(pos)); + printTree(posArrayToBST2(pos)); + System.out.println(isSameValueStructure(head, posArrayToBST1(pos))); + System.out.println(isSameValueStructure(head, posArrayToBST2(pos))); + + int testTimes = 5000000; + System.out.println("test begin, time time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + head = generateRandomBST(min, max, level); + pos = getBstPosArray(head); + if (!isSameValueStructure(head, posArrayToBST1(pos)) || !isSameValueStructure(head, posArrayToBST2(pos))) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class001/Code02_ContainAllCharExactly.java b/公开课/class001/Code02_ContainAllCharExactly.java new file mode 100644 index 0000000..1e67b59 --- /dev/null +++ b/公开课/class001/Code02_ContainAllCharExactly.java @@ -0,0 +1,115 @@ +package class001; + +import java.util.Arrays; + +public class Code02_ContainAllCharExactly { + + public static int containExactly1(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] aim = a.toCharArray(); + Arrays.sort(aim); + String aimSort = String.valueOf(aim); + for (int L = 0; L < s.length(); L++) { + for (int R = L; R < s.length(); R++) { + char[] cur = s.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(aimSort)) { + return L; + } + } + } + return -1; + } + + public static int containExactly2(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] str = s.toCharArray(); + char[] aim = a.toCharArray(); + for (int L = 0; L <= str.length - aim.length; L++) { + if (isCountEqual(str, L, aim)) { + return L; + } + } + return -1; + } + + public static boolean isCountEqual(char[] str, int L, char[] aim) { + int[] count = new int[256]; + for (int i = 0; i < aim.length; i++) { + count[aim[i]]++; + } + for (int i = 0; i < aim.length; i++) { + if (count[str[L + i]]-- == 0) { + return false; + } + } + return true; + } + + public static int containExactly3(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] aim = a.toCharArray(); + int[] count = new int[256]; + for (int i = 0; i < aim.length; i++) { + count[aim[i]]++; + } + int M = aim.length; + char[] str = s.toCharArray(); + int inValidTimes = 0; + int R = 0; + for (; R < M; R++) { + if (count[str[R]]-- <= 0) { + inValidTimes++; + } + } + for (; R < str.length; R++) { + if (inValidTimes == 0) { + return R - M; + } + if (count[str[R]]-- <= 0) { + inValidTimes++; + } + if (count[str[R - M]]++ < 0) { + inValidTimes--; + } + } + return inValidTimes == 0 ? R - M : -1; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 20; + int aimMaxSize = 5; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + String aim = getRandomString(possibilities, aimMaxSize); + int ans1 = containExactly1(str, aim); + int ans2 = containExactly2(str, aim); + int ans3 = containExactly3(str, aim); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class002/Code01_MakeNo.java b/公开课/class002/Code01_MakeNo.java new file mode 100644 index 0000000..82f769c --- /dev/null +++ b/公开课/class002/Code01_MakeNo.java @@ -0,0 +1,50 @@ +package class002; + +public class Code01_MakeNo { + + public static int[] makeNo(int size) { + if (size == 1) { + return new int[] { 1 }; + } + int halfSize = (size + 1) / 2; + int[] base = makeNo(halfSize); + int[] ans = new int[size]; + int index = 0; + for (; index < halfSize; index++) { + ans[index] = base[index] * 2 + 1; + } + for (int i = 0; index < size; index++, i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/公开课/class002/Code02_KthMinPair.java b/公开课/class002/Code02_KthMinPair.java new file mode 100644 index 0000000..56655c0 --- /dev/null +++ b/公开课/class002/Code02_KthMinPair.java @@ -0,0 +1,214 @@ +package class002; + +import java.util.Arrays; +import java.util.Comparator; + +public class Code02_KthMinPair { + + public static class Pair { + public int x; + public int y; + + Pair(int x, int y) { + this.x = x; + this.y = y; + } + } + + public static class PairComparator implements Comparator { + + @Override + public int compare(Pair arg0, Pair arg1) { + return arg0.x != arg1.x ? arg0.x - arg1.x : arg0.y - arg1.y; + } + + } + + // O(N^2 * log (N^2))的复杂度,你肯定过不了 + // 返回的int[] 长度是2,{3,1} int[2] = [3,1] + public static int[] kthMinPair1(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + Pair[] pairs = new Pair[N * N]; + int index = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + pairs[index++] = new Pair(arr[i], arr[j]); + } + } + Arrays.sort(pairs, new PairComparator()); + return new int[] { pairs[k - 1].x, pairs[k - 1].y }; + } + + // O(N*logN)的复杂度,你肯定过了 + public static int[] kthMinPair2(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + // O(N*logN) + Arrays.sort(arr); + // 第K小的数值对,第一维数字,是什么 是arr中 + int fristNum = arr[(k - 1) / N]; + int lessFristNumSize = 0;// 数出比fristNum小的数有几个 + int fristNumSize = 0; // 数出==fristNum的数有几个 + // <= fristNum + for (int i = 0; i < N && arr[i] <= fristNum; i++) { + if (arr[i] < fristNum) { + lessFristNumSize++; + } else { + fristNumSize++; + } + } + int rest = k - (lessFristNumSize * N); + return new int[] { fristNum, arr[(rest - 1) / fristNumSize] }; + } + + // O(N)的复杂度,你肯定蒙了 + public static int[] kthMinPair3(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + // 在无序数组中,找到第K小的数,返回值 + // 第K小,以1作为开始 + int fristNum = getMinKthByBFPRT(arr, ((k - 1) / N) + 1); + int lessFristNumSize = 0; + int fristNumSize = 0; + for (int i = 0; i < N; i++) { + if (arr[i] < fristNum) { + lessFristNumSize++; + } + if (arr[i] == fristNum) { + fristNumSize++; + } + } + int rest = k - (lessFristNumSize * N); + return new int[] { fristNum, getMinKthByBFPRT(arr, ((rest - 1) / fristNumSize) + 1) }; + } + + // 利用bfprt算法求解,是O(N)的过程 + // 在arr上,找到第K小的数,并返回。K范围是[1,N],范围不是[0,N-1] + // 对你来讲,它可能永远不会被你想起,但确实本题最优解的算法原型 + public static int getMinKthByBFPRT(int[] arr, int K) { + return select(arr, 0, arr.length - 1, K - 1); + } + + public static int select(int[] arr, int begin, int end, int i) { + if (begin == end) { + return arr[begin]; + } + int pivot = medianOfMedians(arr, begin, end); + int[] pivotRange = partition(arr, begin, end, pivot); + if (i >= pivotRange[0] && i <= pivotRange[1]) { + return arr[i]; + } else if (i < pivotRange[0]) { + return select(arr, begin, pivotRange[0] - 1, i); + } else { + return select(arr, pivotRange[1] + 1, end, i); + } + } + + public static int medianOfMedians(int[] arr, int begin, int end) { + int num = end - begin + 1; + int offset = num % 5 == 0 ? 0 : 1; + int[] mArr = new int[num / 5 + offset]; + for (int i = 0; i < mArr.length; i++) { + int beginI = begin + i * 5; + int endI = beginI + 4; + mArr[i] = getMedian(arr, beginI, Math.min(end, endI)); + } + return select(mArr, 0, mArr.length - 1, mArr.length / 2); + } + + public static int[] partition(int[] arr, int begin, int end, int pivotValue) { + int small = begin - 1; + int cur = begin; + int big = end + 1; + while (cur != big) { + if (arr[cur] < pivotValue) { + swap(arr, ++small, cur++); + } else if (arr[cur] > pivotValue) { + swap(arr, cur, --big); + } else { + cur++; + } + } + int[] range = new int[2]; + range[0] = small + 1; + range[1] = big - 1; + return range; + } + + public static int getMedian(int[] arr, int begin, int end) { + insertionSort(arr, begin, end); + int sum = end + begin; + int mid = (sum / 2) + (sum % 2); + return arr[mid]; + } + + public static void insertionSort(int[] arr, int begin, int end) { + for (int i = begin + 1; i != end + 1; i++) { + for (int j = i; j != begin; j--) { + if (arr[j - 1] > arr[j]) { + swap(arr, j - 1, j); + } else { + break; + } + } + } + } + + public static void swap(int[] arr, int index1, int index2) { + int tmp = arr[index1]; + arr[index1] = arr[index2]; + arr[index2] = tmp; + } + + // 为了测试,生成值也随机,长度也随机的随机数组 + public static int[] getRandomArray(int max, int len) { + int[] arr = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // 随机测试了百万组,保证三种方法都是对的 + public static void main(String[] args) { + int max = 100; + int len = 30; + int testTimes = 100000; + System.out.println("test bagin, time times : " + testTimes); + for (int i = 0; i < testTimes; i++) { + int[] arr = getRandomArray(max, len); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + int N = arr.length * arr.length; + int k = (int) (Math.random() * N) + 1; + int[] ans1 = kthMinPair1(arr1, k); + int[] ans2 = kthMinPair2(arr2, k); + int[] ans3 = kthMinPair3(arr3, k); + if (ans1[0] != ans2[0] || ans2[0] != ans3[0] || ans1[1] != ans2[1] || ans2[1] != ans3[1]) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class003/Code01_LongestNoRepeatSubstring.java b/公开课/class003/Code01_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..979609b --- /dev/null +++ b/公开课/class003/Code01_LongestNoRepeatSubstring.java @@ -0,0 +1,77 @@ +package class003; + +public class Code01_LongestNoRepeatSubstring { + + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = lnrs2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class003/Code02_HowManyTypes.java b/公开课/class003/Code02_HowManyTypes.java new file mode 100644 index 0000000..884e8eb --- /dev/null +++ b/公开课/class003/Code02_HowManyTypes.java @@ -0,0 +1,82 @@ +package class003; + +import java.util.HashSet; + +public class Code02_HowManyTypes { + + /* + * 只由小写字母(a~z)组成的一批字符串,都放在字符类型的数组String[] arr中, + * 如果其中某两个字符串,所含有的字符种类完全一样,就将两个字符串算作一类 比如:baacba和bac就算作一类 + * 虽然长度不一样,但是所含字符的种类完全一样(a、b、c) 返回arr中有多少类? + * + */ + + public static int types1(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + boolean[] map = new boolean[26]; + for (int i = 0; i < chs.length; i++) { + map[chs[i] - 'a'] = true; + } + String key = ""; + for (int i = 0; i < 26; i++) { + if (map[i]) { + key += String.valueOf((char) (i + 'a')); + } + } + types.add(key); + } + return types.size(); + } + + public static int types2(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + int key = 0; + for(int i = 0 ; i < chs.length;i++) { + key |= (1 << (chs[i] - 'a')); + } + types.add(key); + } + return types.size(); + } + + // for test + public static String[] getRandomStringArray(int possibilities, int strMaxSize, int arrMaxSize) { + String[] ans = new String[(int) (Math.random() * arrMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = getRandomString(possibilities, strMaxSize); + } + return ans; + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 10; + int arrMaxSize = 100; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String[] arr = getRandomStringArray(possibilities, strMaxSize, arrMaxSize); + int ans1 = types1(arr); + int ans2 = types2(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class003/Code03_SubsquenceMaxModM.java b/公开课/class003/Code03_SubsquenceMaxModM.java new file mode 100644 index 0000000..3c5bed5 --- /dev/null +++ b/公开课/class003/Code03_SubsquenceMaxModM.java @@ -0,0 +1,144 @@ +package class003; + +import java.util.HashSet; +import java.util.TreeSet; + +public class Code03_SubsquenceMaxModM { + + /* + * 给定一个非负数组arr,和一个正数m。 返回arr的所有子序列中累加和%m之后的最大值。 + * + */ + + public static int max1(int[] arr, int m) { + HashSet set = new HashSet<>(); + process(arr, 0, 0, set); + int max = 0; + for (Integer sum : set) { + max = Math.max(max, sum % m); + } + return max; + } + + public static void process(int[] arr, int index, int sum, HashSet set) { + if (index == arr.length) { + set.add(sum); + } else { + process(arr, index + 1, sum, set); + process(arr, index + 1, sum + arr[index], set); + } + } + + public static int max2(int[] arr, int m) { + int sum = 0; + int N = arr.length; + for (int i = 0; i < N; i++) { + sum += arr[i]; + } + boolean[][] dp = new boolean[N][sum + 1]; + for (int i = 0; i < N; i++) { + dp[i][0] = true; + } + dp[0][arr[0]] = true; + for (int i = 1; i < N; i++) { + for (int j = 1; j <= sum; j++) { + dp[i][j] = dp[i - 1][j]; + if (j - arr[i] >= 0) { + dp[i][j] = dp[i][j] | dp[i - 1][j - arr[i]]; + } + } + } + int ans = 0; + for (int j = 0; j <= sum; j++) { + if (dp[N - 1][j]) { + ans = Math.max(ans, j % m); + } + } + return ans; + + } + + public static int max3(int[] arr, int m) { + int N = arr.length; + boolean[][] dp = new boolean[N][m]; + for (int i = 0; i < N; i++) { + dp[i][0] = true; + } + dp[0][arr[0] % m] = true; + for (int i = 1; i < N; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = dp[i - 1][j]; + int cur = arr[i] % m; + if (j - cur >= 0) { + dp[i][j] = dp[i][j] | dp[i - 1][j - cur]; + } + if (m + j - cur < m) { + dp[i][j] = dp[i][j] | dp[i - 1][m + j - cur]; + } + } + } + int ans = 0; + for (int i = 0; i < m; i++) { + if (dp[N - 1][i]) { + ans = i; + } + } + return ans; + } + + // 如果arr的累加和很大,m也很大 + // 但是arr的长度相对不大 + public static int max4(int[] arr, int m) { + if (arr.length == 1) { + return arr[0] % m; + } + int mid = (arr.length - 1) / 2; + TreeSet sortSet1 = new TreeSet<>(); + process4(arr, 0, 0, mid, m, sortSet1); + TreeSet sortSet2 = new TreeSet<>(); + process4(arr, mid + 1, 0, arr.length - 1, m, sortSet2); + int ans = 0; + for (Integer left : sortSet1) { + ans = Math.max(ans, left + sortSet2.floor(m - 1 - left)); + } + return ans; + } + + public static void process4(int[] arr, int index, int sum, int end, int m, TreeSet sortSet) { + if (index == end + 1) { + sortSet.add(sum % m); + } else { + process4(arr, index + 1, sum, end, m, sortSet); + process4(arr, index + 1, sum + arr[index], end, m, sortSet); + } + } + + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + public static void main(String[] args) { + int len = 10; + int value = 100; + int m = 76; + int testTime = 500000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = max1(arr, m); + int ans2 = max2(arr, m); + int ans3 = max3(arr, m); + int ans4 = max4(arr, m); + if (ans1 != ans2 || ans2 != ans3 || ans3 != ans4) { + System.out.print("Oops!"); + } + } + System.out.println("test finish!"); + + } + +} diff --git a/公开课/class004/Code01_QueryHobby.java b/公开课/class004/Code01_QueryHobby.java new file mode 100644 index 0000000..076e0bc --- /dev/null +++ b/公开课/class004/Code01_QueryHobby.java @@ -0,0 +1,115 @@ +package class004; + +import java.util.ArrayList; +import java.util.HashMap; + +public class Code01_QueryHobby { + + /* + * 今日头条原题 + * + * 数组为{3, 2, 2, 3, 1},查询为(0, 3, 2)。意思是在数组里下标0~3这个范围上,有几个2?返回2。 + * 假设给你一个数组arr,对这个数组的查询非常频繁,请返回所有查询的结果 + * + */ + + public static class QueryBox1 { + private int[] arr; + + public QueryBox1(int[] array) { + arr = new int[array.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = array[i]; + } + } + + public int query(int L, int R, int v) { + int ans = 0; + for (; L <= R; L++) { + if (arr[L] == v) { + ans++; + } + } + return ans; + } + } + + public static class QueryBox2 { + private HashMap> map; + + public QueryBox2(int[] arr) { + map = new HashMap<>(); + for (int i = 0; i < arr.length; i++) { + if (!map.containsKey(arr[i])) { + map.put(arr[i], new ArrayList<>()); + } + map.get(arr[i]).add(i); + } + } + + public int query(int L, int R, int value) { + if (!map.containsKey(value)) { + return 0; + } + ArrayList indexArr = map.get(value); + // 查询 < L 的下标有几个 + int a = countLess(indexArr, L); + // 查询 < R+1 的下标有几个 + int b = countLess(indexArr, R + 1); + return b - a; + } + + // 在有序数组arr中,用二分的方法数出 arr, int limit) { + int L = 0; + int R = arr.size() - 1; + int mostRight = -1; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr.get(mid) < limit) { + mostRight = mid; + L = mid + 1; + } else { + R = mid - 1; + } + } + return mostRight + 1; + } + + } + + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 300; + int value = 20; + int testTimes = 1000; + int queryTimes = 1000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int N = arr.length; + QueryBox1 box1 = new QueryBox1(arr); + QueryBox2 box2 = new QueryBox2(arr); + for (int j = 0; j < queryTimes; j++) { + int a = (int) (Math.random() * N); + int b = (int) (Math.random() * N); + int L = Math.min(a, b); + int R = Math.max(a, b); + int v = (int) (Math.random() * value) + 1; + if (box1.query(L, R, v) != box2.query(L, R, v)) { + System.out.println("Oops!"); + } + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class004/Code02_MergeRecord.java b/公开课/class004/Code02_MergeRecord.java new file mode 100644 index 0000000..fbd3e0f --- /dev/null +++ b/公开课/class004/Code02_MergeRecord.java @@ -0,0 +1,216 @@ +package class004; + +public class Code02_MergeRecord { + + /* + * 腾讯原题 + * + * 给定整数power,给定一个数组arr,给定一个数组reverse。含义如下: + * arr的长度一定是2的power次方,reverse中的每个值一定都在0~power范围。 + * 例如power = 2, arr = {3, 1, 4, 2},reverse = {0, 1, 0, 2} + * 任何一个在前的数字可以和任何一个在后的数组,构成一对数。可能是升序关系、相等关系或者降序关系。 + * 比如arr开始时有如下的降序对:(3,1)、(3,2)、(4,2),一共3个。 + * 接下来根据reverse对arr进行调整: + * reverse[0] = 0, 表示在arr中,划分每1(2的0次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [3,1,4,2],此时有3个逆序对。 + * reverse[1] = 1, 表示在arr中,划分每2(2的1次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [1,3,2,4],此时有1个逆序对 + * reverse[2] = 0, 表示在arr中,划分每1(2的0次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [1,3,2,4],此时有1个逆序对。 + * reverse[3] = 2, 表示在arr中,划分每4(2的2次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [4,2,3,1],此时有4个逆序对。 + * 所以返回[3,1,1,4],表示每次调整之后的逆序对数量。 + * + * 输入数据状况: + * power的范围[0,20] + * arr长度范围[1,10的7次方] + * reverse长度范围[1,10的6次方] + * + * */ + + public static int[] reversePair1(int[] originArr, int[] reverseArr, int power) { + int[] ans = new int[reverseArr.length]; + for (int i = 0; i < reverseArr.length; i++) { + reverseArray(originArr, 1 << (reverseArr[i])); + ans[i] = countReversePair(originArr); + } + return ans; + } + + public static void reverseArray(int[] originArr, int teamSize) { + if (teamSize < 2) { + return; + } + for (int i = 0; i < originArr.length; i += teamSize) { + reversePart(originArr, i, i + teamSize - 1); + } + } + + public static void reversePart(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int countReversePair(int[] originArr) { + int ans = 0; + for (int i = 0; i < originArr.length; i++) { + for (int j = i + 1; j < originArr.length; j++) { + if (originArr[i] > originArr[j]) { + ans++; + } + } + } + return ans; + } + + public static int[] reversePair2(int[] originArr, int[] reverseArr, int power) { + int[] reverse = copyArray(originArr); + reversePart(reverse, 0, reverse.length - 1); + int[] recordDown = new int[power + 1]; + int[] recordUp = new int[power + 1]; + process(originArr, 0, originArr.length - 1, power, recordDown); + process(reverse, 0, reverse.length - 1, power, recordUp); + int[] ans = new int[reverseArr.length]; + for (int i = 0; i < reverseArr.length; i++) { + int curPower = reverseArr[i]; + for (int p = 1; p <= curPower; p++) { + int tmp = recordDown[p]; + recordDown[p] = recordUp[p]; + recordUp[p] = tmp; + } + for (int p = 1; p <= power; p++) { + ans[i] += recordDown[p]; + } + } + return ans; + } + + public static void process(int[] originArr, int L, int R, int power, int[] record) { + if (L == R) { + return; + } + int mid = L + ((R - L) >> 1); + process(originArr, L, mid, power - 1, record); + process(originArr, mid + 1, R, power - 1, record); + record[power] += merge(originArr, L, mid, R); + } + + public static int merge(int[] arr, int L, int m, int r) { + int[] help = new int[r - L + 1]; + int i = 0; + int p1 = L; + int p2 = m + 1; + int ans = 0; + while (p1 <= m && p2 <= r) { + ans += arr[p1] > arr[p2] ? (m - p1 + 1) : 0; + help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; + } + while (p1 <= m) { + help[i++] = arr[p1++]; + } + while (p2 <= r) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return ans; + } + + // for test + public static int[] generateRandomOriginArray(int power, int value) { + int[] ans = new int[1 << power]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + // for test + public static int[] generateRandomReverseArray(int len, int power) { + int[] ans = new int[len]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * (power + 1)); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + System.out.println("arr size : " + arr.length); + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int powerMax = 8; + int msizeMax = 10; + int value = 30; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int power = (int) (Math.random() * powerMax) + 1; + int msize = (int) (Math.random() * msizeMax) + 1; + int[] originArr = generateRandomOriginArray(power, value); + int[] originArr1 = copyArray(originArr); + int[] originArr2 = copyArray(originArr); + int[] reverseArr = generateRandomReverseArray(msize, power); + int[] reverseArr1 = copyArray(reverseArr); + int[] reverseArr2 = copyArray(reverseArr); + int[] ans1 = reversePair1(originArr1, reverseArr1, power); + int[] ans2 = reversePair2(originArr2, reverseArr2, power); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + powerMax = 20; + msizeMax = 1000000; + value = 1000; + int[] originArr = generateRandomOriginArray(powerMax, value); + int[] reverseArr = generateRandomReverseArray(msizeMax, powerMax); + // int[] ans1 = reversePair1(originArr1, reverseArr1, powerMax); + long start = System.currentTimeMillis(); + reversePair2(originArr, reverseArr, powerMax); + long end = System.currentTimeMillis(); + System.out.println("run time : " + (end - start) + " ms"); + } + +} diff --git a/公开课/class005/Code01_RotateString.java b/公开课/class005/Code01_RotateString.java new file mode 100644 index 0000000..e3979e7 --- /dev/null +++ b/公开课/class005/Code01_RotateString.java @@ -0,0 +1,93 @@ +package class005; + +public class Code01_RotateString { + + public static String rotate1(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + return process1(s.toCharArray(), 0, leftSize - 1, s.length() - 1); + } + + public static String process1(char[] str, int L, int M, int R) { + reverse(str, L, M); + reverse(str, M + 1, R); + reverse(str, L, R); + return String.valueOf(str); + } + + public static void reverse(char[] str, int L, int R) { + while (L < R) { + char tmp = str[L]; + str[L++] = str[R]; + str[R--] = tmp; + } + } + + public static String rotate2(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + char[] str = s.toCharArray(); + int L = 0; + int R = str.length - 1; + int lpart = leftSize; + int rpart = str.length - leftSize; + int same = Math.min(lpart, rpart); + int diff = lpart - rpart; + exchange(str, L, R, same); + while (diff != 0) { + if (diff > 0) { + L += same; + lpart = diff; + } else { + R -= same; + rpart = -diff; + } + same = Math.min(lpart, rpart); + diff = lpart - rpart; + exchange(str, L, R, same); + } + return String.valueOf(str); + } + + public static void exchange(char[] chas, int start, int end, int size) { + int i = end - size + 1; + char tmp = 0; + while (size-- != 0) { + tmp = chas[start]; + chas[start] = chas[i]; + chas[i] = tmp; + start++; + i++; + } + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 10; + int testTimes = 5000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int leftSize = (int) (Math.random() * (str.length() + 1)); + String ans1 = rotate1(str, leftSize); + String ans2 = rotate2(str, leftSize); + if (!ans1.equals(ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class005/Code02_RestoreWays.java b/公开课/class005/Code02_RestoreWays.java new file mode 100644 index 0000000..e1766ef --- /dev/null +++ b/公开课/class005/Code02_RestoreWays.java @@ -0,0 +1,237 @@ +package class005; + +public class Code02_RestoreWays { + + /* + * 整型数组arr长度为n(3 <= n <= 10^4),最初每个数字是<=200的正数且满足如下条件: 1. arr[0] <= arr[1] 2. + * arr[n-1] <= arr[n-2] 3. arr[i] <= max(arr[i-1], arr[i+1]) + * 但是在arr有些数字丢失了,比如k位置的数字之前是正数,丢失之后k位置的数字为0。 请你根据上述条件, 计算可能有多少种不同的arr可以满足以上条件。 + * 比如 [6,0,9] 只有还原成 [6,9,9]满足全部三个条件,所以返回1种。 + * + */ + + public static int ways0(int[] arr) { + return process0(arr, 0); + } + + public static int process0(int[] arr, int index) { + if (index == arr.length) { + return isValid(arr) ? 1 : 0; + } else { + if (arr[index] != 0) { + return process0(arr, index + 1); + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + arr[index] = v; + ways += process0(arr, index + 1); + } + arr[index] = 0; + return ways; + } + } + } + + public static boolean isValid(int[] arr) { + if (arr[0] > arr[1]) { + return false; + } + if (arr[arr.length - 1] > arr[arr.length - 2]) { + return false; + } + for (int i = 1; i < arr.length - 1; i++) { + if (arr[i] > Math.max(arr[i - 1], arr[i + 1])) { + return false; + } + } + return true; + } + + public static int ways1(int[] arr) { + int N = arr.length; + if (arr[N - 1] != 0) { + return process1(arr, N - 1, arr[N - 1], 2); + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + ways += process1(arr, N - 1, v, 2); + } + return ways; + } + } + + // 如果index位置的数字变成了v, + // 并且arr[index]和arr[index+1]的关系为s, + // s==0,代表arr[index] < arr[index+1] + // s==1,代表arr[index] = arr[index+1] + // s==2,代表arr[index] > arr[index+1] + // 返回有多少种转化方式? + public static int process1(int[] arr, int i, int v, int s) { + if (i == 0) { + return ((s == 0 || s == 1) && (arr[i] == 0 || v == arr[i])) ? 1 : 0; + } + if (arr[i] != 0 && v != arr[i]) { + return 0; + } + int ways = 0; + if (s == 0 || s == 1) { + for (int pre = 1; pre < 201; pre++) { + ways += process1(arr, i - 1, pre, pre < v ? 0 : (pre == v ? 1 : 2)); + } + } else { + for (int pre = v; pre < 201; pre++) { + ways += process1(arr, i - 1, pre, pre < v ? 0 : (pre == v ? 1 : 2)); + } + } + return ways; + } + + public static int ways2(int[] arr) { + int N = arr.length; + int[][][] dp = new int[N][201][3]; + if (arr[0] != 0) { + dp[0][arr[0]][0] = 1; + dp[0][arr[0]][1] = 1; + } else { + for (int v = 1; v < 201; v++) { + dp[0][v][0] = 1; + dp[0][v][1] = 1; + } + } + for (int i = 1; i < N; i++) { + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + if (arr[i] == 0 || v == arr[i]) { + if (s == 0 || s == 1) { + // A[i] = dp[i-1][0~v-1][0] + // A[0] = dp[i-1][0][0] + + + // dp[i-1][1~v-1][0] A[v-1] - A[0] + // dp[i-1][v][1] + // dp[i-1][v+1~200][2] + for (int pre = 1; pre < 201; pre++) { + dp[i][v][s] += dp[i - 1][pre][pre < v ? 0 : (pre == v ? 1 : 2)]; + } + } else { + for (int pre = v; pre < 201; pre++) { + dp[i][v][s] += dp[i - 1][pre][pre < v ? 0 : (pre == v ? 1 : 2)]; + } + } + } + } + } + } + if (arr[N - 1] != 0) { + return dp[N - 1][arr[N - 1]][2]; + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + ways += dp[N - 1][v][2]; + } + return ways; + } + } + + public static int ways3(int[] arr) { + int N = arr.length; + int[][][] dp = new int[N][201][3]; + if (arr[0] != 0) { + dp[0][arr[0]][0] = 1; + dp[0][arr[0]][1] = 1; + } else { + for (int v = 1; v < 201; v++) { + dp[0][v][0] = 1; + dp[0][v][1] = 1; + } + } + // presum[0~V][0] -> A + // presum[0~V][1] -> B + // presum[0~V][2] -> C + int[][] presum = new int[201][3]; + // presum -> dp[0][..][..] 三张表 + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + presum[v][s] = presum[v - 1][s] + dp[0][v][s]; + } + } + for (int i = 1; i < N; i++) { + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + if (arr[i] == 0 || v == arr[i]) { + if (s == 0 || s == 1) { + // dp[i][..][..] -> dp[i-1][..][..] + dp[i][v][s] += sum(1, v - 1, 0, presum); + dp[i][v][s] += dp[i - 1][v][1]; + dp[i][v][s] += sum(v + 1, 200, 2, presum); + } else { + dp[i][v][s] += dp[i - 1][v][1]; + dp[i][v][s] += sum(v + 1, 200, 2, presum); + } + } + } + } + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + presum[v][s] = presum[v - 1][s] + dp[i][v][s]; + } + } + } + if (arr[N - 1] != 0) { + return dp[N - 1][arr[N - 1]][2]; + } else { + return sum(1, 200, 2, presum); + } + } + + public static int sum(int begin, int end, int relation, int[][] presum) { + return presum[end][relation] - presum[begin - 1][relation]; + } + + // for test + public static int[] generateRandomArray(int len) { + int[] ans = new int[(int) (Math.random() * len) + 2]; + for (int i = 0; i < ans.length; i++) { + if (Math.random() < 0.5) { + ans[i] = 0; + } else { + ans[i] = (int) (Math.random() * 200) + 1; + } + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + System.out.println("arr size : " + arr.length); + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 3; + int testTime = 10; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len); + int ans0 = ways0(arr); + int ans1 = ways1(arr); + int ans2 = ways2(arr); + int ans3 = ways3(arr); + if (ans0 != ans1 || ans2 != ans3 || ans0 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + int[] arr = generateRandomArray(100000); + System.out.println(arr.length); + long begin = System.currentTimeMillis(); + ways3(arr); + long end = System.currentTimeMillis(); + System.out.println("run time : " + (end - begin) + " ms"); + + } + +} diff --git a/公开课/class006/Code01_Power2Diffs.java b/公开课/class006/Code01_Power2Diffs.java new file mode 100644 index 0000000..b7de461 --- /dev/null +++ b/公开课/class006/Code01_Power2Diffs.java @@ -0,0 +1,97 @@ +package class006; + +import java.util.Arrays; +import java.util.HashSet; + +public class Code01_Power2Diffs { + + /* + * 给定一个有序数组arr,其中值可能为正、负、0。 返回arr中每个数都平方之后不同的结果有多少种? + * + * 给定一个数组arr,先递减然后递增,返回arr中有多少个不同的数字? + * + */ + + // 时间复杂度O(N),额外空间复杂度O(N) + public static int diff1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + HashSet set = new HashSet<>(); + for (int cur : arr) { + set.add(cur * cur); + } + return set.size(); + } + + // 时间复杂度O(N),额外空间复杂度O(1) + public static int diff2(int[] arr) { + int N = arr.length; + int L = 0; + int R = N - 1; + int count = 0; + int leftAbs = 0; + int rightAbs = 0; + while (L <= R) { + count++; + leftAbs = Math.abs(arr[L]); + rightAbs = Math.abs(arr[R]); + if (leftAbs < rightAbs) { + while (R >= 0 && Math.abs(arr[R]) == rightAbs) { + R--; + } + } else if (leftAbs > rightAbs) { + while (L < N && Math.abs(arr[L]) == leftAbs) { + L++; + } + } else { + while (L < N && Math.abs(arr[L]) == leftAbs) { + L++; + } + while (R >= 0 && Math.abs(arr[R]) == rightAbs) { + R--; + } + } + } + return count; + } + + // for test + public static int[] randomSortedArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + Arrays.sort(ans); + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int cur : arr) { + System.out.print(cur + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 100; + int value = 500; + int testTimes = 200000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = randomSortedArray(len, value); + int ans1 = diff1(arr); + int ans2 = diff2(arr); + if (ans1 != ans2) { + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class006/Code02_Water.java b/公开课/class006/Code02_Water.java new file mode 100644 index 0000000..1173051 --- /dev/null +++ b/公开课/class006/Code02_Water.java @@ -0,0 +1,121 @@ +package class006; + +public class Code02_Water { + + /* + * 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + * + */ + + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class007/Code01_RandToRand.java b/公开课/class007/Code01_RandToRand.java new file mode 100644 index 0000000..ec95702 --- /dev/null +++ b/公开课/class007/Code01_RandToRand.java @@ -0,0 +1,93 @@ +package class007; + +public class Code01_RandToRand { + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + // 13 ~ 17 + // 13 + [0,4] + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + RandomBox randomBox = new RandomBox(3, 9); + int from = 17; + int to = 29; + int[] ans = new int[to + 1]; + int testTime1 = 1000000; + for (int i = 0; i < testTime1; i++) { + ans[random(randomBox, from, to)]++; + } + for (int i = 0; i < ans.length; i++) { + System.out.println(ans[i]); + } + System.out.println("=========="); + + } + +} diff --git a/公开课/class007/Code02_EqualProbabilityRandom.java b/公开课/class007/Code02_EqualProbabilityRandom.java new file mode 100644 index 0000000..ed54603 --- /dev/null +++ b/公开课/class007/Code02_EqualProbabilityRandom.java @@ -0,0 +1,46 @@ +package class007; + +public class Code02_EqualProbabilityRandom { + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + double zeroP = 0.88; + RandomBox randomBox = new RandomBox(zeroP); + + int testTime = 10000000; + int count = 0; + for (int i = 0; i < testTime; i++) { + if (rand01(randomBox) == 0) { + count++; + } + } + System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class007/Code03_FindHalfMajority.java b/公开课/class007/Code03_FindHalfMajority.java new file mode 100644 index 0000000..6a2afc4 --- /dev/null +++ b/公开课/class007/Code03_FindHalfMajority.java @@ -0,0 +1,77 @@ +package class007; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code03_FindHalfMajority { + + public static int halfMajor(int[] arr) { + int cand = 0; + int HP = 0; + // 遍历一遍数组arr,一次删掉两个不同的数,谁会剩下来,谁就是cand + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { // 如果没有候选 + cand = arr[i]; + HP = 1; + } else if (arr[i] == cand) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == cand) { + HP++; + } + } + return HP > arr.length / 2 ? cand : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class007/Code04_FindKMajor.java b/公开课/class007/Code04_FindKMajor.java new file mode 100644 index 0000000..a6079d7 --- /dev/null +++ b/公开课/class007/Code04_FindKMajor.java @@ -0,0 +1,135 @@ +package class007; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class Code04_FindKMajor { + + public static List kMajor(int[] arr, int K) { + List ans = new ArrayList<>(); + if (K < 2) { + return ans; + } + // 候选表,记录数一定是O(K) > N/K k-1 + HashMap cands = new HashMap(); + for (int i = 0; i != arr.length; i++) { + if (cands.containsKey(arr[i])) { + cands.put(arr[i], cands.get(arr[i]) + 1); + } else { + if (cands.size() == K - 1) { + allCandsMinusOne(cands); + } else { + cands.put(arr[i], 1); + } + } + } + HashMap reals = getReals(arr, cands); + for (Entry set : cands.entrySet()) { + Integer key = set.getKey(); + if (reals.get(key) > arr.length / K) { + ans.add(key); + } + } + return ans; + } + + public static void allCandsMinusOne(HashMap map) { + List removeList = new LinkedList(); + for (Entry set : map.entrySet()) { + Integer key = set.getKey(); + Integer value = set.getValue(); + if (value == 1) { + removeList.add(key); + } + map.put(key, value - 1); + } + for (Integer removeKey : removeList) { + map.remove(removeKey); + } + } + + // for test + public static List right(int[] arr, int K) { + List ans = new ArrayList<>(); + if (K < 2) { + return ans; + } + HashMap times = new HashMap<>(); + for (int num : arr) { + if (!times.containsKey(num)) { + times.put(num, 1); + } else { + times.put(num, times.get(num) + 1); + } + } + for (Entry entry : times.entrySet()) { + if (entry.getValue() > arr.length / K) { + ans.add(entry.getKey()); + } + } + return ans; + } + + public static HashMap getReals(int[] arr, HashMap cands) { + HashMap reals = new HashMap(); + for (int i = 0; i != arr.length; i++) { + int curNum = arr[i]; + if (cands.containsKey(curNum)) { + if (reals.containsKey(curNum)) { + reals.put(curNum, reals.get(curNum) + 1); + } else { + reals.put(curNum, 1); + } + } + } + return reals; + } + + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static boolean isEqual(List ans1, List ans2) { + if (ans1.size() != ans2.size()) { + return false; + } + HashSet set1 = new HashSet<>(); + for (Integer num : ans1) { + set1.add(num); + } + if (set1.size() != ans1.size()) { + return false; + } + for (Integer num : ans2) { + set1.remove(num); + } + return set1.size() == 0; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int K = 5; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + List ans1 = kMajor(arr, K); + List ans2 = right(arr, K); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class008/Code01_JumpGameII.java b/公开课/class008/Code01_JumpGameII.java new file mode 100644 index 0000000..70bbfc5 --- /dev/null +++ b/公开课/class008/Code01_JumpGameII.java @@ -0,0 +1,27 @@ +package class008; + +public class Code01_JumpGameII { + + /* + * 评测代码可以直接去leetcode搜索:Jump Game II + * + */ + + public static int jump(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int jump = 0; + int cur = 0; + int next = 0; + for (int i = 0; i < arr.length; i++) { + if (cur < i) { + jump++; + cur = next; + } + next = Math.max(next, i + arr[i]); + } + return jump; + } + +} diff --git a/公开课/class008/Code02_LongestConsecutiveSequence.java b/公开课/class008/Code02_LongestConsecutiveSequence.java new file mode 100644 index 0000000..ad545fa --- /dev/null +++ b/公开课/class008/Code02_LongestConsecutiveSequence.java @@ -0,0 +1,24 @@ +package class008; + +import java.util.HashMap; + +public class Code02_LongestConsecutiveSequence { + + public static int longestConsecutive(int[] nums) { + HashMap map = new HashMap<>(); + int len = 0; + for (int num : nums) { + if (!map.containsKey(num)) { + map.put(num, 1); + int preLen = map.containsKey(num - 1) ? map.get(num - 1) : 0; + int posLen = map.containsKey(num + 1) ? map.get(num + 1) : 0; + int all = preLen + posLen + 1; + map.put(num - preLen, all); + map.put(num + posLen, all); + len = Math.max(len, all); + } + } + return len; + } + +} diff --git a/公开课/class009/Code01_SetMatrixZeroes.java b/公开课/class009/Code01_SetMatrixZeroes.java new file mode 100644 index 0000000..355be75 --- /dev/null +++ b/公开课/class009/Code01_SetMatrixZeroes.java @@ -0,0 +1,84 @@ +package class009; + +public class Code01_SetMatrixZeroes { + + /* + * 评测代码可以直接去leetcode搜索:Set Matrix Zeroes + * + */ + public static void setZeroes1(int[][] matrix) { + boolean row0Zero = false; + boolean col0Zero = false; + int i = 0; + int j = 0; + for (i = 0; i < matrix[0].length; i++) { + if (matrix[0][i] == 0) { + row0Zero = true; + break; + } + } + for (i = 0; i < matrix.length; i++) { + if (matrix[i][0] == 0) { + col0Zero = true; + break; + } + } + // 跳过了所有0行、0列的数据 + for (i = 1; i < matrix.length; i++) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = 0; + matrix[0][j] = 0; + } + } + } + for (i = 1; i < matrix.length; i++) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + } + if (row0Zero) { + for (i = 0; i < matrix[0].length; i++) { + matrix[0][i] = 0; + } + } + if (col0Zero) { + for (i = 0; i < matrix.length; i++) { + matrix[i][0] = 0; + } + } + } + + public static void setZeroes2(int[][] matrix) { + boolean col0 = false; + int i = 0; + int j = 0; + for (i = 0; i < matrix.length; i++) { + for (j = 0; j < matrix[0].length; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = 0; + if (j == 0) { + col0 = true; + } else { + matrix[0][j] = 0; + } + } + } + } + for (i = matrix.length - 1; i >= 0; i--) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + } + if (col0) { + for (i = 0; i < matrix.length; i++) { + matrix[i][0] = 0; + } + } + } + +} diff --git a/公开课/class009/Code02_SearchA2DMatrixII.java b/公开课/class009/Code02_SearchA2DMatrixII.java new file mode 100644 index 0000000..3fa803e --- /dev/null +++ b/公开课/class009/Code02_SearchA2DMatrixII.java @@ -0,0 +1,30 @@ +package class009; + +public class Code02_SearchA2DMatrixII { + + /* + * 评测代码可以直接去leetcode搜索:Search a 2D Matrix II + * + */ + + public static boolean searchMatrix(int[][] m, int target) { + if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { + return false; + } + int N = m.length; + int M = m[0].length; + int row = 0; + int col = M - 1; + while (row < N && col >= 0) { + if (m[row][col] > target) { + col--; + } else if (m[row][col] < target) { + row++; + } else { + return true; + } + } + return false; + } + +} diff --git a/公开课/class009/Code03_LongestOnes.java b/公开课/class009/Code03_LongestOnes.java new file mode 100644 index 0000000..fafbd55 --- /dev/null +++ b/公开课/class009/Code03_LongestOnes.java @@ -0,0 +1,155 @@ +package class009; + +import java.util.ArrayList; +import java.util.List; + +public class Code03_LongestOnes { + + public static List longestOnes1(int[][] matrix) { + int N = matrix.length; + int M = matrix[0].length; + List ans = new ArrayList<>(); + int maxLen = 0; + for (int i = 0; i < N; i++) { // 每一行的答案,i + int j = M; // 最左发现的1的位置 + while (j > 0 && matrix[i][j - 1] == 1) { + j--; + } + if (maxLen < M - j) { + maxLen = M - j; + ans.clear(); + } + if (maxLen == M - j) { + ans.add(i); + } + } + return ans; + } + + public static List longestOnes2(int[][] matrix) { + int N = matrix.length; + int M = matrix[0].length; + List ans = new ArrayList<>(); + int maxLen = 0; + int col = M; + for (int i = 0; i < N; i++) { + while (col > 0 && matrix[i][col - 1] == 1) { + col--; + } + // col来到当前i行最左侧的1的位置 + if (maxLen < M - col) { + maxLen = M - col; + ans.clear(); + } + if (matrix[i][col] == 1) { + ans.add(i); + } + } + return ans; + } + + public static List longestOnes3(int[][] matrix) { + int N = matrix.length; + int M = matrix[0].length; + List ans = new ArrayList<>(); + int maxLen = 0; + for (int i = 0; i < N; i++) { + int j = mostLeftOne(matrix[i], 0, M - 1); + if (maxLen < M - j) { + maxLen = M - j; + ans.clear(); + } + if (maxLen == M - j) { + ans.add(i); + } + } + return ans; + } + + public static List longestOnes4(int[][] matrix) { + int N = matrix.length; + int M = matrix[0].length; + List ans = new ArrayList<>(); + int maxLen = 0; + int col = M; + for (int i = 0; i < N; i++) { + col = mostLeftOne(matrix[i], 0, col - 1); + if (maxLen < M - col) { + maxLen = M - col; + ans.clear(); + } + if (matrix[i][col] == 1) { + ans.add(i); + } + } + return ans; + } + + // arr[L..R]上,一定有一个前期:要么都是0,要么都是1,如果0和1都有,0在左侧,1在右侧 + public static int mostLeftOne(int[] arr, int L, int R) { + int ans = R + 1; // R + 1 -> [L..R] 没有1的 + int M = 0; + while (L <= R) { + M = (L + R) / 2; + if (arr[M] == 1) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + // for test + // 随机生成一个最大规模达到rowSize * colSize的矩阵 + // 确保每一行都是0在左侧,1在右侧 + public static int[][] generateRandomMatrix(int rowSize, int colSize) { + int N = (int) (Math.random() * rowSize) + 1; + int M = (int) (Math.random() * colSize) + 1; + int[][] matrix = new int[N][M]; + for (int i = 0; i < N; i++) { + int[] arr = new int[M]; + int rightStart = (int) (Math.random() * M); + for (int j = rightStart; j < M; j++) { + arr[j] = 1; + } + matrix[i] = arr; + } + return matrix; + } + + public static boolean equal(List ans1, List ans2) { + if (ans1.size() != ans2.size()) { + return false; + } + int N = ans1.size(); + for (int i = 0; i < N; i++) { + if (!ans1.get(i).equals(ans2.get(i))) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("Hello!"); + int rowSize = 30; + int colSize = 100; + int testTimes = 300000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[][] matrix = generateRandomMatrix(rowSize, colSize); + List ans1 = longestOnes1(matrix); + List ans2 = longestOnes2(matrix); + List ans3 = longestOnes3(matrix); + List ans4 = longestOnes4(matrix); + if (!equal(ans1, ans2) || !equal(ans3, ans4) || !equal(ans1, ans3)) { + System.out.println("Oops"); + } + } + System.out.println("test end"); + + } + +} diff --git a/公开课/class010/Code01_SwapWithoutTmp.java b/公开课/class010/Code01_SwapWithoutTmp.java new file mode 100644 index 0000000..47a6da2 --- /dev/null +++ b/公开课/class010/Code01_SwapWithoutTmp.java @@ -0,0 +1,17 @@ +package class010; + +public class Code01_SwapWithoutTmp { + + public static void main(String[] args) { + int a = 111; + int b = 111; + System.out.println(a); + System.out.println(b); + a = a ^ b; + b = a ^ b; + a = a ^ b; + System.out.println(a); + System.out.println(b); + } + +} diff --git a/公开课/class010/Code02_GetMax.java b/公开课/class010/Code02_GetMax.java new file mode 100644 index 0000000..478ff13 --- /dev/null +++ b/公开课/class010/Code02_GetMax.java @@ -0,0 +1,53 @@ +package class010; + +public class Code02_GetMax { + + // 请确保,n不是0,就是1 + // 0 -> 1 1 -> 0 + public static int flip(int n) { + return n ^ 1; + } + + // 如果n是非负数,返回1 + // 如果n是负数,返回0 + public static int sign(int n) { + // (n >> 31) & 1 0(int 非负数) 1(int 负数) + // >>> >> + return flip((n >> 31) & 1); + } + + // 确保 a-b 是不会溢出的 + public static int getMax1(int a, int b) { + int c = a - b; + int scA = sign(c); // scA == 1 a - b >= 0 a返回 sca == 0 a-b < 0 b返回 + int scB = flip(scA); + return a * scA + b * scB; + } + + public static int getMax2(int a, int b) { + int c = a - b; + int sa = sign(a); + int sb = sign(b); + int sc = sign(c); + int difSab = sa ^ sb; + int sameSab = flip(difSab); + // 返回a的条件 returnA == 1 应该返回a; + // returnA == 0,不应该返回a + int returnA = difSab * sa + sameSab * sc; + int returnB = flip(returnA); + return a * returnA + b * returnB; + } + + public static void main(String[] args) { + int a = -16; + int b = 1; + System.out.println(getMax1(a, b)); + System.out.println(getMax2(a, b)); + a = 2147483647; + b = -2147480000; + System.out.println(getMax1(a, b)); // wrong answer because of overflow + System.out.println(getMax2(a, b)); + + } + +} diff --git a/公开课/class010/Code03_EvenTimesOddTimes.java b/公开课/class010/Code03_EvenTimesOddTimes.java new file mode 100644 index 0000000..b54d8d6 --- /dev/null +++ b/公开课/class010/Code03_EvenTimesOddTimes.java @@ -0,0 +1,89 @@ +package class010; + +public class Code03_EvenTimesOddTimes { + + // arr中,只有一种数,出现奇数次 + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + // arr中,有两种数,出现奇数次 + public static void printOddTimesNum2(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor必然有一个位置上是1 + // 0110010000 + // 0000010000 + int rightOne = eor & (~eor + 1); // 提取出最右的1 + int onlyOne = 0; // e' + for (int i = 0 ; i < arr.length;i++) { + // arr[1] = 111100011110000 + // rightOne= 000000000010000 + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + + public static int bit1counts(int N) { + int count = 0; + while(N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + + public static int add(int a, int b) { + int t = 0; + while( b != 0) { + t = a; + a = a ^ b; + b = ((t & b) << 1); + } + return a; + } + + public static int minus(int a, int b) { + // -b ~b + 1 + return add(a, add(~b , 1)); + } + + + + + public static void main(String[] args) { + int a = 5; + int b = 7; + + + System.out.println(minus(a,b)); + + a = a ^ b; + b = a ^ b; + a = a ^ b; + + System.out.println(a); + System.out.println(b); + + int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + + } + +} diff --git a/公开课/class011/Code01_CopyListWithRandom.java b/公开课/class011/Code01_CopyListWithRandom.java new file mode 100644 index 0000000..62e7c53 --- /dev/null +++ b/公开课/class011/Code01_CopyListWithRandom.java @@ -0,0 +1,133 @@ +package class011; + +import java.util.HashMap; + +public class Code01_CopyListWithRandom { + + public static class Node { + public int value; + public Node next; + public Node rand; + + public Node(int data) { + this.value = data; + } + } + + public static Node copyListWithRand1(Node head) { + HashMap map = new HashMap(); + Node cur = head; + while (cur != null) { + map.put(cur, new Node(cur.value)); + cur = cur.next; + } + cur = head; + while (cur != null) { + // cur 老 + // map.get(cur) 新 + map.get(cur).next = map.get(cur.next); + map.get(cur).rand = map.get(cur.rand); + cur = cur.next; + } + return map.get(head); + } + + public static Node copyListWithRand2(Node head) { + if (head == null) { + return null; + } + Node cur = head; + Node next = null; + // copy node and link to every node + // 1 -> 2 + // 1 -> 1' -> 2 + while (cur != null) { + // 1 -> 2 -> 3 + // cur next + // 1 -> 1' -> 2 -> 2' + next = cur.next; + cur.next = new Node(cur.value); + cur.next.next = next; + cur = next; + } + cur = head; + Node curCopy = null; + // set copy node rand + // 1 -> 1' -> 2 -> 2' + while (cur != null) { + // 1 -> 1' -> 2 -> 2' + // cur next + // cur 老 cur.next 新 + next = cur.next.next; + curCopy = cur.next; + curCopy.rand = cur.rand != null ? cur.rand.next : null; + cur = next; + } + // head head.next + Node res = head.next; + cur = head; + // split + while (cur != null) { + next = cur.next.next; + curCopy = cur.next; + cur.next = next; + curCopy.next = next != null ? next.next : null; + cur = next; + } + return res; + } + + public static void printRandLinkedList(Node head) { + Node cur = head; + System.out.print("order: "); + while (cur != null) { + System.out.print(cur.value + " "); + cur = cur.next; + } + System.out.println(); + cur = head; + System.out.print("rand: "); + while (cur != null) { + System.out.print(cur.rand == null ? "- " : cur.rand.value + " "); + cur = cur.next; + } + System.out.println(); + } + + public static void main(String[] args) { + Node head = null; + Node res1 = null; + Node res2 = null; + printRandLinkedList(head); + res1 = copyListWithRand1(head); + printRandLinkedList(res1); + res2 = copyListWithRand2(head); + printRandLinkedList(res2); + printRandLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(3); + head.next.next.next = new Node(4); + head.next.next.next.next = new Node(5); + head.next.next.next.next.next = new Node(6); + + head.rand = head.next.next.next.next.next; // 1 -> 6 + head.next.rand = head.next.next.next.next.next; // 2 -> 6 + head.next.next.rand = head.next.next.next.next; // 3 -> 5 + head.next.next.next.rand = head.next.next; // 4 -> 3 + head.next.next.next.next.rand = null; // 5 -> null + head.next.next.next.next.next.rand = head.next.next.next; // 6 -> 4 + + printRandLinkedList(head); + res1 = copyListWithRand1(head); + printRandLinkedList(res1); + res2 = copyListWithRand2(head); + printRandLinkedList(res2); + printRandLinkedList(head); + System.out.println("========================="); + + } + +} diff --git a/公开课/class011/Code02_JumpMinTimes.java b/公开课/class011/Code02_JumpMinTimes.java new file mode 100644 index 0000000..41f3b56 --- /dev/null +++ b/公开课/class011/Code02_JumpMinTimes.java @@ -0,0 +1,201 @@ +package class011; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class Code02_JumpMinTimes { + + public static int jumpMinTimes1(int N, int start, int end, int[] arr) { + boolean[] map = new boolean[N + 1]; + + return f1(N, start, end, 0, arr, map); + } + + // 一共有N个位置, 每个位置如何跳,记录在arr中 + // 之前一共走了步数是step步 + // 当前来到cur位置,最终想去aim位置, + // 返回从start开始到aim,最少的步数 + // 如果map[i] == true 表示i位置,之前来过 + // 如果map[i] == false 表示i位置,之前没来过 + public static int f1(int N, int cur, int aim, int step, int[] arr, boolean[] map) { + if (cur < 1 || cur > N) { + return -1; + } + if (map[cur]) { + return -1; + } + // 有效的位置,又没来过 + if (cur == aim) { + return step; + } + map[cur] = true; + int ans1 = f1(N, cur + arr[cur - 1], aim, step + 1, arr, map); + int ans2 = f1(N, cur - arr[cur - 1], aim, step + 1, arr, map); + map[cur] = false; + if (ans1 != -1 && ans2 != -1) { + return Math.min(ans1, ans2); + } + if (ans1 != -1 && ans2 == -1) { + return ans1; + } + if (ans1 == -1 && ans2 != -1) { + return ans2; + } + return -1; + } + + // 一共有N个位置 + // 最终要去aim位置 + // arr中,描述怎么跳 + // 当前,来到了i位置 + // 已经走了k步 + // 最后到达aim,至少几步? + public static int process(int N, int aim, int[] arr, int i, int k) { + if (i < 1 || i > N || k > N - 1) { + return -1; + } + if (i == aim) { + return k; + } + // 请注意,arr的下标是从0开始的,但是题目规定的下标从1开始 + // 所以,拿出i位置能跳的距离,需要拿arr[i-1]位置的值 + int ans1 = process(N, aim, arr, i + arr[i - 1], k + 1); + int ans2 = process(N, aim, arr, i - arr[i - 1], k + 1); + int ans = -1; + if (ans1 != -1 && ans2 != -1) { + ans = Math.min(ans1, ans2); + } + if (ans1 != -1 && ans2 == -1) { + ans = ans1; + } + if (ans1 == -1 && ans2 != -1) { + ans = ans2; + } + return ans; + } + + public static int jumpMinTimes2(int N, int start, int end, int[] arr) { + int[][] dp = new int[N + 1][N + 1]; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + dp[i][j] = -2; + } + } + // dp[i][k] == -2表示这个过程没算过 + // dp[i][k] != -2表示这个过程算过了 + return f2(N, end, arr, start, 0, dp); + } + + // 一共有N个位置,跳的过程中,如果你又跳回到某个位置,其实这已经说明不是最优步数了 + // 也就是说,如果存在最优的跳法,那么这个最优跳法一定不会大于N-1步 + // 所以,增加了一个参数k,表示已经跳了多少步 + // 整个函数的含义: + // 一共有1~N个位置,目标是aim位置 + // 所有位置能跳的距离都记录在arr中,并且对任意的arr[i] > 0 + // 当前来到的位置是i, 之前已经跳过了k步, + // 返回最后到达aim位置,跳的最少的步数 + // 如果返回-1表示怎么也无法到达 + public static int f2(int N, int aim, int[] arr, int i, int k, int[][] dp) { + if (i < 1 || i > N || k > N - 1) { + return -1; + } + if (dp[i][k] != -2) { + return dp[i][k]; + } + if (i == aim) { + dp[i][k] = k; + return k; + } + // 请注意,arr的下标是从0开始的,但是题目规定的下标从1开始 + // 所以,拿出i位置能跳的距离,需要拿arr[i-1]位置的值 + int ans1 = f2(N, aim, arr, i + arr[i - 1], k + 1, dp); + int ans2 = f2(N, aim, arr, i - arr[i - 1], k + 1, dp); + int ans = -1; + if (ans1 != -1 && ans2 != -1) { + ans = Math.min(ans1, ans2); + } + if (ans1 != -1 && ans2 == -1) { + ans = ans1; + } + if (ans1 == -1 && ans2 != -1) { + ans = ans2; + } + dp[i][k] = ans; + return ans; + } + + // bfs + public static int jumpMinTimes3(int N, int start, int end, int[] arr) { + if (start < 1 || start > N || end < 1 || end > N) { + return -1; + } + Queue queue = new LinkedList<>(); + HashMap levelMap = new HashMap<>(); + queue.add(start); + levelMap.put(start, 0); + while (!queue.isEmpty()) { + int cur = queue.poll(); + int level = levelMap.get(cur); + if (cur == end) { + return level; + } else { + int left = cur - arr[cur - 1]; + int right = cur + arr[cur - 1]; + if (left > 0 && !levelMap.containsKey(left)) { + queue.add(left); + levelMap.put(left, level + 1); + } + if (right <= N && !levelMap.containsKey(right)) { + queue.add(right); + levelMap.put(right, level + 1); + } + } + } + return -1; + } + + // for test + public static int[] gerRandomArray(int N, int R) { + int[] arr = new int[N]; + for (int i = 0; i < N; i++) { + arr[i] = (int) (Math.random() * R); + } + return arr; + } + + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int maxN = 20; + int maxV = 10; + int testTimes = 200000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = gerRandomArray(maxN, maxV); + int N = arr.length; + int start = (int) (Math.random() * N) + 1; + int end = (int) (Math.random() * N) + 1; + int ans1 = jumpMinTimes1(N, start, end, arr); + int ans2 = jumpMinTimes2(N, start, end, arr); + int ans3 = jumpMinTimes3(N, start, end, arr); + if (ans1 != ans2 || ans2 != ans3) { + printArray(arr); + System.out.println("start : " + start); + System.out.println("end : " + end); + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + System.out.println("ans3 : " + ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class012/Code01_MoneyProblem.java b/公开课/class012/Code01_MoneyProblem.java new file mode 100644 index 0000000..80c721c --- /dev/null +++ b/公开课/class012/Code01_MoneyProblem.java @@ -0,0 +1,200 @@ +package class012; + +import java.util.HashMap; + +public class Code01_MoneyProblem { + + public static long func1(int[] d, int[] p) { + return process(d, p, 0, 0); + } + + // int[] d d[i]:i号怪兽的武力 + // int[] p p[i]:i号怪兽要求的钱 + // hp 当前你所具有的能力 + // index 来到了第index个怪兽的面前 + + // 目前,你的能力是hp,你来到了index号怪兽的面前,如果要通过后续所有的怪兽, + // 请返回需要花的最少钱数 + public static long process(int[] d, int[] p, int hp, int index) { + if (index == d.length) { // base case + return 0; + } + // index < d.length 还有怪兽要面对 + if (hp < d[index]) { + return p[index] + process(d, p, hp + d[index], index + 1); + } else { // hp >= d[index] 可以贿赂,也可以不贿赂 + return Math.min(p[index] + process(d, p, hp + d[index], index + 1), + + process(d, p, hp, index + 1)); + } + } + + // 正数数组 d p + public static int dp1(int[] d, int[] p) { + if (d == null || d.length == 0) { + return 0; + } + + int sum = 0; + for (int ability : d) { + sum += ability; + } + int N = d.length; + + int[][] dp = new int[N + 1][sum + 1]; + + // dp[N][...] = 0; + for (int i = N - 1; i >= 0; i--) { + for (int j = 0; j <= sum; j++) { + // j 能力 i 怪兽号 + if (j + d[i] > sum) { + continue; + } + if (j < d[i]) { + dp[i][j] = p[i] + dp[i + 1][j + d[i]]; + } else { + dp[i][j] = Math.min(p[i] + dp[i + 1][j + d[i]], dp[i + 1][j]); + } + } + } + + return dp[0][0]; + + } + + public static long func2(int[] d, int[] p) { + int sum = 0; + for (int num : d) { + sum += num; + } + long[][] dp = new long[d.length + 1][sum + 1]; + for (int i = 0; i <= sum; i++) { + dp[0][i] = 0; + } + for (int cur = d.length - 1; cur >= 0; cur--) { + for (int hp = 0; hp <= sum; hp++) { + // 如果这种情况发生,那么这个hp必然是递归过程中不会出现的状态 + // 既然动态规划是尝试过程的优化,尝试过程碰不到的状态,不必计算 + if (hp + d[cur] > sum) { + continue; + } + if (hp < d[cur]) { + dp[cur][hp] = p[cur] + dp[cur + 1][hp + d[cur]]; + } else { + dp[cur][hp] = Math.min(p[cur] + dp[cur + 1][hp + d[cur]], dp[cur + 1][hp]); + } + } + } + return dp[0][0]; + } + + + + public static long func1dp(int[] d, int[] p) { + HashMap dp = new HashMap<>(); + return processdp(d, p, 0, 0, dp); + } + + // int[] d d[i]:i号怪兽的武力 + // int[] p p[i]:i号怪兽要求的钱 + // hp 当前你所具有的能力 + // index 来到了第index个怪兽的面前 + + // 目前,你的能力是hp,你来到了index号怪兽的面前,如果要通过后续所有的怪兽, + // 请返回需要花的最少钱数 + public static long processdp(int[] d, int[] p, int hp, int index,HashMap dp) { + String key = String.valueOf(hp) + "_" + String.valueOf(index); + if(dp.containsKey(key)) { + return dp.get(key); + } + long ans = 0; + if(index < d.length) { + if (hp < d[index]) { + ans = p[index] + process(d, p, hp + d[index], index + 1); + } else { // hp >= d[index] 可以贿赂,也可以不贿赂 + ans = Math.min(p[index] + process(d, p, hp + d[index], index + 1), + + process(d, p, hp, index + 1)); + } + } + dp.put(key, ans); + return ans; + + } + + + public static long func3(int[] d, int[] p) { + int sum = 0; + for (int num : p) { + sum += num; + } + // dp[i][j]含义: + // 能经过0~i的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? + // 如果dp[i][j]==-1,表示经过0~i的怪兽,花钱为j是无法通过的,或者之前的钱怎么组合也得不到正好为j的钱数 + int[][] dp = new int[d.length][sum + 1]; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j <= sum; j++) { + dp[i][j] = -1; + } + } + // 经过0~i的怪兽,花钱数一定为p[0],达到武力值d[0]的地步。其他第0行的状态一律是无效的 + dp[0][p[0]] = d[0]; + for (int i = 1; i < d.length; i++) { + for (int j = 0; j <= sum; j++) { + // 可能性一,为当前怪兽花钱 + // 存在条件: + // j - p[i]要不越界,并且在钱数为j - p[i]时,要能通过0~i-1的怪兽,并且钱数组合是有效的。 + if (j >= p[i] && dp[i - 1][j - p[i]] != -1) { + dp[i][j] = dp[i - 1][j - p[i]] + d[i]; + } + // 可能性二,不为当前怪兽花钱 + // 存在条件: + // 0~i-1怪兽在花钱为j的情况下,能保证通过当前i位置的怪兽 + if (dp[i - 1][j] >= d[i]) { + // 两种可能性中,选武力值最大的 + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j]); + } + } + } + int ans = 0; + // dp表最后一行上,dp[N-1][j]代表: + // 能经过0~N-1的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? + // 那么最后一行上,最左侧的不为-1的列数(j),就是答案 + for (int j = 0; j <= sum; j++) { + if (dp[d.length - 1][j] != -1) { + ans = j; + break; + } + } + return ans; + } + + public static int[][] generateTwoRandomArray(int len, int value) { + int size = (int) (Math.random() * len) + 1; + int[][] arrs = new int[2][size]; + for (int i = 0; i < size; i++) { + arrs[0][i] = (int) (Math.random() * value) + 1; + arrs[1][i] = (int) (Math.random() * value) + 1; + } + return arrs; + } + + public static void main(String[] args) { + int len = 10; + int value = 20; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + int[][] arrs = generateTwoRandomArray(len, value); + int[] d = arrs[0]; + int[] p = arrs[1]; + long ans1 = func1(d, p); + long ans2 = func2(d, p); + long ans3 = func3(d, p); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + } + } + + } + +} diff --git a/公开课/class013/Code01_PalindromeNumber.java b/公开课/class013/Code01_PalindromeNumber.java new file mode 100644 index 0000000..a682247 --- /dev/null +++ b/公开课/class013/Code01_PalindromeNumber.java @@ -0,0 +1,24 @@ +package class013; + +public class Code01_PalindromeNumber { + + public static boolean isPalindrome(int n) { + if (n < 0) { + return false; + } + n = Math.abs(n); + int help = 1; + while (n / help >= 10) { + help *= 10; + } + while (n != 0) { + if (n / help != n % 10) { + return false; + } + n = (n % help) / 10; + help /= 100; + } + return true; + } + +} diff --git a/公开课/class013/Code03_LongestNoRepeatSubstring.java b/公开课/class013/Code03_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..e3c1d87 --- /dev/null +++ b/公开课/class013/Code03_LongestNoRepeatSubstring.java @@ -0,0 +1,70 @@ +package class013; + +public class Code03_LongestNoRepeatSubstring { + + public static int maxUnique(String str) { + if (str == null || str.equals("")) { + return 0; + } + char[] chas = str.toCharArray(); + // map 替代了哈希表 假设字符的码是0~255 + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + int len = 0; + int pre = -1; + int cur = 0; + for (int i = 0; i != chas.length; i++) { + pre = Math.max(pre, map[chas[i]]); + cur = i - pre; + len = Math.max(len, cur); + map[chas[i]] = i; + } + return len; + } + + // for test + public static String getRandomString(int len) { + char[] str = new char[len]; + int base = 'a'; + int range = 'z' - 'a' + 1; + for (int i = 0; i != len; i++) { + str[i] = (char) ((int) (Math.random() * range) + base); + } + return String.valueOf(str); + } + + // for test + public static String maxUniqueString(String str) { + if (str == null || str.equals("")) { + return str; + } + char[] chas = str.toCharArray(); + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + int len = -1; + int pre = -1; + int cur = 0; + int end = -1; + for (int i = 0; i != chas.length; i++) { + pre = Math.max(pre, map[chas[i]]); + cur = i - pre; + if (cur > len) { + len = cur; + end = i; + } + map[chas[i]] = i; + } + return str.substring(end - len + 1, end + 1); + } + + public static void main(String[] args) { + String str = getRandomString(20); + System.out.println(str); + System.out.println(maxUnique(str)); + System.out.println(maxUniqueString(str)); + } +} diff --git a/公开课/class014/Code01_CoverMax.java b/公开课/class014/Code01_CoverMax.java new file mode 100644 index 0000000..5267691 --- /dev/null +++ b/公开课/class014/Code01_CoverMax.java @@ -0,0 +1,133 @@ +package class014; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code01_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // lines + // + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class014/Code02_MaxDistance.java b/公开课/class014/Code02_MaxDistance.java new file mode 100644 index 0000000..7076c8d --- /dev/null +++ b/公开课/class014/Code02_MaxDistance.java @@ -0,0 +1,154 @@ +package class014; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class Code02_MaxDistance { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static int maxDistance2(Node head) { + return f(head).allTreeMaxDis; + } + + // 左:最大距离、高 + // 右:最大距离、高 + public static class Info { + public int allTreeMaxDis; + public int height; + + public Info(int all, int h) { + allTreeMaxDis = all; + height = h; + } + } + + // 以x为头情况下,两个结果 + public static Info f(Node x) { + if (x == null) { + return new Info(0, 0); + } + Info leftInfo = f(x.left); + Info rightInfo = f(x.right); + int allTreeMaxDis = Math.max(Math.max(leftInfo.allTreeMaxDis, rightInfo.allTreeMaxDis), + leftInfo.height + rightInfo.height + 1); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + return new Info(allTreeMaxDis, height); + } + + public static int maxDistance1(Node head) { + if (head == null) { + return 0; + } + ArrayList arr = getPrelist(head); + HashMap parentMap = getParentMap(head); + int max = 0; + for (int i = 0; i < arr.size(); i++) { + for (int j = i; j < arr.size(); j++) { + max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j))); + } + } + return max; + } + + public static ArrayList getPrelist(Node head) { + ArrayList arr = new ArrayList<>(); + fillPrelist(head, arr); + return arr; + } + + public static void fillPrelist(Node head, ArrayList arr) { + if (head == null) { + return; + } + arr.add(head); + fillPrelist(head.left, arr); + fillPrelist(head.right, arr); + } + + public static HashMap getParentMap(Node head) { + HashMap map = new HashMap<>(); + map.put(head, null); + fillParentMap(head, map); + return map; + } + + public static void fillParentMap(Node head, HashMap parentMap) { + if (head.left != null) { + parentMap.put(head.left, head); + fillParentMap(head.left, parentMap); + } + if (head.right != null) { + parentMap.put(head.right, head); + fillParentMap(head.right, parentMap); + } + } + + public static int distance(HashMap parentMap, Node o1, Node o2) { + HashSet o1Set = new HashSet<>(); + Node cur = o1; + o1Set.add(cur); + while (parentMap.get(cur) != null) { + cur = parentMap.get(cur); + o1Set.add(cur); + } + cur = o2; + while (!o1Set.contains(cur)) { + cur = parentMap.get(cur); + } + Node lowestAncestor = cur; + cur = o1; + int distance1 = 1; + while (cur != lowestAncestor) { + cur = parentMap.get(cur); + distance1++; + } + cur = o2; + int distance2 = 1; + while (cur != lowestAncestor) { + cur = parentMap.get(cur); + distance2++; + } + return distance1 + distance2 - 1; + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (maxDistance1(head) != maxDistance2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class015/Code01_CordCoverMaxPoint.java b/公开课/class015/Code01_CordCoverMaxPoint.java new file mode 100644 index 0000000..9248055 --- /dev/null +++ b/公开课/class015/Code01_CordCoverMaxPoint.java @@ -0,0 +1,88 @@ +package class015; + +import java.util.Arrays; + +public class Code01_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class015/Code02_ColorLeftRight.java b/公开课/class015/Code02_ColorLeftRight.java new file mode 100644 index 0000000..3185cc3 --- /dev/null +++ b/公开课/class015/Code02_ColorLeftRight.java @@ -0,0 +1,72 @@ +package class015; + +public class Code02_ColorLeftRight { + + // RGRGR -> RRRGG + public static int minPaint(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int rAll = 0; + for (int i = 0; i < N; i++) { + rAll += str[i] == 'R' ? 1 : 0; + } + int ans = rAll; // 如果数组所有的范围,都是右侧范围,都变成G + int left = 0; + for (int i = 0; i < N - 1; i++) { // 0..i 左侧 n-1..N-1 + left += str[i] == 'G' ? 1 : 0; + rAll -= str[i] == 'R' ? 1 : 0; + ans = Math.min(ans, left + rAll); + } + // 0...N-1 左全部 右无 + ans = Math.min(ans, left + (str[N - 1] == 'G' ? 1 : 0)); + return ans; + } + + // RGRGR -> RRRGG + public static int test(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int ans = Integer.MAX_VALUE; + for (int leftEnd = -1; leftEnd < str.length; leftEnd++) { + int left = 0; + for (int i = 0; i <= leftEnd; i++) { + left += str[i] == 'G' ? 1 : 0; + } + int right = 0; + for (int i = leftEnd + 1; i < str.length; i++) { + right += str[i] == 'R' ? 1 : 0; + } + ans = Math.min(ans, left + right); + } + return ans; + } + + public static String generateString(int len) { + char[] str = new char[(int) (Math.random() * len) + 1]; + for (int i = 0; i < str.length; i++) { + str[i] = Math.random() < 0.5 ? 'R' : 'G'; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int len = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = generateString(len); + int ans1 = minPaint(str); + int ans2 = test(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class015/Code03_MaxABSBetweenLeftAndRight.java b/公开课/class015/Code03_MaxABSBetweenLeftAndRight.java new file mode 100644 index 0000000..0b69740 --- /dev/null +++ b/公开课/class015/Code03_MaxABSBetweenLeftAndRight.java @@ -0,0 +1,72 @@ +package class015; + +public class Code03_MaxABSBetweenLeftAndRight { + + public static int maxABS1(int[] arr) { + int res = Integer.MIN_VALUE; + int maxLeft = 0; + int maxRight = 0; + for (int i = 0; i != arr.length - 1; i++) { + maxLeft = Integer.MIN_VALUE; + for (int j = 0; j != i + 1; j++) { + maxLeft = Math.max(arr[j], maxLeft); + } + maxRight = Integer.MIN_VALUE; + for (int j = i + 1; j != arr.length; j++) { + maxRight = Math.max(arr[j], maxRight); + } + res = Math.max(Math.abs(maxLeft - maxRight), res); + } + return res; + } + + public static int maxABS2(int[] arr) { + int[] lArr = new int[arr.length]; + int[] rArr = new int[arr.length]; + lArr[0] = arr[0]; + rArr[arr.length - 1] = arr[arr.length - 1]; + for (int i = 1; i < arr.length; i++) { + lArr[i] = Math.max(lArr[i - 1], arr[i]); + } + for (int i = arr.length - 2; i > -1; i--) { + rArr[i] = Math.max(rArr[i + 1], arr[i]); + } + int max = 0; + for (int i = 0; i < arr.length - 1; i++) { + max = Math.max(max, Math.abs(lArr[i] - rArr[i + 1])); + } + return max; + } + + public static int maxABS3(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(arr[i], max); + } + return max - Math.min(arr[0], arr[arr.length - 1]); + } + + public static int[] generateRandomArray(int length) { + int[] arr = new int[length]; + for (int i = 0; i != arr.length; i++) { + arr[i] = (int) (Math.random() * 1000) - 499; + } + return arr; + } + + public static void main(String[] args) { + int len = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len); + int ans1 = maxABS1(arr); + int ans2 = maxABS2(arr); + int ans3 = maxABS3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } +} diff --git a/公开课/class015/Code04_TrappingRainWater.java b/公开课/class015/Code04_TrappingRainWater.java new file mode 100644 index 0000000..fd8145d --- /dev/null +++ b/公开课/class015/Code04_TrappingRainWater.java @@ -0,0 +1,121 @@ +package class015; + +public class Code04_TrappingRainWater { + + /* + * 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + * + */ + + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class016/Code01_RandToRand.java b/公开课/class016/Code01_RandToRand.java new file mode 100644 index 0000000..8804b59 --- /dev/null +++ b/公开课/class016/Code01_RandToRand.java @@ -0,0 +1,138 @@ +package class016; + +public class Code01_RandToRand { + + // 条件,且代码不可修改,1~5 + public static int f() { + return (int) (Math.random() * 5) + 1; + } + + // 等概率返回 0~4 + public static int a() { + return f() - 1; + } + + // 等概率返回 0 ~ 24 + public static int b() { + return a() * 5 + a(); + } + + // 等概率返回 1~7 + public static int g1() { + int t = 0; + do { + t = b(); + } while (t > 20); + // t 0 ~ 20 + return (t % 7) + 1; + } + + // 利用f函数,请等概率生成0和1 + public static int c() { + int t = 0; + do { + t = f(); + } while (t == 3); + return t < 3 ? 0 : 1; + } + + // 等概率返回 1~7 + public static int g2() { + int t = 0; + do { + t = (c() << 2) + (c() << 1) + c(); + } while (t == 7); + return t + 1; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + int hasFrom = 1; + int hasTo = 19; + + int from = 7; + int to = 29; + + RandomBox randomBox = new RandomBox(hasFrom, hasTo); + int[] ans = new int[to + 1]; + int testTime1 = 1000000; + for (int i = 0; i < testTime1; i++) { + ans[random(randomBox, from, to)]++; + } + for (int i = 0; i < ans.length; i++) { + System.out.println(i + " 出现了 : " + ans[i]); + } + System.out.println("=========="); + + } + +} diff --git a/公开课/class016/Code02_EqualProbabilityRandom.java b/公开课/class016/Code02_EqualProbabilityRandom.java new file mode 100644 index 0000000..3d8ed13 --- /dev/null +++ b/公开课/class016/Code02_EqualProbabilityRandom.java @@ -0,0 +1,46 @@ +package class016; + +public class Code02_EqualProbabilityRandom { + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + double zeroP = 0.88; + RandomBox randomBox = new RandomBox(zeroP); + + int testTime = 10000000; + int count = 0; + for (int i = 0; i < testTime; i++) { + if (rand01(randomBox) == 0) { + count++; + } + } + System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class016/Code03_FindHalfMajority.java b/公开课/class016/Code03_FindHalfMajority.java new file mode 100644 index 0000000..acadae6 --- /dev/null +++ b/公开课/class016/Code03_FindHalfMajority.java @@ -0,0 +1,77 @@ +package class016; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code03_FindHalfMajority { + + public static int halfMajor(int[] arr) { + int cand = 0; + int HP = 0; + // 遍历一遍数组arr,一次删掉两个不同的数,谁会剩下来,谁就是cand + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { + cand = arr[i]; + HP = 1; + } else if (arr[i] == cand) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == cand) { + HP++; + } + } + return HP > arr.length / 2 ? cand : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class016/Code04_FindKMajor.java b/公开课/class016/Code04_FindKMajor.java new file mode 100644 index 0000000..a76ab1b --- /dev/null +++ b/公开课/class016/Code04_FindKMajor.java @@ -0,0 +1,135 @@ +package class016; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class Code04_FindKMajor { + + public static List kMajor(int[] arr, int K) { + List ans = new ArrayList<>(); + if (K < 2) { + return ans; + } + // 候选表,记录数一定是O(K) > N/K k-1 + HashMap cands = new HashMap(); + for (int i = 0; i != arr.length; i++) { + if (cands.containsKey(arr[i])) { + cands.put(arr[i], cands.get(arr[i]) + 1); + } else { + if (cands.size() == K - 1) { + allCandsMinusOne(cands); + } else { + cands.put(arr[i], 1); + } + } + } + HashMap reals = getReals(arr, cands); + for (Entry set : cands.entrySet()) { + Integer key = set.getKey(); + if (reals.get(key) > arr.length / K) { + ans.add(key); + } + } + return ans; + } + + public static void allCandsMinusOne(HashMap map) { + List removeList = new LinkedList(); + for (Entry set : map.entrySet()) { + Integer key = set.getKey(); + Integer value = set.getValue(); + if (value == 1) { + removeList.add(key); + } + map.put(key, value - 1); + } + for (Integer removeKey : removeList) { + map.remove(removeKey); + } + } + + // for test + public static List right(int[] arr, int K) { + List ans = new ArrayList<>(); + if (K < 2) { + return ans; + } + HashMap times = new HashMap<>(); + for (int num : arr) { + if (!times.containsKey(num)) { + times.put(num, 1); + } else { + times.put(num, times.get(num) + 1); + } + } + for (Entry entry : times.entrySet()) { + if (entry.getValue() > arr.length / K) { + ans.add(entry.getKey()); + } + } + return ans; + } + + public static HashMap getReals(int[] arr, HashMap cands) { + HashMap reals = new HashMap(); + for (int i = 0; i != arr.length; i++) { + int curNum = arr[i]; + if (cands.containsKey(curNum)) { + if (reals.containsKey(curNum)) { + reals.put(curNum, reals.get(curNum) + 1); + } else { + reals.put(curNum, 1); + } + } + } + return reals; + } + + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static boolean isEqual(List ans1, List ans2) { + if (ans1.size() != ans2.size()) { + return false; + } + HashSet set1 = new HashSet<>(); + for (Integer num : ans1) { + set1.add(num); + } + if (set1.size() != ans1.size()) { + return false; + } + for (Integer num : ans2) { + set1.remove(num); + } + return set1.size() == 0; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int K = 5; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + List ans1 = kMajor(arr, K); + List ans2 = right(arr, K); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class017/Code01_SubArrayMaxSum.java b/公开课/class017/Code01_SubArrayMaxSum.java new file mode 100644 index 0000000..05895f4 --- /dev/null +++ b/公开课/class017/Code01_SubArrayMaxSum.java @@ -0,0 +1,111 @@ +package class017; + +import java.util.ArrayList; +import java.util.List; + +public class Code01_SubArrayMaxSum { + + public static int test(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + // arr[L...R] + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + max = Math.max(max, sum); + } + } + return max; + } + + public static int maxSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // 0结尾时候的答案 + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + // i结尾时候的答案 + pre = arr[i] + (pre > 0 ? pre : 0); + max = Math.max(max, pre); + } + return max; + } + + public static List> maxSum2(int[] arr) { + List> ans = new ArrayList<>(); + if (arr == null || arr.length == 0) { + return ans; + } + int L = 0; + int maxLen = 0; + int maxSum = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + // L...i sum + cur += arr[i]; + if (cur == maxSum && (i - L + 1) == maxLen) { + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + } + if (cur > maxSum || (cur == maxSum && (i - L + 1) > maxLen)) { + ans.clear(); + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + maxLen = i - L + 1; + } + maxSum = Math.max(maxSum, cur); + if (cur < 0) { + cur = 0; + L = i + 1; + } + } + return ans; + } + + public static int[] generateArray(int N, int V) { + int n = (int) (Math.random() * N) + 1; + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + return arr; + } + + public static void main(String[] args) { +// int N = 100; +// int V = 100; +// int testTime = 1000000; +// System.out.println("test begin"); +// for (int i = 0; i < testTime; i++) { +// int[] arr = generateArray(N, V); +// int ans1 = maxSum1(arr); +// int ans2 = maxSum2(arr); +// if (ans1 != ans2) { +// System.out.println("Oops!"); +// } +// } +// System.out.println("test finish"); + + int[] test = { 2, 2, 1, -9, 2, 3, -9, 6, -9, 2, 2, 2, -9, 2, 2, 2, -9, 1, 4, 1 }; + + List> ans = maxSum2(test); + + for (List cur : ans) { + System.out.println("start : " + cur.get(0) + ", end : " + cur.get(1)); + } + + } + +} diff --git a/公开课/class017/Code02_SubMatrixMaxSum.java b/公开课/class017/Code02_SubMatrixMaxSum.java new file mode 100644 index 0000000..442471b --- /dev/null +++ b/公开课/class017/Code02_SubMatrixMaxSum.java @@ -0,0 +1,83 @@ +package class017; + +public class Code02_SubMatrixMaxSum { + + public static int maxSum1(int[][] matrix) { + int n = matrix.length; + int m = matrix[0].length; + int max = Integer.MIN_VALUE; + for (int ai = 0; ai < n; ai++) { + for (int aj = 0; aj < m; aj++) { + for (int bi = ai; bi < n; bi++) { + for (int bj = aj; bj < m; bj++) { + max = Math.max(max, sum(matrix, ai, aj, bi, bj)); + } + } + } + } + return max; + } + + public static int sum(int[][] matrix, int ai, int aj, int bi, int bj) { + int sum = 0; + for (int i = ai; i <= bi; i++) { + for (int j = aj; j <= bj; j++) { + sum += matrix[i][j]; + } + } + return sum; + } + + public static int maxSum2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + int[] s = null; + for (int i = 0; i != matrix.length; i++) { + s = new int[matrix[0].length]; + for (int j = i; j != matrix.length; j++) { + cur = 0; + for (int k = 0; k != s.length; k++) { + s[k] += matrix[j][k]; + cur += s[k]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + } + } + return max; + } + + public static int[][] generateMatrix(int N, int M, int V) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + } + return matrix; + } + + public static void main(String[] args) { + int N = 20; + int M = 20; + int V = 100; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[][] matrix = generateMatrix(N, M, V); + int ans1 = maxSum1(matrix); + int ans2 = maxSum2(matrix); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class018/Code01_SqrtX.java b/公开课/class018/Code01_SqrtX.java new file mode 100644 index 0000000..15bb042 --- /dev/null +++ b/公开课/class018/Code01_SqrtX.java @@ -0,0 +1,31 @@ +package class018; + +public class Code01_SqrtX { + + // x一定非负,输入可以保证 + public static int mySqrt(int x) { + if (x == 0) { + return 0; + } + // x != 0 1 2 + if (x < 3) { + return 1; + } + // x >= 3 + long ans = 1; + long L = 1; + long R = x; + long M = 0; + while (L <= R) { + M = (L + R) / 2; + if (M * M <= x) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return (int) ans; + } + +} diff --git a/公开课/class018/Code02_PowXN.java b/公开课/class018/Code02_PowXN.java new file mode 100644 index 0000000..03e7fa0 --- /dev/null +++ b/公开课/class018/Code02_PowXN.java @@ -0,0 +1,54 @@ +package class018; + +public class Code02_PowXN { + + public static void main(String[] args) { + long a = 2; + long n = 13; // 1101 + long t = a; + long res = 1; + while (n != 0) { + // n = 110111011111 + // & 1 = 000000000001 + // 000000000000 + // n & (n-1) + // 把n抹掉最右侧的1 + // n = 10010101100010000 + // n-1 = 10010101100001111 + // & = 10010101100000000 + if ((n & 1) == 1) { + res *= t; + } + t = t * t; + n = n >> 1; + } + System.out.println(res); + System.out.println(Math.pow(2.0, 13)); + System.out.println(-Integer.MIN_VALUE); + System.out.println(Math.abs(Integer.MIN_VALUE)); + + } + + // 求x的n次方 + public static double myPow(double x, int n) { + if (n == 0) { + return 1D; + } + if (n == Integer.MIN_VALUE) { + return (x == 1D || x == -1D) ? 1D : 0; + } + // 4.5 -3 + int pow = Math.abs(n); + double t = x; + double ans = 1D; + while (pow != 0) { + if ((pow & 1) != 0) { + ans *= t; + } + pow >>= 1; + t = t * t; + } + return n < 0 ? (1D / ans) : ans; + } + +} diff --git a/公开课/class018/Code03_ContainerWithMostWater.java b/公开课/class018/Code03_ContainerWithMostWater.java new file mode 100644 index 0000000..9c4f60d --- /dev/null +++ b/公开课/class018/Code03_ContainerWithMostWater.java @@ -0,0 +1,31 @@ +package class018; + +public class Code03_ContainerWithMostWater { + + public static int maxArea1(int[] h) { + int max = 0; + int N = h.length; + for (int i = 0; i < N; i++) { // h[i] + for (int j = i + 1; j < N; j++) { // h[j] + max = Math.max(max, Math.min(h[i], h[j]) * (j - i)); + } + } + return max; + } + + public static int maxArea2(int[] h) { + int max = 0; + int l = 0; + int r = h.length - 1; + while (l < r) { + max = Math.max(max, Math.min(h[l], h[r]) * (r - l)); + if (h[l] > h[r]) { + r--; + } else { + l++; + } + } + return max; + } + +} diff --git a/公开课/class019/Code01_RotateImage.java b/公开课/class019/Code01_RotateImage.java new file mode 100644 index 0000000..5cecb1d --- /dev/null +++ b/公开课/class019/Code01_RotateImage.java @@ -0,0 +1,27 @@ +package class019; + +public class Code01_RotateImage { + + public static void rotate(int[][] matrix) { + // matrix.length == matrix[0].length + int a = 0; + int b = 0; + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + +} diff --git a/公开课/class019/Code02_ZigZagPrintMatrix.java b/公开课/class019/Code02_ZigZagPrintMatrix.java new file mode 100644 index 0000000..069e84d --- /dev/null +++ b/公开课/class019/Code02_ZigZagPrintMatrix.java @@ -0,0 +1,48 @@ +package class019; + +public class Code02_ZigZagPrintMatrix { + + public static void printMatrixZigZag(int[][] matrix) { + int aRow = 0; + int aCol = 0; + int bRow = 0; + int bCol = 0; + // A和B一定会共同走到右下角的位置 + int endR = matrix.length - 1; + int endC = matrix[0].length - 1; + // fromUp = true 斜线打印方向应该从右上走到左下 + // fromUp = false 斜线打印方向应该从左下走到右上 + boolean fromUp = false; + while (aRow != endR + 1) { + printLevel(matrix, aRow, aCol, bRow, bCol, fromUp); + aRow = aCol == endC ? aRow + 1 : aRow; + aCol = aCol == endC ? aCol : aCol + 1; + bCol = bRow == endR ? bCol + 1 : bCol; + bRow = bRow == endR ? bRow : bRow + 1; + fromUp = !fromUp; + } + System.out.println(); + } + + public static void printLevel(int[][] m, int aRow, int aCol, int bRow, int bCol, boolean f) { + if (f) { + while (aRow != bRow + 1) { + System.out.print(m[aRow++][aCol--] + " "); + } + } else { + while (bRow != aRow - 1) { + System.out.print(m[bRow--][bCol++] + " "); + } + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } }; + printMatrixZigZag(matrix); + + } + +} diff --git a/公开课/class019/Code03_PrintStar.java b/公开课/class019/Code03_PrintStar.java new file mode 100644 index 0000000..286b6dc --- /dev/null +++ b/公开课/class019/Code03_PrintStar.java @@ -0,0 +1,46 @@ +package class019; + +public class Code03_PrintStar { + + public static void printStar(int N) { + int leftUp = 0; + int rightDown = N - 1; + char[][] m = new char[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + m[i][j] = ' '; + } + } + while (leftUp <= rightDown) { + set(m, leftUp, rightDown); + leftUp += 2; + rightDown -= 2; + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void set(char[][] m, int leftUp, int rightDown) { + for (int col = leftUp; col <= rightDown; col++) { + m[leftUp][col] = '*'; + } + for (int row = leftUp + 1; row <= rightDown; row++) { + m[row][rightDown] = '*'; + } + for (int col = rightDown - 1; col > leftUp; col--) { + m[rightDown][col] = '*'; + } + for (int row = rightDown - 1; row > leftUp + 1; row--) { + m[row][leftUp + 1] = '*'; + } + } + + public static void main(String[] args) { + printStar(8); + } + +} diff --git a/公开课/class020/Code01_RotateString.java b/公开课/class020/Code01_RotateString.java new file mode 100644 index 0000000..826ba5a --- /dev/null +++ b/公开课/class020/Code01_RotateString.java @@ -0,0 +1,93 @@ +package class020; + +public class Code01_RotateString { + + public static String rotate1(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + return process1(s.toCharArray(), 0, leftSize - 1, s.length() - 1); + } + + public static String process1(char[] str, int L, int M, int R) { + reverse(str, L, M); + reverse(str, M + 1, R); + reverse(str, L, R); + return String.valueOf(str); + } + + public static void reverse(char[] str, int L, int R) { + while (L < R) { + char tmp = str[L]; + str[L++] = str[R]; + str[R--] = tmp; + } + } + + public static String rotate2(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + char[] str = s.toCharArray(); + int L = 0; + int R = str.length - 1; + int lpart = leftSize; + int rpart = str.length - leftSize; + int same = Math.min(lpart, rpart); + int diff = lpart - rpart; + exchange(str, L, R, same); + while (diff != 0) { + if (diff > 0) { + L += same; + lpart = diff; + } else { + R -= same; + rpart = -diff; + } + same = Math.min(lpart, rpart); + diff = lpart - rpart; + exchange(str, L, R, same); + } + return String.valueOf(str); + } + + public static void exchange(char[] chas, int start, int end, int size) { + int i = end - size + 1; + char tmp = 0; + while (size-- != 0) { + tmp = chas[start]; + chas[start] = chas[i]; + chas[i] = tmp; + start++; + i++; + } + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 10; + int testTimes = 5000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int leftSize = (int) (Math.random() * (str.length() + 1)); + String ans1 = rotate1(str, leftSize); + String ans2 = rotate2(str, leftSize); + if (!ans1.equals(ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class020/Code02_HowManyTypes.java b/公开课/class020/Code02_HowManyTypes.java new file mode 100644 index 0000000..193b242 --- /dev/null +++ b/公开课/class020/Code02_HowManyTypes.java @@ -0,0 +1,111 @@ +package class020; + +import java.util.HashSet; + +public class Code02_HowManyTypes { + + /* + * 只由小写字母(a~z)组成的一批字符串,都放在字符类型的数组String[] arr中, + * 如果其中某两个字符串,所含有的字符种类完全一样,就将两个字符串算作一类 比如:baacba和bac就算作一类 + * 虽然长度不一样,但是所含字符的种类完全一样(a、b、c) 返回arr中有多少类? + * + */ + + public static int types1(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + boolean[] map = new boolean[26]; + for (int i = 0; i < chs.length; i++) { + map[chs[i] - 'a'] = true; + } + String key = ""; + for (int i = 0; i < 26; i++) { + if (map[i]) { + key += String.valueOf((char) (i + 'a')); + } + } + types.add(key); + } + return types.size(); + } + + public static int types2(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + int key = 0; + for (int i = 0; i < chs.length; i++) { + key |= (1 << (chs[i] - 'a')); + } + types.add(key); + } + return types.size(); + } + + // for test + public static String[] getRandomStringArray(int possibilities, int strMaxSize, int arrMaxSize) { + String[] ans = new String[(int) (Math.random() * arrMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = getRandomString(possibilities, strMaxSize); + } + return ans; + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void printIntegerBinary(int num) { + StringBuilder builder = new StringBuilder(); + for (int i = 31; i >= 0; i--) { + // 依次提取出num从0位~31位的状态来 + int status = ((num >> i) & 1); + builder.append(status); + + } + System.out.println(builder.toString()); + } + + public static void main(String[] args) { + int num = 3; + printIntegerBinary(num); + + char[] str = { 'b', 'b', 'z', 'k', 'o' }; + + int key = 0; + // 如何生成str的摘要? + // 00000000000000000000000000000000 + + for (int i = 0; i < str.length; i++) { + char cha = str[i]; + // cha = a + // cha - 'a' ? 0 + // 1 << (cha - 'a') -> 1 << 0 + key = key | (1 << (cha - 'a')); + } + printIntegerBinary(key); + + int possibilities = 5; + int strMaxSize = 10; + int arrMaxSize = 100; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String[] arr = getRandomStringArray(possibilities, strMaxSize, arrMaxSize); + int ans1 = types1(arr); + int ans2 = types2(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class020/Code03_LongestNoRepeatSubstring.java b/公开课/class020/Code03_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..1bbbdb9 --- /dev/null +++ b/公开课/class020/Code03_LongestNoRepeatSubstring.java @@ -0,0 +1,77 @@ +package class020; + +public class Code03_LongestNoRepeatSubstring { + + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = lnrs2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class020/Code04_CoverMax.java b/公开课/class020/Code04_CoverMax.java new file mode 100644 index 0000000..30f5274 --- /dev/null +++ b/公开课/class020/Code04_CoverMax.java @@ -0,0 +1,133 @@ +package class020; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code04_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // lines + // + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class020/Code05_MakeNo.java b/公开课/class020/Code05_MakeNo.java new file mode 100644 index 0000000..fc7d49c --- /dev/null +++ b/公开课/class020/Code05_MakeNo.java @@ -0,0 +1,60 @@ +package class020; + +public class Code05_MakeNo { + + // 生成长度为size的达标数组 + // 达标:对于任意的 i 等长奇数达标来 + // base -> 等长偶数达标来 + int[] ans = new int[size]; + int index = 0; + for(; index < halfSize;index++) { + ans[index] = base[index] * 2 + 1; + } + for(int i = 0 ;index < size;index++,i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/公开课/class021/Code01_SwapWithoutTmp.java b/公开课/class021/Code01_SwapWithoutTmp.java new file mode 100644 index 0000000..df7b9c9 --- /dev/null +++ b/公开课/class021/Code01_SwapWithoutTmp.java @@ -0,0 +1,17 @@ +package class021; + +public class Code01_SwapWithoutTmp { + + public static void main(String[] args) { + int a = 123; + int b = -898121; + System.out.println(a); + System.out.println(b); + a = a ^ b; + b = a ^ b; + a = a ^ b; + System.out.println(a); + System.out.println(b); + } + +} diff --git a/公开课/class021/Code02_GetMax.java b/公开课/class021/Code02_GetMax.java new file mode 100644 index 0000000..339f796 --- /dev/null +++ b/公开课/class021/Code02_GetMax.java @@ -0,0 +1,55 @@ +package class021; + +public class Code02_GetMax { + + // 输入参数:n,一定要保证,n不是1就是0 + // n == 0 -> 1 + // n == 1 -> 0 + public static int flip(int n) { + return n ^ 1; + } + + // 输入参数n,可以是任何一个整数 + // 如果n是非负数,返回1(int) + // 如果n是负数,返回0(int) + public static int sign(int n) { + return flip((n >> 31) & 1); + } + + // a和b中,谁大返回谁 + public static int getMax1(int a, int b) { + int c = a - b; + int scA = sign(c); // c >= 0 scA = 1; c < 0 scA = 0 + int scB = flip(scA); + return a * scA + b * scB; + } + + public static int getMax2(int a, int b) { + int c = a - b; // c是a-b的差值,有可能溢出,也有可能不溢出 + int sa = sign(a); // a的符号,求出,a>=0 1, a<0 0 + int sb = sign(b); // b的符号,求出,b>=0 1, b<0 0 + int sc = sign(c); // c的符号,求出,c>=0 1, c<0 0 + // 如果a和b的符号,不一样,difSab == 1 + // 如果a和b的符号, 一样,difSab == 0 + int difSab = sa ^ sb; + // 如果a和b的符号,一样,sameSab == 1 + // 如果a和b的符号,不一样,sameSab == 0 + int sameSab = flip(difSab); + int returnA = difSab * sa + sameSab * sc; + int returnB = flip(returnA); + return a * returnA + b * returnB; + } + + public static void main(String[] args) { + int a = -16; + int b = 1; + System.out.println(getMax1(a, b)); + System.out.println(getMax2(a, b)); + a = 2147483647; + b = -2147480000; + System.out.println(getMax1(a, b)); // wrong answer because of overflow + System.out.println(getMax2(a, b)); + + } + +} diff --git a/公开课/class021/Code03_EvenTimesOddTimes.java b/公开课/class021/Code03_EvenTimesOddTimes.java new file mode 100644 index 0000000..b93fe1a --- /dev/null +++ b/公开课/class021/Code03_EvenTimesOddTimes.java @@ -0,0 +1,71 @@ +package class021; + +public class Code03_EvenTimesOddTimes { + + // arr中,只有一种数,出现奇数次 + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + // arr中,有两种数,出现奇数次 + public static void printOddTimesNum2(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor必然有一个位置上是1 + // 0110010000 + // 0000010000 + int rightOne = eor & (~eor + 1); // 提取出最右的1 + int onlyOne = 0; // e' + for (int i = 0; i < arr.length; i++) { + // arr[1] = 111100011110000 + // rightOne= 000000000010000 + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + public static int bit1counts(int N) { + int count = 0; + while (N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + public static int add(int a, int b) { + int t = 0; + while (b != 0) { + t = a; + a = a ^ b; + b = ((t & b) << 1); + } + return a; + } + + public static int minus(int a, int b) { + // -b ~b + 1 + return add(a, add(~b, 1)); + } + + public static void main(String[] args) { + int[] arr1 = { 3, 3, 4, 2, 3, 1, 1, 2, 1, 3, 4, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + + } + +} diff --git a/公开课/class022/Code01_MinSwapStep.java b/公开课/class022/Code01_MinSwapStep.java new file mode 100644 index 0000000..9766e2d --- /dev/null +++ b/公开课/class022/Code01_MinSwapStep.java @@ -0,0 +1,53 @@ +package class022; + +public class Code01_MinSwapStep { + + + public static int minSteps1(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int gi = 0; + // 想让G都放在左侧,请问至少需要交换几次,需要算出step1来。 + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { + step1 += i - (gi++); + } + } + int step2 = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'B') { + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + + public static int minSteps(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int step2 = 0; + int gi = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { + step1 += i - (gi++); + } else { + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + public static void main(String[] args) { + String s = "BGGBB"; + System.out.println(minSteps(s)); + } +} \ No newline at end of file diff --git a/公开课/class022/Code02_CountFiles.java b/公开课/class022/Code02_CountFiles.java new file mode 100644 index 0000000..2302728 --- /dev/null +++ b/公开课/class022/Code02_CountFiles.java @@ -0,0 +1,42 @@ +package class022; + +import java.io.File; +import java.util.Stack; + +public class Code02_CountFiles { + + // 注意这个函数也会统计隐藏文件 + public static int getFileNumber(String folderPath) { + // File (文件夹、文件) + File root = new File(folderPath); + if (!root.isDirectory() && !root.isFile()) { + return 0; + } + if (root.isFile()) { + return 1; + } + // File 文件夹 文件 stack只放文件夹 + Stack stack = new Stack<>(); + stack.add(root); + int files = 0; + while (!stack.isEmpty()) { + File folder = stack.pop(); + for (File next : folder.listFiles()) { + if (next.isFile() && next.getName().endsWith("java")) { + files++; + } + if (next.isDirectory()) { + stack.push(next); + } + } + } + return files; + } + + public static void main(String[] args) { + // 你可以自己更改目录 + String path = "/Users/zuochengyun/Desktop/"; + System.out.println(getFileNumber(path)); + } + +} diff --git a/公开课/class022/Code03_Cola.java b/公开课/class022/Code03_Cola.java new file mode 100644 index 0000000..a6eb7c5 --- /dev/null +++ b/公开课/class022/Code03_Cola.java @@ -0,0 +1,146 @@ +package class022; + +public class Code03_Cola { + /* + * 买饮料 时间限制: 3000MS 内存限制: 589824KB 题目描述: + * 游游今年就要毕业了,和同学们在携程上定制了日本毕业旅行。愉快的一天行程结束后大家回到了酒店房间,这时候同学们都很口渴, + * 石头剪刀布选出游游去楼下的自动贩卖机给大家买可乐。 贩卖机只支持硬币支付,且收退都只支持10 ,50,100 + * 三种面额。一次购买行为只能出一瓶可乐,且每次购买后总是找零最小枚数的硬币。(例如投入100圆,可乐30圆,则找零50圆一枚,10圆两枚) + * 游游需要购买的可乐数量是 m,其中手头拥有的 10,50,100 面额硬币的枚数分别是 a,b,c,可乐的价格是x(x是10的倍数)。 + * 如果游游优先使用大面额购买且钱是够的情况下,请计算出需要投入硬币次数? 输入描述 依次输入, 需要可乐的数量为 m 10元的张数为 a 50元的张数为 b + * 100元的张树为 c 1瓶可乐的价格为 x 输出描述 输出当前金额下需要投入硬币的次数 + * 例如需要购买2瓶可乐,每瓶可乐250圆,手里有100圆3枚,50圆4枚,10圆1枚。 购买第1瓶投递100圆3枚,找50圆 购买第2瓶投递50圆5枚 + * 所以是总共需要操作8次金额投递操作 样例输入 2 1 4 3 250 样例输出 8 + */ + + // 暴力尝试,为了验证正式方法而已 + public static int right(int m, int a, int b, int c, int x) { + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + int puts = 0; + while (m != 0) { + int cur = buy(qian, zhang, x); + if (cur == -1) { + return -1; + } + puts += cur; + m--; + } + return puts; + } + + public static int buy(int[] qian, int[] zhang, int rest) { + int first = -1; + for (int i = 0; i < 3; i++) { + if (zhang[i] != 0) { + first = i; + break; + } + } + if (first == -1) { + return -1; + } + if (qian[first] >= rest) { + zhang[first]--; + giveRest(qian, zhang, first + 1, qian[first] - rest, 1); + return 1; + } else { + zhang[first]--; + int next = buy(qian, zhang, rest - qian[first]); + if (next == -1) { + return -1; + } + return 1 + next; + } + } + + // 正式的方法 + public static int putTimes(int m, int a, int b, int c, int x) { + // 0 1 2 + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + // 总共需要多少次投币 + int puts = 0; + // 之前面值的钱还剩下多少总钱数 + int preQianRest = 0; + // 之前面值的钱还剩下多少总张数 + int preQianZhang = 0; + for (int i = 0; i < 3 && m != 0; i++) { + // 要用之前剩下的钱、当前面值的钱,共同买第一瓶可乐 + // 之前的面值剩下多少钱,是preQianRest + // 之前的面值剩下多少张,是preQianZhang + // 之所以之前的面值会剩下来,一定是剩下的钱,一直攒不出一瓶可乐的单价 + // 当前的面值付出一些钱+之前剩下的钱,此时有可能凑出一瓶可乐来 + // 那么当前面值参与搞定第一瓶可乐,需要掏出多少张呢?就是curQianFirstBuyZhang + int curQianFirstBuyZhang = (x - preQianRest + qian[i] - 1) / qian[i]; + if (zhang[i] >= curQianFirstBuyZhang) { // 如果之前的钱和当前面值的钱,能凑出第一瓶可乐 + // 凑出来了一瓶可乐也可能存在找钱的情况, + giveRest(qian, zhang, i + 1, (preQianRest + qian[i] * curQianFirstBuyZhang) - x, 1); + puts += curQianFirstBuyZhang + preQianZhang; + zhang[i] -= curQianFirstBuyZhang; + m--; + } else { // 如果之前的钱和当前面值的钱,不能凑出第一瓶可乐 + preQianRest += qian[i] * zhang[i]; + preQianZhang += zhang[i]; + continue; + } + // 凑出第一瓶可乐之后,当前的面值有可能能继续买更多的可乐 + // 以下过程就是后续的可乐怎么用当前面值的钱来买 + // 用当前面值的钱,买一瓶可乐需要几张 + int curQianBuyOneColaZhang = (x + qian[i] - 1) / qian[i]; + // 用当前面值的钱,一共可以搞定几瓶可乐 + int curQianBuyColas = Math.min(zhang[i] / curQianBuyOneColaZhang, m); + // 用当前面值的钱,每搞定一瓶可乐,收货机会吐出多少零钱 + int oneTimeRest = qian[i] * curQianBuyOneColaZhang - x; + // 每次买一瓶可乐,吐出的找零总钱数是oneTimeRest + // 一共买的可乐数是curQianBuyColas,所以把零钱去提升后面几种面值的硬币数, + // 就是giveRest的含义 + giveRest(qian, zhang, i + 1, oneTimeRest, curQianBuyColas); + // 当前面值去搞定可乐这件事,一共投了几次币 + puts += curQianBuyOneColaZhang * curQianBuyColas; + // 还剩下多少瓶可乐需要去搞定,继续用后面的面值搞定去吧 + m -= curQianBuyColas; + // 当前面值可能剩下若干张,要参与到后续买可乐的过程中去, + // 所以要更新preQianRest和preQianZhang + zhang[i] -= curQianBuyOneColaZhang * curQianBuyColas; + preQianRest = qian[i] * zhang[i]; + preQianZhang = zhang[i]; + } + return m == 0 ? puts : -1; + } + + public static void giveRest(int[] qian, int[] zhang, int i, int oneTimeRest, int times) { + for (; i < 3; i++) { + zhang[i] += (oneTimeRest / qian[i]) * times; + oneTimeRest %= qian[i]; + } + } + + public static void main(String[] args) { + int testTime = 1000; + int zhangMax = 10; + int colaMax = 10; + int priceMax = 20; + System.out.println("如果错误会打印错误数据,否则就是正确"); + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * colaMax); + int a = (int) (Math.random() * zhangMax); + int b = (int) (Math.random() * zhangMax); + int c = (int) (Math.random() * zhangMax); + int x = ((int) (Math.random() * priceMax) + 1) * 10; + int ans1 = putTimes(m, a, b, c, x); + int ans2 = right(m, a, b, c, x); + if (ans1 != ans2) { + System.out.println("int m = " + m + ";"); + System.out.println("int a = " + a + ";"); + System.out.println("int b = " + b + ";"); + System.out.println("int c = " + c + ";"); + System.out.println("int x = " + x + ";"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class023/Code01_FindHalfMajority.java b/公开课/class023/Code01_FindHalfMajority.java new file mode 100644 index 0000000..a50dd2a --- /dev/null +++ b/公开课/class023/Code01_FindHalfMajority.java @@ -0,0 +1,78 @@ +package class023; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code01_FindHalfMajority { + + public static int halfMajor(int[] arr) { + int cand = 0; + int HP = 0; + // 遍历一遍数组arr,一次删掉两个不同的数,谁会剩下来,谁就是cand + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { // 无候选 + cand = arr[i]; + HP = 1; + } else if (arr[i] == cand) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + System.out.println("你他妈在逗我,没水王"); + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == cand) { + HP++; + } + } + return HP > arr.length / 2 ? cand : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class024/Code01_RotateImage.java b/公开课/class024/Code01_RotateImage.java new file mode 100644 index 0000000..bdba6de --- /dev/null +++ b/公开课/class024/Code01_RotateImage.java @@ -0,0 +1,61 @@ +package class024; + +import java.util.HashMap; + +public class Code01_RotateImage { + + public static void rotate(int[][] matrix) { + // matrix.length == matrix[0].length + // (a,b) 左上角的点,在a行b列上 + int a = 0; + int b = 0; + // (a,b) 右下角的点,在c行d列上 + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { // 一定是正方形, + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + + + + public static HashMap map = new HashMap<>(); + + public static void generateMap() { + for(int i = 0 ;i < 32;i++) { + map.put(1 << i , i); + } + } + + // 返回num最右侧的1,在第几位 + // 高 ...... 低 + public static int f(int num) { + // num = 24 + // num = 00000..0000011000 + // 00000..0000001000 + if(map.size() == 0) { + generateMap(); + } + int rightOne = num & (-num); // num & (~num + 1) + return map.get(rightOne); + } + + + public static void main(String[] args) { + + } + + + +} diff --git a/公开课/class024/Code02_ZigZagPrintMatrix.java b/公开课/class024/Code02_ZigZagPrintMatrix.java new file mode 100644 index 0000000..f69863c --- /dev/null +++ b/公开课/class024/Code02_ZigZagPrintMatrix.java @@ -0,0 +1,52 @@ +package class024; + +public class Code02_ZigZagPrintMatrix { + + public static void printMatrixZigZag(int[][] matrix) { + // x -> (a,b) 先往右,再往下 + int a = 0; + int b = 0; + + // y -> (c,d) 先往下,再往右 + int c = 0; + int d = 0; + // (endR, endC) 是最右下角的位置 + int endR = matrix.length - 1; + int endC = matrix[0].length - 1; + // fromUp = true 斜线打印方向应该从右上走到左下 + // fromUp = false 斜线打印方向应该从左下走到右上 + boolean fromUp = false; + while (a != endR + 1) { + // (a,b) (c,d) 方向 + printLevel(matrix, a, b, c, d, fromUp); + a = b == endC ? a + 1 : a; + b = b == endC ? b : b + 1; + d = c == endR ? d + 1 : d; + c = c == endR ? c : c + 1; + fromUp = !fromUp; + } + System.out.println(); + } + + public static void printLevel(int[][] m, int aRow, int aCol, int bRow, int bCol, boolean f) { + if (f) { + while (aRow != bRow + 1) { + System.out.print(m[aRow++][aCol--] + " "); + } + } else { + while (bRow != aRow - 1) { + System.out.print(m[bRow--][bCol++] + " "); + } + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } }; + printMatrixZigZag(matrix); + + } + +} diff --git a/公开课/class024/Code03_PrintStar.java b/公开课/class024/Code03_PrintStar.java new file mode 100644 index 0000000..c451924 --- /dev/null +++ b/公开课/class024/Code03_PrintStar.java @@ -0,0 +1,46 @@ +package class024; + +public class Code03_PrintStar { + + public static void printStar(int N) { + int leftUp = 0; + int rightDown = N - 1; + char[][] m = new char[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + m[i][j] = ' '; + } + } + while (leftUp <= rightDown) { + set(m, leftUp, rightDown); + leftUp += 2; + rightDown -= 2; + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void set(char[][] m, int leftUp, int rightDown) { + for (int col = leftUp; col <= rightDown; col++) { + m[leftUp][col] = '*'; + } + for (int row = leftUp + 1; row <= rightDown; row++) { + m[row][rightDown] = '*'; + } + for (int col = rightDown - 1; col > leftUp; col--) { + m[rightDown][col] = '*'; + } + for (int row = rightDown - 1; row > leftUp + 1; row--) { + m[row][leftUp + 1] = '*'; + } + } + + public static void main(String[] args) { + printStar(8); + } + +} diff --git a/公开课/class025/Code01_Water.java b/公开课/class025/Code01_Water.java new file mode 100644 index 0000000..da68816 --- /dev/null +++ b/公开课/class025/Code01_Water.java @@ -0,0 +1,120 @@ +package class025; + +public class Code01_Water { + + /* + * 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + * */ + + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class025/Code02_RandToRand.java b/公开课/class025/Code02_RandToRand.java new file mode 100644 index 0000000..84a352c --- /dev/null +++ b/公开课/class025/Code02_RandToRand.java @@ -0,0 +1,134 @@ +package class025; + +public class Code02_RandToRand { + + // 此函数只能用,不能修改 + // 等概率返回1~5 + public static int f() { + return (int) (Math.random() * 5) + 1; + } + + // 等概率得到0和1 + public static int a() { + int ans = 0; + do { + ans = f(); + } while (ans == 3); + return ans < 3 ? 0 : 1; + } + + // 等概率返回0~6 + public static int b() { + int ans = 0; + do { + ans = (a() << 2) + (a() << 1) + a(); + } while (ans == 7); + return ans; + } + + // 等概率返回1~7 + public static int c() { + return b() + 1; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + // 13 ~ 17 + // 13 + [0,4] + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + + int[] count = new int[8]; // 0 1 2 .. 7 + int testTime = 10000000; + for (int i = 0; i < testTime; i++) { + int ans = c(); + count[ans]++; + } + + for (int i = 0; i < 8; i++) { + System.out.println(i + " : " + count[i]); + } + +// RandomBox randomBox = new RandomBox(3, 9); +// int from = 17; +// int to = 29; +// int[] ans = new int[to + 1]; +// int testTime1 = 1000000; +// for (int i = 0; i < testTime1; i++) { +// ans[random(randomBox, from, to)]++; +// } +// for (int i = 0; i < ans.length; i++) { +// System.out.println(ans[i]); +// } +// System.out.println("=========="); + + } + +} diff --git a/公开课/class025/Code03_EqualProbabilityRandom.java b/公开课/class025/Code03_EqualProbabilityRandom.java new file mode 100644 index 0000000..9d2d8da --- /dev/null +++ b/公开课/class025/Code03_EqualProbabilityRandom.java @@ -0,0 +1,67 @@ +package class025; + +public class Code03_EqualProbabilityRandom { + + // 内部内容不可见 + public static int f() { + return Math.random() < 0.8 ? 0 : 1; + } + + // 等概率返回0和1 + public static int g() { + int first = 0; + do { + first = f(); // 0 1 + } while (first == f()); + return first; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + int[] count = new int[2];// 0 1 + for (int i = 0; i < 1000000; i++) { + int ans = g(); + count[ans]++; + } + System.out.println(count[0] + " , " + count[1]); + +// double zeroP = 0.88; +// RandomBox randomBox = new RandomBox(zeroP); +// +// int testTime = 10000000; +// int count = 0; +// for (int i = 0; i < testTime; i++) { +// if (rand01(randomBox) == 0) { +// count++; +// } +// } +// System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class026/Code01_SubArrayMaxSum.java b/公开课/class026/Code01_SubArrayMaxSum.java new file mode 100644 index 0000000..7861912 --- /dev/null +++ b/公开课/class026/Code01_SubArrayMaxSum.java @@ -0,0 +1,161 @@ +package class026; + +import java.util.ArrayList; +import java.util.List; + +public class Code01_SubArrayMaxSum { + + public static int test(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + // arr[L...R] + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + max = Math.max(max, sum); + } + } + return max; + } + + public static int dp1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] dp = new int[arr.length]; + // dp[i] 子数组必须以i位置结尾的情况下,能得到的最大累加和 + dp[0] = arr[0]; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + } + int max = Integer.MIN_VALUE; + for (int i = 0; i < dp.length; i++) { + max = Math.max(max, dp[i]); + } + return max; + } + + public static int dp2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int preDp = arr[0]; + int max = preDp; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = preDp + arr[i]; + int dp = Math.max(p1, p2); + max = Math.max(max, dp); + preDp = dp; + } + return max; + } + + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int cur = 0; + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return max; + } + + public static int maxSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // 0结尾时候的答案 + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + // i结尾时候的答案 + pre = arr[i] + (pre > 0 ? pre : 0); + max = Math.max(max, pre); + } + return max; + } + + public static List> maxSum2(int[] arr) { + List> ans = new ArrayList<>(); + if (arr == null || arr.length == 0) { + return ans; + } + int L = 0; + int maxLen = 0; + int maxSum = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + // L...i sum + cur += arr[i]; + if (cur == maxSum && (i - L + 1) == maxLen) { + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + } + if (cur > maxSum || (cur == maxSum && (i - L + 1) > maxLen)) { + ans.clear(); + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + maxLen = i - L + 1; + } + maxSum = Math.max(maxSum, cur); + if (cur < 0) { + cur = 0; + L = i + 1; + } + } + return ans; + } + + public static int[] generateArray(int N, int V) { + int n = (int) (Math.random() * N) + 1; + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + return arr; + } + + public static void main(String[] args) { + int N = 100; + int V = 100; + int testTime = 10000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateArray(N, V); + int ans1 = test(arr); + int ans2 = dp1(arr); + int ans3 = dp2(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + int[] test = { 2, 2, 1, -9, 2, 3, -9, 6, -9, 2, 2, 2, -9, 2, 2, 2, -9, 1, 4, 1 }; + + List> ans = maxSum2(test); + + for (List cur : ans) { + System.out.println("start : " + cur.get(0) + ", end : " + cur.get(1)); + } + + } + +} diff --git a/公开课/class026/Code02_SubArrayMaxSumFollowUp.java b/公开课/class026/Code02_SubArrayMaxSumFollowUp.java new file mode 100644 index 0000000..7e806b5 --- /dev/null +++ b/公开课/class026/Code02_SubArrayMaxSumFollowUp.java @@ -0,0 +1,58 @@ +package class026; + +public class Code02_SubArrayMaxSumFollowUp { + + public static int subSqeMaxSumNoNext(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int[] dp = new int[arr.length]; + // dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < arr.length; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i] + Math.max(dp[i - 2], 0); + dp[i] = Math.max(p1, p2); + } + return dp[arr.length - 1]; + } + + // 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 思路: + // 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类: + // 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1] + // 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候 + // + // 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i] + // 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候 + // + // 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2] + // 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过 + // + // 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] } + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (N == 1) { + return arr[0]; + } + if (N == 2) { + return Math.max(arr[0], arr[1]); + } + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]); + } + return dp[N - 1]; + } + +} diff --git a/公开课/class026/Code03_SubMatrixMaxSum.java b/公开课/class026/Code03_SubMatrixMaxSum.java new file mode 100644 index 0000000..fafa1fe --- /dev/null +++ b/公开课/class026/Code03_SubMatrixMaxSum.java @@ -0,0 +1,83 @@ +package class026; + +public class Code03_SubMatrixMaxSum { + + public static int maxSum1(int[][] matrix) { + int n = matrix.length; + int m = matrix[0].length; + int max = Integer.MIN_VALUE; + for (int ai = 0; ai < n; ai++) { + for (int aj = 0; aj < m; aj++) { + for (int bi = ai; bi < n; bi++) { + for (int bj = aj; bj < m; bj++) { + max = Math.max(max, sum(matrix, ai, aj, bi, bj)); + } + } + } + } + return max; + } + + public static int sum(int[][] matrix, int ai, int aj, int bi, int bj) { + int sum = 0; + for (int i = ai; i <= bi; i++) { + for (int j = aj; j <= bj; j++) { + sum += matrix[i][j]; + } + } + return sum; + } + + public static int maxSum2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + int[] s = null; + for (int i = 0; i != matrix.length; i++) { + s = new int[matrix[0].length]; + for (int j = i; j != matrix.length; j++) { + cur = 0; + for (int k = 0; k != s.length; k++) { + s[k] += matrix[j][k]; + cur += s[k]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + } + } + return max; + } + + public static int[][] generateMatrix(int N, int M, int V) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + } + return matrix; + } + + public static void main(String[] args) { + int N = 20; + int M = 20; + int V = 100; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[][] matrix = generateMatrix(N, M, V); + int ans1 = maxSum1(matrix); + int ans2 = maxSum2(matrix); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class027/Code01_MinSwapStep.java b/公开课/class027/Code01_MinSwapStep.java new file mode 100644 index 0000000..be9ee0a --- /dev/null +++ b/公开课/class027/Code01_MinSwapStep.java @@ -0,0 +1,71 @@ +package class027; + +public class Code01_MinSwapStep { + + // 一个数组中只有两种字符'G'和'B',想让所有的G都放在左侧,所有的B都放在右侧 + // 但是只能在相邻字符之间进行交换操作,请问请问至少需要交换几次, + public static int minSteps1(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int gi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { + step1 += i - (gi++); + } + } + int step2 = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'B') { + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + public static int minSteps2(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int step2 = 0; + int gi = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { + step1 += i - (gi++); + } else { + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + // 为了测试 + public static String randomString(int maxLen) { + char[] str = new char[(int) (Math.random() * maxLen)]; + for (int i = 0; i < str.length; i++) { + str[i] = Math.random() < 0.5 ? 'G' : 'B'; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int maxLen = 100; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = randomString(maxLen); + int ans1 = minSteps1(str); + int ans2 = minSteps2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } +} \ No newline at end of file diff --git a/公开课/class027/Code02_CountFiles.java b/公开课/class027/Code02_CountFiles.java new file mode 100644 index 0000000..e318671 --- /dev/null +++ b/公开课/class027/Code02_CountFiles.java @@ -0,0 +1,42 @@ +package class027; + +import java.io.File; +import java.util.Stack; + +public class Code02_CountFiles { + + // 注意这个函数也会统计隐藏文件 + public static int getFileNumber(String folderPath) { + File root = new File(folderPath); + if (!root.isDirectory() && !root.isFile()) { + return 0; + } + if (root.isFile()) { + return 1; + } + // File分为文件夹和文件 + // 而stack只放文件夹 + Stack stack = new Stack<>(); + stack.add(root); + int files = 0; + while (!stack.isEmpty()) { + File folder = stack.pop(); + for (File next : folder.listFiles()) { + if (next.isFile()) { + files++; + } + if (next.isDirectory()) { + stack.push(next); + } + } + } + return files; + } + + public static void main(String[] args) { + // 你可以自己更改目录来查看这个目录下的文件个数 + String path = "/Users/zuochengyun/Desktop"; + System.out.println(getFileNumber(path)); + } + +} diff --git a/公开课/class027/Code03_Cola.java b/公开课/class027/Code03_Cola.java new file mode 100644 index 0000000..c53414a --- /dev/null +++ b/公开课/class027/Code03_Cola.java @@ -0,0 +1,148 @@ +package class027; + +public class Code03_Cola { + /* + * 买饮料 时间限制: 3000MS 内存限制: 589824KB 题目描述: + * 游游今年就要毕业了,和同学们在携程上定制了日本毕业旅行。愉快的一天行程结束后大家回到了酒店房间,这时候同学们都很口渴, + * 石头剪刀布选出游游去楼下的自动贩卖机给大家买可乐。 贩卖机只支持硬币支付,且收退都只支持10 ,50,100 + * 三种面额。一次购买行为只能出一瓶可乐,且每次购买后总是找零最小枚数的硬币。(例如投入100圆,可乐30圆,则找零50圆一枚,10圆两枚) + * 游游需要购买的可乐数量是 m,其中手头拥有的 10,50,100 面额硬币的枚数分别是 a,b,c,可乐的价格是x(x是10的倍数)。 + * 如果游游优先使用大面额购买且钱是够的情况下,请计算出需要投入硬币次数? 输入描述 依次输入, 需要可乐的数量为 m 10元的张数为 a 50元的张数为 b + * 100元的张树为 c 1瓶可乐的价格为 x 输出描述 输出当前金额下需要投入硬币的次数 + * 例如需要购买2瓶可乐,每瓶可乐250圆,手里有100圆3枚,50圆4枚,10圆1枚。 购买第1瓶投递100圆3枚,找50圆 购买第2瓶投递50圆5枚 + * 所以是总共需要操作8次金额投递操作 样例输入 2 1 4 3 250 样例输出 8 + */ + + // 暴力尝试,为了验证正式方法而已 + public static int right(int m, int a, int b, int c, int x) { + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + int puts = 0; + while (m != 0) { + int cur = buy(qian, zhang, x); + if (cur == -1) { + return -1; + } + puts += cur; + m--; + } + return puts; + } + + public static int buy(int[] qian, int[] zhang, int rest) { + int first = -1; + for (int i = 0; i < 3; i++) { + if (zhang[i] != 0) { + first = i; + break; + } + } + if (first == -1) { + return -1; + } + if (qian[first] >= rest) { + zhang[first]--; + giveRest(qian, zhang, first + 1, qian[first] - rest, 1); + return 1; + } else { + zhang[first]--; + int next = buy(qian, zhang, rest - qian[first]); + if (next == -1) { + return -1; + } + return 1 + next; + } + } + + // 正式的方法 + public static int putTimes(int m, int a, int b, int c, int x) { + // 0 1 2 + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + // 总共需要多少次投币 + int puts = 0; + // 之前面值的钱还剩下多少总钱数 + int preQianRest = 0; + // 之前面值的钱还剩下多少总张数 + int preQianZhang = 0; + for (int i = 0; i < qian.length && m != 0; i++) { + // 要用之前剩下的钱、当前面值的钱,共同买第一瓶可乐 + // 之前的面值剩下多少钱,是preQianRest + // 之前的面值剩下多少张,是preQianZhang + // 之所以之前的面值会剩下来,一定是剩下的钱,一直攒不出一瓶可乐的单价 + // 当前的面值付出一些钱+之前剩下的钱,此时有可能凑出一瓶可乐来 + // 那么当前面值参与搞定第一瓶可乐,需要掏出多少张呢?就是curQianFirstBuyZhang + int curQianFirstBuyZhang = (x - preQianRest + qian[i] - 1) / qian[i]; + if (zhang[i] >= curQianFirstBuyZhang) { // 如果之前的钱和当前面值的钱,能凑出第一瓶可乐 + // 凑出来了一瓶可乐也可能存在找钱的情况, + giveRest(qian, zhang, i + 1, (preQianRest + qian[i] * curQianFirstBuyZhang) - x, 1); + puts += curQianFirstBuyZhang + preQianZhang; + zhang[i] -= curQianFirstBuyZhang; + m--; + } else { // 如果之前的钱和当前面值的钱,不能凑出第一瓶可乐 + preQianRest += qian[i] * zhang[i]; + preQianZhang += zhang[i]; + continue; + } + // 凑出第一瓶可乐之后,当前的面值有可能能继续买更多的可乐 + // 以下过程就是后续的可乐怎么用当前面值的钱来买 + // 用当前面值的钱,买一瓶可乐需要几张 + int curQianBuyOneColaZhang = (x + qian[i] - 1) / qian[i]; + // 用当前面值的钱,一共可以搞定几瓶可乐 + int curQianBuyColas = Math.min(zhang[i] / curQianBuyOneColaZhang, m); + // 用当前面值的钱,每搞定一瓶可乐,收货机会吐出多少零钱 + int oneTimeRest = qian[i] * curQianBuyOneColaZhang - x; + // 每次买一瓶可乐,吐出的找零总钱数是oneTimeRest + // 一共买的可乐数是curQianBuyColas,所以把零钱去提升后面几种面值的硬币数, + // 就是giveRest的含义 + giveRest(qian, zhang, i + 1, oneTimeRest, curQianBuyColas); + // 当前面值去搞定可乐这件事,一共投了几次币 + puts += curQianBuyOneColaZhang * curQianBuyColas; + // 还剩下多少瓶可乐需要去搞定,继续用后面的面值搞定去吧 + m -= curQianBuyColas; + // 当前面值可能剩下若干张,要参与到后续买可乐的过程中去, + // 所以要更新preQianRest和preQianZhang + zhang[i] -= curQianBuyOneColaZhang * curQianBuyColas; + preQianRest = qian[i] * zhang[i]; + preQianZhang = zhang[i]; + } + return m == 0 ? puts : -1; + } + + // qian [100, 50, 10] + // zhang [ 2, 10, 5] + public static void giveRest(int[] qian, int[] zhang, int i, int oneTimeRest, int times) { + for (; i < 3; i++) { + zhang[i] += (oneTimeRest / qian[i]) * times; + oneTimeRest %= qian[i]; + } + } + + public static void main(String[] args) { + int testTime = 1000; + int zhangMax = 10; + int colaMax = 10; + int priceMax = 20; + System.out.println("如果错误会打印错误数据,否则就是正确"); + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * colaMax); + int a = (int) (Math.random() * zhangMax); + int b = (int) (Math.random() * zhangMax); + int c = (int) (Math.random() * zhangMax); + int x = ((int) (Math.random() * priceMax) + 1) * 10; + int ans1 = putTimes(m, a, b, c, x); + int ans2 = right(m, a, b, c, x); + if (ans1 != ans2) { + System.out.println("int m = " + m + ";"); + System.out.println("int a = " + a + ";"); + System.out.println("int b = " + b + ";"); + System.out.println("int c = " + c + ";"); + System.out.println("int x = " + x + ";"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class028/Code01_RotateString.java b/公开课/class028/Code01_RotateString.java new file mode 100644 index 0000000..e717068 --- /dev/null +++ b/公开课/class028/Code01_RotateString.java @@ -0,0 +1,93 @@ +package class028; + +public class Code01_RotateString { + + public static String rotate1(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + return process1(s.toCharArray(), 0, leftSize - 1, s.length() - 1); + } + + public static String process1(char[] str, int L, int M, int R) { + reverse(str, L, M); + reverse(str, M + 1, R); + reverse(str, L, R); + return String.valueOf(str); + } + + public static void reverse(char[] str, int L, int R) { + while (L < R) { + char tmp = str[L]; + str[L++] = str[R]; + str[R--] = tmp; + } + } + + public static String rotate2(String s, int leftSize) { + if (leftSize <= 0 || leftSize >= s.length()) { + return s; + } + char[] str = s.toCharArray(); + int L = 0; + int R = str.length - 1; + int lpart = leftSize; + int rpart = str.length - leftSize; + int same = Math.min(lpart, rpart); + int diff = lpart - rpart; + exchange(str, L, R, same); + while (diff != 0) { + if (diff > 0) { + L += same; + lpart = diff; + } else { + R -= same; + rpart = -diff; + } + same = Math.min(lpart, rpart); + diff = lpart - rpart; + exchange(str, L, R, same); + } + return String.valueOf(str); + } + + public static void exchange(char[] chas, int start, int end, int size) { + int i = end - size + 1; + char tmp = 0; + while (size-- != 0) { + tmp = chas[start]; + chas[start] = chas[i]; + chas[i] = tmp; + start++; + i++; + } + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 10; + int testTimes = 5000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int leftSize = (int) (Math.random() * (str.length() + 1)); + String ans1 = rotate1(str, leftSize); + String ans2 = rotate2(str, leftSize); + if (!ans1.equals(ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class028/Code02_HowManyTypes.java b/公开课/class028/Code02_HowManyTypes.java new file mode 100644 index 0000000..e6d6802 --- /dev/null +++ b/公开课/class028/Code02_HowManyTypes.java @@ -0,0 +1,111 @@ +package class028; + +import java.util.HashSet; + +public class Code02_HowManyTypes { + + /* + * 只由小写字母(a~z)组成的一批字符串,都放在字符类型的数组String[] arr中, + * 如果其中某两个字符串,所含有的字符种类完全一样,就将两个字符串算作一类 比如:baacba和bac就算作一类 + * 虽然长度不一样,但是所含字符的种类完全一样(a、b、c) 返回arr中有多少类? + * + */ + + public static int types1(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + boolean[] map = new boolean[26]; + for (int i = 0; i < chs.length; i++) { + map[chs[i] - 'a'] = true; + } + String key = ""; + for (int i = 0; i < 26; i++) { + if (map[i]) { + key += String.valueOf((char) (i + 'a')); + } + } + types.add(key); + } + return types.size(); + } + + public static int types2(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + int key = 0; + for (int i = 0; i < chs.length; i++) { + key = key | (1 << (chs[i] - 'a')); + } + types.add(key); + } + return types.size(); + } + + // for test + public static String[] getRandomStringArray(int possibilities, int strMaxSize, int arrMaxSize) { + String[] ans = new String[(int) (Math.random() * arrMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = getRandomString(possibilities, strMaxSize); + } + return ans; + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void printIntegerBinary(int num) { + StringBuilder builder = new StringBuilder(); + for (int i = 31; i >= 0; i--) { + // 依次提取出num从0位~31位的状态来 + int status = ((num >> i) & 1); + builder.append(status); + + } + System.out.println(builder.toString()); + } + + public static void main(String[] args) { + int num = 3; + printIntegerBinary(num); + + char[] str = { 'b', 'b', 'z', 'k', 'o' }; + + int key = 0; + // 如何生成str的摘要? + // 00000000000000000000000000000000 + + for (int i = 0; i < str.length; i++) { + char cha = str[i]; + // cha = a + // cha - 'a' ? 0 + // 1 << (cha - 'a') -> 1 << 0 + key = key | (1 << (cha - 'a')); + } + printIntegerBinary(key); + + int possibilities = 5; + int strMaxSize = 10; + int arrMaxSize = 100; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String[] arr = getRandomStringArray(possibilities, strMaxSize, arrMaxSize); + int ans1 = types1(arr); + int ans2 = types2(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class028/Code03_LongestNoRepeatSubstring.java b/公开课/class028/Code03_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..1730279 --- /dev/null +++ b/公开课/class028/Code03_LongestNoRepeatSubstring.java @@ -0,0 +1,83 @@ +package class028; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code03_LongestNoRepeatSubstring { + + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int maxNoRepeatSubstringLen1(char[] str) { + int len = 0; + for (int L = 0; L < str.length; L++) { + for (int R = L; R < str.length; R++) { + HashSet set = new HashSet<>(); + boolean noRepeat = true; + for (int i = L; i <= R; i++) { + if (set.contains(str[i])) { + noRepeat = false; + break; + } + set.add(str[i]); + } + if (noRepeat) { + int cur = R - L + 1; + len = Math.max(len, cur); + } + } + } + return len; + } + + public static int maxNoRepeatSubstringLen2(char[] str) { + if (str == null || str.length == 0) { + return 0; + } + int N = str.length; + int[] dp = new int[N]; + HashMap lastMap = new HashMap<>(); + dp[0] = 1; + lastMap.put(str[0], 0); + int max = 1; + for (int i = 1; i < N; i++) { + int lastIndex = lastMap.containsKey(str[i]) ? lastMap.get(str[i]) : -1; + int preNo = i - 1 - dp[i - 1]; + int no = Math.max(lastIndex, preNo); + int curAns = i - no; + dp[i] = curAns; + lastMap.put(str[i], i); + max = Math.max(max, dp[i]); + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 10000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = maxNoRepeatSubstringLen1(str.toCharArray()); + int ans2 = maxNoRepeatSubstringLen2(str.toCharArray()); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class028/Code04_CoverMax.java b/公开课/class028/Code04_CoverMax.java new file mode 100644 index 0000000..5ccbc2f --- /dev/null +++ b/公开课/class028/Code04_CoverMax.java @@ -0,0 +1,133 @@ +package class028; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code04_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // lines + // + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class029/Code01_AppleMinBags.java b/公开课/class029/Code01_AppleMinBags.java new file mode 100644 index 0000000..ffbc657 --- /dev/null +++ b/公开课/class029/Code01_AppleMinBags.java @@ -0,0 +1,77 @@ +package class029; + +public class Code01_AppleMinBags { + + public static int minBag1(int apple) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + // 最多的8号袋开始试, + for (int max = (apple / 8); max >= 0; max--) { + int rest = apple - (max * 8); + if (rest % 6 == 0) { + return max + rest / 6; + } + } + return -1; + } + + // 18 ~ + // 18 ~ 25 0组 奇数 -1 偶数 3 + // 26 ~ 33 1组 奇数 -1 偶数 4 + // 34 ~ 41 2组 奇数 -1 偶数 5 + // X + // i组 奇数 -1 偶数 i+3 + + // 规律解 通过 + public static int minBag2(int apple) { + if (apple < 18) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple == 6 || apple == 8) { + return 1; + } + if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } + return -1; + } + int team = (apple - 18) / 8; + return (apple & 1) == 0 ? (team + 3) : -1; + } + + public static int minBagAwesome(int apple) { + if (apple < 0 || (apple & 1) != 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple < 18) { + if (apple == 6 || apple == 8) { + return 1; + } else if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } else { + return -1; + } + } else { + return (apple - 18) / 8 + 3; + } + } + + public static void main(String[] args) { + for (int apple = 0; apple <= 100; apple++) { + System.out.println(minBag1(apple) == minBag2(apple)); + } + + } + +} diff --git a/公开课/class029/Code02_EatGrass.java b/公开课/class029/Code02_EatGrass.java new file mode 100644 index 0000000..a3e3704 --- /dev/null +++ b/公开课/class029/Code02_EatGrass.java @@ -0,0 +1,40 @@ +package class029; + +public class Code02_EatGrass { + + // 当前有N份草,当前轮到先手先吃,然后后手再吃 + // 1,4,16,64,... + // 返回 String "先手" "后手" + public static String winner1(int n) { + if (n < 5) { + return (n == 0 || n == 2) ? "后手" : "先手"; + } + // 先手吃的草数 + int eat = 1; + while (eat <= n) { + if (winner1(n - eat).equals("后手")) { + return "先手"; + } + if (eat > n / 4) { // 防止溢出 + break; + } + eat *= 4; + } + return "后手"; + } + + public static String winner2(int n) { + if (n % 5 == 0 || n % 5 == 2) { + return "后手"; + } else { + return "先手"; + } + } + + public static void main(String[] args) { + for (int i = 0; i <= 50; i++) { + System.out.println(i + " : " + winner1(i)); + } + } + +} diff --git a/公开课/class029/Code03_MaxLeftMaxRight.java b/公开课/class029/Code03_MaxLeftMaxRight.java new file mode 100644 index 0000000..0b1a82a --- /dev/null +++ b/公开课/class029/Code03_MaxLeftMaxRight.java @@ -0,0 +1,80 @@ +package class029; + +public class Code03_MaxLeftMaxRight { + + // 笨办法,但是好想 + public static int solution1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int ans = Integer.MIN_VALUE; + for (int leftEnd = 0; leftEnd < N - 1; leftEnd++) { + int leftMax = arr[0]; + for (int i = 1; i <= leftEnd; i++) { + leftMax = Math.max(leftMax, arr[i]); + } + int rightMax = arr[leftEnd + 1]; + for (int i = leftEnd + 2; i < N; i++) { + rightMax = Math.max(rightMax, arr[i]); + } + ans = Math.max(ans, Math.abs(leftMax - rightMax)); + } + return ans; + } + + // 好办法,是我们真正想测试的 + public static int solution2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int max = arr[0]; + for (int i = 1; i < N; i++) { + max = Math.max(max, arr[i]); + } + return max - Math.min(arr[0], arr[N - 1]); + } + + public static int[] randomArray(int maxLen, int maxValue) { + int len = (int) (Math.random() * (maxLen + 1)); + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + public static void main(String[] args) { + // 随机数组的最大长度 + int maxLen = 6; + // 随机数组值的范围 + int maxValue = 30; + // 测试次数 + int testTime = 3000000; + System.out.println("如果没有错误信息打印,说明所有测试通过"); + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + // 随机得到一个数组,长度也随机,每个值也随机 + int[] arr = randomArray(maxLen, maxValue); + // 用方法1跑出答案1 + int ans1 = solution1(arr); + // 用方法2跑出答案2 + int ans2 = solution2(arr); + // 如果答案1和答案2不一样,提示出错了 + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1 + " , " + ans2); + + for (int k = 0; k < arr.length; k++) { + System.out.print(arr[k] + " "); + } + System.out.println(); + + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class030/Code01_IsPalindrome.java b/公开课/class030/Code01_IsPalindrome.java new file mode 100644 index 0000000..d41667c --- /dev/null +++ b/公开课/class030/Code01_IsPalindrome.java @@ -0,0 +1,25 @@ +package class030; + +// 测试链接:https://leetcode.com/problems/palindrome-number +public class Code01_IsPalindrome { + + // n<0 不是回文数 + public static boolean isPalindrome(int n) { + if (n < 0) { + return false; + } + int help = 1; + while (n / help >= 10) { + help *= 10; + } + while (n != 0) { + if (n / help != n % 10) { + return false; + } + n = (n % help) / 10; + help /= 100; + } + return true; + } + +} diff --git a/公开课/class030/Code02_MySqrt.java b/公开课/class030/Code02_MySqrt.java new file mode 100644 index 0000000..ca6275c --- /dev/null +++ b/公开课/class030/Code02_MySqrt.java @@ -0,0 +1,30 @@ +package class030; + +// 测试链接:https://leetcode.com/problems/sqrtx +public class Code02_MySqrt { + + public static int mySqrt(int x) { + if (x == 0) { + return 0; + } + if (x < 3) { + return 1; + } + long ans = 1; + long L = 1; + long R = x; + long M = 0; + // L...R 1....x + while (L <= R) { + M = (L + R) / 2; + if (M * M <= x) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return (int) ans; + } + +} diff --git a/公开课/class030/Code03_UglyNumber.java b/公开课/class030/Code03_UglyNumber.java new file mode 100644 index 0000000..a307344 --- /dev/null +++ b/公开课/class030/Code03_UglyNumber.java @@ -0,0 +1,51 @@ +package class030; + +// 测试链接:https://leetcode.com/problems/ugly-number-ii +public class Code03_UglyNumber { + + public static boolean isUgly(int num) { + while (num % 2 == 0) { + num /= 2; + } + while (num % 3 == 0) { + num /= 3; + } + while (num % 5 == 0) { + num /= 5; + } + return num == 1; + } + + public static int nthUglyNumber1(int n) { + int find = 0; + int num = 1; + for (; find < n; num++) { + if (isUgly(num)) { + find++; + } + } + return num - 1; + } + + public static int nthUglyNumber2(int n) { + int[] ugly = new int[n + 1]; + ugly[1] = 1; + int i2 = 1; + int i3 = 1; + int i5 = 1; + for (int i = 2; i <= n; i++) { + ugly[i] = Math.min(Math.min(ugly[i2] * 2, ugly[i3] * 3), ugly[i5] * 5); + if (ugly[i] == ugly[i2] * 2) { + i2++; + } + if (ugly[i] == ugly[i3] * 3) { + i3++; + } + if (ugly[i] == ugly[i5] * 5) { + i5++; + } + } + return ugly[n]; + } + +} diff --git a/公开课/class030/Code04_EnterLoopNode.java b/公开课/class030/Code04_EnterLoopNode.java new file mode 100644 index 0000000..082b3f3 --- /dev/null +++ b/公开课/class030/Code04_EnterLoopNode.java @@ -0,0 +1,34 @@ +package class030; + +// 测试链接:https://leetcode.com/problems/linked-list-cycle-ii +public class Code04_EnterLoopNode { + + // 这个类不用提交 + public static class ListNode { + public int val; + public ListNode next; + } + + // 只提交以下的代码 + public static ListNode detectCycle(ListNode head) { + if (head == null || head.next == null || head.next.next == null) { + return null; + } + ListNode slow = head.next; + ListNode fast = head.next.next; + while (slow != fast) { + if (fast.next == null || fast.next.next == null) { + return null; + } + fast = fast.next.next; + slow = slow.next; + } + fast = head; + while (slow != fast) { + slow = slow.next; + fast = fast.next; + } + return slow; + } + +} diff --git a/公开课/class031/Code01_SwapWithoutTmp.java b/公开课/class031/Code01_SwapWithoutTmp.java new file mode 100644 index 0000000..805908b --- /dev/null +++ b/公开课/class031/Code01_SwapWithoutTmp.java @@ -0,0 +1,24 @@ +package class031; + +public class Code01_SwapWithoutTmp { + + public static void main(String[] args) { + + int test1 = 23; + int test2 = 15; + System.out.println(test1 ^ test2); + + + + int a = -111; + int b = 343242111; + System.out.println(a); + System.out.println(b); + a = a ^ b; + b = a ^ b; + a = a ^ b; + System.out.println(a); + System.out.println(b); + } + +} diff --git a/公开课/class031/Code02_AddMinus.java b/公开课/class031/Code02_AddMinus.java new file mode 100644 index 0000000..9ed4638 --- /dev/null +++ b/公开课/class031/Code02_AddMinus.java @@ -0,0 +1,29 @@ +package class031; + +public class Code02_AddMinus { + + public static int add(int a, int b) { + int t = 0; + while (b != 0) { + t = a; + a = a ^ b; // a -> a' 无进位相加信息 + b = ((t & b) << 1); // b -> b' 进位信息 + } + return a; + } + + public static int minus(int a, int b) { + return add(a, add(~b, 1)); + } + + public static void main(String[] args) { + int a = 8739284; + int b = 7348472; + System.out.println(a + b); + System.out.println(add(a, b)); + + System.out.println(a - b); + System.out.println(minus(a, b)); + } + +} diff --git a/公开课/class031/Code03_GetMax.java b/公开课/class031/Code03_GetMax.java new file mode 100644 index 0000000..c3f54a5 --- /dev/null +++ b/公开课/class031/Code03_GetMax.java @@ -0,0 +1,49 @@ +package class031; + +public class Code03_GetMax { + + // n 0或1 + // 0 -> 1 1 -> 0 + public static int flip(int n) { + return n ^ 1; + } + + // n 任意整数 + // n非负的,返回1 + // n负的,返回0 + public static int sign(int n) { + // (n >> 31) & 1 n非负的 0 n负的 1 + return flip((n >> 31) & 1); + } + + public static int getMax1(int a, int b) { + int c = a - b; + int scA = sign(c); // a - b >= 0 scA = 1; a - b <0 scA = 0 + int scB = flip(scA); // a - b >= 0 scB = 0; a - b <0 scB = 1 + return a * scA + b * scB; + } + + public static int getMax2(int a, int b) { + int c = a - b; + int sa = sign(a); // a的符号,非负 1 负 0 + int sb = sign(b); // b的符号,非负 1 负 0 + int sc = sign(c); // a-b的符号,非负 1 负 0 + int difSab = sa ^ sb; // 如果不一样,1;如果一样,0 + int sameSab = flip(difSab);// 如果一样,1;如果不一样,0 + int returnA = difSab * sa + sameSab * sc; + int returnB = flip(returnA); + return a * returnA + b * returnB; + } + + public static void main(String[] args) { + int a = -16; + int b = -19; + System.out.println(getMax1(a, b)); + System.out.println(getMax2(a, b)); + a = 2147483647; + b = -2147480000; + System.out.println(getMax1(a, b)); // wrong answer because of overflow + System.out.println(getMax2(a, b)); + } + +} diff --git a/公开课/class031/Code04_MissingNumber.java b/公开课/class031/Code04_MissingNumber.java new file mode 100644 index 0000000..c750981 --- /dev/null +++ b/公开课/class031/Code04_MissingNumber.java @@ -0,0 +1,27 @@ +package class031; + +// 测试链接:https://leetcode.com/problems/first-missing-positive/ +public class Code04_MissingNumber { + + public static int firstMissingPositive(int[] arr) { + int l = 0; + int r = arr.length; + while (l != r) { + if (arr[l] == l + 1) { + l++; + } else if (arr[l] <= l + 1 || arr[l] > r || arr[arr[l] - 1] == arr[l]) { + swap(arr, l, --r); + } else { + swap(arr, l, arr[l] - 1); + } + } + return l + 1; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/公开课/class032/Code01_BinaryTreeLevelOrderTraversal.java b/公开课/class032/Code01_BinaryTreeLevelOrderTraversal.java new file mode 100644 index 0000000..3b913ca --- /dev/null +++ b/公开课/class032/Code01_BinaryTreeLevelOrderTraversal.java @@ -0,0 +1,46 @@ +package class032; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +// 测试链接:https://leetcode.com/problems/binary-tree-level-order-traversal +public class Code01_BinaryTreeLevelOrderTraversal { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + TreeNode(int val) { + this.val = val; + } + } + + public List> levelOrder(TreeNode root) { + List> ans = new ArrayList<>(); + if (root == null) { + return ans; + } + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + int size = queue.size(); + List curAns = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode curNode = queue.poll(); + curAns.add(curNode.val); + if (curNode.left != null) { + queue.add(curNode.left); + } + if (curNode.right != null) { + queue.add(curNode.right); + } + } + ans.add(curAns); + } + return ans; + } + +} diff --git a/公开课/class032/Code01_BinaryTreeLevelOrderTraversalII.java b/公开课/class032/Code01_BinaryTreeLevelOrderTraversalII.java new file mode 100644 index 0000000..a5217e0 --- /dev/null +++ b/公开课/class032/Code01_BinaryTreeLevelOrderTraversalII.java @@ -0,0 +1,45 @@ +package class032; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +// 测试链接:https://leetcode.com/problems/binary-tree-level-order-traversal-ii +public class Code01_BinaryTreeLevelOrderTraversalII { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + TreeNode(int val) { + this.val = val; + } + } + + public List> levelOrderBottom(TreeNode root) { + List> ans = new LinkedList<>(); + if (root == null) { + return ans; + } + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + int size = queue.size(); + List curAns = new LinkedList<>(); + for (int i = 0; i < size; i++) { + TreeNode curNode = queue.poll(); + curAns.add(curNode.val); + if (curNode.left != null) { + queue.add(curNode.left); + } + if (curNode.right != null) { + queue.add(curNode.right); + } + } + ans.add(0, curAns); + } + return ans; + } + +} diff --git a/公开课/class032/Code02_ConstructBinaryTreeFromPreorderAndInorderTraversal.java b/公开课/class032/Code02_ConstructBinaryTreeFromPreorderAndInorderTraversal.java new file mode 100644 index 0000000..15cde6b --- /dev/null +++ b/公开课/class032/Code02_ConstructBinaryTreeFromPreorderAndInorderTraversal.java @@ -0,0 +1,74 @@ +package class032; + +import java.util.HashMap; + +// 测试链接:https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal +public class Code02_ConstructBinaryTreeFromPreorderAndInorderTraversal { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int val) { + this.val = val; + } + } + + public static TreeNode buildTree1(int[] pre, int[] in) { + if (pre == null || in == null || pre.length != in.length) { + return null; + } + return process1(pre, 0, pre.length - 1, in, 0, in.length - 1); + } + + // 只使用pre[L1...R1]这一段,作为这棵树先序遍历的结果 + // 只使用in [L2...R2]这一段,作为这棵树中序遍历的结果 + // 建立好这棵树,然后把头节点返回 + public static TreeNode process1(int[] pre, int L1, int R1, int[] in, int L2, int R2) { + if (L1 > R1) { + return null; + } + if (L1 == R1) { + return new TreeNode(pre[L1]); + } + TreeNode head = new TreeNode(pre[L1]); + int find = L2; + while (in[find] != pre[L1]) { + find++; + } + head.left = process1(pre, L1 + 1, L1 + find - L2, in, L2, find - 1); + head.right = process1(pre, L1 + find - L2 + 1, R1, in, find + 1, R2); + return head; + } + + public static TreeNode buildTree2(int[] pre, int[] in) { + if (pre == null || in == null || pre.length != in.length) { + return null; + } + HashMap valueIndexMap = new HashMap<>(); + for (int i = 0; i < in.length; i++) { + valueIndexMap.put(in[i], i); + } + return process2(pre, 0, pre.length - 1, in, 0, in.length - 1, valueIndexMap); + } + + // 只使用pre[L1...R1]这一段,作为这棵树先序遍历的结果 + // 只使用in [L2...R2]这一段,作为这棵树中序遍历的结果 + // 建立好这棵树,然后把头节点返回 + public static TreeNode process2(int[] pre, int L1, int R1, int[] in, int L2, int R2, + HashMap valueIndexMap) { + if (L1 > R1) { + return null; + } + if (L1 == R1) { + return new TreeNode(pre[L1]); + } + TreeNode head = new TreeNode(pre[L1]); + int find = valueIndexMap.get(pre[L1]); + head.left = process2(pre, L1 + 1, L1 + find - L2, in, L2, find - 1, valueIndexMap); + head.right = process2(pre, L1 + find - L2 + 1, R1, in, find + 1, R2, valueIndexMap); + return head; + } + +} diff --git a/公开课/class032/Code03_PaperFolding.java b/公开课/class032/Code03_PaperFolding.java new file mode 100644 index 0000000..88a229b --- /dev/null +++ b/公开课/class032/Code03_PaperFolding.java @@ -0,0 +1,26 @@ +package class032; + +public class Code03_PaperFolding { + + public static void printAllFolds(int N) { + process(1, N, true); + System.out.println(); + } + + // 假想中的当前节点,在i层,一共N层, + // 假想中的当前节点凹还是凸?down决定!down = true 凹 down = false 凸 + // 打印以假想节点为头的整棵树,中序打印 + public static void process(int i, int N, boolean down) { + if (i > N) { + return; + } + process(i + 1, N, true); + System.out.print(down ? "凹 " : "凸 "); + process(i + 1, N, false); + } + + public static void main(String[] args) { + int N = 4; + printAllFolds(N); + } +} diff --git a/公开课/class032/Code04_DiameterOfBinaryTree.java b/公开课/class032/Code04_DiameterOfBinaryTree.java new file mode 100644 index 0000000..fc18efa --- /dev/null +++ b/公开课/class032/Code04_DiameterOfBinaryTree.java @@ -0,0 +1,38 @@ +package class032; + +// 测试链接:https://leetcode.com/problems/diameter-of-binary-tree +public class Code04_DiameterOfBinaryTree { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public static int diameterOfBinaryTree(TreeNode root) { + return process(root).maxDistance; + } + + public static class Info { + public int maxDistance; + public int height; + + public Info(int m, int h) { + maxDistance = m; + height = h; + } + } + + public static Info process(TreeNode x) { + if (x == null) { + return new Info(0, 0); + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance), + leftInfo.height + rightInfo.height); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + return new Info(maxDistance, height); + } + +} diff --git a/公开课/class033/Code01_FindHalfMajority.java b/公开课/class033/Code01_FindHalfMajority.java new file mode 100644 index 0000000..4bd4acc --- /dev/null +++ b/公开课/class033/Code01_FindHalfMajority.java @@ -0,0 +1,76 @@ +package class033; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code01_FindHalfMajority { + + public static int halfMajor(int[] arr) { + int target = 0; + int HP = 0; + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { + target = arr[i]; + HP = 1; + } else if (arr[i] == target) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == target) { + HP++; + } + } + return HP > arr.length / 2 ? target : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class033/Code02_RotateImage.java b/公开课/class033/Code02_RotateImage.java new file mode 100644 index 0000000..e3d48f2 --- /dev/null +++ b/公开课/class033/Code02_RotateImage.java @@ -0,0 +1,52 @@ +package class033; + +public class Code02_RotateImage { + + public static void rotate(int[][] matrix) { + int a = 0; + int b = 0; + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + // 在二维数组m中,左上角点(a,b), 右下角点(c,d) + // 只在规定好的框上,顺时针转好 + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + + public static void printMatrix(int[][] m) { + int N = m.length; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 } }; + printMatrix(matrix); + System.out.println("========"); + + rotate(matrix); + printMatrix(matrix); + System.out.println("========"); + + } + +} diff --git a/公开课/class033/Code03_ZigZagPrintMatrix.java b/公开课/class033/Code03_ZigZagPrintMatrix.java new file mode 100644 index 0000000..b062447 --- /dev/null +++ b/公开课/class033/Code03_ZigZagPrintMatrix.java @@ -0,0 +1,48 @@ +package class033; + +public class Code03_ZigZagPrintMatrix { + + public static void printMatrixZigZag(int[][] m) { + // (a,b) A 先往右,再往下 + int a = 0; + int b = 0; + // (c,d) B 先往下,在往右 + int c = 0; + int d = 0; + int er = m.length - 1; + int ec = m[0].length - 1; + boolean fromUp = false; + while (a != er + 1) { + printLevel(m, a, b, c, d, fromUp); + // A 先往右,再往下 + a = b == ec ? a + 1 : a; + b = b == ec ? b : b + 1; + // B 先往下,在往右 + d = c == er ? d + 1 : d; + c = c == er ? c : c + 1; + fromUp = !fromUp; + } + } + + public static void printLevel(int[][] m, int a, int b, int c, int d, boolean f) { + if (f) { + while (a != c + 1) { + System.out.print(m[a++][b--] + " "); + } + } else { + while (c != a - 1) { + System.out.print(m[c--][d++] + " "); + } + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } }; + printMatrixZigZag(matrix); + + } + +} diff --git a/公开课/class033/Code04_PrintStar.java b/公开课/class033/Code04_PrintStar.java new file mode 100644 index 0000000..bf51f37 --- /dev/null +++ b/公开课/class033/Code04_PrintStar.java @@ -0,0 +1,46 @@ +package class033; + +public class Code04_PrintStar { + + public static void printStar(int N) { + int leftUp = 0; + int rightDown = N - 1; + char[][] m = new char[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + m[i][j] = ' '; + } + } + while (leftUp <= rightDown) { + set(m, leftUp, rightDown); + leftUp += 2; + rightDown -= 2; + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void set(char[][] m, int leftUp, int rightDown) { + for (int col = leftUp; col <= rightDown; col++) { + m[leftUp][col] = '*'; + } + for (int row = leftUp + 1; row <= rightDown; row++) { + m[row][rightDown] = '*'; + } + for (int col = rightDown - 1; col > leftUp; col--) { + m[rightDown][col] = '*'; + } + for (int row = rightDown - 1; row > leftUp + 1; row--) { + m[row][leftUp + 1] = '*'; + } + } + + public static void main(String[] args) { + printStar(8); + } + +} diff --git a/公开课/class034/Code01_Water.java b/公开课/class034/Code01_Water.java new file mode 100644 index 0000000..cea25b0 --- /dev/null +++ b/公开课/class034/Code01_Water.java @@ -0,0 +1,118 @@ +package class034; + +public class Code01_Water { + + // 本题测试链接:https://leetcode.com/problems/trapping-rain-water/ + // 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class034/Code02_RandToRand.java b/公开课/class034/Code02_RandToRand.java new file mode 100644 index 0000000..a81c764 --- /dev/null +++ b/公开课/class034/Code02_RandToRand.java @@ -0,0 +1,134 @@ +package class034; + +public class Code02_RandToRand { + + // 此函数只能用,不能修改 + // 等概率返回1~5 + public static int f() { + return (int) (Math.random() * 5) + 1; + } + + // 等概率得到0和1 + public static int a() { + int ans = 0; + do { + ans = f(); + } while (ans == 3); + return ans < 3 ? 0 : 1; + } + + // 等概率返回0~6 + public static int b() { + int ans = 0; + do { + ans = (a() << 2) + (a() << 1) + a(); + } while (ans == 7); + return ans; + } + + // 等概率返回1~7 + public static int c() { + return b() + 1; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + // 13 ~ 17 + // 13 + [0,4] + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + + int[] count = new int[8]; // 0 1 2 .. 7 + int testTime = 10000000; + for (int i = 0; i < testTime; i++) { + int ans = c(); + count[ans]++; + } + + for (int i = 0; i < 8; i++) { + System.out.println(i + " : " + count[i]); + } + +// RandomBox randomBox = new RandomBox(3, 9); +// int from = 17; +// int to = 29; +// int[] ans = new int[to + 1]; +// int testTime1 = 1000000; +// for (int i = 0; i < testTime1; i++) { +// ans[random(randomBox, from, to)]++; +// } +// for (int i = 0; i < ans.length; i++) { +// System.out.println(ans[i]); +// } +// System.out.println("=========="); + + } + +} diff --git a/公开课/class034/Code03_EqualProbabilityRandom.java b/公开课/class034/Code03_EqualProbabilityRandom.java new file mode 100644 index 0000000..8254281 --- /dev/null +++ b/公开课/class034/Code03_EqualProbabilityRandom.java @@ -0,0 +1,67 @@ +package class034; + +public class Code03_EqualProbabilityRandom { + + // 内部内容不可见 + public static int f() { + return Math.random() < 0.8 ? 0 : 1; + } + + // 等概率返回0和1 + public static int g() { + int first = 0; + do { + first = f(); // 0 1 + } while (first == f()); + return first; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + int[] count = new int[2];// 0 1 + for (int i = 0; i < 1000000; i++) { + int ans = g(); + count[ans]++; + } + System.out.println(count[0] + " , " + count[1]); + +// double zeroP = 0.88; +// RandomBox randomBox = new RandomBox(zeroP); +// +// int testTime = 10000000; +// int count = 0; +// for (int i = 0; i < testTime; i++) { +// if (rand01(randomBox) == 0) { +// count++; +// } +// } +// System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class034/Code04_EvenTimesOddTimes.java b/公开课/class034/Code04_EvenTimesOddTimes.java new file mode 100644 index 0000000..7851b57 --- /dev/null +++ b/公开课/class034/Code04_EvenTimesOddTimes.java @@ -0,0 +1,52 @@ +package class034; + +public class Code04_EvenTimesOddTimes { + + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + public static void printOddTimesNum2(int[] arr) { + // arr中,a和b出现了奇数次,其他数都是偶数次 + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor : 001100100 + // rightOne : 000000100 + int rightOne = eor & (~eor + 1); + int onlyOne = 0; // eor' + for (int i = 0; i < arr.length; i++) { + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + // eor' = a or b + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + public static int bit1counts(int N) { + int count = 0; + while (N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + public static void main(String[] args) { + int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + } + +} diff --git a/公开课/class034/Code05_MakeNo.java b/公开课/class034/Code05_MakeNo.java new file mode 100644 index 0000000..f5bd4ea --- /dev/null +++ b/公开课/class034/Code05_MakeNo.java @@ -0,0 +1,50 @@ +package class034; + +public class Code05_MakeNo { + + public static int[] makeNo(int size) { + if (size == 1) { + return new int[] { 1 }; + } + int halfSize = (size + 1) / 2; + int[] base = makeNo(halfSize); + int[] ans = new int[size]; + int index = 0; + for (; index < halfSize; index++) { + ans[index] = base[index] * 2 + 1; + } + for (int i = 0; index < size; index++, i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/公开课/class035/Code01_UglyNumber.java b/公开课/class035/Code01_UglyNumber.java new file mode 100644 index 0000000..23dcb10 --- /dev/null +++ b/公开课/class035/Code01_UglyNumber.java @@ -0,0 +1,62 @@ +package class035; + +// 测试链接:https://leetcode.com/problems/ugly-number-ii +public class Code01_UglyNumber { + + public static boolean isUgly(int num) { + while (num % 2 == 0) { + num /= 2; + } + while (num % 3 == 0) { + num /= 3; + } + while (num % 5 == 0) { + num /= 5; + } + return num == 1; + } + + public static int nthUglyNumber1(int n) { + int find = 0; + int num = 1; + for (; find < n; num++) { + if (isUgly(num)) { + find++; + } + } + return num - 1; + } + + + + + + + + + public static int nthUglyNumber2(int n) { + int[] ugly = new int[n + 1]; + ugly[1] = 1; + int i2 = 1; + int i3 = 1; + int i5 = 1; + for (int i = 2; i <= n; i++) { + ugly[i] = Math.min(Math.min(ugly[i2] * 2, ugly[i3] * 3), ugly[i5] * 5); + if (ugly[i] == ugly[i2] * 2) { + i2++; + } + if (ugly[i] == ugly[i3] * 3) { + i3++; + } + if (ugly[i] == ugly[i5] * 5) { + i5++; + } + } + return ugly[n]; + } + + public static void main(String[] args) { + System.out.println(nthUglyNumber2(10000)); + } + +} diff --git a/公开课/class035/Code02_MakeNo.java b/公开课/class035/Code02_MakeNo.java new file mode 100644 index 0000000..25027ed --- /dev/null +++ b/公开课/class035/Code02_MakeNo.java @@ -0,0 +1,50 @@ +package class035; + +public class Code02_MakeNo { + + public static int[] makeNo(int size) { + if (size == 1) { + return new int[] { 1 }; + } + int halfSize = (size + 1) / 2; + int[] base = makeNo(halfSize); + int[] ans = new int[size]; + int index = 0; + for (; index < halfSize; index++) { + ans[index] = base[index] * 2 + 1; + } + for (int i = 0; index < size; index++, i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/公开课/class035/Code03_CoverMax.java b/公开课/class035/Code03_CoverMax.java new file mode 100644 index 0000000..fa7a3cb --- /dev/null +++ b/公开课/class035/Code03_CoverMax.java @@ -0,0 +1,131 @@ +package class035; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code03_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class036/Code01_LongestNoRepeatSubstring.java b/公开课/class036/Code01_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..b61e15b --- /dev/null +++ b/公开课/class036/Code01_LongestNoRepeatSubstring.java @@ -0,0 +1,76 @@ +package class036; + +public class Code01_LongestNoRepeatSubstring { + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = lnrs2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class036/Code02_CordCoverMaxPoint.java b/公开课/class036/Code02_CordCoverMaxPoint.java new file mode 100644 index 0000000..634826f --- /dev/null +++ b/公开课/class036/Code02_CordCoverMaxPoint.java @@ -0,0 +1,88 @@ +package class036; + +import java.util.Arrays; + +public class Code02_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class036/Code03_ColorLeftRight.java b/公开课/class036/Code03_ColorLeftRight.java new file mode 100644 index 0000000..c11c3d9 --- /dev/null +++ b/公开课/class036/Code03_ColorLeftRight.java @@ -0,0 +1,72 @@ +package class036; + +public class Code03_ColorLeftRight { + + // RGRGR -> RRRGG + public static int minPaint(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int rAll = 0; + for (int i = 0; i < N; i++) { + rAll += str[i] == 'R' ? 1 : 0; + } + int ans = rAll; // 如果数组所有的范围,都是右侧范围,都变成G + int left = 0; + for (int i = 0; i < N - 1; i++) { // 0..i 左侧 n-1..N-1 + left += str[i] == 'G' ? 1 : 0; + rAll -= str[i] == 'R' ? 1 : 0; + ans = Math.min(ans, left + rAll); + } + // 0...N-1 左全部 右无 + ans = Math.min(ans, left + (str[N - 1] == 'G' ? 1 : 0)); + return ans; + } + + // RGRGR -> RRRGG + public static int test(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int ans = Integer.MAX_VALUE; + for (int leftEnd = -1; leftEnd < str.length; leftEnd++) { + int left = 0; + for (int i = 0; i <= leftEnd; i++) { + left += str[i] == 'G' ? 1 : 0; + } + int right = 0; + for (int i = leftEnd + 1; i < str.length; i++) { + right += str[i] == 'R' ? 1 : 0; + } + ans = Math.min(ans, left + right); + } + return ans; + } + + public static String generateString(int len) { + char[] str = new char[(int) (Math.random() * len) + 1]; + for (int i = 0; i < str.length; i++) { + str[i] = Math.random() < 0.5 ? 'R' : 'G'; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int len = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = generateString(len); + int ans1 = minPaint(str); + int ans2 = test(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class037/Code01_MaxABSBetweenLeftAndRight.java b/公开课/class037/Code01_MaxABSBetweenLeftAndRight.java new file mode 100644 index 0000000..8c21244 --- /dev/null +++ b/公开课/class037/Code01_MaxABSBetweenLeftAndRight.java @@ -0,0 +1,72 @@ +package class037; + +public class Code01_MaxABSBetweenLeftAndRight { + + public static int maxABS1(int[] arr) { + int res = Integer.MIN_VALUE; + int maxLeft = 0; + int maxRight = 0; + for (int i = 0; i != arr.length - 1; i++) { + maxLeft = Integer.MIN_VALUE; + for (int j = 0; j != i + 1; j++) { + maxLeft = Math.max(arr[j], maxLeft); + } + maxRight = Integer.MIN_VALUE; + for (int j = i + 1; j != arr.length; j++) { + maxRight = Math.max(arr[j], maxRight); + } + res = Math.max(Math.abs(maxLeft - maxRight), res); + } + return res; + } + + public static int maxABS2(int[] arr) { + int[] lArr = new int[arr.length]; + int[] rArr = new int[arr.length]; + lArr[0] = arr[0]; + rArr[arr.length - 1] = arr[arr.length - 1]; + for (int i = 1; i < arr.length; i++) { + lArr[i] = Math.max(lArr[i - 1], arr[i]); + } + for (int i = arr.length - 2; i > -1; i--) { + rArr[i] = Math.max(rArr[i + 1], arr[i]); + } + int max = 0; + for (int i = 0; i < arr.length - 1; i++) { + max = Math.max(max, Math.abs(lArr[i] - rArr[i + 1])); + } + return max; + } + + public static int maxABS3(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(arr[i], max); + } + return max - Math.min(arr[0], arr[arr.length - 1]); + } + + public static int[] generateRandomArray(int length) { + int[] arr = new int[length]; + for (int i = 0; i != arr.length; i++) { + arr[i] = (int) (Math.random() * 1000) - 499; + } + return arr; + } + + public static void main(String[] args) { + int len = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len); + int ans1 = maxABS1(arr); + int ans2 = maxABS2(arr); + int ans3 = maxABS3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } +} diff --git a/公开课/class037/Code02_JumpGameII.java b/公开课/class037/Code02_JumpGameII.java new file mode 100644 index 0000000..6fc6134 --- /dev/null +++ b/公开课/class037/Code02_JumpGameII.java @@ -0,0 +1,23 @@ +package class037; + +// 测试页面:https://leetcode.com/problems/jump-game-ii/ +public class Code02_JumpGameII { + + public static int jump(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int step = 0; + int cur = 0; + int next = 0; + for (int i = 0; i < arr.length; i++) { + if (cur < i) { + step++; + cur = next; + } + next = Math.max(next, i + arr[i]); + } + return step; + } + +} diff --git a/公开课/class037/Code03_FindHalfMajority.java b/公开课/class037/Code03_FindHalfMajority.java new file mode 100644 index 0000000..0cb2953 --- /dev/null +++ b/公开课/class037/Code03_FindHalfMajority.java @@ -0,0 +1,76 @@ +package class037; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code03_FindHalfMajority { + + public static int halfMajor(int[] arr) { + int target = 0; + int HP = 0; + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { + target = arr[i]; + HP = 1; + } else if (arr[i] == target) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == target) { + HP++; + } + } + return HP > arr.length / 2 ? target : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class038/Code01_SubArrayMaxSum.java b/公开课/class038/Code01_SubArrayMaxSum.java new file mode 100644 index 0000000..e80f0e3 --- /dev/null +++ b/公开课/class038/Code01_SubArrayMaxSum.java @@ -0,0 +1,195 @@ +package class038; + +import java.util.ArrayList; +import java.util.List; + +public class Code01_SubArrayMaxSum { + + public static int test(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + // arr[L...R] + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + max = Math.max(max, sum); + } + } + return max; + } + + public static int max1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] ans = new int[N]; + ans[0] = arr[0]; + int max = ans[0]; + for (int end = 1; end < N; end++) { + int p1 = arr[end]; + int p2 = ans[end - 1] + arr[end]; + ans[end] = Math.max(p1, p2); + max = Math.max(max, ans[end]); + } + return max; + } + + + public static int max2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0]; + int max = pre; + for (int end = 1; end < N; end++) { + int p1 = arr[end]; + int p2 = pre + arr[end]; + max = Math.max(max, Math.max(p1, p2)); + pre = Math.max(p1, p2); + } + return max; + } + + public static int dp1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] dp = new int[arr.length]; + // dp[i] 子数组必须以i位置结尾的情况下,能得到的最大累加和 + dp[0] = arr[0]; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + } + int max = Integer.MIN_VALUE; + for (int i = 0; i < dp.length; i++) { + max = Math.max(max, dp[i]); + } + return max; + } + + public static int dp2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int preDp = arr[0]; + int max = preDp; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = preDp + arr[i]; + int dp = Math.max(p1, p2); + max = Math.max(max, dp); + preDp = dp; + } + return max; + } + + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int cur = 0; + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return max; + } + + public static int maxSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // 0结尾时候的答案 + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + // i结尾时候的答案 + pre = arr[i] + (pre > 0 ? pre : 0); + max = Math.max(max, pre); + } + return max; + } + + public static List> maxSum2(int[] arr) { + List> ans = new ArrayList<>(); + if (arr == null || arr.length == 0) { + return ans; + } + int L = 0; + int maxLen = 0; + int maxSum = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + // L...i sum + cur += arr[i]; + if (cur == maxSum && (i - L + 1) == maxLen) { + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + } + if (cur > maxSum || (cur == maxSum && (i - L + 1) > maxLen)) { + ans.clear(); + List curAns = new ArrayList<>(); + curAns.add(L); + curAns.add(i); + ans.add(curAns); + maxLen = i - L + 1; + } + maxSum = Math.max(maxSum, cur); + if (cur < 0) { + cur = 0; + L = i + 1; + } + } + return ans; + } + + public static int[] generateArray(int N, int V) { + int n = (int) (Math.random() * N) + 1; + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + return arr; + } + + public static void main(String[] args) { + int N = 100; + int V = 100; + int testTime = 10000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateArray(N, V); + int ans1 = test(arr); + int ans2 = dp1(arr); + int ans3 = dp2(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + int[] test = { 2, 2, 1, -9, 2, 3, -9, 6, -9, 2, 2, 2, -9, 2, 2, 2, -9, 1, 4, 1 }; + + List> ans = maxSum2(test); + + for (List cur : ans) { + System.out.println("start : " + cur.get(0) + ", end : " + cur.get(1)); + } + + } + +} diff --git a/公开课/class038/Code02_MaxSumFollowUp.java b/公开课/class038/Code02_MaxSumFollowUp.java new file mode 100644 index 0000000..93e52e3 --- /dev/null +++ b/公开课/class038/Code02_MaxSumFollowUp.java @@ -0,0 +1,83 @@ +package class038; + +public class Code02_MaxSumFollowUp { + + public static int maxSum1(int[][] matrix) { + int n = matrix.length; + int m = matrix[0].length; + int max = Integer.MIN_VALUE; + for (int ai = 0; ai < n; ai++) { + for (int aj = 0; aj < m; aj++) { + for (int bi = ai; bi < n; bi++) { + for (int bj = aj; bj < m; bj++) { + max = Math.max(max, sum(matrix, ai, aj, bi, bj)); + } + } + } + } + return max; + } + + public static int sum(int[][] matrix, int ai, int aj, int bi, int bj) { + int sum = 0; + for (int i = ai; i <= bi; i++) { + for (int j = aj; j <= bj; j++) { + sum += matrix[i][j]; + } + } + return sum; + } + + public static int maxSum2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + int[] s = null; + for (int i = 0; i != matrix.length; i++) { + s = new int[matrix[0].length]; + for (int j = i; j != matrix.length; j++) { + cur = 0; + for (int k = 0; k != s.length; k++) { + s[k] += matrix[j][k]; + cur += s[k]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + } + } + return max; + } + + public static int[][] generateMatrix(int N, int M, int V) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + } + return matrix; + } + + public static void main(String[] args) { + int N = 20; + int M = 20; + int V = 100; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[][] matrix = generateMatrix(N, M, V); + int ans1 = maxSum1(matrix); + int ans2 = maxSum2(matrix); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class038/Code03_MaxSumNoAdjoin.java b/公开课/class038/Code03_MaxSumNoAdjoin.java new file mode 100644 index 0000000..fe8c17a --- /dev/null +++ b/公开课/class038/Code03_MaxSumNoAdjoin.java @@ -0,0 +1,58 @@ +package class038; + +public class Code03_MaxSumNoAdjoin { + + public static int subSqeMaxSumNoNext(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int[] dp = new int[arr.length]; + // dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < arr.length; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i] + Math.max(dp[i - 2], 0); + dp[i] = Math.max(p1, p2); + } + return dp[arr.length - 1]; + } + + // 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 思路: + // 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类: + // 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1] + // 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候 + // + // 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i] + // 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候 + // + // 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2] + // 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过 + // + // 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] } + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (N == 1) { + return arr[0]; + } + if (N == 2) { + return Math.max(arr[0], arr[1]); + } + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]); + } + return dp[N - 1]; + } + +} diff --git a/公开课/class038/Code04_MissingNumber.java b/公开课/class038/Code04_MissingNumber.java new file mode 100644 index 0000000..a96221e --- /dev/null +++ b/公开课/class038/Code04_MissingNumber.java @@ -0,0 +1,27 @@ +package class038; + +// 测试链接:https://leetcode.com/problems/first-missing-positive/ +public class Code04_MissingNumber { + + public static int firstMissingPositive(int[] arr) { + int l = 0; + int r = arr.length; + while (l != r) { + if (arr[l] == l + 1) { + l++; + } else if (arr[l] <= l || arr[l] > r || arr[arr[l] - 1] == arr[l]) { + swap(arr, l, --r); + } else { + swap(arr, l, arr[l] - 1); + } + } + return l + 1; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/公开课/class039/Code01_Water.java b/公开课/class039/Code01_Water.java new file mode 100644 index 0000000..2a28dc1 --- /dev/null +++ b/公开课/class039/Code01_Water.java @@ -0,0 +1,120 @@ +package class039; + +public class Code01_Water { + + // 本题测试链接:https://leetcode.com/problems/trapping-rain-water/ + // 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + // O位置无水量 + // 0~N-1,N-1无水量 + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class039/Code02_RandToRand.java b/公开课/class039/Code02_RandToRand.java new file mode 100644 index 0000000..c55c7d5 --- /dev/null +++ b/公开课/class039/Code02_RandToRand.java @@ -0,0 +1,134 @@ +package class039; + +public class Code02_RandToRand { + + // 此函数只能用,不能修改 + // 等概率返回1~5 + public static int f() { + return (int) (Math.random() * 5) + 1; + } + + // 等概率得到0和1 + public static int a() { + int ans = 0; + do { + ans = f(); + } while (ans == 3); + return ans < 3 ? 0 : 1; + } + + // 等概率返回0~6 + public static int b() { + int ans = 0; + do { + ans = (a() << 2) + (a() << 1) + a(); + } while (ans == 7); + return ans; + } + + // 等概率返回1~7 + public static int c() { + return b() + 1; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + // 13 ~ 17 + // 13 + [0,4] + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + + int[] count = new int[8]; // 0 1 2 .. 7 + int testTime = 10000000; + for (int i = 0; i < testTime; i++) { + int ans = c(); + count[ans]++; + } + + for (int i = 0; i < 8; i++) { + System.out.println(i + " : " + count[i]); + } + +// RandomBox randomBox = new RandomBox(3, 9); +// int from = 17; +// int to = 29; +// int[] ans = new int[to + 1]; +// int testTime1 = 1000000; +// for (int i = 0; i < testTime1; i++) { +// ans[random(randomBox, from, to)]++; +// } +// for (int i = 0; i < ans.length; i++) { +// System.out.println(ans[i]); +// } +// System.out.println("=========="); + + } + +} diff --git a/公开课/class039/Code03_EqualProbabilityRandom.java b/公开课/class039/Code03_EqualProbabilityRandom.java new file mode 100644 index 0000000..bee7c63 --- /dev/null +++ b/公开课/class039/Code03_EqualProbabilityRandom.java @@ -0,0 +1,67 @@ +package class039; + +public class Code03_EqualProbabilityRandom { + + // 内部内容不可见 + public static int f() { + return Math.random() < 0.8 ? 0 : 1; + } + + // 等概率返回0和1 + public static int g() { + int first = 0; + do { + first = f(); // 0 1 + } while (first == f()); + return first; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + int[] count = new int[2];// 0 1 + for (int i = 0; i < 1000000; i++) { + int ans = g(); + count[ans]++; + } + System.out.println(count[0] + " , " + count[1]); + +// double zeroP = 0.88; +// RandomBox randomBox = new RandomBox(zeroP); +// +// int testTime = 10000000; +// int count = 0; +// for (int i = 0; i < testTime; i++) { +// if (rand01(randomBox) == 0) { +// count++; +// } +// } +// System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class039/Code04_EvenTimesOddTimes.java b/公开课/class039/Code04_EvenTimesOddTimes.java new file mode 100644 index 0000000..cc80817 --- /dev/null +++ b/公开课/class039/Code04_EvenTimesOddTimes.java @@ -0,0 +1,52 @@ +package class039; + +public class Code04_EvenTimesOddTimes { + + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + public static void printOddTimesNum2(int[] arr) { + // arr中,a和b出现了奇数次,其他数都是偶数次 + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor : 001100100 + // rightOne : 000000100 + int rightOne = eor & (~eor + 1); + int onlyOne = 0; // eor' + for (int i = 0; i < arr.length; i++) { + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + // eor' = a or b + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + public static int bit1counts(int N) { + int count = 0; + while (N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + public static void main(String[] args) { + int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + } + +} diff --git a/公开课/class039/Code05_KM.java b/公开课/class039/Code05_KM.java new file mode 100644 index 0000000..6b52eb7 --- /dev/null +++ b/公开课/class039/Code05_KM.java @@ -0,0 +1,144 @@ +package class039; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code05_KM { + + public static int test(int[] arr, int k, int m) { + HashMap map = new HashMap<>(); + for (int num : arr) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + for (int num : map.keySet()) { + if (map.get(num) == k) { + return num; + } + } + return -1; + } + + public static HashMap map = new HashMap<>(); + + // 请保证arr中,只有一种数出现了K次,其他数都出现了M次 + public static int onlyKTimes(int[] arr, int k, int m) { + if (map.size() == 0) { + mapCreater(map); + } + int[] t = new int[32]; + // t[0] 0位置的1出现了几个 + // t[i] i位置的1出现了几个 + for (int num : arr) { + while (num != 0) { + int rightOne = num & (-num); + t[map.get(rightOne)]++; + num ^= rightOne; + } + } + int ans = 0; + for (int i = 0; i < 32; i++) { + if (t[i] % m != 0) { + if (t[i] % m == k) { + ans |= (1 << i); + } else { + return -1; + } + } + } + if (ans == 0) { + int count = 0; + for (int num : arr) { + if (num == 0) { + count++; + } + } + if (count != k) { + return -1; + } + } + return ans; + } + + public static void mapCreater(HashMap map) { + int value = 1; + for (int i = 0; i < 32; i++) { + map.put(value, i); + value <<= 1; + } + } + + public static int[] randomArray(int maxKinds, int range, int k, int m) { + int ktimeNum = randomNumber(range); + // 真命天子出现的次数 + int times = Math.random() < 0.5 ? k : ((int) (Math.random() * (m - 1)) + 1); + // 2 + int numKinds = (int) (Math.random() * maxKinds) + 2; + // k * 1 + (numKinds - 1) * m + int[] arr = new int[times + (numKinds - 1) * m]; + int index = 0; + for (; index < times; index++) { + arr[index] = ktimeNum; + } + numKinds--; + HashSet set = new HashSet<>(); + set.add(ktimeNum); + while (numKinds != 0) { + int curNum = 0; + do { + curNum = randomNumber(range); + } while (set.contains(curNum)); + set.add(curNum); + numKinds--; + for (int i = 0; i < m; i++) { + arr[index++] = curNum; + } + } + // arr 填好了 + for (int i = 0; i < arr.length; i++) { + // i 位置的数,我想随机和j位置的数做交换 + int j = (int) (Math.random() * arr.length);// 0 ~ N-1 + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + // [-range, +range] + public static int randomNumber(int range) { + return ((int) (Math.random() * range) + 1) - ((int) (Math.random() * range) + 1); + } + + public static void main(String[] args) { + int kinds = 5; + int range = 30; + int testTime = 100000; + int max = 9; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * max) + 1; // a 1 ~ 9 + int b = (int) (Math.random() * max) + 1; // b 1 ~ 9 + int k = Math.min(a, b); + int m = Math.max(a, b); + // k < m + if (k == m) { + m++; + } + int[] arr = randomArray(kinds, range, k, m); + int ans1 = test(arr, k, m); + int ans2 = onlyKTimes(arr, k, m); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class040/Code01_PalindromeNumber.java b/公开课/class040/Code01_PalindromeNumber.java new file mode 100644 index 0000000..86e81db --- /dev/null +++ b/公开课/class040/Code01_PalindromeNumber.java @@ -0,0 +1,23 @@ +package class040; + +public class Code01_PalindromeNumber { + + public static boolean isPalindrome(int n) { + if (n < 0) { + return false; + } + int help = 1; + while (n / help >= 10) { + help *= 10; + } + while (n != 0) { + if (n / help != n % 10) { + return false; + } + n = (n % help) / 10; + help /= 100; + } + return true; + } + +} diff --git a/公开课/class040/Code02_MaximumProductSubarray.java b/公开课/class040/Code02_MaximumProductSubarray.java new file mode 100644 index 0000000..6e33994 --- /dev/null +++ b/公开课/class040/Code02_MaximumProductSubarray.java @@ -0,0 +1,24 @@ +package class040; + +// 本次测试链接 : https://leetcode.com/problems/maximum-product-subarray/ +public class Code02_MaximumProductSubarray { + + public static int maxProduct(int[] nums) { + // 答案,最终要返回的! + int ans = nums[0]; + // min : 必须以0结尾时,最小累乘积 + int min = nums[0]; + // min : 必须以0结尾时,最大累乘积 + int max = nums[0]; + for (int i = 1; i < nums.length; i++) { + // curmin : 必须以i结尾时,最小累乘积 + int curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); + int curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); + min = curmin; + max = curmax; + ans = Math.max(ans, max); + } + return ans; + } + +} diff --git a/公开课/class040/Code03_CoverMax.java b/公开课/class040/Code03_CoverMax.java new file mode 100644 index 0000000..6480260 --- /dev/null +++ b/公开课/class040/Code03_CoverMax.java @@ -0,0 +1,135 @@ +package class040; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code03_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + // [10,30] + // [1,5] + // [3,9] + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // 小根堆 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class040/Code04_KM.java b/公开课/class040/Code04_KM.java new file mode 100644 index 0000000..0cd9175 --- /dev/null +++ b/公开课/class040/Code04_KM.java @@ -0,0 +1,144 @@ +package class040; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code04_KM { + + public static int test(int[] arr, int k, int m) { + HashMap map = new HashMap<>(); + for (int num : arr) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + for (int num : map.keySet()) { + if (map.get(num) == k) { + return num; + } + } + return -1; + } + + public static HashMap map = new HashMap<>(); + + // 请保证arr中,只有一种数出现了K次,其他数都出现了M次 + public static int onlyKTimes(int[] arr, int k, int m) { + if (map.size() == 0) { + mapCreater(map); + } + int[] t = new int[32]; + // t[0] 0位置的1出现了几个 + // t[i] i位置的1出现了几个 + for (int num : arr) { + while (num != 0) { + int rightOne = num & (-num); + t[map.get(rightOne)]++; + num ^= rightOne; + } + } + int ans = 0; + for (int i = 0; i < 32; i++) { + if (t[i] % m != 0) { + if (t[i] % m == k) { + ans |= (1 << i); + } else { + return -1; + } + } + } + if (ans == 0) { + int count = 0; + for (int num : arr) { + if (num == 0) { + count++; + } + } + if (count != k) { + return -1; + } + } + return ans; + } + + public static void mapCreater(HashMap map) { + int value = 1; + for (int i = 0; i < 32; i++) { + map.put(value, i); + value <<= 1; + } + } + + public static int[] randomArray(int maxKinds, int range, int k, int m) { + int ktimeNum = randomNumber(range); + // 真命天子出现的次数 + int times = Math.random() < 0.5 ? k : ((int) (Math.random() * (m - 1)) + 1); + // 2 + int numKinds = (int) (Math.random() * maxKinds) + 2; + // k * 1 + (numKinds - 1) * m + int[] arr = new int[times + (numKinds - 1) * m]; + int index = 0; + for (; index < times; index++) { + arr[index] = ktimeNum; + } + numKinds--; + HashSet set = new HashSet<>(); + set.add(ktimeNum); + while (numKinds != 0) { + int curNum = 0; + do { + curNum = randomNumber(range); + } while (set.contains(curNum)); + set.add(curNum); + numKinds--; + for (int i = 0; i < m; i++) { + arr[index++] = curNum; + } + } + // arr 填好了 + for (int i = 0; i < arr.length; i++) { + // i 位置的数,我想随机和j位置的数做交换 + int j = (int) (Math.random() * arr.length);// 0 ~ N-1 + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + // [-range, +range] + public static int randomNumber(int range) { + return ((int) (Math.random() * range) + 1) - ((int) (Math.random() * range) + 1); + } + + public static void main(String[] args) { + int kinds = 5; + int range = 30; + int testTime = 100000; + int max = 9; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * max) + 1; // a 1 ~ 9 + int b = (int) (Math.random() * max) + 1; // b 1 ~ 9 + int k = Math.min(a, b); + int m = Math.max(a, b); + // k < m + if (k == m) { + m++; + } + int[] arr = randomArray(kinds, range, k, m); + int ans1 = test(arr, k, m); + int ans2 = onlyKTimes(arr, k, m); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class041/Code01_LongestNoRepeatSubstring.java b/公开课/class041/Code01_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..e9fe9b7 --- /dev/null +++ b/公开课/class041/Code01_LongestNoRepeatSubstring.java @@ -0,0 +1,76 @@ +package class041; + +public class Code01_LongestNoRepeatSubstring { + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = lnrs2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class041/Code02_CordCoverMaxPoint.java b/公开课/class041/Code02_CordCoverMaxPoint.java new file mode 100644 index 0000000..97297c0 --- /dev/null +++ b/公开课/class041/Code02_CordCoverMaxPoint.java @@ -0,0 +1,88 @@ +package class041; + +import java.util.Arrays; + +public class Code02_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class041/Code03_MaxABSBetweenLeftAndRight.java b/公开课/class041/Code03_MaxABSBetweenLeftAndRight.java new file mode 100644 index 0000000..418dff6 --- /dev/null +++ b/公开课/class041/Code03_MaxABSBetweenLeftAndRight.java @@ -0,0 +1,72 @@ +package class041; + +public class Code03_MaxABSBetweenLeftAndRight { + + public static int maxABS1(int[] arr) { + int res = Integer.MIN_VALUE; + int maxLeft = 0; + int maxRight = 0; + for (int i = 0; i != arr.length - 1; i++) { + maxLeft = Integer.MIN_VALUE; + for (int j = 0; j != i + 1; j++) { + maxLeft = Math.max(arr[j], maxLeft); + } + maxRight = Integer.MIN_VALUE; + for (int j = i + 1; j != arr.length; j++) { + maxRight = Math.max(arr[j], maxRight); + } + res = Math.max(Math.abs(maxLeft - maxRight), res); + } + return res; + } + + public static int maxABS2(int[] arr) { + int[] lArr = new int[arr.length]; + int[] rArr = new int[arr.length]; + lArr[0] = arr[0]; + rArr[arr.length - 1] = arr[arr.length - 1]; + for (int i = 1; i < arr.length; i++) { + lArr[i] = Math.max(lArr[i - 1], arr[i]); + } + for (int i = arr.length - 2; i > -1; i--) { + rArr[i] = Math.max(rArr[i + 1], arr[i]); + } + int max = 0; + for (int i = 0; i < arr.length - 1; i++) { + max = Math.max(max, Math.abs(lArr[i] - rArr[i + 1])); + } + return max; + } + + public static int maxABS3(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(arr[i], max); + } + return max - Math.min(arr[0], arr[arr.length - 1]); + } + + public static int[] generateRandomArray(int length) { + int[] arr = new int[length]; + for (int i = 0; i != arr.length; i++) { + arr[i] = (int) (Math.random() * 1000) - 499; + } + return arr; + } + + public static void main(String[] args) { + int len = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len); + int ans1 = maxABS1(arr); + int ans2 = maxABS2(arr); + int ans3 = maxABS3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } +} diff --git a/公开课/class041/Code04_KM.java b/公开课/class041/Code04_KM.java new file mode 100644 index 0000000..a4e6c07 --- /dev/null +++ b/公开课/class041/Code04_KM.java @@ -0,0 +1,143 @@ +package class041; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code04_KM { + + public static int test(int[] arr, int k, int m) { + HashMap map = new HashMap<>(); + for (int num : arr) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + for (int num : map.keySet()) { + if (map.get(num) == k) { + return num; + } + } + return -1; + } + + public static HashMap map = new HashMap<>(); + // 请保证arr中,只有一种数出现了K次,其他数都出现了M次 + public static int onlyKTimes(int[] arr, int k, int m) { + if (map.size() == 0) { + mapCreater(map); + } + int[] t = new int[32]; + // t[0] 0位置的1出现了几个 + // t[i] i位置的1出现了几个 + for (int num : arr) { + while (num != 0) { + int rightOne = num & (-num); + t[map.get(rightOne)]++; + num ^= rightOne; + } + } + int ans = 0; + for (int i = 0; i < 32; i++) { + if (t[i] % m != 0) { + if (t[i] % m == k) { + ans |= (1 << i); + } else { + return -1; + } + } + } + if (ans == 0) { + int count = 0; + for (int num : arr) { + if (num == 0) { + count++; + } + } + if (count != k) { + return -1; + } + } + return ans; + } + + public static void mapCreater(HashMap map) { + int value = 1; + for (int i = 0; i < 32; i++) { + map.put(value, i); + value <<= 1; + } + } + + public static int[] randomArray(int maxKinds, int range, int k, int m) { + int ktimeNum = randomNumber(range); + // 真命天子出现的次数 + int times = Math.random() < 0.5 ? k : ((int) (Math.random() * (m - 1)) + 1); + // 2 + int numKinds = (int) (Math.random() * maxKinds) + 2; + // k * 1 + (numKinds - 1) * m + int[] arr = new int[times + (numKinds - 1) * m]; + int index = 0; + for (; index < times; index++) { + arr[index] = ktimeNum; + } + numKinds--; + HashSet set = new HashSet<>(); + set.add(ktimeNum); + while (numKinds != 0) { + int curNum = 0; + do { + curNum = randomNumber(range); + } while (set.contains(curNum)); + set.add(curNum); + numKinds--; + for (int i = 0; i < m; i++) { + arr[index++] = curNum; + } + } + // arr 填好了 + for (int i = 0; i < arr.length; i++) { + // i 位置的数,我想随机和j位置的数做交换 + int j = (int) (Math.random() * arr.length);// 0 ~ N-1 + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + // [-range, +range] + public static int randomNumber(int range) { + return ((int) (Math.random() * range) + 1) - ((int) (Math.random() * range) + 1); + } + + public static void main(String[] args) { + int kinds = 5; + int range = 30; + int testTime = 100000; + int max = 9; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * max) + 1; // a 1 ~ 9 + int b = (int) (Math.random() * max) + 1; // b 1 ~ 9 + int k = Math.min(a, b); + int m = Math.max(a, b); + // k < m + if (k == m) { + m++; + } + int[] arr = randomArray(kinds, range, k, m); + int ans1 = test(arr, k, m); + int ans2 = onlyKTimes(arr, k, m); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class042/Code02_ContainAllCharExactly.java b/公开课/class042/Code02_ContainAllCharExactly.java new file mode 100644 index 0000000..265acfe --- /dev/null +++ b/公开课/class042/Code02_ContainAllCharExactly.java @@ -0,0 +1,114 @@ +package class042; + +import java.util.Arrays; + +public class Code02_ContainAllCharExactly { + + public static int containExactly1(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] aim = a.toCharArray(); + Arrays.sort(aim); + String aimSort = String.valueOf(aim); + for (int L = 0; L < s.length(); L++) { + for (int R = L; R < s.length(); R++) { + char[] cur = s.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(aimSort)) { + return L; + } + } + } + return -1; + } + + public static int containExactly2(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] str = s.toCharArray(); + char[] aim = a.toCharArray(); + for (int L = 0; L <= str.length - aim.length; L++) { + if (isCountEqual(str, L, aim)) { + return L; + } + } + return -1; + } + + public static boolean isCountEqual(char[] str, int L, char[] aim) { + int[] count = new int[256]; + for (int i = 0; i < aim.length; i++) { + count[aim[i]]++; + } + for (int i = 0; i < aim.length; i++) { + if (count[str[L + i]]-- == 0) { + return false; + } + } + return true; + } + + public static int containExactly3(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] aim = a.toCharArray(); + int[] count = new int[256]; + for (int i = 0; i < aim.length; i++) { + count[aim[i]]++; + } + int M = aim.length; + char[] str = s.toCharArray(); + int inValidTimes = 0; + int R = 0; + for (; R < M; R++) { + if (count[str[R]]-- <= 0) { + inValidTimes++; + } + } + for (; R < str.length; R++) { + if (inValidTimes == 0) { + return R - M; + } + if (count[str[R]]-- <= 0) { + inValidTimes++; + } + if (count[str[R - M]]++ < 0) { + inValidTimes--; + } + } + return inValidTimes == 0 ? R - M : -1; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 20; + int aimMaxSize = 5; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + String aim = getRandomString(possibilities, aimMaxSize); + int ans1 = containExactly1(str, aim); + int ans2 = containExactly2(str, aim); + int ans3 = containExactly3(str, aim); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class042/Code03_FindHalfMajority.java b/公开课/class042/Code03_FindHalfMajority.java new file mode 100644 index 0000000..4ca6ea6 --- /dev/null +++ b/公开课/class042/Code03_FindHalfMajority.java @@ -0,0 +1,137 @@ +package class042; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class Code03_FindHalfMajority { + + public static int superWater(int[] arr) { + int candidate = 0; + int hp = 0; + for (int num : arr) { + if (hp == 0) { + candidate = num; + hp = 1; + } else if (num == candidate) { + hp++; + } else { + hp--; + } + } + if (hp == 0) { + return -1; + } + hp = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == candidate) { + hp++; + } + } + return hp > arr.length / 2 ? candidate : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = superWater(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + + public static void printKMajor(int[] arr, int K) { + if (K < 2) { + System.out.println("the value of K is invalid."); + return; + } + HashMap cands = new HashMap(); + for (int i = 0; i != arr.length; i++) { + if (cands.containsKey(arr[i])) { + cands.put(arr[i], cands.get(arr[i]) + 1); + } else { + if (cands.size() == K - 1) { + allCandsMinusOne(cands); + } else { + cands.put(arr[i], 1); + } + } + } + HashMap reals = getReals(arr, cands); + boolean hasPrint = false; + for (Entry set : cands.entrySet()) { + Integer key = set.getKey(); + if (reals.get(key) > arr.length / K) { + hasPrint = true; + System.out.print(key + " "); + } + } + System.out.println(hasPrint ? "" : "no such number."); + } + + public static void allCandsMinusOne(HashMap map) { + List removeList = new LinkedList(); + for (Entry set : map.entrySet()) { + Integer key = set.getKey(); + Integer value = set.getValue(); + if (value == 1) { + removeList.add(key); + } + map.put(key, value - 1); + } + for (Integer removeKey : removeList) { + map.remove(removeKey); + } + } + + public static HashMap getReals(int[] arr, HashMap cands) { + HashMap reals = new HashMap(); + for (int i = 0; i != arr.length; i++) { + int curNum = arr[i]; + if (cands.containsKey(curNum)) { + if (reals.containsKey(curNum)) { + reals.put(curNum, reals.get(curNum) + 1); + } else { + reals.put(curNum, 1); + } + } + } + return reals; + } + +} diff --git a/公开课/class043/Code01_AppleMinBags.java b/公开课/class043/Code01_AppleMinBags.java new file mode 100644 index 0000000..d437f78 --- /dev/null +++ b/公开课/class043/Code01_AppleMinBags.java @@ -0,0 +1,69 @@ +package class043; + +public class Code01_AppleMinBags { + + public static int minBag1(int apple) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + // 最多的8号袋开始试,apple / 8 + for (int max = (apple / 8); max >= 0; max--) { + int rest = apple - (max * 8); + if (rest % 6 == 0) { + return max + rest / 6; + } + } + return -1; + } + + public static int minBag2(int apple) { + if (apple < 18) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple == 6 || apple == 8) { + return 1; + } + if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } + return -1; + } + int team = (apple - 18) / 8; + return (apple & 1) == 0 ? (team + 3) : -1; + } + + public static int minBagAwesome(int apple) { + if (apple < 0 || (apple & 1) != 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple < 18) { + if (apple == 6 || apple == 8) { + return 1; + } else if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } else { + return -1; + } + } else { // apple >= 18 且 是偶数 + return (apple - 18) / 8 + 3; + } + } + + public static void main(String[] args) { + for (int apple = 0; apple <= 100; apple++) { + System.out.println(apple + " : " + minBag1(apple)); + } + + } + +} diff --git a/公开课/class043/Code02_SplitNumber.java b/公开课/class043/Code02_SplitNumber.java new file mode 100644 index 0000000..fe1b176 --- /dev/null +++ b/公开课/class043/Code02_SplitNumber.java @@ -0,0 +1,49 @@ +package class043; + +public class Code02_SplitNumber { + + public static boolean isSplit1(int num) { + if (num <= 2) { + return false; + } + // num > 2 + for (int start = 1; start < num; start++) { + int ans = start; + for (int next = start + 1; next < num; next++) { + if (ans + next > num) { + break; + } + if (ans + next == num) { + return true; + } + ans += next; + } + } + return false; + } + + public static boolean isSplit2(int num) { + // num > 0 + return num > 0 && (num & (num - 1)) != 0; + } + + public static void printNumberBinary(int num) { + for (int i = 31; i >= 0; i--) { + int status = (num & (1 << i)) != 0 ? 1 : 0; + System.out.print(status); + } + System.out.println(); + } + + public static void main(String[] args) { + int num = 128; // 0...0111 + printNumberBinary(num); + + printNumberBinary(num - 1); + +// for (int i = 0; i < 100; i++) { +// System.out.println(i + " : " + isSplit2(i)); +// } + } + +} diff --git a/公开课/class043/Code03_EatGrass.java b/公开课/class043/Code03_EatGrass.java new file mode 100644 index 0000000..176f2c8 --- /dev/null +++ b/公开课/class043/Code03_EatGrass.java @@ -0,0 +1,44 @@ +package class043; + +public class Code03_EatGrass { + + public static String whoWin(int grass) { + return process(grass, "先手"); + } + + // 只有两个选手,分别是字符串 "先手" "后手" + // 当前,轮到cur这个选手 + // 还剩rest份草 + // 返回谁赢,"先手"先手赢 "后手"后手赢 + public static String process(int rest, String cur) { + String against = cur.equals("先手") ? "后手" : "先手"; + if (rest == 0) { + return against; + } + // 还剩下不止0份草 1 4 16 64 rest + int choose = 1; + while (choose <= rest) { + int nextRest = rest - choose; + if (process(nextRest, against).equals(cur)) { + return cur; + } + choose *= 4; + } + return against; + } + + public static String winner2(int n) { + if (n % 5 == 0 || n % 5 == 2) { + return "后手"; + } else { + return "先手"; + } + } + + public static void main(String[] args) { + for (int i = 0; i <= 50; i++) { + System.out.println(i + " : " + whoWin(i)); + } + } + +} diff --git a/公开课/class043/Code04_FindOddTimesNumber.java b/公开课/class043/Code04_FindOddTimesNumber.java new file mode 100644 index 0000000..abf064f --- /dev/null +++ b/公开课/class043/Code04_FindOddTimesNumber.java @@ -0,0 +1,18 @@ +package class043; + +public class Code04_FindOddTimesNumber { + + public static int oddTimesNumber(int[] arr) { + int xor = 0; + for (int num : arr) { + xor ^= num; + } + return xor; + } + + public static void main(String[] args) { + int[] arr = { 3, 4, 3, 4, 2, 1, 4, 3, 3, 2, 4, 2, 3, 6, 2, 3, 6, 1, 6 }; + System.out.println(oddTimesNumber(arr)); + } + +} diff --git a/公开课/class044/Code01_SubArrayMaxSum.java b/公开课/class044/Code01_SubArrayMaxSum.java new file mode 100644 index 0000000..ed1234f --- /dev/null +++ b/公开课/class044/Code01_SubArrayMaxSum.java @@ -0,0 +1,95 @@ +package class044; + +// 本题测试链接 : https://leetcode.com/problems/maximum-subarray/ +public class Code01_SubArrayMaxSum { + + public static int maxSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + // 0 arr[0..0] + dp[0] = arr[0]; + int max = dp[0]; + for (int end = 1; end < N; end++) { + int p1 = arr[end]; + int p2 = dp[end - 1] + arr[end]; + dp[end] = Math.max(p1, p2); + max = Math.max(max, dp[end]); + } + return max; + } + + + public static int maxSubArray5(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0];// 上一步的dp值 pre dp[0] + int max = pre; + for (int end = 1; end < N; end++) { + pre = Math.max(arr[end], pre + arr[end]); + max = Math.max(max, pre); + } + return max; + } + + + + + + + + + public static int maxSubArray1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + max = Math.max(max, sum); + } + } + return max; + } + + public static int maxSubArray2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] dp = new int[arr.length]; + dp[0] = arr[0]; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + } + int max = Integer.MIN_VALUE; + for (int i = 0; i < dp.length; i++) { + max = Math.max(max, dp[i]); + } + return max; + } + + public static int maxSubArray3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int pre = arr[0]; + int max = pre; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], pre + arr[i]); + max = Math.max(max, pre); + } + return max; + } + +} diff --git a/公开课/class044/Code02_MaxSumFollowUp.java b/公开课/class044/Code02_MaxSumFollowUp.java new file mode 100644 index 0000000..c64d71b --- /dev/null +++ b/公开课/class044/Code02_MaxSumFollowUp.java @@ -0,0 +1,119 @@ +package class044; + +public class Code02_MaxSumFollowUp { + + public static int maxSumSubMatrix(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return 0; + } + int N = matrix.length; + int M = matrix[0].length; + // 0..0 0..1 0..2 0..N-1 + // 1..1 + int max = Integer.MIN_VALUE; + for (int s = 0; s < N; s++) { + int[] arr = new int[M]; + for (int e = s; e < N; e++) { + for (int i = 0; i < M; i++) { + arr[i] += matrix[e][i]; + } + // arr 被加工好了 + max = Math.max(max, maxSumSubArray(arr)); + } + } + return max; + } + + public static int maxSumSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0];// 上一步的dp值 pre dp[0] + int max = pre; + for (int end = 1; end < N; end++) { + pre = Math.max(arr[end], pre + arr[end]); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSum1(int[][] matrix) { + int n = matrix.length; + int m = matrix[0].length; + int max = Integer.MIN_VALUE; + for (int ai = 0; ai < n; ai++) { + for (int aj = 0; aj < m; aj++) { + for (int bi = ai; bi < n; bi++) { + for (int bj = aj; bj < m; bj++) { + max = Math.max(max, sum(matrix, ai, aj, bi, bj)); + } + } + } + } + return max; + } + + public static int sum(int[][] matrix, int ai, int aj, int bi, int bj) { + int sum = 0; + for (int i = ai; i <= bi; i++) { + for (int j = aj; j <= bj; j++) { + sum += matrix[i][j]; + } + } + return sum; + } + + public static int maxSum2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + int[] s = null; + for (int i = 0; i != matrix.length; i++) { + s = new int[matrix[0].length]; + for (int j = i; j != matrix.length; j++) { + cur = 0; + for (int k = 0; k != s.length; k++) { + s[k] += matrix[j][k]; + cur += s[k]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + } + } + return max; + } + + public static int[][] generateMatrix(int N, int M, int V) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + } + return matrix; + } + + public static void main(String[] args) { + int N = 20; + int M = 20; + int V = 100; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[][] matrix = generateMatrix(N, M, V); + int ans1 = maxSum1(matrix); + int ans2 = maxSum2(matrix); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class044/Code03_MaxSumNoAdjoin.java b/公开课/class044/Code03_MaxSumNoAdjoin.java new file mode 100644 index 0000000..63834d8 --- /dev/null +++ b/公开课/class044/Code03_MaxSumNoAdjoin.java @@ -0,0 +1,97 @@ +package class044; + +public class Code03_MaxSumNoAdjoin { + + public static int maxSumSubseqNoAdjoin(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + if (arr.length == 2) { + return Math.max(arr[0], arr[1]); + } + // arr不止两个数 + int N = arr.length; + int[] dp = new int[N]; + // dp[i] 0...i 不能选相邻数的情况下,子序列的最大累加和 + // dp[N-1] 0..N-1 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i]; + int p3 = arr[i] + dp[i - 2]; + dp[i] = Math.max(Math.max(p1, p2), p3); + } + return dp[N - 1]; + } + + public static int subSqeMaxSumNoNext(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int[] dp = new int[arr.length]; + // dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < arr.length; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i] + Math.max(dp[i - 2], 0); + dp[i] = Math.max(p1, p2); + } + return dp[arr.length - 1]; + } + + // 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 思路: + // 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类: + // 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1] + // 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候 + // + // 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i] + // 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候 + // + // 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2] + // 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过 + // + // 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] } + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (N == 1) { + return arr[0]; + } + if (N == 2) { + return Math.max(arr[0], arr[1]); + } + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]); + } + return dp[N - 1]; + } + + 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) - (V >> 1); + } + return ans; + } + + public static void main(String[] args) { + + } + +} diff --git a/公开课/class045/Code01_MaxLeftMaxRight.java b/公开课/class045/Code01_MaxLeftMaxRight.java new file mode 100644 index 0000000..66384b2 --- /dev/null +++ b/公开课/class045/Code01_MaxLeftMaxRight.java @@ -0,0 +1,80 @@ +package class045; + +public class Code01_MaxLeftMaxRight { + + // 笨办法,但是好想 + public static int solution1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int ans = Integer.MIN_VALUE; + for (int leftEnd = 0; leftEnd < N - 1; leftEnd++) { + int leftMax = arr[0]; + for (int i = 1; i <= leftEnd; i++) { + leftMax = Math.max(leftMax, arr[i]); + } + int rightMax = arr[leftEnd + 1]; + for (int i = leftEnd + 2; i < N; i++) { + rightMax = Math.max(rightMax, arr[i]); + } + ans = Math.max(ans, Math.abs(leftMax - rightMax)); + } + return ans; + } + + // 好办法,是我们真正想测试的 + public static int solution2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int max = arr[0]; + for (int i = 1; i < N; i++) { + max = Math.max(max, arr[i]); + } + return max - Math.min(arr[0], arr[N - 1]); + } + + public static int[] randomArray(int maxLen, int maxValue) { + int len = (int) (Math.random() * (maxLen + 1)); + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + public static void main(String[] args) { + // 随机数组的最大长度 + int maxLen = 6; + // 随机数组值的范围 + int maxValue = 30; + // 测试次数 + int testTime = 3000000; + System.out.println("如果没有错误信息打印,说明所有测试通过"); + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + // 随机得到一个数组,长度也随机,每个值也随机 + int[] arr = randomArray(maxLen, maxValue); + // 用方法1跑出答案1 + int ans1 = solution1(arr); + // 用方法2跑出答案2 + int ans2 = solution2(arr); + // 如果答案1和答案2不一样,提示出错了 + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1 + " , " + ans2); + + for (int k = 0; k < arr.length; k++) { + System.out.print(arr[k] + " "); + } + System.out.println(); + + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class045/Code02_RotateImage.java b/公开课/class045/Code02_RotateImage.java new file mode 100644 index 0000000..bf6cd01 --- /dev/null +++ b/公开课/class045/Code02_RotateImage.java @@ -0,0 +1,52 @@ +package class045; + +public class Code02_RotateImage { + + public static void rotate(int[][] matrix) { + int a = 0; + int b = 0; + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { // a>=c + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + // 在二维数组m中,左上角点(a,b), 右下角点(c,d) + // 只在规定好的框上,顺时针转好 + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + + public static void printMatrix(int[][] m) { + int N = m.length; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 } }; + printMatrix(matrix); + System.out.println("========"); + + rotate(matrix); + printMatrix(matrix); + System.out.println("========"); + + } + +} diff --git a/公开课/class045/Code03_ZigZagPrintMatrix.java b/公开课/class045/Code03_ZigZagPrintMatrix.java new file mode 100644 index 0000000..47c4f2c --- /dev/null +++ b/公开课/class045/Code03_ZigZagPrintMatrix.java @@ -0,0 +1,48 @@ +package class045; + +public class Code03_ZigZagPrintMatrix { + + public static void printMatrixZigZag(int[][] m) { + // (a,b) A 先往右,再往下 + int a = 0; + int b = 0; + // (c,d) B 先往下,在往右 + int c = 0; + int d = 0; + int er = m.length - 1; + int ec = m[0].length - 1; + boolean fromUp = false; + while (a != er + 1) { + printLevel(m, a, b, c, d, fromUp); + // A 先往右,再往下 + a = b == ec ? a + 1 : a; + b = b == ec ? b : b + 1; + // B 先往下,在往右 + d = c == er ? d + 1 : d; + c = c == er ? c : c + 1; + fromUp = !fromUp; + } + } + + public static void printLevel(int[][] m, int a, int b, int c, int d, boolean f) { + if (f) { + while (a != c + 1) { + System.out.print(m[a++][b--] + " "); + } + } else { + while (c != a - 1) { + System.out.print(m[c--][d++] + " "); + } + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } }; + printMatrixZigZag(matrix); + + } + +} diff --git a/公开课/class045/Code04_MissingNumber.java b/公开课/class045/Code04_MissingNumber.java new file mode 100644 index 0000000..b5f5cab --- /dev/null +++ b/公开课/class045/Code04_MissingNumber.java @@ -0,0 +1,29 @@ +package class045; + +// 测试链接:https://leetcode.com/problems/first-missing-positive/ +public class Code04_MissingNumber { + + public static int firstMissingPositive(int[] arr) { + // l是盯着的位置 + // 0~l-1 + int l = 0; + int r = arr.length; + while (l != r) { + if (arr[l] == l + 1) { + l++; + } else if (arr[l] <= l || arr[l] > r || arr[arr[l] - 1] == arr[l]) { + swap(arr, l, --r); + } else { + swap(arr, l, arr[l] - 1); + } + } + return l + 1; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/公开课/class046/Code01_MergeKSortedLists.java b/公开课/class046/Code01_MergeKSortedLists.java new file mode 100644 index 0000000..736b494 --- /dev/null +++ b/公开课/class046/Code01_MergeKSortedLists.java @@ -0,0 +1,52 @@ +package class046; + +import java.util.Comparator; +import java.util.PriorityQueue; + +// 本题测试链接 : https://leetcode.com/problems/merge-k-sorted-lists/ +public class Code01_MergeKSortedLists { + + public static class ListNode { + public int val; + public ListNode next; + } + + public static class ListNodeComparator implements Comparator { + + @Override + public int compare(ListNode o1, ListNode o2) { + return o1.val - o2.val; + } + + } + + public static ListNode mergeKLists(ListNode[] lists) { + if (lists == null) { + return null; + } + PriorityQueue heap = new PriorityQueue<>(new ListNodeComparator()); + for (int i = 0; i < lists.length; i++) { + if (lists[i] != null) { + heap.add(lists[i]); + } + } + if (heap.isEmpty()) { + return null; + } + ListNode head = heap.poll(); + ListNode pre = head; + if (pre.next != null) { + heap.add(pre.next); + } + while (!heap.isEmpty()) { + ListNode cur = heap.poll(); + pre.next = cur; + pre = cur; + if (cur.next != null) { + heap.add(cur.next); + } + } + return head; + } + +} diff --git a/公开课/class046/Code02_LongestNoRepeatSubstring.java b/公开课/class046/Code02_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..f0fe821 --- /dev/null +++ b/公开课/class046/Code02_LongestNoRepeatSubstring.java @@ -0,0 +1,97 @@ +package class046; + +public class Code02_LongestNoRepeatSubstring { + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + + public static int max(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] map = new int[256]; + for (int i = 0; i < 255; i++) { + map[i] = -1; + } + map[str[0]] = 0; + int pre = 1; + int max = 1; + for (int i = 1; i < N; i++) { + pre = Math.min(i - map[str[i]], pre + 1); + max = Math.max(max, pre); + map[str[i]] = i; + } + return max; + } + + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = lnrs2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class046/Code03_CordCoverMaxPoint.java b/公开课/class046/Code03_CordCoverMaxPoint.java new file mode 100644 index 0000000..66e3040 --- /dev/null +++ b/公开课/class046/Code03_CordCoverMaxPoint.java @@ -0,0 +1,88 @@ +package class046; + +import java.util.Arrays; + +public class Code03_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class047/Code01_Water.java b/公开课/class047/Code01_Water.java new file mode 100644 index 0000000..db6b1a8 --- /dev/null +++ b/公开课/class047/Code01_Water.java @@ -0,0 +1,120 @@ +package class047; + +public class Code01_Water { + + // 本题测试链接:https://leetcode.com/problems/trapping-rain-water/ + // 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + // O位置无水量 + // 0~N-1,N-1无水量 + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class047/Code02_RandToRand.java b/公开课/class047/Code02_RandToRand.java new file mode 100644 index 0000000..c3487e0 --- /dev/null +++ b/公开课/class047/Code02_RandToRand.java @@ -0,0 +1,137 @@ +package class047; + +public class Code02_RandToRand { + + // 此函数只能用,不能修改 + // 等概率返回1~5 黑盒! + public static int f() { + return (int) (Math.random() * 5) + 1; + } + + // 等概率得到0和1 + public static int a() { + int ans = 0; + do { + ans = f(); + } while (ans == 3); + // 1 2 4 5 + return ans < 3 ? 0 : 1; + } + + // 等概率返回0~6 + public static int b() { + int ans = 0; + do { + // 0/1 << 2 0/1 << 1 0/1 + ans = (a() << 2) + (a() << 1) + a(); + } while (ans == 7); + // 0 1 2 3 4 5 6 + return ans; + } + + // 等概率返回1~7 + public static int c() { + return b() + 1; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final int min; + private final int max; + + // 初始化时请一定不要让mi==ma + public RandomBox(int mi, int ma) { + min = mi; + max = ma; + } + + // 13 ~ 17 + // 13 + [0,4] + public int random() { + return min + (int) (Math.random() * (max - min + 1)); + } + + public int min() { + return min; + } + + public int max() { + return max; + } + } + + // 利用条件RandomBox,如何等概率返回0和1 + public static int rand01(RandomBox randomBox) { + int min = randomBox.min(); + int max = randomBox.max(); + // min ~ max + int size = max - min + 1; + // size是不是奇数,odd 奇数 + boolean odd = (size & 1) != 0; + int mid = size / 2; + int ans = 0; + do { + ans = randomBox.random() - min; + } while (odd && ans == mid); + return ans < mid ? 0 : 1; + } + + // 给你一个RandomBox,这是唯一能借助的随机机制 + // 等概率返回from~to范围上任何一个数 + // 要求from<=to + public static int random(RandomBox randomBox, int from, int to) { + if (from == to) { + return from; + } + // 3 ~ 9 + // 0 ~ 6 + // 0 ~ range + int range = to - from; + int num = 1; + // 求0~range需要几个2进制位 + while ((1 << num) - 1 < range) { + num++; + } + + // 我们一共需要num位 + // 最终的累加和,首先+0位上是1还是0,1位上是1还是0,2位上是1还是0... + int ans = 0; + do { + ans = 0; + for (int i = 0; i < num; i++) { + ans |= (rand01(randomBox) << i); + } + } while (ans > range); + return ans + from; + } + + public static void main(String[] args) { + + int[] count = new int[8]; // 0 1 2 .. 7 + int testTime = 10000000; + for (int i = 0; i < testTime; i++) { + int ans = c(); + count[ans]++; + } + + for (int i = 0; i < 8; i++) { + System.out.println(i + " : " + count[i]); + } + +// RandomBox randomBox = new RandomBox(3, 9); +// int from = 17; +// int to = 29; +// int[] ans = new int[to + 1]; +// int testTime1 = 1000000; +// for (int i = 0; i < testTime1; i++) { +// ans[random(randomBox, from, to)]++; +// } +// for (int i = 0; i < ans.length; i++) { +// System.out.println(ans[i]); +// } +// System.out.println("=========="); + + } + +} diff --git a/公开课/class047/Code03_EqualProbabilityRandom.java b/公开课/class047/Code03_EqualProbabilityRandom.java new file mode 100644 index 0000000..f99826b --- /dev/null +++ b/公开课/class047/Code03_EqualProbabilityRandom.java @@ -0,0 +1,67 @@ +package class047; + +public class Code03_EqualProbabilityRandom { + + // 内部内容不可见 + public static int f() { + return Math.random() < 0.8 ? 0 : 1; + } + + // 等概率返回0和1 + public static int g() { + int first = 0; + do { + first = f(); // 0 1 + } while (first == f()); + return first; + } + + // 这个结构是唯一的随机机制 + // 你只能初始化并使用,不可修改 + public static class RandomBox { + private final double p; + + // 初始化时请一定满足:0 < zeroP < 1 + public RandomBox(double zeroP) { + p = zeroP; + } + + public int random() { + return Math.random() < p ? 0 : 1; + } + + } + + // 底层依赖一个以p概率返回0,以1-p概率返回1的随机函数rand01p + // 如何加工出等概率返回0和1的函数 + public static int rand01(RandomBox randomBox) { + int num; + do { + num = randomBox.random(); + } while (num == randomBox.random()); + return num; + } + + public static void main(String[] args) { + int[] count = new int[2];// 0 1 + for (int i = 0; i < 1000000; i++) { + int ans = g(); + count[ans]++; + } + System.out.println(count[0] + " , " + count[1]); + +// double zeroP = 0.88; +// RandomBox randomBox = new RandomBox(zeroP); +// +// int testTime = 10000000; +// int count = 0; +// for (int i = 0; i < testTime; i++) { +// if (rand01(randomBox) == 0) { +// count++; +// } +// } +// System.out.println((double) count / (double) testTime); + + } + +} diff --git a/公开课/class047/Code04_EvenTimesOddTimes.java b/公开课/class047/Code04_EvenTimesOddTimes.java new file mode 100644 index 0000000..a185fda --- /dev/null +++ b/公开课/class047/Code04_EvenTimesOddTimes.java @@ -0,0 +1,52 @@ +package class047; + +public class Code04_EvenTimesOddTimes { + + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + public static void printOddTimesNum2(int[] arr) { + // arr中,a和b出现了奇数次,其他数都是偶数次 + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor : 001100100 + // rightOne : 000000100 + int rightOne = eor & (~eor + 1); + int onlyOne = 0; // eor' + for (int i = 0; i < arr.length; i++) { + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + // eor' = a or b + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + public static int bit1counts(int N) { + int count = 0; + while (N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + public static void main(String[] args) { + int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + } + +} diff --git a/公开课/class048/Code01_SubArrayMaxSum.java b/公开课/class048/Code01_SubArrayMaxSum.java new file mode 100644 index 0000000..1771b0d --- /dev/null +++ b/公开课/class048/Code01_SubArrayMaxSum.java @@ -0,0 +1,137 @@ +package class048; + +// 本题测试链接 : https://leetcode.com/problems/maximum-subarray/ +public class Code01_SubArrayMaxSum { + + public static int maxSubarraySum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int ans = 0; + for (int L = 0; L < arr.length; L++) { // 枚举所有子数组的开头 + for (int R = L; R < arr.length; R++) { + // arr[L...R] 求这个子数组的累加和 + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + // sum -> arr[L..R] + ans = Math.max(ans, sum); + } + } + return ans; + } + + public static int maxSubarraySum2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; // dp[i] : 子数组必须以i位置的数结尾的情况下,获得的最大累加和 + dp[0] = arr[0];// arr[...0] + int ans = dp[0]; + for (int i = 1; i < N; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + ans = Math.max(ans, dp[i]); + } + return ans; + } + + public static int maxSubarraySum3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0];// pre -> dp[0] + int ans = arr[0]; + for (int i = 1; i < N; i++) { + pre = Math.max(arr[i], pre + arr[i]); + ans = Math.max(ans, pre); + } + return ans; + } + + public static int maxSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + // 0 arr[0..0] + dp[0] = arr[0]; + int max = dp[0]; + for (int end = 1; end < N; end++) { + int p1 = arr[end]; + int p2 = dp[end - 1] + arr[end]; + dp[end] = Math.max(p1, p2); + max = Math.max(max, dp[end]); + } + return max; + } + + public static int maxSubArray5(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0];// 上一步的dp值 pre dp[0] + int max = pre; + for (int end = 1; end < N; end++) { + pre = Math.max(arr[end], pre + arr[end]); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSubArray1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + max = Math.max(max, sum); + } + } + return max; + } + + public static int maxSubArray2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] dp = new int[arr.length]; + dp[0] = arr[0]; + for (int i = 1; i < arr.length; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + } + int max = Integer.MIN_VALUE; + for (int i = 0; i < dp.length; i++) { + max = Math.max(max, dp[i]); + } + return max; + } + + public static int maxSubArray3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int pre = arr[0]; + int max = pre; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], pre + arr[i]); + max = Math.max(max, pre); + } + return max; + } + +} diff --git a/公开课/class048/Code02_MaxSumFollowUp.java b/公开课/class048/Code02_MaxSumFollowUp.java new file mode 100644 index 0000000..ac6dc16 --- /dev/null +++ b/公开课/class048/Code02_MaxSumFollowUp.java @@ -0,0 +1,119 @@ +package class048; + +public class Code02_MaxSumFollowUp { + + public static int maxSumSubMatrix(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return 0; + } + int N = matrix.length; + int M = matrix[0].length; + // 0..0 0..1 0..2 0..N-1 + // 1..1 + int max = Integer.MIN_VALUE; + for (int s = 0; s < N; s++) { + int[] arr = new int[M]; + for (int e = s; e < N; e++) { + for (int i = 0; i < M; i++) { + arr[i] += matrix[e][i]; + } + // arr 被加工好了 + max = Math.max(max, maxSumSubArray(arr)); + } + } + return max; + } + + public static int maxSumSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0];// 上一步的dp值 pre dp[0] + int max = pre; + for (int end = 1; end < N; end++) { + pre = Math.max(arr[end], pre + arr[end]); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSum1(int[][] matrix) { + int n = matrix.length; + int m = matrix[0].length; + int max = Integer.MIN_VALUE; + for (int ai = 0; ai < n; ai++) { + for (int aj = 0; aj < m; aj++) { + for (int bi = ai; bi < n; bi++) { + for (int bj = aj; bj < m; bj++) { + max = Math.max(max, sum(matrix, ai, aj, bi, bj)); + } + } + } + } + return max; + } + + public static int sum(int[][] matrix, int ai, int aj, int bi, int bj) { + int sum = 0; + for (int i = ai; i <= bi; i++) { + for (int j = aj; j <= bj; j++) { + sum += matrix[i][j]; + } + } + return sum; + } + + public static int maxSum2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + int[] s = null; + for (int i = 0; i != matrix.length; i++) { + s = new int[matrix[0].length]; + for (int j = i; j != matrix.length; j++) { + cur = 0; + for (int k = 0; k != s.length; k++) { + s[k] += matrix[j][k]; + cur += s[k]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + } + } + return max; + } + + public static int[][] generateMatrix(int N, int M, int V) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * V) - (int) (Math.random() * V); + } + } + return matrix; + } + + public static void main(String[] args) { + int N = 20; + int M = 20; + int V = 100; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[][] matrix = generateMatrix(N, M, V); + int ans1 = maxSum1(matrix); + int ans2 = maxSum2(matrix); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class048/Code03_MaxSumNoAdjoin.java b/公开课/class048/Code03_MaxSumNoAdjoin.java new file mode 100644 index 0000000..a1aed7b --- /dev/null +++ b/公开课/class048/Code03_MaxSumNoAdjoin.java @@ -0,0 +1,118 @@ +package class048; + +public class Code03_MaxSumNoAdjoin { + + public static int max1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int N = arr.length; + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]);// arr[0..1] + for (int i = 2; i < N; i++) { + // dp[i] + int p1 = arr[i]; + int p2 = dp[i - 1]; + int p3 = dp[i - 2] + arr[i]; + dp[i] = Math.max(Math.max(p1, p2), p3); + } + return dp[N - 1]; + } + + public static int maxSumSubseqNoAdjoin(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + if (arr.length == 2) { + return Math.max(arr[0], arr[1]); + } + // arr不止两个数 + int N = arr.length; + int[] dp = new int[N]; + // dp[i] 0...i 不能选相邻数的情况下,子序列的最大累加和 + // dp[N-1] 0..N-1 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i]; + int p3 = arr[i] + dp[i - 2]; + dp[i] = Math.max(Math.max(p1, p2), p3); + } + return dp[N - 1]; + } + + public static int subSqeMaxSumNoNext(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int[] dp = new int[arr.length]; + // dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < arr.length; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i] + Math.max(dp[i - 2], 0); + dp[i] = Math.max(p1, p2); + } + return dp[arr.length - 1]; + } + + // 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 思路: + // 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类: + // 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1] + // 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候 + // + // 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i] + // 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候 + // + // 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2] + // 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过 + // + // 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] } + public static int maxSum(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (N == 1) { + return arr[0]; + } + if (N == 2) { + return Math.max(arr[0], arr[1]); + } + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]); + } + return dp[N - 1]; + } + + 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) - (V >> 1); + } + return ans; + } + + public static void main(String[] args) { + + } + +} diff --git a/公开课/class049/Code01_AppleMinBags.java b/公开课/class049/Code01_AppleMinBags.java new file mode 100644 index 0000000..11632cb --- /dev/null +++ b/公开课/class049/Code01_AppleMinBags.java @@ -0,0 +1,114 @@ +package class049; + +public class Code01_AppleMinBags { + + public static int minBags1(int apple) { + if (apple < 0) { + return -1; + } + // apple 奇数 -1 + if ((apple & 1) != 0) { + return -1; + } + for (int bag8 = apple / 8; bag8 >= 0; bag8--) { + int already = (bag8 << 3); + int rest = apple - already; + if (rest % 6 == 0) { + return bag8 + rest / 6; + } + } + return -1; + } + + public static int minBags2(int apple) { + if (apple < 0) { + return -1; + } + // apple 奇数 -1 + if ((apple & 1) != 0) { + return -1; + } + if (apple == 0) { + return 0; + } + // apple必为偶数 + if (apple < 18) { + if (apple == 6 || apple == 8) { + return 1; + } + if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } + return -1; + } + // apple >= 18; + int rest = apple - 18; + int i = rest / 8; + return i + 3; + } + + public static int minBag1(int apple) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + // 最多的8号袋开始试,apple / 8 + for (int max = (apple / 8); max >= 0; max--) { + int rest = apple - (max * 8); + if (rest % 6 == 0) { + return max + rest / 6; + } + } + return -1; + } + + public static int minBag2(int apple) { + if (apple < 18) { + if (apple < 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple == 6 || apple == 8) { + return 1; + } + if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } + return -1; + } + int team = (apple - 18) / 8; + return (apple & 1) == 0 ? (team + 3) : -1; + } + + public static int minBagAwesome(int apple) { + if (apple < 0 || (apple & 1) != 0) { + return -1; + } + if (apple == 0) { + return 0; + } + if (apple < 18) { + if (apple == 6 || apple == 8) { + return 1; + } else if (apple == 12 || apple == 14 || apple == 16) { + return 2; + } else { + return -1; + } + } else { // apple >= 18 且 是偶数 + return (apple - 18) / 8 + 3; + } + } + + public static void main(String[] args) { + for (int apple = 0; apple <= 100; apple++) { + System.out.println(apple + " : " + minBags1(apple)); + } + + } + +} diff --git a/公开课/class049/Code02_SplitNumber.java b/公开课/class049/Code02_SplitNumber.java new file mode 100644 index 0000000..0d46fb3 --- /dev/null +++ b/公开课/class049/Code02_SplitNumber.java @@ -0,0 +1,48 @@ +package class049; + +public class Code02_SplitNumber { + + public static boolean isSplit1(int num) { + if (num <= 2) { + return false; + } + for (int start = 1; start < num; start++) { // 开始的最小数字 + int ans = start; + for (int next = start + 1; next < num; next++) { + if (ans + next > num) { + break; + } + if (ans + next == num) { + return true; + } + ans += next; + } + } + return false; + } + + public static boolean isSplit2(int num) { + // num > 0 + return num > 0 && (num & (num - 1)) != 0; + } + + public static void printNumberBinary(int num) { + for (int i = 31; i >= 0; i--) { + int status = (num & (1 << i)) != 0 ? 1 : 0; + System.out.print(status); + } + System.out.println(); + } + + public static void main(String[] args) { +// int num = 128; // 0...0111 +// printNumberBinary(num); +// +// printNumberBinary(num - 1); + + for (int i = 0; i < 100; i++) { + System.out.println(i + " : " + isSplit1(i)); + } + } + +} diff --git a/公开课/class049/Code03_EatGrass.java b/公开课/class049/Code03_EatGrass.java new file mode 100644 index 0000000..faf11de --- /dev/null +++ b/公开课/class049/Code03_EatGrass.java @@ -0,0 +1,41 @@ +package class049; + +public class Code03_EatGrass { + + public static String whoWin(int N) { + return who(N, "先手"); + } + + // int rest : 剩下多少草 + // String cur : "先手"、"后手" 表示,当前轮到谁 + // 返回:"先手"、"后手",最终谁会赢 + public static String who(int rest, String cur) { + if (rest == 0) { + return cur.equals("先手") ? "后手" : "先手"; + } + // 接下来,rest > 0, cur 将穷尽一切努力! + int pick = 1; + while (pick <= rest) { + if (cur.equals(who(rest - pick, cur.equals("先手") ? "后手" : "先手"))) { + return cur; + } + pick *= 4; + } + return cur.equals("先手") ? "后手" : "先手"; + } + + public static String winner2(int n) { + if (n % 5 == 0 || n % 5 == 2) { + return "后手"; + } else { + return "先手"; + } + } + + public static void main(String[] args) { + for (int i = 0; i <= 50; i++) { + System.out.println(i + " : " + whoWin(i)); + } + } + +} diff --git a/公开课/class050/Code01_HowManyTypes.java b/公开课/class050/Code01_HowManyTypes.java new file mode 100644 index 0000000..37fac50 --- /dev/null +++ b/公开课/class050/Code01_HowManyTypes.java @@ -0,0 +1,83 @@ +package class050; + +import java.util.HashSet; + +public class Code01_HowManyTypes { + + /* + * 只由小写字母(a~z)组成的一批字符串,都放在字符类型的数组String[] arr中, + * 如果其中某两个字符串,所含有的字符种类完全一样,就将两个字符串算作一类 比如:baacba和bac就算作一类 + * 虽然长度不一样,但是所含字符的种类完全一样(a、b、c) 返回arr中有多少类? + * + */ + + public static int types1(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + boolean[] map = new boolean[26]; + for (int i = 0; i < chs.length; i++) { + map[chs[i] - 'a'] = true; + } + String key = ""; + for (int i = 0; i < 26; i++) { + if (map[i]) { + key += String.valueOf((char) (i + 'a')); + } + } + types.add(key); + } + return types.size(); + } + + public static int types2(String[] arr) { + // 放整型的set + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + int key = 0; + for (int i = 0; i < chs.length; i++) { + key |= (1 << (chs[i] - 'a')); + } + types.add(key); + } + return types.size(); + } + + // for test + public static String[] getRandomStringArray(int possibilities, int strMaxSize, int arrMaxSize) { + String[] ans = new String[(int) (Math.random() * arrMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = getRandomString(possibilities, strMaxSize); + } + return ans; + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { +// int possibilities = 5; +// int strMaxSize = 10; +// int arrMaxSize = 100; +// int testTimes = 500000; +// System.out.println("test begin, test time : " + testTimes); +// for (int i = 0; i < testTimes; i++) { +// String[] arr = getRandomStringArray(possibilities, strMaxSize, arrMaxSize); +// int ans1 = types1(arr); +// int ans2 = types2(arr); +// if (ans1 != ans2) { +// System.out.println("Oops!"); +// } +// } +// System.out.println("test finish"); + + } + +} diff --git a/公开课/class050/Code02_CordCoverMaxPoint.java b/公开课/class050/Code02_CordCoverMaxPoint.java new file mode 100644 index 0000000..9b63b87 --- /dev/null +++ b/公开课/class050/Code02_CordCoverMaxPoint.java @@ -0,0 +1,88 @@ +package class050; + +import java.util.Arrays; + +public class Code02_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class050/Code03_FindHalfMajority.java b/公开课/class050/Code03_FindHalfMajority.java new file mode 100644 index 0000000..59ce2ac --- /dev/null +++ b/公开课/class050/Code03_FindHalfMajority.java @@ -0,0 +1,138 @@ +package class050; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class Code03_FindHalfMajority { + + public static int superWater(int[] arr) { + int candidate = 0; + int hp = 0; + for (int num : arr) { + if (hp == 0) { + candidate = num; + hp = 1; + } else if (num == candidate) { + hp++; + } else { + hp--; + } + } + if (hp == 0) { + return -1; + } + // hp!=0 , candidate + hp = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == candidate) { + hp++; + } + } + return hp > arr.length / 2 ? candidate : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = superWater(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + + public static void printKMajor(int[] arr, int K) { + if (K < 2) { + System.out.println("the value of K is invalid."); + return; + } + HashMap cands = new HashMap(); + for (int i = 0; i != arr.length; i++) { + if (cands.containsKey(arr[i])) { + cands.put(arr[i], cands.get(arr[i]) + 1); + } else { + if (cands.size() == K - 1) { + allCandsMinusOne(cands); + } else { + cands.put(arr[i], 1); + } + } + } + HashMap reals = getReals(arr, cands); + boolean hasPrint = false; + for (Entry set : cands.entrySet()) { + Integer key = set.getKey(); + if (reals.get(key) > arr.length / K) { + hasPrint = true; + System.out.print(key + " "); + } + } + System.out.println(hasPrint ? "" : "no such number."); + } + + public static void allCandsMinusOne(HashMap map) { + List removeList = new LinkedList(); + for (Entry set : map.entrySet()) { + Integer key = set.getKey(); + Integer value = set.getValue(); + if (value == 1) { + removeList.add(key); + } + map.put(key, value - 1); + } + for (Integer removeKey : removeList) { + map.remove(removeKey); + } + } + + public static HashMap getReals(int[] arr, HashMap cands) { + HashMap reals = new HashMap(); + for (int i = 0; i != arr.length; i++) { + int curNum = arr[i]; + if (cands.containsKey(curNum)) { + if (reals.containsKey(curNum)) { + reals.put(curNum, reals.get(curNum) + 1); + } else { + reals.put(curNum, 1); + } + } + } + return reals; + } + +} diff --git a/公开课/class050/Code04_ZigZagPrintMatrix.java b/公开课/class050/Code04_ZigZagPrintMatrix.java new file mode 100644 index 0000000..ff40d95 --- /dev/null +++ b/公开课/class050/Code04_ZigZagPrintMatrix.java @@ -0,0 +1,55 @@ +package class050; + +public class Code04_ZigZagPrintMatrix { + + public static void printZigZag(int[][] m) { + int N = m.length; + int M = m[0].length; + int Ar = 0; + int Ac = 0; + int Br = 0; + int Bc = 0; + boolean up = true; + while (Ar != N) { + // B 左下角的点 A右上角的点 + print(m, Br, Bc, Ar, Ac, up); + up = !up; + Ar = Ac == M - 1 ? (Ar + 1) : Ar; + Ac = Ac == M - 1 ? Ac : (Ac + 1); + Bc = Br == N - 1 ? (Bc + 1) : Bc; + Br = (Br == N - 1) ? Br : (Br + 1); + } + } + + // (a,b)这个位置,一定在左下方 + // (c,d)这个位置,一定在右上方 + // (a,b) -> (c,d)是一条笔直的斜线! + // up == true, 请你从(a,b)打印到(c,d),斜线 + // up == false, 请你从(c,d)打印到(a,b),斜线 + public static void print(int[][] m, int a, int b, int c, int d, boolean up) { + if (up) { + for (;; a--, b++) { + System.out.print(m[a][b] + " "); + if (a == c && b == d) { + return; + } + } + } else { + for (;; c++, d--) { + System.out.print(m[c][d] + " "); + if (a == c && b == d) { + return; + } + } + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3, 4 }, + { 5, 6, 7, 8 }, + { 9, 10, 11, 12 } }; + printZigZag(matrix); + } + +} diff --git a/公开课/class050/Code05_RotateImage.java b/公开课/class050/Code05_RotateImage.java new file mode 100644 index 0000000..ebfccf2 --- /dev/null +++ b/公开课/class050/Code05_RotateImage.java @@ -0,0 +1,52 @@ +package class050; + +public class Code05_RotateImage { + + public static void rotate(int[][] matrix) { + int a = 0; + int b = 0; + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { // a>=c + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + // 在二维数组m中,左上角点(a,b), 右下角点(c,d) + // 只在规定好的框上,顺时针转好 + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + + public static void printMatrix(int[][] m) { + int N = m.length; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int[][] matrix = { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 } }; + printMatrix(matrix); + System.out.println("========"); + + rotate(matrix); + printMatrix(matrix); + System.out.println("========"); + + } + +} diff --git a/公开课/class051/Code01_LongestNoRepeatSubstring.java b/公开课/class051/Code01_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..cdf9f32 --- /dev/null +++ b/公开课/class051/Code01_LongestNoRepeatSubstring.java @@ -0,0 +1,124 @@ +package class051; + +public class Code01_LongestNoRepeatSubstring { + + // 返回str的最长无重复字符子串长度 + public static int maxLen1(String str) { + if (str == null || str.length() == 0) { + return 0; + } + char[] s = str.toCharArray(); + int N = s.length; + int[] dp = new int[N]; // 0 dp[0] 1 dp[1] ... dp max + int[] map = new int[256];// a 上次出现的位置 b 0 ~ 255 + for (int i = 0; i < 256; i++) { + // i 字符的asc码 map[i] == -1 + map[i] = -1; + } + int ans = 1; + dp[0] = 1; + map[s[0]] = 0; + for (int i = 1; i < N; i++) { + // i [i]上次出现的位置,i距离 + int p1 = i - map[s[i]]; + int p2 = dp[i - 1] + 1; + dp[i] = Math.min(p1, p2); + map[s[i]] = i; + ans = Math.max(ans, dp[i]); + } + return ans; + } + + public static int maxLen2(String str) { + if (str == null || str.length() == 0) { + return 0; + } + char[] s = str.toCharArray(); + int N = s.length; + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + int ans = 1; + int pre = 1; + map[s[0]] = 0; + for (int i = 1; i < N; i++) { + pre = Math.min(i - map[s[i]], pre + 1); + map[s[i]] = i; + ans = Math.max(ans, pre); + } + return ans; + } + + /* + * 给定一个只由小写字母(a~z)组成的字符串str, 返回其中最长无重复字符的子串长度 + * + */ + public static int lnrs1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + boolean[] set = new boolean[26]; + for (int j = i; j < N; j++) { + if (set[str[j] - 'a']) { + break; + } + set[str[j] - 'a'] = true; + max = Math.max(max, j - i + 1); + } + } + return max; + } + + public static int lnrs2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] last = new int[26]; + for (int i = 0; i < 26; i++) { + last[i] = -1; + } + last[str[0] - 'a'] = 0; + int max = 1; + int preMaxLen = 1; + for (int i = 1; i < N; i++) { + preMaxLen = Math.min(i - last[str[i] - 'a'], preMaxLen + 1); + max = Math.max(max, preMaxLen); + last[str[i] - 'a'] = i; + } + return max; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 26; + int strMaxSize = 100; + int testTimes = 1000000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + int ans1 = lnrs1(str); + int ans2 = maxLen2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/公开课/class051/Code02_CoverMax.java b/公开课/class051/Code02_CoverMax.java new file mode 100644 index 0000000..935ec3c --- /dev/null +++ b/公开课/class051/Code02_CoverMax.java @@ -0,0 +1,204 @@ +package class051; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code02_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + public static class LineStartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static int maxOver(int[][] ls) { + if (ls == null || ls.length == 0) { + return 0; + } + int N = ls.length; + Line[] lines = new Line[N]; + for (int i = 0; i < N; i++) { + lines[i] = new Line(ls[i][0], ls[i][1]); + } + // 把所有线段,根据开头位置排序 + Arrays.sort(lines, new LineStartComparator()); + PriorityQueue heap = new PriorityQueue<>(); + int ans = 0; + for (Line line : lines) { + int start = line.start; + int end = line.end; + while (!heap.isEmpty() && heap.peek() <= start) { + heap.poll(); + } + heap.add(end); + ans = Math.max(ans, heap.size()); + } + return ans; + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + +// Line l1 = new Line(4, 9); +// Line l2 = new Line(1, 4); +// Line l3 = new Line(7, 15); +// Line l4 = new Line(2, 4); +// Line l5 = new Line(4, 6); +// Line l6 = new Line(3, 7); +// +// Line[] lines = { l1, l2, l3, l4, l5, l6 }; +// Arrays.sort(lines, new StartComparator()); +// +// for (Line l : lines) { +// System.out.println(l.start + "," + l.end); +// } + +// // add peek poll O(logN) +// PriorityQueue heap = new PriorityQueue<>(); +// +// heap.add(6); +// +// // 6 +// System.out.println(heap.peek()); +// +// heap.add(3); +// // 3 6 +// System.out.println(heap.peek()); +// +// heap.add(9); +// // 3 6 9 +// System.out.println(heap.peek()); +// +// heap.add(3); +// // 3 3 6 9 +// heap.add(6); +// System.out.println("======"); +// // 3 3 6 6 9; +// while(!heap.isEmpty()) { +// System.out.println(heap.poll()); +// } + +// Line l1 = new Line(4, 9); +// Line l2 = new Line(1, 4); +// Line l3 = new Line(7, 15); +// Line l4 = new Line(2, 4); +// Line l5 = new Line(4, 6); +// Line l6 = new Line(3, 7); +// +// // 底层堆结构,heap +// PriorityQueue heap = new PriorityQueue<>(new StartComparator()); +// heap.add(l1); +// heap.add(l2); +// heap.add(l3); +// heap.add(l4); +// heap.add(l5); +// heap.add(l6); +// +// while (!heap.isEmpty()) { +// Line cur = heap.poll(); +// System.out.println(cur.start + "," + cur.end); +// } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxOver(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class052/Code01_EvenTimesOddTimes.java b/公开课/class052/Code01_EvenTimesOddTimes.java new file mode 100644 index 0000000..265c71d --- /dev/null +++ b/公开课/class052/Code01_EvenTimesOddTimes.java @@ -0,0 +1,75 @@ +package class052; + +public class Code01_EvenTimesOddTimes { + + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + public static void printOddTimesHas2Nums(int[] arr) { + int eor = 0; + for (int num : arr) { + eor ^= num; + } + // a和b,出现了奇数次 eor -> a ^ b + // eor : 0..000101011000 + // 最右的1: 0..000000001000 + // ~num + 1 -> -num + int mostRightOne = eor & (-eor); + int eor2 = 0; + for (int num : arr) { + // 第3位上有1的数字,异或进 eor2里 + if ((num & mostRightOne) != 0) { + eor2 ^= num; + } + } + System.out.println("找到的第一个是 : " + eor2); + System.out.println("找到的第二个是 : " + (eor ^ eor2)); + } + + public static void printOddTimesNum2(int[] arr) { + // arr中,a和b出现了奇数次,其他数都是偶数次 + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // eor = a ^ b + // eor != 0 + // eor : 001100100 + // rightOne : 000000100 + int rightOne = eor & (~eor + 1); + int onlyOne = 0; // eor' + for (int i = 0; i < arr.length; i++) { + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + // eor' = a or b + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + public static int bit1counts(int N) { + int count = 0; + while (N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N -= rightOne; + } + return count; + } + + public static void main(String[] args) { + int[] arr1 = { 1500, 3, 1, 1500, 1, 1, 1500, 3, 1500 }; + // 3 3 1500 1500 1 1 1 + printOddTimesNum1(arr1); + + int[] arr = { 4, 3, 3, 4, 3, 3, 2, 5, 5, 5, 5, 2, 2, 1, 1, 5, 1, 2, 2, 2 }; + + printOddTimesHas2Nums(arr); + } + +} diff --git a/公开课/class052/Code02_KM.java b/公开课/class052/Code02_KM.java new file mode 100644 index 0000000..47089c3 --- /dev/null +++ b/公开课/class052/Code02_KM.java @@ -0,0 +1,176 @@ +package class052; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code02_KM { + + public static int test(int[] arr, int k, int m) { + HashMap map = new HashMap<>(); + for (int num : arr) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + for (int num : map.keySet()) { + if (map.get(num) == k) { + return num; + } + } + return -1; + } + + // 其他所有的数字都出现了M次,只有一种数,出现了K次 + // 请打印出现了K次的数,K < M + public static void find(int[] arr, int K, int M) { + // 1) 先遍历一遍数组,看看0,是不是出现了K次的数 + // 如果是,直接打印0 + + int[] tmp = new int[32]; + for (int num : arr) { + // 我要提取出,num的每一位 + for (int i = 31; i >= 0; i--) { + // 第i位如果是1,curbit = 1;第i位如果是0,curbit = 0; + int curbit = (num & (1 << i)) != 0 ? 1 : 0; + tmp[i] += curbit; + tmp[i] %= M; + } + } + // tmp 得到了! + int ans = 0; + for(int i = 31; i >=0 ;i--) { + // tmp[i]有没有数字剩下来! + // i位上,有数剩下来,has = 1 + // i位上,没有数剩下来,has = 0 + ans |= ((tmp[i] != 0 ? 1 : 0) << i); + } + System.out.println(ans); + // 打印ans + + + + + } + + public static HashMap map = new HashMap<>(); + + // 请保证arr中,只有一种数出现了K次,其他数都出现了M次 + public static int onlyKTimes(int[] arr, int k, int m) { + if (map.size() == 0) { + mapCreater(map); + } + int[] t = new int[32]; + // t[0] 0位置的1出现了几个 + // t[i] i位置的1出现了几个 + for (int num : arr) { + while (num != 0) { + int rightOne = num & (-num); + t[map.get(rightOne)]++; + num ^= rightOne; + } + } + int ans = 0; + for (int i = 0; i < 32; i++) { + if (t[i] % m != 0) { + if (t[i] % m == k) { + ans |= (1 << i); + } else { + return -1; + } + } + } + if (ans == 0) { + int count = 0; + for (int num : arr) { + if (num == 0) { + count++; + } + } + if (count != k) { + return -1; + } + } + return ans; + } + + public static void mapCreater(HashMap map) { + int value = 1; + for (int i = 0; i < 32; i++) { + map.put(value, i); + value <<= 1; + } + } + + public static int[] randomArray(int maxKinds, int range, int k, int m) { + int ktimeNum = randomNumber(range); + // 真命天子出现的次数 + int times = Math.random() < 0.5 ? k : ((int) (Math.random() * (m - 1)) + 1); + // 2 + int numKinds = (int) (Math.random() * maxKinds) + 2; + // k * 1 + (numKinds - 1) * m + int[] arr = new int[times + (numKinds - 1) * m]; + int index = 0; + for (; index < times; index++) { + arr[index] = ktimeNum; + } + numKinds--; + HashSet set = new HashSet<>(); + set.add(ktimeNum); + while (numKinds != 0) { + int curNum = 0; + do { + curNum = randomNumber(range); + } while (set.contains(curNum)); + set.add(curNum); + numKinds--; + for (int i = 0; i < m; i++) { + arr[index++] = curNum; + } + } + // arr 填好了 + for (int i = 0; i < arr.length; i++) { + // i 位置的数,我想随机和j位置的数做交换 + int j = (int) (Math.random() * arr.length);// 0 ~ N-1 + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + // [-range, +range] + public static int randomNumber(int range) { + return ((int) (Math.random() * range) + 1) - ((int) (Math.random() * range) + 1); + } + + public static void main(String[] args) { + int kinds = 5; + int range = 30; + int testTime = 100000; + int max = 9; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * max) + 1; // a 1 ~ 9 + int b = (int) (Math.random() * max) + 1; // b 1 ~ 9 + int k = Math.min(a, b); + int m = Math.max(a, b); + // k < m + if (k == m) { + m++; + } + int[] arr = randomArray(kinds, range, k, m); + int ans1 = test(arr, k, m); + int ans2 = onlyKTimes(arr, k, m); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class052/Code03_UglyNumber.java b/公开课/class052/Code03_UglyNumber.java new file mode 100644 index 0000000..a3284ba --- /dev/null +++ b/公开课/class052/Code03_UglyNumber.java @@ -0,0 +1,63 @@ +package class052; + +// 测试链接:https://leetcode.com/problems/ugly-number-ii +public class Code03_UglyNumber { + + public static boolean isUgly(int num) { + while (num % 2 == 0) { + num /= 2; + } + while (num % 3 == 0) { + num /= 3; + } + while (num % 5 == 0) { + num /= 5; + } + return num == 1; + } + + public static int nthUglyNumber1(int n) { + int find = 0; + int num = 1; + for (; find < n; num++) { + if (isUgly(num)) { + find++; + } + } + return num - 1; + } + + + + + + + + + public static int nthUglyNumber2(int n) { + int[] ugly = new int[n + 1]; + // ugly[0] 不用了,废掉 + ugly[1] = 1; + int i2 = 1; + int i3 = 1; + int i5 = 1; + for (int i = 2; i <= n; i++) { + ugly[i] = Math.min(Math.min(ugly[i2] * 2, ugly[i3] * 3), ugly[i5] * 5); + if (ugly[i] == ugly[i2] * 2) { + i2++; + } + if (ugly[i] == ugly[i3] * 3) { + i3++; + } + if (ugly[i] == ugly[i5] * 5) { + i5++; + } + } + return ugly[n]; + } + + public static void main(String[] args) { + System.out.println(nthUglyNumber2(10000)); + } + +} diff --git a/公开课/class052/Code04_MakeNo.java b/公开课/class052/Code04_MakeNo.java new file mode 100644 index 0000000..b687a5f --- /dev/null +++ b/公开课/class052/Code04_MakeNo.java @@ -0,0 +1,52 @@ +package class052; + +public class Code04_MakeNo { + + public static int[] makeNo(int N) { + if (N == 1) { + return new int[] { 1 }; + } + // N -> 一半 + // N = 7 -> 4 + int halfSize = (N + 1) / 2; + int[] base = makeNo(halfSize); + int[] ans = new int[N]; + int index = 0; + for (; index < halfSize; index++) { + ans[index] = base[index] * 2 + 1; + } + for (int i = 0; index < N; index++, i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/公开课/class053/Code01_BestTimeToBuyAndSellStock.java b/公开课/class053/Code01_BestTimeToBuyAndSellStock.java new file mode 100644 index 0000000..22e1c38 --- /dev/null +++ b/公开课/class053/Code01_BestTimeToBuyAndSellStock.java @@ -0,0 +1,18 @@ +package class053; + +public class Code01_BestTimeToBuyAndSellStock { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + int ans = 0; + int min = prices[0]; + for (int i = 1; i < prices.length; i++) { + min = Math.min(min, prices[i]); + ans = Math.max(ans, prices[i] - min); + } + return ans; + } + +} diff --git a/公开课/class053/Code02_BestTimeToBuyAndSellStockII.java b/公开课/class053/Code02_BestTimeToBuyAndSellStockII.java new file mode 100644 index 0000000..bc5d22a --- /dev/null +++ b/公开课/class053/Code02_BestTimeToBuyAndSellStockII.java @@ -0,0 +1,17 @@ +package class053; + +//leetcode 122 +public class Code02_BestTimeToBuyAndSellStockII { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + int ans = 0; + for (int i = 1; i < prices.length; i++) { + ans += Math.max(prices[i] - prices[i-1], 0); + } + return ans; + } + +} diff --git a/公开课/class053/Code03_BestTimeToBuyAndSellStockIII.java b/公开课/class053/Code03_BestTimeToBuyAndSellStockIII.java new file mode 100644 index 0000000..1e3c7d7 --- /dev/null +++ b/公开课/class053/Code03_BestTimeToBuyAndSellStockIII.java @@ -0,0 +1,23 @@ +package class053; + +//leetcode 123 +public class Code03_BestTimeToBuyAndSellStockIII { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + int ans = 0; + int doneOnceMinusBuyMax = -prices[0]; + int doneOnceMax = 0; + int min = prices[0]; + for (int i = 1; i < prices.length; i++) { + min = Math.min(min, prices[i]); + ans = Math.max(ans, doneOnceMinusBuyMax + prices[i]); + doneOnceMax = Math.max(doneOnceMax, prices[i] - min); + doneOnceMinusBuyMax = Math.max(doneOnceMinusBuyMax, doneOnceMax - prices[i]); + } + return ans; + } + +} diff --git a/公开课/class053/Code04_RabbitsInForest.java b/公开课/class053/Code04_RabbitsInForest.java new file mode 100644 index 0000000..025cb5c --- /dev/null +++ b/公开课/class053/Code04_RabbitsInForest.java @@ -0,0 +1,28 @@ +package class053; + +import java.util.Arrays; + +public class Code04_RabbitsInForest { + + public static int numRabbits(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int x = arr[0]; + int c = 1; + int ans = 0; + for (int i = 1; i < arr.length; i++) { + if (x != arr[i]) { + ans += ((c + x) / (x + 1)) * (x + 1); + x = arr[i]; + c = 1; + } else { + c++; + } + } + return ans + ((c + x) / (x + 1)) * (x + 1); + } + + +} diff --git a/公开课/class053/Code05_LIS.java b/公开课/class053/Code05_LIS.java new file mode 100644 index 0000000..b8b91f2 --- /dev/null +++ b/公开课/class053/Code05_LIS.java @@ -0,0 +1,58 @@ +package class053; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code05_LIS { + + // 时间复杂度O(N^2) + public static int maxLen(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + dp[0] = 1; + int max = 1; + for (int i = 1; i < N; i++) { + dp[i] = 1; + int preMax = 0; + for (int j = 0; j < i; j++) { + if (arr[j] < arr[i]) { + preMax = Math.max(preMax, dp[j]); + } + } + dp[i] = Math.max(dp[i], preMax + 1); + max = Math.max(max, dp[i]); + } + return max; + } + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + int max = 1; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/公开课/class053/Code06_EnvelopesProblem.java b/公开课/class053/Code06_EnvelopesProblem.java new file mode 100644 index 0000000..0a0719a --- /dev/null +++ b/公开课/class053/Code06_EnvelopesProblem.java @@ -0,0 +1,60 @@ +package class053; + +import java.util.Arrays; +import java.util.Comparator; + +// 本题测试链接 : https://leetcode.com/problems/russian-doll-envelopes/ +public class Code06_EnvelopesProblem { + + public static int maxEnvelopes(int[][] matrix) { + Envelope[] arr = sort(matrix); + int[] ends = new int[matrix.length]; + ends[0] = arr[0].h; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i].h > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i].h; + } + return right + 1; + } + + public static class Envelope { + public int l; + public int h; + + public Envelope(int weight, int hight) { + l = weight; + h = hight; + } + } + + public static class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.l != o2.l ? o1.l - o2.l : o2.h - o1.h; + } + } + + public static Envelope[] sort(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} diff --git a/公开课/class054/Code01_FindHalfMajority.java b/公开课/class054/Code01_FindHalfMajority.java new file mode 100644 index 0000000..6763c84 --- /dev/null +++ b/公开课/class054/Code01_FindHalfMajority.java @@ -0,0 +1,104 @@ +package class054; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code01_FindHalfMajority { + + // 时间复杂度O(N),额外空间复杂度O(1) + // [1,5,2,5,3,5] + public static int find(int[] arr) { + int target = 0; + int hp = 0; + for (int num : arr) { + if (hp == 0) { + target = num; + hp = 1; + } else if (num != target) { + hp--; + } else { + hp++; + } + } + if (hp == 0) { + return -1; + } + // hp > 0, target + hp = 0; + for (int num : arr) { + if (num == target) { + hp++; + } + } + return hp > arr.length / 2 ? target : -1; + } + + public static int halfMajor(int[] arr) { + int target = 0; + int HP = 0; + for (int i = 0; i != arr.length; i++) { + if (HP == 0) { + target = arr[i]; + HP = 1; + } else if (arr[i] == target) { + HP++; + } else { + HP--; + } + } + if (HP == 0) { + return -1; + } + HP = 0; + for (int i = 0; i != arr.length; i++) { + if (arr[i] == target) { + HP++; + } + } + return HP > arr.length / 2 ? target : -1; + } + + // for test + public static int right(int[] arr) { + HashMap map = new HashMap<>(); + for (int cur : arr) { + if (!map.containsKey(cur)) { + map.put(cur, 0); + } + map.put(cur, map.get(cur) + 1); + } + for (Entry entry : map.entrySet()) { + if (entry.getValue() > arr.length / 2) { + return entry.getKey(); + } + } + return -1; + } + + // for test + public static int[] genareteRandomArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 10; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = genareteRandomArray(len, max); + int ans1 = halfMajor(arr); + int ans2 = right(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class054/Code02_PalindromeNumber.java b/公开课/class054/Code02_PalindromeNumber.java new file mode 100644 index 0000000..d1765c8 --- /dev/null +++ b/公开课/class054/Code02_PalindromeNumber.java @@ -0,0 +1,30 @@ +package class054; + +public class Code02_PalindromeNumber { + + public static boolean isPalindrome(int n) { + if (n < 0) { + return false; + } + int tmp = 1; + // num = 7654567 + // tmp = 1000000 + while (n / tmp >= 10) { + tmp *= 10; + } + while (n != 0) { + if (n / tmp != n % 10) { + return false; + } + // num = 7654567 + // tmp = 1000000 + // n%t = 654567 + // /10 = 65456 + // = 10000 + n = (n % tmp) / 10; + tmp /= 100; + } + return true; + } + +} diff --git a/公开课/class054/Code03_Water.java b/公开课/class054/Code03_Water.java new file mode 100644 index 0000000..863ec5a --- /dev/null +++ b/公开课/class054/Code03_Water.java @@ -0,0 +1,120 @@ +package class054; + +public class Code03_Water { + + // 本题测试链接:https://leetcode.com/problems/trapping-rain-water/ + // 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + // O位置无水量 + // 0~N-1,N-1无水量 + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class054/Code04_JumpGameII.java b/公开课/class054/Code04_JumpGameII.java new file mode 100644 index 0000000..b1ed486 --- /dev/null +++ b/公开课/class054/Code04_JumpGameII.java @@ -0,0 +1,24 @@ +package class054; + +// 测试页面:https://leetcode.com/problems/jump-game-ii/ +public class Code04_JumpGameII { + + public static int jump(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + // 一开始,人在0位置,一步都没有跳过 + int jump = 0; + int cur = 0; + int next = arr[0]; + for (int i = 1; i < arr.length; i++) { + if(cur < i) { + jump++; + cur = next; + } + next = Math.max(next, i + arr[i]); + } + return jump; + } + +} diff --git a/公开课/class055/Code01_LongestNoRepeatSubstring.java b/公开课/class055/Code01_LongestNoRepeatSubstring.java new file mode 100644 index 0000000..4350268 --- /dev/null +++ b/公开课/class055/Code01_LongestNoRepeatSubstring.java @@ -0,0 +1,26 @@ +package class055; + +// 链接 : https://leetcode.com/problems/longest-substring-without-repeating-characters/ +public class Code01_LongestNoRepeatSubstring { + + public static int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + map[str[0]] = 0; + int pre = 1; + int ans = 1; + for (int i = 1; i < str.length; i++) { + pre = Math.min(i - map[str[i]], pre + 1); + ans = Math.max(ans, pre); + map[str[i]] = i; + } + return ans; + } + +} diff --git a/公开课/class055/Code02_SubArrayMaxSum.java b/公开课/class055/Code02_SubArrayMaxSum.java new file mode 100644 index 0000000..0903c4a --- /dev/null +++ b/公开课/class055/Code02_SubArrayMaxSum.java @@ -0,0 +1,22 @@ +package class055; + +// 本题测试链接 : https://leetcode.com/problems/maximum-subarray/ +public class Code02_SubArrayMaxSum { + + public static int maxSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int pre = arr[0]; // 上一步的答案 + int ans = pre; + for (int i = 1; i < N; i++) { + // 子数组必须以i位置的数结尾的情况下,最大累加和是多少 + // arr[i] pre + arr[i] + pre = Math.max(arr[i], pre + arr[i]); + ans = Math.max(ans, pre); + } + return ans; + } + +} diff --git a/公开课/class055/Code03_MaximumProductSubarray.java b/公开课/class055/Code03_MaximumProductSubarray.java new file mode 100644 index 0000000..c037c50 --- /dev/null +++ b/公开课/class055/Code03_MaximumProductSubarray.java @@ -0,0 +1,40 @@ +package class055; + +// 本次测试链接 : https://leetcode.com/problems/maximum-product-subarray/ +public class Code03_MaximumProductSubarray { + + public static double max(double[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + double premax = arr[0]; + double premin = arr[0]; + double ans = arr[0]; + for (int i = 1; i < arr.length; i++) { + double p1 = arr[i]; + double p2 = arr[i] * premax; + double p3 = arr[i] * premin; + double curmax = Math.max(Math.max(p1, p2), p3); + double curmin = Math.min(Math.min(p1, p2), p3); + ans = Math.max(ans, curmax); + premax = curmax; + premin = curmin; + } + return ans; + } + + public static int maxProduct(int[] nums) { + int ans = nums[0]; + int min = nums[0]; + int max = nums[0]; + for (int i = 1; i < nums.length; i++) { + int curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); + int curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); + min = curmin; + max = curmax; + ans = Math.max(ans, max); + } + return ans; + } + +} diff --git a/公开课/class055/Code04_PaperFolding.java b/公开课/class055/Code04_PaperFolding.java new file mode 100644 index 0000000..77fd039 --- /dev/null +++ b/公开课/class055/Code04_PaperFolding.java @@ -0,0 +1,26 @@ +package class055; + +public class Code04_PaperFolding { + + public static void printAllFolds(int N) { + process(1, N, true); + System.out.println(); + } + + // 想象中的节点,在第i层 + // N,最多有多少层,固定参数 + // 想象中的节点,是凸还是凹,down = true 凹 down = false 凸 + public static void process(int i, int N, boolean down) { + if (i > N) { + return; + } + process(i + 1, N, true); + System.out.print(down ? "凹 " : "凸 "); + process(i + 1, N, false); + } + + public static void main(String[] args) { + int N = 3; + printAllFolds(N); + } +} diff --git a/公开课/class056/Code01_MissingNumber.java b/公开课/class056/Code01_MissingNumber.java new file mode 100644 index 0000000..4c87618 --- /dev/null +++ b/公开课/class056/Code01_MissingNumber.java @@ -0,0 +1,34 @@ +package class056; + +// 测试链接:https://leetcode.com/problems/first-missing-positive/ +public class Code01_MissingNumber { + + public static int firstMissingPositive(int[] arr) { + int l = 0; // l的左边,每一个位置i,上面都放着i+1这个数 + // 最好预期:1~r每个数字收集全,且只收集一次 + // 垃圾区:r.... + int r = arr.length; + while (l != r) { + if (arr[l] == l + 1) { + l++; + } else if (arr[l] <= l || arr[l] > r || arr[arr[l] - 1] == arr[l]) { + // [l] = 83 + // 83应该放在82位置 + // arr [ arr[l] - 1 ] = arr[l] + // arr[82] = 83 + // 此时的arr[l]是垃圾 + swap(arr, l, --r); + } else { // l上面的数不是l+1 l上面的数也不是垃圾 + swap(arr, l, arr[l] - 1); + } + } + return l + 1; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/公开课/class056/Code02_RabbitsInForest.java b/公开课/class056/Code02_RabbitsInForest.java new file mode 100644 index 0000000..9a96dd8 --- /dev/null +++ b/公开课/class056/Code02_RabbitsInForest.java @@ -0,0 +1,30 @@ +package class056; + +import java.util.Arrays; + +public class Code02_RabbitsInForest { + + public static int numRabbits(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int x = arr[0]; + int k = 1; + int ans = 0; + for (int i = 1; i < arr.length; i++) { + if (x != arr[i]) { + // (k / (x + 1)) 上 * (x+1) + // (k + x + 1 - 1) / (x + 1) + // (k + x) / (x + 1) (x + 1) + ans += ((k + x) / (x + 1)) * (x + 1); + x = arr[i]; + k = 1; + } else { + k++; + } + } + return ans + ((k + x) / (x + 1)) * (x + 1); + } + +} diff --git a/公开课/class056/Code03_LIS.java b/公开课/class056/Code03_LIS.java new file mode 100644 index 0000000..5eac4eb --- /dev/null +++ b/公开课/class056/Code03_LIS.java @@ -0,0 +1,58 @@ +package class056; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code03_LIS { + + // 时间复杂度O(N^2) + public static int maxLen(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + dp[0] = 1; + int max = 1; + for (int i = 1; i < N; i++) { + dp[i] = 1; + int preMax = 0; + for (int j = 0; j < i; j++) { + if (arr[j] < arr[i]) { + preMax = Math.max(preMax, dp[j]); + } + } + dp[i] = Math.max(dp[i], preMax + 1); + max = Math.max(max, dp[i]); + } + return max; + } + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + int max = 1; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/公开课/class056/Code04_EnvelopesProblem.java b/公开课/class056/Code04_EnvelopesProblem.java new file mode 100644 index 0000000..977ad79 --- /dev/null +++ b/公开课/class056/Code04_EnvelopesProblem.java @@ -0,0 +1,60 @@ +package class056; + +import java.util.Arrays; +import java.util.Comparator; + +// 本题测试链接 : https://leetcode.com/problems/russian-doll-envelopes/ +public class Code04_EnvelopesProblem { + + public static int maxEnvelopes(int[][] matrix) { + Envelope[] arr = sort(matrix); + int[] ends = new int[matrix.length]; + ends[0] = arr[0].h; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i].h > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i].h; + } + return right + 1; + } + + public static class Envelope { + public int l; + public int h; + + public Envelope(int weight, int hight) { + l = weight; + h = hight; + } + } + + public static class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.l != o2.l ? o1.l - o2.l : o2.h - o1.h; + } + } + + public static Envelope[] sort(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} diff --git a/公开课/class057/Code01_MagicStone.java b/公开课/class057/Code01_MagicStone.java new file mode 100644 index 0000000..79c20a8 --- /dev/null +++ b/公开课/class057/Code01_MagicStone.java @@ -0,0 +1,49 @@ +package class057; + +import java.util.Arrays; + +// 来自小红书 +// [0,4,7] : 0表示这里石头没有颜色,如果变红代价是4,如果变蓝代价是7 +// [1,X,X] : 1表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义 +// [2,X,X] : 2表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义 +// 颜色只可能是0、1、2,代价一定>=0 +// 给你一批这样的小数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价 +// 如果怎么都无法做到所有石头都有颜色、且红色和蓝色一样多,返回-1 +public class Code01_MagicStone { + + public static int minCost(int[][] stones) { + int n = stones.length; + if ((n & 1) != 0) { + return -1; + } + Arrays.sort(stones, (a, b) -> a[0] == 0 && b[0] == 0 ? (b[1] - b[2] - a[1] + a[2]) : (a[0] - b[0])); + int zero = 0; + int red = 0; + int blue = 0; + int cost = 0; + for (int i = 0; i < n; i++) { + if (stones[i][0] == 0) { + zero++; + cost += stones[i][1]; + } else if (stones[i][0] == 1) { + red++; + } else { + blue++; + } + } + if (red > (n >> 1) || blue > (n >> 1)) { + return -1; + } + blue = zero - ((n >> 1) - red); + for (int i = 0; i < blue; i++) { + cost += stones[i][2] - stones[i][1]; + } + return cost; + } + + public static void main(String[] args) { + int[][] stones = { { 1, 5, 3 }, { 2, 7, 9 }, { 0, 6, 4 }, { 0, 7, 9 }, { 0, 2, 1 }, { 0, 5, 9 } }; + System.out.println(minCost(stones)); + } + +} diff --git a/公开课/class057/Code02_CircleCandy.java b/公开课/class057/Code02_CircleCandy.java new file mode 100644 index 0000000..f6242af --- /dev/null +++ b/公开课/class057/Code02_CircleCandy.java @@ -0,0 +1,58 @@ +package class057; + +// 来自网易 +// 给定一个正数数组arr,表示每个小朋友的得分 +// 任何两个相邻的小朋友,如果得分一样,怎么分糖果无所谓,但如果得分不一样,分数大的一定要比分数少的多拿一些糖果 +// 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数 +public class Code02_CircleCandy { + + public static int minCandy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return 1; + } + int n = arr.length; + int minIndex = 0; + for (int i = 0; i < n; i++) { + if (arr[i] <= arr[lastIndex(i, n)] && arr[i] <= arr[nextIndex(i, n)]) { + minIndex = i; + break; + } + } + int[] nums = new int[n + 1]; + for (int i = 0; i <= n; i++, minIndex = nextIndex(minIndex, n)) { + nums[i] = arr[minIndex]; + } + int[] left = new int[n + 1]; + left[0] = 1; + for (int i = 1; i <= n; i++) { + left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1; + } + int[] right = new int[n + 1]; + right[n] = 1; + for (int i = n - 1; i >= 0; i--) { + right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1; + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(left[i], right[i]); + } + return ans; + } + + public static int nextIndex(int i, int n) { + return i == n - 1 ? 0 : (i + 1); + } + + public static int lastIndex(int i, int n) { + return i == 0 ? (n - 1) : (i - 1); + } + + public static void main(String[] args) { + int[] arr = { 3, 4, 2, 3, 2 }; + System.out.println(minCandy(arr)); + } + +} diff --git a/公开课/class057/Code03_ComputeExpressionValue.java b/公开课/class057/Code03_ComputeExpressionValue.java new file mode 100644 index 0000000..98d24da --- /dev/null +++ b/公开课/class057/Code03_ComputeExpressionValue.java @@ -0,0 +1,58 @@ +package class057; + +// 来自美团 +// () 分值为2 +// (()) 分值为3 +// ((())) 分值为4 +// 也就是说,每包裹一层,分数就是里面的分值+1 +// ()() 分值为2 * 2 +// (())() 分值为3 * 2 +// 也就是说,每连接一段,分数就是各部分相乘,以下是一个结合起来的例子 +// (()())()(()) -> (2 * 2 + 1) * 2 * 3 -> 30 +// 给定一个括号字符串str,已知str一定是正确的括号结合,不会有违规嵌套 +// 返回分数 +public class Code03_ComputeExpressionValue { + + public static int sores(String s) { + return compute(s.toCharArray(), 0)[0]; + } + + // s[i.....]一旦遇到 ) 或者 字符串终止位置,停! + // 返回值: + // 0) 负责这一段的结果(得分)是多少? + // 1) 计算到了什么位置也返回 + public static int[] compute(char[] s, int i) { + if (s[i] == ')') { + return new int[] { 1, i }; + } + int ans = 1; + while (i < s.length && s[i] != ')') { + int[] info = compute(s, i + 1); + ans *= info[0] + 1; + i = info[1] + 1; + } + return new int[] { ans, i }; + } + + public static void main(String[] args) { + + String str1 = "(()())()(())"; + System.out.println(sores(str1)); + + // (()()) + (((()))) + ((())()) + // (()()) -> 2 * 2 + 1 -> 5 + // (((()))) -> 5 + // ((())()) -> ((2 + 1) * 2) + 1 -> 7 + // 所以下面的结果应该是175 + String str2 = "(()())(((())))((())())"; + System.out.println(sores(str2)); + + // (()()()) + (()(())) + // (()()()) -> 2 * 2 * 2 + 1 -> 9 + // (()(())) -> 2 * 3 + 1 -> 7 + // 所以下面的结果应该是63 + String str3 = "(()()())(()(()))"; + System.out.println(sores(str3)); + } + +} diff --git a/公开课/class057/Code04_Ratio01Split.java b/公开课/class057/Code04_Ratio01Split.java new file mode 100644 index 0000000..4f9e63b --- /dev/null +++ b/公开课/class057/Code04_Ratio01Split.java @@ -0,0 +1,67 @@ +package class057; + +import java.util.HashMap; + +// 来自网易 +// 把一个01字符串切成多个部分,要求每一部分的0和1比例一样,同时要求尽可能多的划分 +// 比如 : 01010101 +// 01 01 01 01 这是一种切法,0和1比例为 1 : 1 +// 0101 0101 也是一种切法,0和1比例为 1 : 1 +// 两种切法都符合要求,但是那么尽可能多的划分为第一种切法,部分数为4 +// 比如 : 00001111 +// 只有一种切法就是00001111整体作为一块,那么尽可能多的划分,部分数为1 +// 给定一个01字符串str,假设长度为N,要求返回一个长度为N的数组ans +// 其中ans[i] = str[0...i]这个前缀串,要求每一部分的0和1比例一样, +// 同时要求尽可能多的划分下,部分数是多少 +// 输入: str = "010100001" +// 输出: ans = [1, 1, 1, 2, 1, 2, 1, 1, 3] +public class Code04_Ratio01Split { + + public static int[] split(int[] arr) { + + // key -> int -> 分子 + // value -> key这个分子下的,分母表 -> 次数 + HashMap> pre = new HashMap<>(); + int n = arr.length; + int[] ans = new int[n]; + int zero = 0; + int one = 0; + for (int i = 0; i < n; i++) { + if (arr[i] == 0) { + zero++; + } else { + one++; + } + if (zero == 0 || one == 0) { + ans[i] = i + 1; + } else { // 0和1都有数量 -> 0和1的比例的最简形式 + int gcd = gcd(zero, one); + int a = zero / gcd; + int b = one / gcd; + if (!pre.containsKey(a)) { + pre.put(a, new HashMap<>()); + } + if (!pre.get(a).containsKey(b)) { + pre.get(a).put(b, 1); + } else { + pre.get(a).put(b, pre.get(a).get(b) + 1); + } + ans[i] = pre.get(a).get(b); + } + } + return ans; + } + + public static int gcd(int m, int n) { + return n == 0 ? m : gcd(n, m % n); + } + + public static void main(String[] args) { + int[] arr = { 0, 1, 0, 1, 0, 1, 1, 0 }; + int[] ans = split(arr); + for (int i = 0; i < ans.length; i++) { + System.out.print(ans[i] + " "); + } + } + +} diff --git a/公开课/class058/Code01_MinBoatEvenNumbers.java b/公开课/class058/Code01_MinBoatEvenNumbers.java new file mode 100644 index 0000000..d0d6311 --- /dev/null +++ b/公开课/class058/Code01_MinBoatEvenNumbers.java @@ -0,0 +1,78 @@ +package class058; + +import java.util.Arrays; + +// 来自腾讯 +// 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重,所有船都是同样的载重量 +// 每个人的体重都一定不大于船的载重 +// 要求: +// 1, 可以1个人单独一搜船 +// 2, 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重 +// 3, 一艘船最多坐2人 +// 返回如果想所有人同时坐船,船的最小数量 +public class Code01_MinBoatEvenNumbers { + + public static int minBoat(int[] arr, int limit) { + Arrays.sort(arr); + int odd = 0; + int even = 0; + for (int num : arr) { + if ((num & 1) == 0) { + even++; + } else { + odd++; + } + } + int[] odds = new int[odd]; + int[] evens = new int[even]; + for (int i = arr.length - 1; i >= 0; i--) { + if ((arr[i] & 1) == 0) { + evens[--even] = arr[i]; + } else { + odds[--odd] = arr[i]; + } + } + return min(odds, limit) + min(evens, limit); + } + + public static int min(int[] arr, int limit) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (arr[N - 1] > limit) { + return -1; + } + int lessR = -1; + for (int i = N - 1; i >= 0; i--) { + if (arr[i] <= (limit / 2)) { + lessR = i; + break; + } + } + if (lessR == -1) { + return N; + } + int L = lessR; + int R = lessR + 1; + int noUsed = 0; + while (L >= 0) { + int solved = 0; + while (R < N && arr[L] + arr[R] <= limit) { + R++; + solved++; + } + if (solved == 0) { + noUsed++; + L--; + } else { + L = Math.max(-1, L - solved); + } + } + int all = lessR + 1; + int used = all - noUsed; + int moreUnsolved = (N - all) - used; + return used + ((noUsed + 1) >> 1) + moreUnsolved; + } + +} diff --git a/公开课/class058/Code02_MaxKLenSequence.java b/公开课/class058/Code02_MaxKLenSequence.java new file mode 100644 index 0000000..0473f6d --- /dev/null +++ b/公开课/class058/Code02_MaxKLenSequence.java @@ -0,0 +1,87 @@ +package class058; + +import java.util.TreeSet; + +// 来自腾讯 +// 给定一个字符串str,和一个正数k +// 返回长度为k的所有子序列中,字典序最大的子序列 +public class Code02_MaxKLenSequence { + + public static String maxString(String s, int k) { + if (k <= 0 || s.length() < k) { + return ""; + } + char[] str = s.toCharArray(); + int n = str.length; + char[] stack = new char[n]; + int size = 0; + for (int i = 0; i < n; i++) { + while (size > 0 && stack[size - 1] < str[i] && size + n - i > k) { + size--; + } + if (size + n - i == k) { + return String.valueOf(stack, 0, size) + s.substring(i); + } + stack[size++] = str[i]; + } + return String.valueOf(stack, 0, k); + } + + // 为了测试 + public static String test(String str, int k) { + if (k <= 0 || str.length() < k) { + return ""; + } + TreeSet ans = new TreeSet<>(); + process(0, 0, str.toCharArray(), new char[k], ans); + return ans.last(); + } + + // 为了测试 + public static void process(int si, int pi, char[] str, char[] path, TreeSet ans) { + if (si == str.length) { + if (pi == path.length) { + ans.add(String.valueOf(path)); + } + } else { + process(si + 1, pi, str, path, ans); + if (pi < path.length) { + path[pi] = str[si]; + process(si + 1, pi + 1, str, path, ans); + } + } + } + + // 为了测试 + public static String randomString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int n = 12; + int r = 5; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * (n + 1)); + String str = randomString(len, r); + int k = (int) (Math.random() * (str.length() + 1)); + String ans1 = maxString(str, k); + String ans2 = test(str, k); + if (!ans1.equals(ans2)) { + System.out.println("出错了!"); + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class058/Code03_StoneGameIV.java b/公开课/class058/Code03_StoneGameIV.java new file mode 100644 index 0000000..3871748 --- /dev/null +++ b/公开课/class058/Code03_StoneGameIV.java @@ -0,0 +1,99 @@ +package class058; + +// 来自哈喽单车 +// 本题是leetcode原题 : https://leetcode.com/problems/stone-game-iv/ +public class Code03_StoneGameIV { + + // 当前的!拿分数的人,如果面对x这个分数,最终 当前的!拿分数的人 会不会赢 + public static boolean win(int x) { + if (x == 0) { + return false; + } + for (int p = 1; p * p <= x; p++) { + // 某一个决定 + int pick = p * p; + // 下一轮的对手,面对的分值 + int rest = x - pick; + if (!win(rest)) { + return true; + } + } + return false; + } + + public static boolean win2(int n) { + // f[x] -> win(x) + boolean[] f = new boolean[n + 1]; + // f[0] -> win(0) -> false + for (int i = 1; i <= n; i++) { + // f[1] : f[0...0] + // f[2] : f[0...1] + // f[3] : f[0...2] + // f[i] : f[0...i-1] + for (int p = 1; p * p <= i; p++) { + if (!f[i - p * p]) { + f[i] = true; + break; + } + } + } + return f[n]; + } + + // 当前的!先手,会不会赢 + // 打表,不能发现规律 + public static boolean winnerSquareGame1(int n) { + if (n == 0) { + return false; + } + // 当前的先手,会尝试所有的情况,1,4,9,16,25,36.... + for (int i = 1; i * i <= n; i++) { + // 当前的先手,决定拿走 i * i 这个平方数 + // 它的对手会不会赢? winnerSquareGame1(n - i * i) + if (!winnerSquareGame1(n - i * i)) { + return true; + } + } + return false; + } + + public static boolean winnerSquareGame2(int n) { + int[] dp = new int[n + 1]; + dp[0] = -1; + return process2(n, dp); + } + + public static boolean process2(int n, int[] dp) { + if (dp[n] != 0) { + return dp[n] == 1 ? true : false; + } + boolean ans = false; + for (int i = 1; i * i <= n; i++) { + if (!process2(n - i * i, dp)) { + ans = true; + break; + } + } + dp[n] = ans ? 1 : -1; + return ans; + } + + public static boolean winnerSquareGame3(int n) { + boolean[] dp = new boolean[n + 1]; + for (int i = 1; i <= n; i++) { + for (int j = 1; j * j <= i; j++) { + if (!dp[i - j * j]) { + dp[i] = true; + break; + } + } + } + return dp[n]; + } + + public static void main(String[] args) { + int n = 10000000; + System.out.println(winnerSquareGame3(n)); + } + +} diff --git a/公开课/class059/Code01_MagicStone.java b/公开课/class059/Code01_MagicStone.java new file mode 100644 index 0000000..8b73087 --- /dev/null +++ b/公开课/class059/Code01_MagicStone.java @@ -0,0 +1,49 @@ +package class059; + +import java.util.Arrays; + +// 来自小红书 +// [0,4,7] : 0表示这里石头没有颜色,如果变红代价是4,如果变蓝代价是7 +// [1,X,X] : 1表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义 +// [2,X,X] : 2表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义 +// 颜色只可能是0、1、2,代价一定>=0 +// 给你一批这样的小数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价 +// 如果怎么都无法做到所有石头都有颜色、且红色和蓝色一样多,返回-1 +public class Code01_MagicStone { + + public static int minCost(int[][] stones) { + int n = stones.length; + if ((n & 1) != 0) { + return -1; + } + Arrays.sort(stones, (a, b) -> a[0] == 0 && b[0] == 0 ? (b[1] - b[2] - a[1] + a[2]) : (a[0] - b[0])); + int zero = 0; + int red = 0; + int blue = 0; + int cost = 0; + for (int i = 0; i < n; i++) { + if (stones[i][0] == 0) { + zero++; + cost += stones[i][1]; + } else if (stones[i][0] == 1) { + red++; + } else { + blue++; + } + } + if (red > (n >> 1) || blue > (n >> 1)) { + return -1; + } + blue = zero - ((n >> 1) - red); + for (int i = 0; i < blue; i++) { + cost += stones[i][2] - stones[i][1]; + } + return cost; + } + + public static void main(String[] args) { + int[][] stones = { { 1, 5, 3 }, { 2, 7, 9 }, { 0, 6, 4 }, { 0, 7, 9 }, { 0, 2, 1 }, { 0, 5, 9 } }; + System.out.println(minCost(stones)); + } + +} diff --git a/公开课/class059/Code02_CircleCandy.java b/公开课/class059/Code02_CircleCandy.java new file mode 100644 index 0000000..d31afc8 --- /dev/null +++ b/公开课/class059/Code02_CircleCandy.java @@ -0,0 +1,58 @@ +package class059; + +// 来自网易 +// 给定一个正数数组arr,表示每个小朋友的得分 +// 任何两个相邻的小朋友,如果得分一样,怎么分糖果无所谓,但如果得分不一样,分数大的一定要比分数少的多拿一些糖果 +// 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数 +public class Code02_CircleCandy { + + public static int minCandy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return 1; + } + int n = arr.length; + int minIndex = 0; + for (int i = 0; i < n; i++) { + if (arr[i] <= arr[lastIndex(i, n)] && arr[i] <= arr[nextIndex(i, n)]) { + minIndex = i; + break; + } + } + int[] nums = new int[n + 1]; + for (int i = 0; i <= n; i++, minIndex = nextIndex(minIndex, n)) { + nums[i] = arr[minIndex]; + } + int[] left = new int[n + 1]; + left[0] = 1; + for (int i = 1; i <= n; i++) { + left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1; + } + int[] right = new int[n + 1]; + right[n] = 1; + for (int i = n - 1; i >= 0; i--) { + right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1; + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(left[i], right[i]); + } + return ans; + } + + public static int nextIndex(int i, int n) { + return i == n - 1 ? 0 : (i + 1); + } + + public static int lastIndex(int i, int n) { + return i == 0 ? (n - 1) : (i - 1); + } + + public static void main(String[] args) { + int[] arr = { 3, 4, 2, 3, 2 }; + System.out.println(minCandy(arr)); + } + +} diff --git a/公开课/class059/Code03_MinBoatEvenNumbers.java b/公开课/class059/Code03_MinBoatEvenNumbers.java new file mode 100644 index 0000000..111e7c6 --- /dev/null +++ b/公开课/class059/Code03_MinBoatEvenNumbers.java @@ -0,0 +1,78 @@ +package class059; + +import java.util.Arrays; + +// 来自腾讯 +// 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重,所有船都是同样的载重量 +// 每个人的体重都一定不大于船的载重 +// 要求: +// 1, 可以1个人单独一搜船 +// 2, 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重 +// 3, 一艘船最多坐2人 +// 返回如果想所有人同时坐船,船的最小数量 +public class Code03_MinBoatEvenNumbers { + + public static int minBoat(int[] arr, int limit) { + Arrays.sort(arr); + int odd = 0; + int even = 0; + for (int num : arr) { + if ((num & 1) == 0) { + even++; + } else { + odd++; + } + } + int[] odds = new int[odd]; + int[] evens = new int[even]; + for (int i = arr.length - 1; i >= 0; i--) { + if ((arr[i] & 1) == 0) { + evens[--even] = arr[i]; + } else { + odds[--odd] = arr[i]; + } + } + return min(odds, limit) + min(evens, limit); + } + + public static int min(int[] arr, int limit) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (arr[N - 1] > limit) { + return -1; + } + int lessR = -1; + for (int i = N - 1; i >= 0; i--) { + if (arr[i] <= (limit / 2)) { + lessR = i; + break; + } + } + if (lessR == -1) { + return N; + } + int L = lessR; + int R = lessR + 1; + int noUsed = 0; + while (L >= 0) { + int solved = 0; + while (R < N && arr[L] + arr[R] <= limit) { + R++; + solved++; + } + if (solved == 0) { + noUsed++; + L--; + } else { + L = Math.max(-1, L - solved); + } + } + int all = lessR + 1; + int used = all - noUsed; + int moreUnsolved = (N - all) - used; + return used + ((noUsed + 1) >> 1) + moreUnsolved; + } + +} diff --git a/公开课/class059/Code04_StringKth.java b/公开课/class059/Code04_StringKth.java new file mode 100644 index 0000000..d89104e --- /dev/null +++ b/公开课/class059/Code04_StringKth.java @@ -0,0 +1,97 @@ +package class059; + +import java.util.ArrayList; +import java.util.List; + +// 来自阿里 +// 给定一个长度len,表示一共有几位 +// 所有字符都是小写(a~z),可以生成长度为1,长度为2, +// 长度为3...长度为len的所有字符串 +// 如果把所有字符串根据字典序排序,每个字符串都有所在的位置。 +// 给定一个字符串str,给定len,请返回str是总序列中的第几个 +// 比如len = 4,字典序的前几个字符串为: +// a aa aaa aaaa aaab ... aaaz ... azzz b ba baa baaa ... bzzz c ... +// a是这个序列中的第1个,bzzz是这个序列中的第36558个 +public class Code04_StringKth { + // 思路: + // cdb,总共长度为7,请问cdb是第几个? + // 第一位c : + // 以a开头,剩下长度为(0~6)的所有可能性有几个 + // + + // 以b开头,剩下长度为(0~6)的所有可能性有几个 + // + + // 以c开头,剩下长度为(0)的所有可能性有几个 + // 第二位d : + // + + // 以ca开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cb开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cc开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cd开头的情况下,剩下长度为(0)的所有可能性有几个 + // 第三位b + // + + // 以cda开头的情况下,剩下长度为(0~4)的所有可能性有几个 + // + + // 以cdb开头的情况下,剩下长度为(0)的所有可能性有几个 + public static int kth(String s, int len) { + if (s == null || s.length() == 0 || s.length() > len) { + return -1; + } + char[] num = s.toCharArray(); + int ans = 0; + for (int i = 0, rest = len - 1; i < num.length; i++, rest--) { + ans += (num[i] - 'a') * f(rest) + 1; + } + return ans; + } + + // 不管以什么开头,剩下长度为(0~len)的所有可能性有几个 + public static int f(int len) { + int ans = 1; + for (int i = 1, base = 26; i <= len; i++, base *= 26) { + ans += base; + } + return ans; + } + + // 为了测试 + public static List all(int len) { + List ans = new ArrayList<>(); + for (int i = 1; i <= len; i++) { + char[] path = new char[i]; + process(path, 0, ans); + } + return ans; + } + + // 为了测试 + public static void process(char[] path, int index, List ans) { + if (index == path.length) { + ans.add(String.valueOf(path)); + } else { + for (char c = 'a'; c <= 'z'; c++) { + path[index] = c; + process(path, index + 1, ans); + } + } + } + + public static void main(String[] args) { + int len = 4; + // 暴力方法得到所有字符串 + List ans = all(len); + // 根据字典序排序,所有字符串都在其中 + ans.sort((a, b) -> a.compareTo(b)); + + String test = "bzzz"; + // 根据我们的方法算出test是第几个? + // 注意我们算出的第几个,是从1开始的 + // 而下标是从0开始的,所以变成index,还需要-1 + int index = kth(test, len) - 1; + // 验证 + System.out.println(ans.get(index)); + } + +} diff --git a/公开课/class059/Code05_SubarraySumEqualsK.java b/公开课/class059/Code05_SubarraySumEqualsK.java new file mode 100644 index 0000000..7376f87 --- /dev/null +++ b/公开课/class059/Code05_SubarraySumEqualsK.java @@ -0,0 +1,34 @@ +package class059; + +import java.util.HashMap; + +public class Code05_SubarraySumEqualsK { + + public static int subarraySum(int[] nums, int sum) { + if (nums == null || nums.length == 0) { + return 0; + } + // key : 某一个前缀和! + // value : 这个前缀和,出现了几次! + HashMap preSumTimesMap = new HashMap<>(); + preSumTimesMap.put(0, 1); + // 每一步的整体和, 当你遍历到i位置,0~i整体的累加和! + int all = 0; + int ans = 0; + for (int i = 0; i < nums.length; i++) { + // 0...i的整体累加和了!1000 200 1000 - 200 = 800 + all += nums[i]; + if (preSumTimesMap.containsKey(all - sum)) { + ans += preSumTimesMap.get(all - sum); + } + // 0....i 这个前缀和,一定要去!更新map! + if (!preSumTimesMap.containsKey(all)) { + preSumTimesMap.put(all, 1); + } else { + preSumTimesMap.put(all, preSumTimesMap.get(all) + 1); + } + } + return ans; + } + +} diff --git a/公开课/class059/Code06_Ratio01Split.java b/公开课/class059/Code06_Ratio01Split.java new file mode 100644 index 0000000..a38022c --- /dev/null +++ b/公开课/class059/Code06_Ratio01Split.java @@ -0,0 +1,68 @@ +package class059; + +import java.util.HashMap; + +// 来自京东 +// 把一个01字符串切成多个部分,要求每一部分的0和1比例一样,同时要求尽可能多的划分 +// 比如 : 01010101 +// 01 01 01 01 这是一种切法,0和1比例为 1 : 1 +// 0101 0101 也是一种切法,0和1比例为 1 : 1 +// 两种切法都符合要求,但是那么尽可能多的划分为第一种切法,部分数为4 +// 比如 : 00001111 +// 只有一种切法就是00001111整体作为一块,那么尽可能多的划分,部分数为1 +// 给定一个01字符串str,假设长度为N,要求返回一个长度为N的数组ans +// 其中ans[i] = str[0...i]这个前缀串,要求每一部分的0和1比例一样,同时要求尽可能多的划分下,部分数是多少 +// 输入: str = "010100001" +// 输出: ans = [1, 1, 1, 2, 1, 2, 1, 1, 3] +public class Code06_Ratio01Split { + + // 001010010100... + public static int[] split(int[] arr) { + + // key : 分子 + // value : 属于key的分母表, 每一个分母,及其 分子/分母 这个比例,多少个前缀拥有 + HashMap> pre = new HashMap<>(); + int n = arr.length; + int[] ans = new int[n]; + int zero = 0; // 0出现的次数 + int one = 0; // 1出现的次数 + for (int i = 0; i < n; i++) { + if (arr[i] == 0) { + zero++; + } else { + one++; + } + if (zero == 0 || one == 0) { + ans[i] = i + 1; + } else { // 0和1,都有数量 -> 最简分数 + int gcd = gcd(zero, one); + int a = zero / gcd; + int b = one / gcd; + // a / b 比例,之前有多少前缀拥有? 3+1 4 5+1 6 + if (!pre.containsKey(a)) { + pre.put(a, new HashMap<>()); + } + if (!pre.get(a).containsKey(b)) { + pre.get(a).put(b, 1); + } else { + pre.get(a).put(b, pre.get(a).get(b) + 1); + } + ans[i] = pre.get(a).get(b); + } + } + return ans; + } + + public static int gcd(int m, int n) { + return n == 0 ? m : gcd(n, m % n); + } + + public static void main(String[] args) { + int[] arr = { 0, 1, 0, 1, 0, 1, 1, 0 }; + int[] ans = split(arr); + for (int i = 0; i < ans.length; i++) { + System.out.print(ans[i] + " "); + } + } + +} diff --git a/公开课/class060/Code01_MinSwapTimes.java b/公开课/class060/Code01_MinSwapTimes.java new file mode 100644 index 0000000..d20d191 --- /dev/null +++ b/公开课/class060/Code01_MinSwapTimes.java @@ -0,0 +1,88 @@ +package class060; + +// 来自小红书 +// 一个无序数组长度为n,所有数字都不一样,并且值都在[0...n-1]范围上 +// 返回让这个无序数组变成有序数组的最少交换次数 +public class Code01_MinSwapTimes { + + // 纯暴力,arr长度大一点都会超时 + // 但是绝对正确 + public static int minSwap1(int[] arr) { + return process1(arr, 0); + } + + // 让arr变有序,最少的交换次数是多少!返回 + // times, 之前已经做了多少次交换 + public static int process1(int[] arr, int times) { + boolean sorted = true; + for (int i = 1; i < arr.length; i++) { + if (arr[i - 1] > arr[i]) { + sorted = false; + break; + } + } + if (sorted) { + return times; + } + // 数组现在是无序的状态! + if (times >= arr.length - 1) { + return Integer.MAX_VALUE; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + swap(arr, i, j); + ans = Math.min(ans, process1(arr, times + 1)); + swap(arr, i, j); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 已知arr中,只有0~n-1这些值,并且都出现1次 + public static int minSwap2(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { // i -> i + while (i != arr[i]) { + swap(arr, i, arr[i]); + ans++; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = i; + } + for (int i = 0; i < len; i++) { + swap(arr, i, (int) (Math.random() * len)); + } + return arr; + } + + public static void main(String[] args) { + int n = 6; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len); + int ans1 = minSwap1(arr); + int ans2 = minSwap2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class060/Code02_MaxMeetingScore.java b/公开课/class060/Code02_MaxMeetingScore.java new file mode 100644 index 0000000..8a0f764 --- /dev/null +++ b/公开课/class060/Code02_MaxMeetingScore.java @@ -0,0 +1,114 @@ +package class060; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自阿里 +// 给定int[][] meetings,比如 +// { +// {66, 70} 0号会议截止时间66,获得收益70 +// {25, 90} 1号会议截止时间25,获得收益90 +// {50, 30} 2号会议截止时间50,获得收益30 +// } +// 一开始的时间是0,任何会议都持续10的时间,但是一个会议一定要在该会议截止时间之前开始 +// 只有一个会议室,任何会议不能共用会议室,一旦一个会议被正确安排,将获得这个会议的收益 +// 请返回最大的收益 +public class Code02_MaxMeetingScore { + + public static int maxScore1(int[][] meetings) { + Arrays.sort(meetings, (a, b) -> a[0] - b[0]); + int[][] path = new int[meetings.length][]; + int size = 0; + return process1(meetings, 0, path, size); + } + + public static int process1(int[][] meetings, int index, int[][] path, int size) { + if (index == meetings.length) { + int time = 0; + int ans = 0; + for (int i = 0; i < size; i++) { + if (time + 10 <= path[i][0]) { + ans += path[i][1]; + time += 10; + } else { + return 0; + } + } + return ans; + } + int p1 = process1(meetings, index + 1, path, size); + path[size] = meetings[index]; + int p2 = process1(meetings, index + 1, path, size + 1); + // path[size] = null; + return Math.max(p1, p2); + } + + // 所有会议,先根据截止时间排序 + // [10,40] 、 [5,32] -> [5,32] [10,40] + // 比较器!java c++ 重载比较运算符 + public static int maxScore2(int[][] meetings) { + Arrays.sort(meetings, (a, b) -> a[0] - b[0]); + // 小根堆,里面放收益,收益小的在顶,收益大的在底 + PriorityQueue heap = new PriorityQueue<>(); + int time = 0; + // 已经把所有会议,按照截止时间,从小到大,排序了! + // 截止时间一样的,谁排前谁排后,无所谓 + for (int i = 0; i < meetings.length; i++) { + if (time + 10 <= meetings[i][0]) { + heap.add(meetings[i][1]); + time += 10; + } else { // 不能通过增加会议数量,来安排;只能看,能不能挤掉之前的、最不行的会议 + if (!heap.isEmpty() && heap.peek() < meetings[i][1]) { + heap.poll(); + heap.add(meetings[i][1]); + } + } + } + int ans = 0; + while (!heap.isEmpty()) { + ans += heap.poll(); + } + return ans; + } + + public static int[][] randomMeetings(int n, int t, int s) { + int[][] ans = new int[n][2]; + for (int i = 0; i < n; i++) { + ans[i][0] = (int) (Math.random() * t) + 1; + ans[i][1] = (int) (Math.random() * s) + 1; + } + return ans; + } + + public static int[][] copyMeetings(int[][] meetings) { + int n = meetings.length; + int[][] ans = new int[n][2]; + for (int i = 0; i < n; i++) { + ans[i][0] = meetings[i][0]; + ans[i][1] = meetings[i][1]; + } + return ans; + } + + public static void main(String[] args) { + int n = 12; + int t = 100; + int s = 500; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * n) + 1; + int[][] meetings1 = randomMeetings(size, t, s); + int[][] meetings2 = copyMeetings(meetings1); + int ans1 = maxScore1(meetings1); + int ans2 = maxScore2(meetings2); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class060/Code03_NextPermutation.java b/公开课/class060/Code03_NextPermutation.java new file mode 100644 index 0000000..21dc1b1 --- /dev/null +++ b/公开课/class060/Code03_NextPermutation.java @@ -0,0 +1,46 @@ +package class060; + +// 来自Amazon +// leetcode原题 : https://leetcode.com/problems/next-permutation/ +public class Code03_NextPermutation { + + public static void nextPermutation(int[] nums) { + int N = nums.length; + // 从右往左第一次降序的位置 + int firstLess = -1; + for (int i = N - 2; i >= 0; i--) { + if (nums[i] < nums[i + 1]) { + firstLess = i; + break; + } + } + if (firstLess < 0) { + reverse(nums, 0, N - 1); + } else { + int rightClosestMore = -1; + // 找最靠右的、同时比nums[firstLess]大的数,位置在哪 + // 这里其实也可以用二分优化,但是这种优化无关紧要了 + for (int i = N - 1; i > firstLess; i--) { + if (nums[i] > nums[firstLess]) { + rightClosestMore = i; + break; + } + } + swap(nums, firstLess, rightClosestMore); + reverse(nums, firstLess + 1, N - 1); + } + } + + public static void reverse(int[] nums, int L, int R) { + while (L < R) { + swap(nums, L++, R--); + } + } + + public static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + +} diff --git a/公开课/class060/Code04_SequenceKDifferentKinds.java b/公开课/class060/Code04_SequenceKDifferentKinds.java new file mode 100644 index 0000000..c208eaf --- /dev/null +++ b/公开课/class060/Code04_SequenceKDifferentKinds.java @@ -0,0 +1,111 @@ +package class060; + +// 来自百度 +// 给定一个字符串str,和一个正数k +// str子序列的字符种数必须是k种,返回有多少子序列满足这个条件 +// 已知str中都是小写字母 +public class Code04_SequenceKDifferentKinds { + +// public static int waysAll(String str, int k) { +// +// int[] arr = 通过str生成; +// // arr[0....]自由选择,一定要选出k中字符来!不同的子序列有多少个 +// return ways(arr, 0, k); +// +// } + + // "aababddfeeef" + // a 3个 + // b 2个 + // c 0个 + // d 2个 + // e 3个 + // f 2个 + // g 0个 + // ... + // z 0个 + // int[] arr = { 3 2 0 2 3 } + // 0 1 2 3 4... + // 原始的字符串,变成了arr的形式来表达! + // restKinds : 还剩下几种字符,需要去选! + // "....." K = 3 + // "....." -> int[] arr + // return ways(arr, 3); + // arr[i....]桶,自由选择,一定要选出restKinds种来 + public static int zuo(int[] arr, int i, int restKinds) { + if (i == arr.length) { // 结束了 + return restKinds == 0 ? 1 : 0; + } else { // 没结束,还有字符,可供选择 + + // 就是不考虑i位置的字符 + int p1 = zuo(arr, i + 1, restKinds); + + // 一定要选择,i位置的字符 + int p2 = 0; + if (arr[i] != 0) { + +// 选i字符的方法 = C n 1 + C n 2 + C n 3 + ..... + C n n +// +// +// p2 = 选i字符的方法 * ways(arr, i+1, restKinds-1); +// +// p2 = (2的arr[i]次方 - 1) * ways(arr, i+1, restKinds-1); + + p2 = ((1 << arr[i]) - 1) * zuo(arr, i + 1, restKinds - 1); + } + return p1 + p2; + } + } + + // bu -> {6,7,0,0,6,3} + // 0 1 2 3 4 5 + // a b c d e f + // 在桶数组bu[index....] 一定要凑出rest种来!请问几种方法! + public static int f(int[] bu, int index, int rest) { + if (index == bu.length) { + return rest == 0 ? 1 : 0; + } + // 最后形成的子序列,一个index代表的字符也没有! + int p1 = f(bu, index + 1, rest); + // 最后形成的子序列,一定要包含index代表的字符,几个呢?(所有可能性都要算上!) + int p2 = 0; + if (rest > 0) { // 剩余的种数,没耗尽,可以包含当前桶的字符 + p2 = (1 << bu[index] - 1) * f(bu, index + 1, rest - 1); + } + return p1 + p2; + } + + public static int nums(String s, int k) { + char[] str = s.toCharArray(); + int[] counts = new int[26]; + for (char c : str) { + counts[c - 97]++; + } + return ways(counts, 0, k); + } + + public static int ways(int[] c, int i, int r) { + if (r == 0) { + return 1; + } + if (i == c.length) { + return 0; + } + // math(n) -> 2 ^ n -1 + return math(c[i]) * ways(c, i + 1, r - 1) + ways(c, i + 1, r); + } + + // n个不同的球 + // 挑出1个的方法数 + 挑出2个的方法数 + ... + 挑出n个的方法数为: + // C(n,1) + C(n,2) + ... + C(n,n) == (2 ^ n) -1 + public static int math(int n) { + return (1 << n) - 1; + } + + public static void main(String[] args) { + String str = "acbbca"; + int k = 3; + System.out.println(nums(str, k)); + } + +} diff --git a/公开课/class061/Code01_HashFunction.java b/公开课/class061/Code01_HashFunction.java new file mode 100644 index 0000000..28091b3 --- /dev/null +++ b/公开课/class061/Code01_HashFunction.java @@ -0,0 +1,51 @@ +package class061; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +import javax.xml.bind.DatatypeConverter; + +public class Code01_HashFunction { + + public static class Hash { + + private MessageDigest hash; + + public Hash(String algorithm) { + try { + hash = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public String hashCode(String input) { + return DatatypeConverter.printHexBinary(hash.digest(input.getBytes())).toUpperCase(); + } + } + + public static void main(String[] args) { + System.out.println("支持的算法 : "); + for (String str : Security.getAlgorithms("MessageDigest")) { + System.out.println(str); + } + System.out.println("======="); + + String algorithm = "MD5"; + Hash hash = new Hash(algorithm); + + String input1 = "zuofdjkafjksadfjasdkljfalsjflaskfjklasdfjklsdjflkdsfjadksfjlkdfjasdlkfjadlskjfkldjflkadsfjlkasdjflkdfjlkdjf"; + String input2 = "zuochengyunzuochengyun2"; + String input3 = "zuochengyunzuochengyun3"; + String input4 = "zuochengyunzuochengyun4"; + String input5 = "zuochengyunzuochengyun5"; + System.out.println(hash.hashCode(input1)); + System.out.println(hash.hashCode(input2)); + System.out.println(hash.hashCode(input3)); + System.out.println(hash.hashCode(input4)); + System.out.println(hash.hashCode(input5)); + + } + +} diff --git a/公开课/class061/Code02_FillGapMinStep.java b/公开课/class061/Code02_FillGapMinStep.java new file mode 100644 index 0000000..9aaa3fc --- /dev/null +++ b/公开课/class061/Code02_FillGapMinStep.java @@ -0,0 +1,103 @@ +package class061; + +// 来自字节 +// 给定两个数a和b +// 第1轮,把1选择给a或者b +// 第2轮,把2选择给a或者b +// ... +// 第i轮,把i选择给a或者b +// 想让a和b的值一样大,请问至少需要多少轮? +public class Code02_FillGapMinStep { + + // 暴力方法 + // 不要让a、b过大! + public static int minStep0(int a, int b) { + if (a == b) { + return 0; + } + int limit = 15; + return process(a, b, 1, limit); + } + + public static int process(int a, int b, int i, int n) { + if (i > n) { + return Integer.MAX_VALUE; + } + if (a + i == b || a == b + i) { + return i; + } + return Math.min(process(a + i, b, i + 1, n), process(a, b + i, i + 1, n)); + } + + public static int minStep1(int a, int b) { + if (a == b) { + return 0; + } + int s = Math.abs(a - b); + int num = 1; + int sum = 0; + for (; !(sum >= s && (sum - s) % 2 == 0); num++) { + sum += num; + } + return num - 1; + } + + public static int minStep2(int a, int b) { + if (a == b) { + return 0; + } + int s = Math.abs(a - b); + // 找到sum >= s, 最小的i + int begin = best(s << 1); + for (; (begin * (begin + 1) / 2 - s) % 2 != 0;) { + begin++; + } + return begin; + } + + public static int best(int s2) { + int L = 0; + int R = 1; + for (; R * (R + 1) < s2;) { + L = R; + R *= 2; + } + int ans = 0; + while (L <= R) { + int M = (L + R) / 2; + if (M * (M + 1) >= s2) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + public static void main(String[] args) { + System.out.println("功能测试开始"); + for (int a = 1; a < 100; a++) { + for (int b = 1; b < 100; b++) { + int ans1 = minStep0(a, b); + int ans2 = minStep1(a, b); + int ans3 = minStep2(a, b); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + System.out.println(a + " , " + b); + break; + } + } + } + System.out.println("功能测试结束"); + + int a = 19019; + int b = 8439284; + int ans2 = minStep1(a, b); + int ans3 = minStep2(a, b); + System.out.println(ans2); + System.out.println(ans3); + + } + +} diff --git a/公开课/class061/Code03_Mod3Max.java b/公开课/class061/Code03_Mod3Max.java new file mode 100644 index 0000000..236db82 --- /dev/null +++ b/公开课/class061/Code03_Mod3Max.java @@ -0,0 +1,160 @@ +package class061; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.TreeSet; + +// 来自去哪儿网 +// 给定一个arr,里面的数字都是0~9 +// 你可以随意使用arr中的数字,哪怕打乱顺序也行 +// 请拼出一个能被3整除的,最大的数字,用str形式返回 +public class Code03_Mod3Max { + + public static String max1(int[] arr) { + Arrays.sort(arr); + for (int l = 0, r = arr.length - 1; l < r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + StringBuilder builder = new StringBuilder(); + TreeSet set = new TreeSet<>((a, b) -> Integer.valueOf(b).compareTo(Integer.valueOf(a))); + process1(arr, 0, builder, set); + return set.isEmpty() ? "" : set.first(); + } + + public static void process1(int[] arr, int index, StringBuilder builder, TreeSet set) { + if (index == arr.length) { + if (builder.length() != 0 && Integer.valueOf(builder.toString()) % 3 == 0) { + set.add(builder.toString()); + } + } else { + process1(arr, index + 1, builder, set); + builder.append(arr[index]); + process1(arr, index + 1, builder, set); + builder.deleteCharAt(builder.length() - 1); + } + } + + // 贪心的思路解法 : + // 先得到数组的累加和,记为sum + // 1) 如果sum%3==0,说明所有数从大到小拼起来就可以了 + // 2) 如果sum%3==1,说明多了一个余数1, + // 只需要删掉一个最小的数(该数是%3==1的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==2的数); + // 3) 如果sum%3==2,说明多了一个余数2, + // 只需要删掉一个最小的数(该数是%3==2的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==1的数); + // 如果上面都做不到,说明拼不成 + public static String max2(int[] A) { + if (A == null || A.length == 0) { + return ""; + } + int mod = 0; + ArrayList arr = new ArrayList<>(); + for (int num : A) { + arr.add(num); + mod += num; + mod %= 3; + } + if ((mod == 1 || mod == 2) && !remove(arr, mod, 3 - mod)) { + return ""; + } + if (arr.isEmpty()) { + return ""; + } + arr.sort((a, b) -> b - a); + if (arr.get(0) == 0) { + return "0"; + } + StringBuilder builder = new StringBuilder(); + for (int num : arr) { + builder.append(num); + } + return builder.toString(); + } + + // 在arr中,要么删掉最小的一个、且%3之后余数是first的数 + // 如果做不到,删掉最小的两个、且%3之后余数是second的数 + // 如果能做到返回true,不能做到返回false + public static boolean remove(ArrayList arr, int first, int second) { + if (arr.size() == 0) { + return false; + } + arr.sort((a, b) -> compare(a, b, first, second)); + int size = arr.size(); + if (arr.get(size - 1) % 3 == first) { + arr.remove(size - 1); + return true; + } else if (size > 1 && arr.get(size - 1) % 3 == second && arr.get(size - 2) % 3 == second) { + arr.remove(size - 1); + arr.remove(size - 2); + return true; + } else { + return false; + } + } + + // a和b比较: + // 如果余数一样,谁大谁放前面 + // 如果余数不一样,余数是0的放最前面、余数是s的放中间、余数是f的放最后 + public static int compare(int a, int b, int f, int s) { + int ma = a % 3; + int mb = b % 3; + if (ma == mb) { + return b - a; + } else { + if (ma == 0 || mb == 0) { + return ma == 0 ? -1 : 1; + } else { + return ma == s ? -1 : 1; + } + } + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * 10); + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * N); + int[] arr1 = randomArray(len); + int[] arr2 = copyArray(arr1); + int[] arr3 = copyArray(arr1); + String ans1 = max1(arr1); + String ans2 = max2(arr2); + if (!ans1.equals(ans2) ) { + System.out.println("出错了!"); + for (int num : arr3) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class062/Code01_MonotonousStack.java b/公开课/class062/Code01_MonotonousStack.java new file mode 100644 index 0000000..f7d2ae2 --- /dev/null +++ b/公开课/class062/Code01_MonotonousStack.java @@ -0,0 +1,172 @@ +package class062; + +import java.util.List; +import java.util.ArrayList; +import java.util.Stack; + +public class Code01_MonotonousStack { + + // arr = [ 3, 1, 2, 3] + // 0 1 2 3 + // [ + // 0 : [-1, 1] + // 1 : [-1, -1] + // 2 : [ 1, -1] + // 3 : [ 2, -1] + // ] + public static int[][] getNearLessNoRepeat(int[] arr) { + int[][] res = new int[arr.length][2]; + // 只存位置! + Stack stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // 当遍历到i位置的数,arr[i] + while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = i; + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = -1; + } + return res; + } + + + // arr [ 3 , 4, 3, 2, 3, 4, 1 ] + // 0 1 2 3 4 5 6 + + + // 0 -> 3 [-1, 3] + // 1 -> 4 [0 , 2] + public static int[][] getNearLess(int[] arr) { + int[][] res = new int[arr.length][2]; + Stack> stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈 + while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = i; + } + } + if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) { + stack.peek().add(Integer.valueOf(i)); + } else { + ArrayList list = new ArrayList<>(); + list.add(i); + stack.push(list); + } + } + while (!stack.isEmpty()) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = -1; + } + } + return res; + } + + // for test + public static int[] getRandomArrayNoRepeat(int size) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + for (int i = 0; i < arr.length; i++) { + int swapIndex = (int) (Math.random() * arr.length); + int tmp = arr[swapIndex]; + arr[swapIndex] = arr[i]; + arr[i] = tmp; + } + return arr; + } + + // for test + public static int[] getRandomArray(int size, int max) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // for test + public static int[][] rightWay(int[] arr) { + int[][] res = new int[arr.length][2]; + for (int i = 0; i < arr.length; i++) { + int leftLessIndex = -1; + int rightLessIndex = -1; + int cur = i - 1; + while (cur >= 0) { + if (arr[cur] < arr[i]) { + leftLessIndex = cur; + break; + } + cur--; + } + cur = i + 1; + while (cur < arr.length) { + if (arr[cur] < arr[i]) { + rightLessIndex = cur; + break; + } + cur++; + } + res[i][0] = leftLessIndex; + res[i][1] = rightLessIndex; + } + return res; + } + + // for test + public static boolean isEqual(int[][] res1, int[][] res2) { + if (res1.length != res2.length) { + return false; + } + for (int i = 0; i < res1.length; i++) { + if (res1[i][0] != res2[i][0] || res1[i][1] != res2[i][1]) { + return false; + } + } + + return true; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int size = 10; + int max = 20; + int testTimes = 2000000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr1 = getRandomArrayNoRepeat(size); + int[] arr2 = getRandomArray(size, max); + if (!isEqual(getNearLessNoRepeat(arr1), rightWay(arr1))) { + System.out.println("Oops!"); + printArray(arr1); + break; + } + if (!isEqual(getNearLess(arr2), rightWay(arr2))) { + System.out.println("Oops!"); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } +} diff --git a/公开课/class062/Code02_LargestRectangleInHistogram.java b/公开课/class062/Code02_LargestRectangleInHistogram.java new file mode 100644 index 0000000..fb57699 --- /dev/null +++ b/公开课/class062/Code02_LargestRectangleInHistogram.java @@ -0,0 +1,58 @@ +package class062; + +import java.util.Stack; + +// 测试链接:https://leetcode.com/problems/largest-rectangle-in-histogram +public class Code02_LargestRectangleInHistogram { + + public static int largestRectangleArea1(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int maxArea = 0; + Stack stack = new Stack(); + for (int i = 0; i < height.length; i++) { + while (!stack.isEmpty() && height[i] <= height[stack.peek()]) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + + public static int largestRectangleArea2(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int N = height.length; + int[] stack = new int[N]; + int si = -1; + int maxArea = 0; + for (int i = 0; i < height.length; i++) { + while (si != -1 && height[i] <= height[stack[si]]) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack[++si] = i; + } + while (si != -1) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + +} diff --git a/公开课/class062/Code03_ValidSequence.java b/公开课/class062/Code03_ValidSequence.java new file mode 100644 index 0000000..79a0480 --- /dev/null +++ b/公开课/class062/Code03_ValidSequence.java @@ -0,0 +1,113 @@ +package class062; + +// 来自腾讯 +// 给定一个长度为n的数组arr,求有多少个子数组满足 : +// 子数组两端的值,是这个子数组的最小值和次小值,最小值和次小值谁在最左和最右无所谓 +// n<=100000(10^5) n*logn O(N) +public class Code03_ValidSequence { + + + public static int nums(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int n = arr.length; + int[] values = new int[n]; + int[] times = new int[n]; + int size = 0; + int ans = 0; + for (int i = 0; i < arr.length; i++) { + while (size != 0 && values[size - 1] > arr[i]) { + size--; + ans += times[size] + cn2(times[size]); + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + while (size != 0) { + ans += cn2(times[--size]); + } + for (int i = arr.length - 1; i >= 0; i--) { + while (size != 0 && values[size - 1] > arr[i]) { + ans += times[--size]; + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + return ans; + } + + public static int cn2(int n) { + return (n * (n - 1)) >> 1; + } + + // 为了测试 + // 暴力方法 + public static int test(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int ans = 0; + for (int s = 0; s < arr.length; s++) { + for (int e = s + 1; e < arr.length; e++) { + int max = Math.max(arr[s], arr[e]); + boolean valid = true; + for (int i = s + 1; i < e; i++) { + if (arr[i] < max) { + valid = false; + break; + } + } + ans += valid ? 1 : 0; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int n = 30; + int v = 30; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * n); + int[] arr = randomArray(m, v); + int ans1 = nums(arr); + int ans2 = test(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class062/Code04_MaxKLenSequence.java b/公开课/class062/Code04_MaxKLenSequence.java new file mode 100644 index 0000000..111c86b --- /dev/null +++ b/公开课/class062/Code04_MaxKLenSequence.java @@ -0,0 +1,92 @@ +package class062; + +import java.util.TreeSet; + +// 来自腾讯 +// 给定一个字符串str,和一个正数k +// 返回长度为k的所有子序列中,字典序最大的子序列 +public class Code04_MaxKLenSequence { + + public static String maxString(String s, int k) { + if (k <= 0 || s.length() < k) { + return ""; + } + char[] str = s.toCharArray(); + int n = str.length; + // 栈,栈大小 size 栈顶stack[size-1] + char[] stack = new char[n]; + int size = 0; + for (int i = 0; i < n; i++) { + // 当前字符 str[i] + while (size > 0 && stack[size - 1] < str[i] && size + n - i > k) { + size--; + } + if (size + n - i == k) { + return String.valueOf(stack, 0, size) + s.substring(i); + } + stack[size++] = str[i]; + } + return String.valueOf(stack, 0, k); + } + + // 为了测试 + // 纯暴力,我把str,所有的、长度为k的,子序列全生成了!找出一个最大的! + public static String test(String str, int k) { + if (k <= 0 || str.length() < k) { + return ""; + } + TreeSet ans = new TreeSet<>(); + process(0, 0, str.toCharArray(), new char[k], ans); + return ans.last(); + } + + // 为了测试 + public static void process(int si, int pi, char[] str, char[] path, TreeSet ans) { + if (si == str.length) { + if (pi == path.length) { + ans.add(String.valueOf(path)); + } + } else { + process(si + 1, pi, str, path, ans); + if (pi < path.length) { + path[pi] = str[si]; + process(si + 1, pi + 1, str, path, ans); + } + } + } + + // 为了测试 + public static String randomString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + System.out.println(maxString("ccfaa", 3)); + + int n = 12; + int r = 5; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * (n + 1)); + String str = randomString(len, r); + int k = (int) (Math.random() * (str.length() + 1)); + String ans1 = maxString(str, k); + String ans2 = test(str, k); + if (!ans1.equals(ans2)) { + System.out.println("出错了!"); + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class063/Code01_SumNoPositiveMinCost.java b/公开课/class063/Code01_SumNoPositiveMinCost.java new file mode 100644 index 0000000..f893321 --- /dev/null +++ b/公开课/class063/Code01_SumNoPositiveMinCost.java @@ -0,0 +1,138 @@ +package class063; + +import java.util.Arrays; + +// 来自微软面试 +// 给定一个正数数组arr长度为n、正数x、正数y +// 你的目标是让arr整体的累加和<=0 +// 你可以对数组中的数num执行以下三种操作中的一种,且每个数最多能执行一次操作 : +// 1)不变 +// 2)可以选择让num变成0,承担x的代价 +// 3)可以选择让num变成-num,承担y的代价 +// 返回你达到目标的最小代价 +// 数据规模 : 面试时面试官没有说数据规模 +public class Code01_SumNoPositiveMinCost { + + // 暴力尝试 + public static int minOpStep1(int[] arr, int x, int y) { + int sum = 0; + for (int num : arr) { + sum += num; + } + return process1(arr, x, y, 0, sum); + } + + // arr[i...]自由选择,每个位置的数可以执行三种操作中的一种! + // 执行变0的操作,x操作,代价 -> x + // 执行变相反数的操作,y操作,代价 -> y + // 还剩下sum这么多累加和,需要去搞定! + // 返回搞定了sum,最低代价是多少? + public static int process1(int[] arr, int x, int y, int i, int sum) { + if (sum <= 0) { + return 0; + } + // sum > 0 没搞定 + if (i == arr.length) { + return Integer.MAX_VALUE; + } + // 第一选择,什么也不干! + int p1 = process1(arr, x, y, i + 1, sum); + // 第二选择,执行x的操作,变0 x + 后续 + int p2 = Integer.MAX_VALUE; + int next2 = process1(arr, x, y, i + 1, sum - arr[i]); + if (next2 != Integer.MAX_VALUE) { + p2 = x + next2; + } + // 第三选择,执行y的操作,变相反数 x + 后续 7 -7 -14 + int p3 = Integer.MAX_VALUE; + int next3 = process1(arr, x, y, i + 1, sum - (arr[i] << 1)); + if (next3 != Integer.MAX_VALUE) { + p3 = y + next3; + } + return Math.min(p1, Math.min(p2, p3)); + } + + // 最优解(贪心) + public static int minOpStep2(int[] arr, int x, int y) { + // 系统排序,小 -> 大 + Arrays.sort(arr); + int n = arr.length; + // 如何变成 大 -> 小 + for (int l = 0, r = n - 1; l <= r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + if (x >= y) { + int sum = 0; + for (int num : arr) { + sum += num; + } + int cost = 0; + for (int i = 0; i < n && sum > 0; i++) { + sum -= arr[i] << 1; + cost += y; + } + return cost; + } else { + // 0个数执行Y + int benefit = 0; + // 全部的数都需要执行x,才能让累加和<=0 + int cost = arr.length * x; + int holdSum = 0; + for (int yRight = 0, holdLeft = n; yRight < holdLeft - 1; yRight++) { + benefit += arr[yRight]; + while (holdLeft - 1 > yRight && holdSum + arr[holdLeft - 1] <= benefit) { + holdSum += arr[holdLeft - 1]; + holdLeft--; + } + // 0...yRight x holdLeft.... + int curCost = (yRight + 1) * y + (holdLeft - yRight - 1) * x; + cost = Math.min(cost, curCost); + } + return cost; + } + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 14; + int v = 20; + int c = 6; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n); + int[] arr = randomArray(len, v); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int x = (int) (Math.random() * c); + int y = (int) (Math.random() * c); + int ans1 = minOpStep1(arr1, x, y); + int ans2 = minOpStep2(arr2, x, y); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class063/Code02_SplitBuildingBlock.java b/公开课/class063/Code02_SplitBuildingBlock.java new file mode 100644 index 0000000..d70a1e9 --- /dev/null +++ b/公开课/class063/Code02_SplitBuildingBlock.java @@ -0,0 +1,61 @@ +package class063; + +import java.util.Arrays; + +// 来自京东笔试 +// 小明手中有n块积木,并且小明知道每块积木的重量。现在小明希望将这些积木堆起来 +// 要求是任意一块积木如果想堆在另一块积木上面,那么要求: +// 1) 上面的积木重量不能小于下面的积木重量 +// 2) 上面积木的重量减去下面积木的重量不能超过x +// 3) 每堆中最下面的积木没有重量要求 +// 现在小明有一个机会,除了这n块积木,还可以获得k块任意重量的积木。 +// 小明希望将积木堆在一起,同时希望积木堆的数量越少越好,你能帮他找到最好的方案么? +// 输入描述: +// 第一行三个整数n,k,x,1<=n<=200000,0<=x,k<=1000000000 +// 第二行n个整数,表示积木的重量,任意整数范围都在[1,1000000000] +// 样例输出: +// 13 1 38 +// 20 20 80 70 70 70 420 5 1 5 1 60 90 +// 1 1 5 5 20 20 60 70 70 70 80 90 420 -> 只有1块魔法积木,x = 38 +// 输出:2 +// 解释: +// 两堆分别是 +// 1 1 5 5 20 20 (50) 60 70 70 70 80 90 +// 420 +// 其中x是一个任意重量的积木,夹在20和60之间可以让积木继续往上搭 +public class Code02_SplitBuildingBlock { + + // arr里装着所有积木的重量 + // k是魔法积木的数量,每一块魔法积木都能变成任何重量 + // x差值,后 - 前 <= x + public static int minSplit(int[] arr, int k, int x) { + // 从小到大排序! + Arrays.sort(arr); + int n = arr.length; + int[] needs = new int[n]; + int size = 0; + int splits = 1; + for (int i = 1; i < n; i++) { + if (arr[i] - arr[i - 1] > x) { + needs[size++] = arr[i] - arr[i - 1]; + splits++; + } + } + if (splits == 1 || x == 0 || k == 0) { + return splits; + } + // 试图去利用魔法积木,弥合堆! + Arrays.sort(needs, 0, size); + for (int i = 0; i < size; i++) { + int need = (needs[i] - 1) / x; + if (k >= need) { + splits--; + k -= need; + } else { + break; + } + } + return splits; + } + +} diff --git a/公开课/class064/Code01_LIS.java b/公开课/class064/Code01_LIS.java new file mode 100644 index 0000000..481e54b --- /dev/null +++ b/公开课/class064/Code01_LIS.java @@ -0,0 +1,58 @@ +package class064; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code01_LIS { + + // 时间复杂度O(N^2) + public static int maxLen(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + dp[0] = 1; + int max = 1; + for (int i = 1; i < N; i++) { + dp[i] = 1; + int preMax = 0; + for (int j = 0; j < i; j++) { + if (arr[j] < arr[i]) { + preMax = Math.max(preMax, dp[j]); + } + } + dp[i] = Math.max(dp[i], preMax + 1); + max = Math.max(max, dp[i]); + } + return max; + } + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + int max = 1; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/公开课/class064/Code02_EnvelopesProblem.java b/公开课/class064/Code02_EnvelopesProblem.java new file mode 100644 index 0000000..76387d9 --- /dev/null +++ b/公开课/class064/Code02_EnvelopesProblem.java @@ -0,0 +1,60 @@ +package class064; + +import java.util.Arrays; +import java.util.Comparator; + +// 本题测试链接 : https://leetcode.com/problems/russian-doll-envelopes/ +public class Code02_EnvelopesProblem { + + public static int maxEnvelopes(int[][] matrix) { + Envelope[] arr = sort(matrix); + int[] ends = new int[matrix.length]; + ends[0] = arr[0].h; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i].h > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i].h; + } + return right + 1; + } + + public static class Envelope { + public int l; + public int h; + + public Envelope(int weight, int hight) { + l = weight; + h = hight; + } + } + + public static class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.l != o2.l ? o1.l - o2.l : o2.h - o1.h; + } + } + + public static Envelope[] sort(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} diff --git a/公开课/class064/Code03_MinSwapTimes.java b/公开课/class064/Code03_MinSwapTimes.java new file mode 100644 index 0000000..93ab4ec --- /dev/null +++ b/公开课/class064/Code03_MinSwapTimes.java @@ -0,0 +1,95 @@ +package class064; + +// 来自小红书 +// 一个无序数组长度为n,所有数字都不一样,并且值都在[0...n-1]范围上 +// 返回让这个无序数组变成有序数组的最小交换次数 +public class Code03_MinSwapTimes { + + // 纯暴力,arr长度大一点都会超时 + // 但是绝对正确 + public static int minSwap1(int[] arr) { + return process1(arr, 0); + } + + // 让arr变有序,最少的交换次数是多少!返回 + // times, 之前已经做了多少次交换 + public static int process1(int[] arr, int times) { + boolean sorted = true; + for (int i = 1; i < arr.length; i++) { + if (arr[i - 1] > arr[i]) { + sorted = false; + break; + } + } + if (sorted) { + return times; + } + // 数组现在是无序的状态! + if (times >= arr.length - 1) { + return Integer.MAX_VALUE; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + swap(arr, i, j); + ans = Math.min(ans, process1(arr, times + 1)); + swap(arr, i, j); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 已知arr中,只有0~n-1这些值,并且都出现1次 + public static int minSwap2(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { + // 来到0、1、2、3、。。。。i + // 0 -> 0 跳过 + // 7 -> 7 跳过 + // 6 -> 13 不能跳过! + // 6 != 13 6位置,13位置的数,交换 + // [6] = 13 -> 正确归为 + // [13] = ? -> 来到6位置 + while (i != arr[i]) { + swap(arr, i, arr[i]); + ans++; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = i; + } + for (int i = 0; i < len; i++) { + swap(arr, i, (int) (Math.random() * len)); + } + return arr; + } + + public static void main(String[] args) { + int n = 6; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len); + int ans1 = minSwap1(arr); + int ans2 = minSwap2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class064/Code04_PoemProblem.java b/公开课/class064/Code04_PoemProblem.java new file mode 100644 index 0000000..f271199 --- /dev/null +++ b/公开课/class064/Code04_PoemProblem.java @@ -0,0 +1,149 @@ +package class064; + +import java.util.Arrays; +import java.util.HashMap; + +// 来自小红书 +// 有四种诗的韵律分别为: AABB、ABAB、ABBA、AAAA +// 比如 : 1 1 3 3就属于AABB型的韵律、6 6 6 6就属于AAAA型的韵律等等 +// 一个数组arr,当然可以生成很多的子序列,如果某个子序列一直以韵律的方式连接起来,我们称这样的子序列是有效的 +// 比如, arr = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 } +// arr的一个子序列为{1, 1, 1, 1, 2, 3, 3, 2, 4, 3, 4, 3, 7, 7, 9, 9} +// 其中1, 1, 1, 1是AAAA、2, 3, 3, 2是ABBA、4, 3, 4, 3是ABAB、7, 7, 9, 9是AABB +// 可以看到,整个子序列一直以韵律的方式连接起来,所以这个子序列是有效的 +// 给定一个数组arr, 返回最长的有效子序列长度 +// 题目限制 : arr长度 <= 4000, arr中的值<= 10^9 +// 离散化之后,arr长度 <= 4000, arr中的值<= 4000 +public class Code04_PoemProblem { + + public static int maxLen1(int[] arr) { + if (arr == null || arr.length < 4) { + return 0; + } + int[] path = new int[arr.length]; + return process1(arr, 0, path, 0); + } + + public static int process1(int[] arr, int index, int[] path, int size) { + if (index == arr.length) { + if (size % 4 != 0) { + return 0; + } else { + for (int i = 0; i < size; i += 4) { + if (!valid(path, i)) { + return 0; + } + } + return size; + } + } else { + int p1 = process1(arr, index + 1, path, size); + path[size] = arr[index]; + int p2 = process1(arr, index + 1, path, size + 1); + return Math.max(p1, p2); + } + } + + public static boolean valid(int[] p, int i) { + // AABB + // ABAB + // ABBA + // AAAA + return (p[i] == p[i + 1] && p[i + 2] == p[i + 3]) + || (p[i] == p[i + 2] && p[i + 1] == p[i + 3] && p[i] != p[i + 1]) + || (p[i] == p[i + 3] && p[i + 1] == p[i + 2] && p[i] != p[i + 1]); + } + + // AABB + // ABAB + // ABBA + // AAAA + // 先看前三个规则:AABB、ABAB、ABBA + // 首先A、A、B、B的全排列为: + // AABB -> AABB + // ABAB -> ABAB + // ABBA -> ABBA + // BBAA -> 等同于AABB,因为A和B谁在前、谁在后都算是 : AABB的范式 + // BABA -> 等同于ABAB,因为A和B谁在前、谁在后都算是 : ABAB的范式 + // BAAB -> 等同于ABBA,因为A和B谁在前、谁在后都算是 : ABBA的范式 + // 也就是说,AABB、ABAB、ABBA这三个规则,可以这么用: + // 只要有两个不同的数,都出现2次,那么这一共4个数就一定符合韵律规则。 + // 所以: + // 1) 当来到arr中的一个数字num的时候, + // 如果num已经出现了2次了, 只要之前还有一个和num不同的数, + // 也出现了两次,则一定符合了某个规则, 长度直接+4,然后清空所有的统计 + // 2) 当来到arr中的一个数字num的时候, + // 如果num已经出现了4次了(规则四), 长度直接+4,然后清空所有的统计 + public static int maxLen2(int[] arr) { + // 统计某个数(key),出现的次数(value) + HashMap map = new HashMap<>(); + // tow代表目前有多少数出现了2次 + int two = 0; + // ans代表目前符合韵律链接的子序列增长到了多长 + int ans = 0; + // 当前的num出现了几次 + int numTimes = 0; + for (int num : arr) { + // 对当前的num,做次数统计 + map.put(num, map.getOrDefault(num, 0) + 1); + // 把num出现的次数拿出来 + numTimes = map.get(num); + // 如果num刚刚出现了2次, 那么目前出现了2次的数,的数量,需要增加1个 + two += numTimes == 2 ? 1 : 0; + // 下面的if代表 : + // 如果目前有2个数出现2次了,可以连接了 + // 如果目前有1个数出现4次了,可以连接了 + if (two == 2 || numTimes == 4) { + ans += 4; + map.clear(); + two = 0; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + // 1111 2332 4343 7799 + int[] test = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 }; + System.out.println(maxLen1(test)); + System.out.println(maxLen2(test)); + System.out.println("==========="); + System.out.println("随机测试开始"); + int len = 20; + int value = 6; + int testTime = 1000; + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int[] arr1 = Arrays.copyOf(arr, arr.length); + int[] arr2 = Arrays.copyOf(arr, arr.length); + int ans1 = maxLen1(arr1); + int ans2 = maxLen2(arr2); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("随机测试结束"); + System.out.println("==========="); + + long start; + long end; + int[] longArr = randomArray(5000000, 20); + start = System.currentTimeMillis(); + maxLen2(longArr); + end = System.currentTimeMillis(); + System.out.println("大样本量运行时间(毫秒) : " + (end - start)); + System.out.println("==========="); + } + +} diff --git a/公开课/class065/Code01_CoverMax.java b/公开课/class065/Code01_CoverMax.java new file mode 100644 index 0000000..a64a9c6 --- /dev/null +++ b/公开课/class065/Code01_CoverMax.java @@ -0,0 +1,157 @@ +package class065; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code01_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // lines + // + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + + PriorityQueue t = new PriorityQueue<>(); + + // add poll 极快,O(logN) + t.add(5); + t.add(3); + t.add(2); + t.add(4); + t.add(2); + t.add(3); + + while(!t.isEmpty()) { + System.out.println(t.poll()); + } + + + + + + + + + + +// Line l1 = new Line(4, 9); +// Line l2 = new Line(1, 4); +// Line l3 = new Line(7, 15); +// Line l4 = new Line(2, 4); +// Line l5 = new Line(4, 6); +// Line l6 = new Line(3, 7); +// +// // 底层堆结构,heap +// PriorityQueue heap = new PriorityQueue<>(new StartComparator()); +// heap.add(l1); +// heap.add(l2); +// heap.add(l3); +// heap.add(l4); +// heap.add(l5); +// heap.add(l6); +// +// while (!heap.isEmpty()) { +// Line cur = heap.poll(); +// System.out.println(cur.start + "," + cur.end); +// } +// +// System.out.println("test begin"); +// int N = 100; +// int L = 0; +// int R = 200; +// int testTimes = 200000; +// for (int i = 0; i < testTimes; i++) { +// int[][] lines = generateLines(N, L, R); +// int ans1 = maxCover1(lines); +// int ans2 = maxCover2(lines); +// if (ans1 != ans2) { +// System.out.println("Oops!"); +// } +// } +// System.out.println("test end"); + } + +} diff --git a/公开课/class065/Code02_Heaters.java b/公开课/class065/Code02_Heaters.java new file mode 100644 index 0000000..3d1fc0e --- /dev/null +++ b/公开课/class065/Code02_Heaters.java @@ -0,0 +1,114 @@ +package class065; + +import java.util.Arrays; + +// leetcode 475题 +public class Code02_Heaters { + + // 比如地点是7, 9, 14 + // 供暖点的位置: 1 3 4 5 13 15 17 + // 先看地点7 + // 由1供暖,半径是6 + // 由3供暖,半径是4 + // 由4供暖,半径是3 + // 由5供暖,半径是2 + // 由13供暖,半径是6 + // 由此可知,地点7应该由供暖点5来供暖,半径是2 + // 再看地点9 + // 供暖点不回退 + // 由5供暖,半径是4 + // 由13供暖,半径是4 + // 由15供暖,半径是6 + // 由此可知,地点9应该由供暖点13来供暖,半径是4 + // 为什么是13而不是5?因为接下来的地点都会更靠右,所以半径一样的时候,就应该选更右的供暖点 + // 再看地点14 + // 供暖点不回退 + // 由13供暖,半径是1 + // 由15供暖,半径是1 + // 由17供暖,半径是3 + // 由此可知,地点14应该由供暖点15来供暖,半径是1 + // 以此类推 + public static int findRadius(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + // 这个函数含义: + // 当前的地点houses[i]由heaters[j]来供暖是最优的吗? + // 当前的地点houses[i]由heaters[j]来供暖,产生的半径是a + // 当前的地点houses[i]由heaters[j + 1]来供暖,产生的半径是b + // 如果a < b, 说明是最优,供暖不应该跳下一个位置 + // 如果a >= b, 说明不是最优,应该跳下一个位置 + public static boolean best(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 + || Math.abs(heaters[j] - houses[i]) < Math.abs(heaters[j + 1] - houses[i]); + } + + // 下面这个方法不对,你能找出原因嘛?^_^ + public static int findRadius2(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best2(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + public static boolean best2(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 || Math.abs(heaters[j] - houses[i]) <= Math.abs(heaters[j + 1] - houses[i]); + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 5; + int v = 10; + int testTime = 10000; + for (int i = 0; i < testTime; i++) { + int[] a = randomArray(len, v); + int[] b = randomArray(len, v); + int ans1 = findRadius(a, b); + int ans2 = findRadius2(a, b); + if (ans1 != ans2) { + System.out.println("A : "); + for (int num : a) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("B : "); + for (int num : b) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + } + +} diff --git a/公开课/class065/Code03_TrappingRainWater.java b/公开课/class065/Code03_TrappingRainWater.java new file mode 100644 index 0000000..9428906 --- /dev/null +++ b/公开课/class065/Code03_TrappingRainWater.java @@ -0,0 +1,121 @@ +package class065; + +public class Code03_TrappingRainWater { + + /* + * 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + * + */ + + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class065/Code04_CombinationSumIV.java b/公开课/class065/Code04_CombinationSumIV.java new file mode 100644 index 0000000..77b746e --- /dev/null +++ b/公开课/class065/Code04_CombinationSumIV.java @@ -0,0 +1,65 @@ +package class065; + +import java.util.Arrays; + +// leetcode 377题 +public class Code04_CombinationSumIV { + + // 当前剩下的值是rest, + // nums中所有的值,都可能作为分解rest的,第一块!全试一试 + // nums, 无重复,都是正 + // rest, + public static int ways(int rest, int[] nums) { + if (rest < 0) { + return 0; + } + if (rest == 0) { + return 1; + } + int ways = 0; + for (int num : nums) { + ways += ways(rest - num, nums); + } + return ways; + } + + public static int[] dp = new int[1001]; + + public static int combinationSum41(int[] nums, int target) { + Arrays.fill(dp, 0, target + 1, -1); + return process1(nums, target); + } + + public static int process1(int[] nums, int rest) { + if (rest < 0) { + return 0; + } + if (dp[rest] != -1) { + return dp[rest]; + } + int ans = 0; + if (rest == 0) { + ans = 1; + } else { + for (int num : nums) { + ans += process1(nums, rest - num); + } + } + dp[rest] = ans; + return ans; + } + + // 剪枝 + 严格位置依赖的动态规划 + public static int combinationSum42(int[] nums, int target) { + Arrays.sort(nums); + int[] dp = new int[target + 1]; + dp[0] = 1; + for (int rest = 1; rest <= target; rest++) { + for (int i = 0; i < nums.length && nums[i] <= rest; i++) { + dp[rest] += dp[rest - nums[i]]; + } + } + return dp[target]; + } + +} diff --git a/公开课/class066/Code01_BestTimeToBuyAndSellStock.java b/公开课/class066/Code01_BestTimeToBuyAndSellStock.java new file mode 100644 index 0000000..79de03c --- /dev/null +++ b/公开课/class066/Code01_BestTimeToBuyAndSellStock.java @@ -0,0 +1,25 @@ +package class066; + +// leetcode题目 : https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ +public class Code01_BestTimeToBuyAndSellStock { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + // 最好的一笔收入,是多少 + int ans = 0; + // 0..0 范围上的最小值 + int min = prices[0]; + // 假设每一个i,都是卖出时机! + for (int i = 1; i < prices.length; i++) { + // 更新min,min 0...i-1的最小值 + // min 0...i整体的最小值! + min = Math.min(min, prices[i]); + // prices[i] - min 挣得最多的钱 + ans = Math.max(ans, prices[i] - min); + } + return ans; + } + +} diff --git a/公开课/class066/Code02_BestTimeToBuyAndSellStockII.java b/公开课/class066/Code02_BestTimeToBuyAndSellStockII.java new file mode 100644 index 0000000..7b730dc --- /dev/null +++ b/公开课/class066/Code02_BestTimeToBuyAndSellStockII.java @@ -0,0 +1,17 @@ +package class066; + +//leetcode题目 : https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/ +public class Code02_BestTimeToBuyAndSellStockII { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + int ans = 0; + for (int i = 1; i < prices.length; i++) { + ans += Math.max(prices[i] - prices[i-1], 0); + } + return ans; + } + +} diff --git a/公开课/class066/Code03_BestTimeToBuyAndSellStockIII.java b/公开课/class066/Code03_BestTimeToBuyAndSellStockIII.java new file mode 100644 index 0000000..1dd09fc --- /dev/null +++ b/公开课/class066/Code03_BestTimeToBuyAndSellStockIII.java @@ -0,0 +1,123 @@ +package class066; + +//leetcode题目 : https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/ +public class Code03_BestTimeToBuyAndSellStockIII { + +// public static int maxProfit1(int[] prices) { +// if (prices == null || prices.length < 2) { +// return 0; +// } +// int n = prices.length; +// int ans = 0; +// int[] doneOnceMax = new int[n]; +// // doneOnceMax 去生成! +// // doneOnceMax[i] 指的是:0~i范围上,只做一笔交易!获得的最大钱! +// for(int j = 0; j < n; j++) { +// // 第二笔交易的卖出时机 +// // j时刻 +// // 13时刻,第二笔交易的卖出时机 +// // 第二笔交易的买入时机:0 【0~0】 0 +// // 1 [0~1] 1 13 +// // 2 [0~2] 2 13 +// // i j +// // 0 13 +// // 1 13 +// // .. +// // 13..13 +// for(int i = 0; i <= j; i++) { +// int second = prices[j] - prices[i]; +// int first = doneOnceMax[i];// 0...i 只做一笔交易!获得的最大钱! +// int cur = first + second; +// ans +// +// +// } +// return ans; +// +//// } +// +// +// +// +// +// +// +// for (int i = 1; i < n; i++) { +// +// } +// return ans; +// } + +// public static int maxProfit2(int[] prices) { +// if (prices == null || prices.length < 2) { +// return 0; +// } +// int n = prices.length; +// // 指标:doneOnceMinusOneBuyMax[i] : 0 ~ i上,一定要做完一次交易,还得扣除掉一次买入, +// // 所有情况中,整体的最优! +// +// +// +// // 第一步:请把如下的含义结构生成 +// int[] doneOnceMax = new int[n]; // 该怎么生成怎么生成! +// // doneOnceMax[i] : 0..i 做完一笔交易的最好钱数 +// +// // 第二步,把指标数组生成! +// int[] doneOnceMinusOneBuyMax = new int[n]; +// +// // doneOnceMinusOneBuyMax[0] : 0 ~ 0 +// // doneOnceMinusOneBuyMax[1] : 0 ~ 1 +// // doneOnceMinusOneBuyMax[2] : 0 ~ 2 +// // ... +// +// doneOnceMinusOneBuyMax[0] = -prices[0]; +// +// +// for (int i = 1; i < n; i++) { +// // doneOnceMinusOneBuyMax[i] +// // 0 ~ i上,一定要做完一次交易,还得扣除掉一次买入 +// // 可能性1:扣除掉的这一次买入,和i无关! +// int p1 = doneOnceMinusOneBuyMax[i-1]; +// // 可能性2 :扣除掉的这一次买入,和i有关! +// int p2 = doneOnceMax[i] - prices[i]; +// doneOnceMinusOneBuyMax[i] = Math.max(p1, p2); +// } +// +// // 第三步 +// int ans = 0; +// for(int i = 0; i < n; i++) { +// ans = Math.max(ans, doneOnceMinusOneBuyMax[i] + prices[i]); +// } +// return ans; +// } + + + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + int ans = 0; + // doneOnceMinusBuyMax -> 指标0 + int doneOnceMinusBuyMax = -prices[0]; + // doneOnceMax -> 0 ~ 0 只做一次交易,获得的最好钱数 + int doneOnceMax = 0; + // 0 ~ 0 上的最小值! + int min = prices[0]; + for (int i = 1; i < prices.length; i++) { + // 0 ~ i整体的最小值! + min = Math.min(min, prices[i]); + ans = Math.max(ans, doneOnceMinusBuyMax + prices[i]); + // 0 ~ i上,只做一次交易,获得的最好钱数 + // 可能性1:只做的这一次交易,不是在i位置卖的,(0~i-1)上一步的doneOnceMax + // 可能性2:只做的这一次交易,是在i位置卖的, prices[i] - min + doneOnceMax = Math.max(doneOnceMax, prices[i] - min); + // 0 ~ i上,只做一次交易 并且 扣掉一次买入,整体获得的最好钱数 + // 可能性1:扣掉一次的这一次买入,不在i上发生,(0~i-1)上一步的doneOnceMinusBuyMax + // 可能性2:扣掉一次的这一次买入,必须在i上发生,(0~i)上的那次最好交易,- prices[i] + doneOnceMinusBuyMax = Math.max(doneOnceMinusBuyMax, doneOnceMax - prices[i]); + } + return ans; + } + +} diff --git a/公开课/class066/Code04_SplitArrayLargestSum.java b/公开课/class066/Code04_SplitArrayLargestSum.java new file mode 100644 index 0000000..a30afe1 --- /dev/null +++ b/公开课/class066/Code04_SplitArrayLargestSum.java @@ -0,0 +1,47 @@ +package class066; + +// leetcode原题 +// 测试链接:https://leetcode.com/problems/split-array-largest-sum/ +public class Code04_SplitArrayLargestSum { + + public static int splitArray(int[] nums, int M) { + long sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + long l = 0; + long r = sum; + long ans = 0; + while (l <= r) { + long mid = (l + r) / 2; + long cur = getNeedParts(nums, mid); + if (cur <= M) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return (int) ans; + } + + public static int getNeedParts(int[] arr, long aim) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] > aim) { + return Integer.MAX_VALUE; + } + } + int parts = 1; + int all = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (all + arr[i] > aim) { + parts++; + all = arr[i]; + } else { + all += arr[i]; + } + } + return parts; + } + +} diff --git a/公开课/class066/Code05_KokoEatingBananas.java b/公开课/class066/Code05_KokoEatingBananas.java new file mode 100644 index 0000000..86ad9de --- /dev/null +++ b/公开课/class066/Code05_KokoEatingBananas.java @@ -0,0 +1,50 @@ +package class066; + +// leetcode题目 : https://leetcode.com/problems/koko-eating-bananas/ +public class Code05_KokoEatingBananas { + + public static int minEatingSpeed(int[] piles, int h) { + int L = 1; + int R = 0; + for (int pile : piles) { + R = Math.max(R, pile); + } + // L --- R + // 去寻找最合适的速度! + int ans = 0; + int M = 0; + while (L <= R) { + // M = (L + R) / 2 + M = L + ((R - L) >> 1); + if (hours(piles, M) <= h) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + // 一堆香蕉,piles + // 给定一个速度,speed + // 同一堆,吃完就躺平, + // 请返回需要几小时? + public static int hours(int[] piles, int speed) { + int ans = 0; + for (int pile : piles) { + ans += (pile + speed - 1) / speed; + } + return ans; + } + + + public static void main(String[] args) { + int a = 8; + int b = 2; + // 8 / 2 4 4 + int ans = (a + b - 1) / b; + System.out.println(ans); + } + +} diff --git a/公开课/class067/Code01_IsStepSum.java b/公开课/class067/Code01_IsStepSum.java new file mode 100644 index 0000000..0ccd89d --- /dev/null +++ b/公开课/class067/Code01_IsStepSum.java @@ -0,0 +1,68 @@ +package class067; + +import java.util.HashMap; + +public class Code01_IsStepSum { + + public static boolean isStepSum(int x) { + // L = 0 R = stepSum; + // 可能的答案,只会在L ~ R上 + int L = 0; + int R = x; + int M = 0; + int cur = 0; + // L <= R 还能继续二分 + while (L <= R) { + // 求中点 + // M = (L + R) / 2; + // M = L + (R - L) / 2 + // a / 2 -> a >> 1 + M = L + ((R - L) >> 1); + cur = stepSum(M); + if (cur == x) { + return true; + } else if (cur < x) { + L = M + 1; + } else { + R = M - 1; + } + } + return false; + } + + // 输入num, + // 输出num的step sum + // 代价 : num的十进制位的数量 + public static int stepSum(int num) { + int sum = 0; + while (num != 0) { + sum += num; + num /= 10; + } + return sum; + } + + // for test + public static HashMap generateStepSumNumberMap(int numMax) { + HashMap map = new HashMap<>(); + for (int i = 0; i <= numMax; i++) { + map.put(stepSum(i), i); + } + return map; + } + + // for test + public static void main(String[] args) { + int max = 1000000; + int maxStepSum = stepSum(max); + HashMap ans = generateStepSumNumberMap(max); + System.out.println("测试开始"); + for (int i = 0; i <= maxStepSum; i++) { + if (ans.containsKey(i) ^ isStepSum(i)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class067/Code02_KokoEatingBananas.java b/公开课/class067/Code02_KokoEatingBananas.java new file mode 100644 index 0000000..5a1aac4 --- /dev/null +++ b/公开课/class067/Code02_KokoEatingBananas.java @@ -0,0 +1,40 @@ +package class067; + +// 测试链接 : https://leetcode.com/problems/koko-eating-bananas/ +public class Code02_KokoEatingBananas { + + public static int minEatingSpeed(int[] piles, int h) { + int L = 1; + int R = 0; + for (int pile : piles) { + R = Math.max(R, pile); + } + // 只需要在L ~ R范围上,最小的达标速度 + // 1 ~ max + int ans = 0; + int M = 0; + while (L <= R) { + M = L + ((R - L) >> 1); + if (hours(piles, M) <= h) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + // 一堆香蕉都在piles数组里 + // 猩猩以speed的速度吃! + // 但是,这一小时吃完,就躺! + // 返回一共要几小时? + public static int hours(int[] piles, int speed) { + int ans = 0; + for (int pile : piles) { + ans += (pile + speed - 1) / speed; + } + return ans; + } + +} diff --git a/公开课/class067/Code03_SplitArrayLargestSum.java b/公开课/class067/Code03_SplitArrayLargestSum.java new file mode 100644 index 0000000..c9dcdc7 --- /dev/null +++ b/公开课/class067/Code03_SplitArrayLargestSum.java @@ -0,0 +1,49 @@ +package class067; + +// 测试链接:https://leetcode.com/problems/split-array-largest-sum/ +public class Code03_SplitArrayLargestSum { + + public static int splitArray(int[] nums, int M) { + int sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + int l = 0; + int r = sum; + int ans = 0; + while (l <= r) { + int mid = (l + r) / 2; + int cur = need(nums, mid); + if (cur <= M) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans; + } + + // 每一幅画需要的时间,都在arr里 + // 如果我一定要求整体不能超过aim小时 + // 返回需要的最少画家数量 + public static int need(int[] arr, long aim) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] > aim) { + return Integer.MAX_VALUE; + } + } + int parts = 1; + int all = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (all + arr[i] > aim) { + parts++; + all = arr[i]; + } else { + all += arr[i]; + } + } + return parts; + } + +} diff --git a/公开课/class067/Code04_MinimumWindowSubstring.java b/公开课/class067/Code04_MinimumWindowSubstring.java new file mode 100644 index 0000000..a2b5d0a --- /dev/null +++ b/公开课/class067/Code04_MinimumWindowSubstring.java @@ -0,0 +1,53 @@ +package class067; + +// 测试链接 : https://leetcode.com/problems/minimum-window-substring/ +public class Code04_MinimumWindowSubstring { + + // 在s中,找到包含t的最短子串 + // 返回最短子串 + public static String minWindow(String s, String t) { + if (s.length() < t.length()) { + return ""; + } + char[] str = s.toCharArray(); + char[] target = t.toCharArray(); + int[] map = new int[256]; + for (char cha : target) { + map[cha]++; + } + int all = target.length; + int L = 0; + int R = 0; + // [L,R) + // [1,2) -> 1..1 + // [1,5) -> 1..4 + // [1,1) -> 窗口一个字符也没有了 + // 所有发现达标的窗口中,最短窗口的长度是多少minLen + // ansl...ansr 到底是哪一段的子串,记一下! + int minLen = Integer.MAX_VALUE; + int ansl = -1; + int ansr = -1; + // 窗口右边界没到底的时候 + while (R != str.length) { + map[str[R]]--; + if (map[str[R]] >= 0) { + all--; + } + if (all == 0) { + while (map[str[L]] < 0) { + map[str[L++]]++; + } + if (minLen > R - L + 1) { + minLen = R - L + 1; + ansl = L; + ansr = R; + } + all++; + map[str[L++]]++; + } + R++; + } + return minLen == Integer.MAX_VALUE ? "" : s.substring(ansl, ansr + 1); + } + +} diff --git a/公开课/class067/Code05_SubarraysWithKDifferentIntegers.java b/公开课/class067/Code05_SubarraysWithKDifferentIntegers.java new file mode 100644 index 0000000..318a348 --- /dev/null +++ b/公开课/class067/Code05_SubarraysWithKDifferentIntegers.java @@ -0,0 +1,66 @@ +package class067; + +// 测试链接 : https://leetcode.com/problems/subarrays-with-k-different-integers/ +public class Code05_SubarraysWithKDifferentIntegers { + + public static int subarraysWithKDistinct1(int[] arr, int k) { + return numsMostK(arr, k) - numsMostK(arr, k - 1); + } + + public static int numsMostK(int[] arr, int k) { + int n = arr.length; + int ans = 0; + int[] counts = new int[n + 1]; + for (int left = 0, right = 0, kinds = 0; right < n; right++) { + if (counts[arr[right]]++ == 0) { + kinds++; + } + while (kinds > k) { + if (counts[arr[left++]]-- == 1) { + kinds--; + } + } + ans += right - left; + } + return ans; + } + + public static int subarraysWithKDistinct2(int[] nums, int k) { + int n = nums.length; + // <= k-1种数的窗口词频统计 + int[] minusOneWindow = new int[n + 1]; + int minusOneWindowLeft = 0; + int minusOneWindowKinds = 0; + // <= k种数的窗口词频统计 + int[] kWindow = new int[n + 1]; + int kWindowLeft = 0; + int kWindowKinds = 0; + int ans = 0; + for (int r = 0; r < n; r++) { + // 当前刚来到r位置! + if (minusOneWindow[nums[r]] == 0) { + minusOneWindowKinds++; + } + if (kWindow[nums[r]] == 0) { + kWindowKinds++; + } + minusOneWindow[nums[r]]++; + kWindow[nums[r]]++; + while (minusOneWindowKinds == k) { + if (minusOneWindow[nums[minusOneWindowLeft]] == 1) { + minusOneWindowKinds--; + } + minusOneWindow[nums[minusOneWindowLeft++]]--; + } + while (kWindowKinds > k) { + if (kWindow[nums[kWindowLeft]] == 1) { + kWindowKinds--; + } + kWindow[nums[kWindowLeft++]]--; + } + ans += minusOneWindowLeft - kWindowLeft; + } + return ans; + } + +} diff --git a/公开课/class068/Code01_MagicStone.java b/公开课/class068/Code01_MagicStone.java new file mode 100644 index 0000000..1ef770f --- /dev/null +++ b/公开课/class068/Code01_MagicStone.java @@ -0,0 +1,100 @@ +package class068; + +import java.util.Arrays; + +// 来自小红书 +// [0,4,7] : 0表示这里石头没有颜色,如果变红代价是4,如果变蓝代价是7 +// [1,X,X] : 1表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义 +// [2,X,X] : 2表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义 +// 颜色只可能是0、1、2,代价一定>=0 +// 给你一批这样的小数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价 +// 如果怎么都无法做到所有石头都有颜色、且红色和蓝色一样多,返回-1 +public class Code01_MagicStone { + + // + // [0, 5 , 10] + // [1, X , X] + // [0, 10, 6] + // .. + // N stones N * 3 + public static int minCost(int[][] stones) { + int n = stones.length; + // n : 0011100010 + // 偶数 + // n : 0011110111 + // 奇数 + // 1 : 0000000001 + if ((n & 1) != 0) { + return -1; + } + // O(N * logN) + Arrays.sort(stones, (a, b) -> a[0] == 0 && b[0] == 0 ? (b[1] - b[2] - a[1] + a[2]) : (a[0] - b[0])); + int zero = 0; + int red = 0; + int blue = 0; + int cost = 0; + for (int i = 0; i < n; i++) { + if (stones[i][0] == 0) { + zero++; + cost += stones[i][1]; + } else if (stones[i][0] == 1) { + red++; + } else { + blue++; + } + } + // 红色如果超过了一半 或者 蓝色如果超过了一半 + // 返回-1 + // red > n /2 || blue > n / 2 + // n / 2 -> n >> 1 + if (red > (n >> 1) || blue > (n >> 1)) { + return -1; + } + // 所有无色的时候,都给红色了 + // 要把适当的石头,从红色,叛逃到蓝色 + // 叛逃的数量,blue + blue = zero - ((n >> 1) - red); + // stones: 0 1 2 3 12 13块 + for (int i = 0; i < blue; i++) { + cost += stones[i][2] - stones[i][1]; + } + return cost; + } + + public static class Student { + public int age; + public int classNo; + + public Student(int a, int c) { + age = a; + classNo = c; + } + } + + public static void main(String[] args) { + Student s1 = new Student(17, 2); + Student s2 = new Student(21, 1); + Student s3 = new Student(70, 3); + Student s4 = new Student(35, 1); + Student s5 = new Student(21, 3); + Student[] arr = { s1, s2, s3, s4, s5 }; + +// for (Student s : arr) { +// System.out.println(s.age + " , " + s.classNo); +// } + + // 大 -> 小 + // 只要返回负:第一个参数放前面 + // 只要返回正:第二个参数放前面 + // 整体按照班级从小到大排序,但是班级一样的!请按照年龄从小到大排序? + Arrays.sort(arr, (a, b) -> a.classNo != b.classNo ? (a.classNo - b.classNo) : (a.age - b.age)); + + for (Student s : arr) { + System.out.println(s.age + " , " + s.classNo); + } + +// int[][] stones = { { 1, 5, 3 }, { 2, 7, 9 }, { 0, 6, 4 }, { 0, 7, 9 }, { 0, 2, 1 }, { 0, 5, 9 } }; +// System.out.println(minCost(stones)); + } + +} diff --git a/公开课/class068/Code02_CircleCandy.java b/公开课/class068/Code02_CircleCandy.java new file mode 100644 index 0000000..8e40eed --- /dev/null +++ b/公开课/class068/Code02_CircleCandy.java @@ -0,0 +1,58 @@ +package class068; + +// 来自网易 +// 给定一个正数数组arr,表示每个小朋友的得分 +// 任何两个相邻的小朋友,如果得分一样,怎么分糖果无所谓,但如果得分不一样,分数大的一定要比分数少的多拿一些糖果 +// 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数 +public class Code02_CircleCandy { + + public static int minCandy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return 1; + } + int n = arr.length; + int minIndex = 0; + for (int i = 0; i < n; i++) { + if (arr[i] <= arr[lastIndex(i, n)] && arr[i] <= arr[nextIndex(i, n)]) { + minIndex = i; + break; + } + } + int[] nums = new int[n + 1]; + for (int i = 0; i <= n; i++, minIndex = nextIndex(minIndex, n)) { + nums[i] = arr[minIndex]; + } + int[] left = new int[n + 1]; + left[0] = 1; + for (int i = 1; i <= n; i++) { + left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1; + } + int[] right = new int[n + 1]; + right[n] = 1; + for (int i = n - 1; i >= 0; i--) { + right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1; + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(left[i], right[i]); + } + return ans; + } + + public static int nextIndex(int i, int n) { + return i == n - 1 ? 0 : (i + 1); + } + + public static int lastIndex(int i, int n) { + return i == 0 ? (n - 1) : (i - 1); + } + + public static void main(String[] args) { + int[] arr = { 3, 4, 2, 3, 2 }; + System.out.println(minCandy(arr)); + } + +} diff --git a/公开课/class068/Code03_MinBoatEvenNumbers.java b/公开课/class068/Code03_MinBoatEvenNumbers.java new file mode 100644 index 0000000..b08133e --- /dev/null +++ b/公开课/class068/Code03_MinBoatEvenNumbers.java @@ -0,0 +1,78 @@ +package class068; + +import java.util.Arrays; + +// 来自腾讯 +// 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重,所有船都是同样的载重量 +// 每个人的体重都一定不大于船的载重 +// 要求: +// 1, 可以1个人单独一搜船 +// 2, 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重 +// 3, 一艘船最多坐2人 +// 返回如果想所有人同时坐船,船的最小数量 +public class Code03_MinBoatEvenNumbers { + + public static int minBoat(int[] arr, int limit) { + Arrays.sort(arr); + int odd = 0; + int even = 0; + for (int num : arr) { + if ((num & 1) == 0) { + even++; + } else { + odd++; + } + } + int[] odds = new int[odd]; + int[] evens = new int[even]; + for (int i = arr.length - 1; i >= 0; i--) { + if ((arr[i] & 1) == 0) { + evens[--even] = arr[i]; + } else { + odds[--odd] = arr[i]; + } + } + return min(odds, limit) + min(evens, limit); + } + + public static int min(int[] arr, int limit) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (arr[N - 1] > limit) { + return -1; + } + int lessR = -1; + for (int i = N - 1; i >= 0; i--) { + if (arr[i] <= (limit / 2)) { + lessR = i; + break; + } + } + if (lessR == -1) { + return N; + } + int L = lessR; + int R = lessR + 1; + int noUsed = 0; + while (L >= 0) { + int solved = 0; + while (R < N && arr[L] + arr[R] <= limit) { + R++; + solved++; + } + if (solved == 0) { + noUsed++; + L--; + } else { + L = Math.max(-1, L - solved); + } + } + int all = lessR + 1; + int used = all - noUsed; + int moreUnsolved = (N - all) - used; + return used + ((noUsed + 1) >> 1) + moreUnsolved; + } + +} diff --git a/公开课/class069/Code01_Hash.java b/公开课/class069/Code01_Hash.java new file mode 100644 index 0000000..b07b79c --- /dev/null +++ b/公开课/class069/Code01_Hash.java @@ -0,0 +1,52 @@ +package class069; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +// 这个类所包含的jar包需要自己找到,然后导入项目 +import javax.xml.bind.DatatypeConverter; + +public class Code01_Hash { + + public static class Hash { + + private MessageDigest hash; + + public Hash(String algorithm) { + try { + hash = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public String hashCode(String input) { + return DatatypeConverter.printHexBinary(hash.digest(input.getBytes())).toUpperCase(); + } + } + + public static void main(String[] args) { + System.out.println("支持的算法 : "); + for (String str : Security.getAlgorithms("MessageDigest")) { + System.out.println(str); + } + System.out.println("======="); + + String algorithm = "SHA-256"; + Hash hash = new Hash(algorithm); + + String input1 = "zuochengyunzuochengyun1"; + String input2 = "zuochengyunzuochengyun2"; + String input3 = "zuochengyunzuochengyun3"; + String input4 = "zuochengyunzuochengyun4"; + String input5 = "zuochengyunzuochengyun5"; + System.out.println(hash.hashCode(input1)); + System.out.println(hash.hashCode(input2)); + System.out.println(hash.hashCode(input3)); + System.out.println(hash.hashCode(input4)); + System.out.println(hash.hashCode(input5)); + + } + +} diff --git a/公开课/class069/Code02_BitMap.java b/公开课/class069/Code02_BitMap.java new file mode 100644 index 0000000..7579ed1 --- /dev/null +++ b/公开课/class069/Code02_BitMap.java @@ -0,0 +1,87 @@ +package class069; + +public class Code02_BitMap { + + // 位图 + public static class BitMap { + + private long[] bits; + + public BitMap(int max) { + bits = new long[(max + 64) >> 6]; + } + + public void add(int num) { + bits[num >> 6] |= (1L << (num & 63)); + } + + public void delete(int num) { + bits[num >> 6] &= ~(1L << (num & 63)); + } + + public boolean contains(int num) { + return (bits[num >> 6] & (1L << (num & 63))) != 0; + } + + } + + public static void main(String[] args) { + + // 表示 0~ 31 谁出现了,谁没出现 +// int a = 0; +// int num = 7; +// // 请把7位描黑! +// // 0000000000010000000 +// // 0000000000010000000 +// a |= 1 << 7; +// a |= 1 << 13; +// a |= 1 << 29; +// // 7 13 29 +// // 请告诉我,7有没有进去 +// boolean has =( a & (1 << 7)) != 0; +// +// + int[] set = new int[10]; + // set : 10个数 + // 每个数,32位 + // 0~319 + int num = 176; + // set[0] : 0~31 + // set[1] : 32~ + int team = num / 32; + set[team] |= 1 << (num % 32); + + + + + +// System.out.println("测试开始!"); +// int max = 2000000; +// BitMap bitMap = new BitMap(max); +// HashSet set = new HashSet<>(); +// int testTime = 6000000; +// for (int i = 0; i < testTime; i++) { +// int num = (int) (Math.random() * (max + 1)); +// double decide = Math.random(); +// if (decide < 0.333) { +// bitMap.add(num); +// set.add(num); +// } else if (decide < 0.666) { +// bitMap.delete(num); +// set.remove(num); +// } else { +// if (bitMap.contains(num) != set.contains(num)) { +// System.out.println("Oops!"); +// break; +// } +// } +// } +// for (int num = 0; num <= max; num++) { +// if (bitMap.contains(num) != set.contains(num)) { +// System.out.println("Oops!"); +// } +// } +// System.out.println("测试结束!"); + } + +} diff --git a/公开课/class070/Code01_FindTheCelebrity.java b/公开课/class070/Code01_FindTheCelebrity.java new file mode 100644 index 0000000..61f1ae2 --- /dev/null +++ b/公开课/class070/Code01_FindTheCelebrity.java @@ -0,0 +1,40 @@ +package class070; + +// 测试链接 : https://leetcode.com/problems/find-the-celebrity/ +public class Code01_FindTheCelebrity { + + // 提交时不要提交这个函数,因为默认系统会给你这个函数 + // knows方法默认自己认识自己 + public static boolean knows(int x, int i) { + return true; + } + + // 只提交下面的方法 + public int findCelebrity(int n) { + // 谁可能成为明星,谁就是cand + int cand = 0; + for (int i = 1; i < n; i++) { + if (knows(cand, i)) { + cand = i; + } + } + // cand....右边,cand都不认识! + // cand是什么?唯一可能是明星的人! + // 下一步就是验证,它到底是不是明星 + // 1) cand是不是不认识所有的人 cand...(右侧cand都不认识) + // 所以,只用验证 ....cand的左侧即可 + for (int i = 0; i < cand; i++) { + if (knows(cand, i)) { + return -1; + } + } + // 2) 是不是所有的人都认识cand + for (int i = 0; i < n; i++) { + if (!knows(i, cand)) { + return -1; + } + } + return cand; + } + +} diff --git a/公开课/class070/Code02_AvoidFloodInTheCity.java b/公开课/class070/Code02_AvoidFloodInTheCity.java new file mode 100644 index 0000000..0644025 --- /dev/null +++ b/公开课/class070/Code02_AvoidFloodInTheCity.java @@ -0,0 +1,82 @@ +package class070; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.PriorityQueue; + +// 测试链接 : https://leetcode.com/problems/avoid-flood-in-the-city/ +public class Code02_AvoidFloodInTheCity { + + // rains[i] = j 第i天轮到j号湖泊下雨 + // 规定,下雨日,干啥 : -1 + // 不下雨日,如果没有湖泊可抽 : 1 + public static int[] avoidFlood(int[] rains) { + int n = rains.length; + int[] ans = new int[n]; + int[] invalid = new int[0]; + // key : 某个湖 + // value : 这个湖在哪些位置降雨 + // 4 : {3,7,19,21} + // 1 : { 13 } + // 2 : {4, 56} + HashMap> map = new HashMap<>(); + for (int i = 0; i < n; i++) { + if (rains[i] != 0) { // 第i天要下雨,rains[i] + // 3天 9号 + // 9号 { 3 } + // 9号 {1, 3} + if (!map.containsKey(rains[i])) { + map.put(rains[i], new LinkedList<>()); + } + map.get(rains[i]).addLast(i); + } + } + // 没抽干的湖泊表 + // 某个湖如果满了,加入到set里 + // 某个湖被抽干了,从set中移除 + HashSet set = new HashSet<>(); + // 这个堆的堆顶表示最先处理的湖是哪个 + PriorityQueue heap = new PriorityQueue<>(); + for (int i = 0; i < n; i++) { // 0 1 2 3 ... + if (rains[i] != 0) { + if (set.contains(rains[i])) { + return invalid; + } + // 放入到没抽干的表里 + set.add(rains[i]); + map.get(rains[i]).pollFirst(); + if (!map.get(rains[i]).isEmpty()) { + heap.add(new Work(rains[i], map.get(rains[i]).peekFirst())); + } + // 题目规定 + ans[i] = -1; + } else { // 今天干活! + if (heap.isEmpty()) { + ans[i] = 1; + } else { + Work cur = heap.poll(); + set.remove(cur.lake); + ans[i] = cur.lake; + } + } + } + return ans; + } + + public static class Work implements Comparable { + public int lake; + public int nextRain; + + public Work(int l, int p) { + lake = l; + nextRain = p; + } + + @Override + public int compareTo(Work o) { + return nextRain - o.nextRain; + } + } + +} diff --git a/公开课/class070/Code03_BurstBalloons.java b/公开课/class070/Code03_BurstBalloons.java new file mode 100644 index 0000000..95a44f3 --- /dev/null +++ b/公开课/class070/Code03_BurstBalloons.java @@ -0,0 +1,154 @@ +package class070; + +// 本题测试链接 : https://leetcode.com/problems/burst-balloons/ +public class Code03_BurstBalloons { + + public static int max(int[] arr) { + int n = arr.length; + int[] help = new int[n + 2]; + help[0] = 1; + help[n + 1] = 1; + // 1 1 + for (int i = 0; i < arr.length; i++) { + help[i + 1] = arr[i]; + } + // arr 0 1 2 3 4 + // help 0 1 2 3 4 5 6 + return f(help, 1, n); + } + + // balls[L...R] 这个范围的气球,选择最优的打爆顺序,返回最大的得分 + // 你想调用这个函数,必须遵循一个潜台词: + // ball[L-1]气球,一定没爆! + // ball[R+1]气球,一定没爆! + // 为什么要设计这样的潜台词?是因为可能性是按照最后打爆的气球来展开的 + public static int f(int[] balls, int L, int R) { + if(L == R) { + return balls[L-1] * balls[L] * balls[R+1]; + } + // L...R 不止一个气球! + // 可能性1,L位置的气球最后打爆 + int p1 = f(balls, L+1,R) + balls[L-1] * balls[L] * balls[R+1]; + // 可能性2,R位置的气球最后打爆 + int p2 = f(balls, L, R-1) + balls[L-1] * balls[R] * balls[R+1]; + int ans = Math.max(p1, p2); + // 枚举,任何一个中间气球,最后打爆! + for(int mid = L + 1; mid < R;mid++) { + // L .... mid-1 mid mid+1.....R + int curP = f(balls, L, mid-1) + f(balls, mid+1, R) + + balls[L-1] * balls[mid] * balls[R+1]; + ans = Math.max(ans, curP); + } + return ans; + } + + + + + + + + + + public static int maxCoins0(int[] arr) { + // [3,2,1,3] + // [1,3,2,1,3,1] + int N = arr.length; + int[] help = new int[N + 2]; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + help[0] = 1; + help[N + 1] = 1; + return func(help, 1, N); + } + + // L-1位置,和R+1位置,永远不越界,并且,[L-1] 和 [R+1] 一定没爆呢! + // 返回,arr[L...R]打爆所有气球,最大得分是什么 + public static int func(int[] arr, int L, int R) { + if (L == R) { + return arr[L - 1] * arr[L] * arr[R + 1]; + } + // 尝试每一种情况,最后打爆的气球,是什么位置 + // L...R + // L位置的气球,最后打爆 + int max = func(arr, L + 1, R) + arr[L - 1] * arr[L] * arr[R + 1]; + // R位置的气球,最后打爆 + max = Math.max(max, func(arr, L, R - 1) + arr[L - 1] * arr[R] * arr[R + 1]); + // 尝试所有L...R,中间的位置,(L,R) + for (int i = L + 1; i < R; i++) { + // i位置的气球,最后打爆 + int left = func(arr, L, i - 1); + int right = func(arr, i + 1, R); + int last = arr[L - 1] * arr[i] * arr[R + 1]; + int cur = left + right + last; + max = Math.max(max, cur); + } + return max; + } + + public static int maxCoins1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int N = arr.length; + int[] help = new int[N + 2]; + help[0] = 1; + help[N + 1] = 1; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + return process(help, 1, N); + } + + // 打爆arr[L..R]范围上的所有气球,返回最大的分数 + // 假设arr[L-1]和arr[R+1]一定没有被打爆 + public static int process(int[] arr, int L, int R) { + if (L == R) {// 如果arr[L..R]范围上只有一个气球,直接打爆即可 + return arr[L - 1] * arr[L] * arr[R + 1]; + } + // 最后打爆arr[L]的方案,和最后打爆arr[R]的方案,先比较一下 + int max = Math.max(arr[L - 1] * arr[L] * arr[R + 1] + process(arr, L + 1, R), + arr[L - 1] * arr[R] * arr[R + 1] + process(arr, L, R - 1)); + // 尝试中间位置的气球最后被打爆的每一种方案 + for (int i = L + 1; i < R; i++) { + max = Math.max(max, arr[L - 1] * arr[i] * arr[R + 1] + process(arr, L, i - 1) + process(arr, i + 1, R)); + } + return max; + } + + public static int maxCoins2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int N = arr.length; + int[] help = new int[N + 2]; + help[0] = 1; + help[N + 1] = 1; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + int[][] dp = new int[N + 2][N + 2]; + for (int i = 1; i <= N; i++) { + dp[i][i] = help[i - 1] * help[i] * help[i + 1]; + } + for (int L = N; L >= 1; L--) { + for (int R = L + 1; R <= N; R++) { + int ans = help[L - 1] * help[L] * help[R + 1] + dp[L + 1][R]; + ans = Math.max(ans, help[L - 1] * help[R] * help[R + 1] + dp[L][R - 1]); + for (int i = L + 1; i < R; i++) { + ans = Math.max(ans, help[L - 1] * help[i] * help[R + 1] + dp[L][i - 1] + dp[i + 1][R]); + } + dp[L][R] = ans; + } + } + return dp[1][N]; + } + +} diff --git a/公开课/class070/SuperWaterKing.java b/公开课/class070/SuperWaterKing.java new file mode 100644 index 0000000..7cd690e --- /dev/null +++ b/公开课/class070/SuperWaterKing.java @@ -0,0 +1,50 @@ +package class070; + +public class SuperWaterKing { + + // int[] arr = {1 , 1, 3, 3, 1} + // 长度5,长度是N + // 水王:某个数,出现次数,大于 N / 2 (必须大于!不包括等于!) + // 1 1 3次 N = 5 N/2 = 2 + // arr = {1,1,2,2} + // n =4 n/2 2次 + // arr中的数,都不是负数!返回水王数! + // 如果没有水王数!返回-1 + // 只能使用有限几个变量,就完成寻找水王数的功能 + // 时间复杂度O(N) + public static int king(int[] arr) { + if (arr == null || arr.length == 0) { + return -1; + } + if (arr.length == 1) { + return arr[0]; + } + int cand = 0; + int hp = 0; + for (int num : arr) { + if (hp == 0) { + cand = num; + hp = 1; + } else if (num == cand) { + hp++; + } else { + hp--; + } + } + if (hp == 0) { + return -1; + } + int times = 0; + for (int num : arr) { + if (num == cand) { + times++; + } + } + if (times > arr.length / 2) { + return cand; + } else { + return -1; + } + } + +} diff --git a/公开课/class071/Code01_CoverMax.java b/公开课/class071/Code01_CoverMax.java new file mode 100644 index 0000000..852a1e0 --- /dev/null +++ b/公开课/class071/Code01_CoverMax.java @@ -0,0 +1,90 @@ +package class071; + +import java.util.Arrays; +import java.util.PriorityQueue; + +public class Code01_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + // [ [4, 5], [3, 6] , [1, 3] ] + // [ [1, 3] [3, 6] [4 ,5] + public static int maxCover2(int[][] lines) { + if (lines == null || lines.length == 0) { + return 0; + } + // 排序:每个线段,根据开头位置排序,开头的位置如果一样,怎么排序无所谓! + // java Arrays sort 比较 + // java lambda 表达式 + Arrays.sort(lines, (a, b) -> a[0] - b[0]); + // 准备一个堆 + // 在这里,不去谈堆的实现,只是使用 + // 小根堆:小 到 大 组织 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int[] line : lines) { + int start = line[0]; + int end = line[1]; + // <= start的数字,都从小根堆里弹出! + while (!heap.isEmpty() && heap.peek() <= start) { + heap.poll(); + } + heap.add(end); + int cur = heap.size(); + max = Math.max(max, cur); + } + return max; + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static void main(String[] args) { + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class071/Code02_Heaters.java b/公开课/class071/Code02_Heaters.java new file mode 100644 index 0000000..8120cbf --- /dev/null +++ b/公开课/class071/Code02_Heaters.java @@ -0,0 +1,114 @@ +package class071; + +import java.util.Arrays; + +// leetcode 475题 +public class Code02_Heaters { + + // 比如地点是7, 9, 14 + // 供暖点的位置: 1 3 4 5 13 15 17 + // 先看地点7 + // 由1供暖,半径是6 + // 由3供暖,半径是4 + // 由4供暖,半径是3 + // 由5供暖,半径是2 + // 由13供暖,半径是6 + // 由此可知,地点7应该由供暖点5来供暖,半径是2 + // 再看地点9 + // 供暖点不回退 + // 由5供暖,半径是4 + // 由13供暖,半径是4 + // 由15供暖,半径是6 + // 由此可知,地点9应该由供暖点13来供暖,半径是4 + // 为什么是13而不是5?因为接下来的地点都会更靠右,所以半径一样的时候,就应该选更右的供暖点 + // 再看地点14 + // 供暖点不回退 + // 由13供暖,半径是1 + // 由15供暖,半径是1 + // 由17供暖,半径是3 + // 由此可知,地点14应该由供暖点15来供暖,半径是1 + // 以此类推 + public static int findRadius(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + // 这个函数含义: + // 当前的地点houses[i]由heaters[j]来供暖是最优的吗? + // 当前的地点houses[i]由heaters[j]来供暖,产生的半径是a + // 当前的地点houses[i]由heaters[j + 1]来供暖,产生的半径是b + // 如果a < b, 说明是最优,供暖不应该跳下一个位置 + // 如果a >= b, 说明不是最优,应该跳下一个位置 + public static boolean best(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 + || Math.abs(heaters[j] - houses[i]) < Math.abs(heaters[j + 1] - houses[i]); + } + + // 下面这个方法不对,你能找出原因嘛?^_^ + public static int findRadius2(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best2(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + public static boolean best2(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 || Math.abs(heaters[j] - houses[i]) <= Math.abs(heaters[j + 1] - houses[i]); + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 5; + int v = 10; + int testTime = 10000; + for (int i = 0; i < testTime; i++) { + int[] a = randomArray(len, v); + int[] b = randomArray(len, v); + int ans1 = findRadius(a, b); + int ans2 = findRadius2(a, b); + if (ans1 != ans2) { + System.out.println("A : "); + for (int num : a) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("B : "); + for (int num : b) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + } + +} diff --git a/公开课/class071/Code03_TrappingRainWater.java b/公开课/class071/Code03_TrappingRainWater.java new file mode 100644 index 0000000..fb30e76 --- /dev/null +++ b/公开课/class071/Code03_TrappingRainWater.java @@ -0,0 +1,121 @@ +package class071; + +public class Code03_TrappingRainWater { + + /* + * 给定一个正整数数组arr,把arr想象成一个直方图。返回这个直方图如果装水,能装下几格水? + * + */ + + public static int water1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int water2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int water3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int water4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + + // for test + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 200; + int testTimes = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = water1(arr); + int ans2 = water2(arr); + int ans3 = water3(arr); + int ans4 = water4(arr); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class071/Code04_ExpressionCompute.java b/公开课/class071/Code04_ExpressionCompute.java new file mode 100644 index 0000000..47c1644 --- /dev/null +++ b/公开课/class071/Code04_ExpressionCompute.java @@ -0,0 +1,81 @@ +package class071; + +import java.util.LinkedList; + +// 本题测试链接 : https://leetcode.com/problems/basic-calculator-iii/ +public class Code04_ExpressionCompute { + + public static int calculate(String str) { + return f(str.toCharArray(), 0)[0]; + } + + // 请从str[i...]往下算,遇到字符串终止位置或者右括号,就停止 + // 返回两个值,长度为2的数组 int[] ans + // ans[0] 负责的这一段的结果是多少 + // ans[1] 负责的这一段,算到了哪个位置停止的 + public static int[] f(char[] str, int i) { + // 12 + 345 - 5 * + LinkedList que = new LinkedList(); + int cur = 0; + int[] bra = null; + // 从i出发,开始撸串 + while (i < str.length && str[i] != ')') { + if (str[i] >= '0' && str[i] <= '9') { //遇到数字字符 + cur = cur * 10 + str[i++] - '0'; + } else if (str[i] != '(') { // 遇到的是运算符号 + // cur = 34 str[i] = '+' + addNum(que, cur); + que.addLast(String.valueOf(str[i++])); + cur = 0; + } else { // 遇到左括号了 + // ( + // i i+1 + bra = f(str, i + 1); + cur = bra[0]; + i = bra[1] + 1; + } + } + // 1)终止了!2)遇到右括号了 + addNum(que, cur); + return new int[] { getNum(que), i }; + } + + public static void addNum(LinkedList que, int num) { + if (!que.isEmpty()) { + int cur = 0; + String top = que.pollLast(); + if (top.equals("+") || top.equals("-")) { + que.addLast(top); + } else { + cur = Integer.valueOf(que.pollLast()); + num = top.equals("*") ? (cur * num) : (cur / num); + } + } + que.addLast(String.valueOf(num)); + } + + public static int getNum(LinkedList que) { + int res = 0; + boolean add = true; + String cur = null; + int num = 0; + while (!que.isEmpty()) { + cur = que.pollFirst(); + if (cur.equals("+")) { + add = true; + } else if (cur.equals("-")) { + add = false; + } else { + num = Integer.valueOf(cur); + res += add ? num : (-num); + } + } + return res; + } + + public static void main(String[] args) { + String test = "52+13*(5+2-(5*(23-4)))-40+((3*2)+(5/2))"; + System.out.println(calculate(test)); + } + +} diff --git a/公开课/class072/Code01_SplitApples.java b/公开课/class072/Code01_SplitApples.java new file mode 100644 index 0000000..a4cd5b7 --- /dev/null +++ b/公开课/class072/Code01_SplitApples.java @@ -0,0 +1,163 @@ +package class072; + +// 有m个同样的苹果,认为苹果之间无差别 +// 有n个同样的盘子,认为盘子之间也无差别 +// 还有,比如5个苹果如果放进3个盘子, +// 那么1、3、1和1、1、3和3、1、1的放置方法,也认为是一种方法 +// 如上的设定下,返回有多少种放置方法 +// 测试链接 : https://www.nowcoder.com/practice/bfd8234bb5e84be0b493656e390bdebf +// 提交以下的code,提交时请把类名改成"Main" +import java.util.Arrays; +import java.util.Scanner; + +public class Code01_SplitApples { + + public static int ways1(int apples, int plates) { + if (apples == 0) { // 没有苹果了!摆法有一种,“什么也不用摆” + return 1; + } + if (plates == 0) { + return 0; // 还剩下苹果,但是盘子没了!0种方法 + } + // 苹果也有,盘子也有 + if (apples < plates) { + // 多余的盘子都敲碎,因为没有,不会影响分布的种数 + return ways1(apples, apples); + } + // apples >= plates + // 1) 所有的盘子全部都要使用,方法数是a + int a = ways1(apples - plates, plates); + // 2) 不全使用!所有的盘子不都使用,方法数是b + int b = ways1(apples, plates - 1); + return a + b; + } + + // 苹果数量,初始时候,不超过100个 + // 盘子数量,初始视乎,不超过100个 + // 参数apples,0~100之间! + // 参数plates,0~100之间! + public static int[][] map = null; + // int apples, int plates -> map + + // map[7][5] -> 苹果有7个,盘子有5个,有几种摆放方法! + + // map[7][5] == 0, 苹果有7个,盘子有5个,有0种摆放方法! + + // map[7][5] == -1,苹果有7个,盘子有5个,这个过程,还没有计算过! + + public static int ways2(int apples, int plates) { + if(map == null) { + map = new int[101][101]; + for (int i = 0; i <= apples; i++) { + for (int j = 0; j <= plates; j++) { + map[i][j] = -1; + } + } + } + return process(apples, plates); + } + + // map可以用 + + public static int process(int apples, int plates) { + if(map[apples][plates] != -1) { + return map[apples][plates]; + } + int ans = 0; + if (apples == 0) { // 没有苹果了!摆法有一种,“什么也不用摆” + ans = 1; + } else if (plates == 0) { + ans = 0; // 还剩下苹果,但是盘子没了!0种方法 + } else if (apples < plates) { + ans = ways1(apples, apples); + }else { + // apples >= plates + // 1) 所有的盘子全部都要使用,方法数是a + int a = ways1(apples - plates, plates); + // 2) 不全使用!所有的盘子不都使用,方法数是b + int b = ways1(apples, plates - 1); + ans = a + b; + } + map[apples][plates] = ans; + return ans; + } + + + + + + + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int m = sc.nextInt(); + int n = sc.nextInt(); + int ways = f1(m, n); + System.out.println(ways); + } + sc.close(); + } + + // 苹果有apples个,盘子有plates个 + // 返回有几种摆法 + // 如果苹果数为0,有1种摆法:什么也不摆 + // 如果苹果数不为0,但是盘子数为0,有0种摆法(做不到) + // 如果苹果数不为0,盘子数也不为0,进行如下的情况讨论: + // 假设苹果数为apples,盘子数为plates + // 可能性 1) apples < plates + // 这种情况下,一定有多余的盘子,这些盘子完全没用,所以砍掉 + // 后续是f(apples, apples) + // 可能性 2) apples >= plates + // 在可能性2)下,讨论摆法,有如下两种选择 + // 选择a) 不是所有的盘子都使用 + // 选择b) 就是所有的盘子都使用 + // 对于选择a),既然不是所有盘子都使用,那么后续就是f(apples, plates - 1) + // 意思是:既然不是所有盘子都使用,那盘子减少一个,然后继续讨论吧! + // 对于选择b),既然就是所有的盘子都使用,那么先把所有盘子都摆上1个苹果。 + // 剩余苹果数 = apples - plates + // 然后继续讨论,剩下的这些苹果,怎么摆进plates个盘子里, + // 所以后续是f(apples - plates, plates) + public static int f1(int apples, int plates) { + if (apples == 0) { + return 1; + } + if (plates == 0) { + return 0; + } + if (plates > apples) { + return f1(apples, apples); + } else { // apples >= plates; + return f1(apples, plates - 1) + f1(apples - plates, plates); + } + } + + // 下面的方法就是上面方法的记忆化搜索版本 + public static int[][] dp = new int[20][20]; + + public static int f2(int apples, int plates) { + for (int i = 0; i <= apples; i++) { + Arrays.fill(dp[i], -1); + } + return g(apples, plates, dp); + } + + public static int g(int apples, int plates, int[][] dp) { + if (dp[apples][plates] != -1) { + return dp[apples][plates]; + } + int ans = 0; + if (apples == 0) { + ans = 1; + } else if (plates == 0) { + ans = 0; + } else if (plates > apples) { + ans = g(apples, apples, dp); + } else { + ans = g(apples, plates - 1, dp) + g(apples - plates, plates, dp); + } + dp[apples][plates] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/公开课/class072/Code02_SplitBuildingBlock.java b/公开课/class072/Code02_SplitBuildingBlock.java new file mode 100644 index 0000000..5e28eae --- /dev/null +++ b/公开课/class072/Code02_SplitBuildingBlock.java @@ -0,0 +1,61 @@ +package class072; + +import java.util.Arrays; + +// 来自京东笔试 +// 小明手中有n块积木,并且小明知道每块积木的重量。现在小明希望将这些积木堆起来 +// 要求是任意一块积木如果想堆在另一块积木上面,那么要求: +// 1) 上面的积木重量不能小于下面的积木重量 +// 2) 上面积木的重量减去下面积木的重量不能超过x +// 3) 每堆中最下面的积木没有重量要求 +// 现在小明有一个机会,除了这n块积木,还可以获得k块任意重量的积木。 +// 小明希望将积木堆在一起,同时希望积木堆的数量越少越好,你能帮他找到最好的方案么? +// 输入描述: +// 第一行三个整数n,k,x,1<=n<=200000,0<=x,k<=1000000000 +// 第二行n个整数,表示积木的重量,任意整数范围都在[1,1000000000] +// 样例输出: +// 13 1 38 +// 20 20 80 70 70 70 420 5 1 5 1 60 90 +// 1 1 5 5 20 20 60 70 70 70 80 90 420 -> 只有1块魔法积木,x = 38 +// 输出:2 +// 解释: +// 两堆分别是 +// 1 1 5 5 20 20 (50) 60 70 70 70 80 90 +// 420 +// 其中x是一个任意重量的积木,夹在20和60之间可以让积木继续往上搭 +public class Code02_SplitBuildingBlock { + + // 这是最优解 + // arr里装着所有积木的重量 + // k是魔法积木的数量,每一块魔法积木都能变成任何重量 + // x差值,后 - 前 <= x + public static int minSplit(int[] arr, int k, int x) { + Arrays.sort(arr); + int n = arr.length; + int[] needs = new int[n]; + int size = 0; + int splits = 1; + for (int i = 1; i < n; i++) { + if (arr[i] - arr[i - 1] > x) { + needs[size++] = arr[i] - arr[i - 1]; + splits++; + } + } + if (splits == 1 || x == 0 || k == 0) { + return splits; + } + // 试图去利用魔法积木,弥合堆! + Arrays.sort(needs, 0, size); + for (int i = 0; i < size; i++) { + int need = (needs[i] - 1) / x; + if (k >= need) { + splits--; + k -= need; + } else { + break; + } + } + return splits; + } + +} diff --git a/公开课/class072/Code03_BestMeetingPoint.java b/公开课/class072/Code03_BestMeetingPoint.java new file mode 100644 index 0000000..daa7c76 --- /dev/null +++ b/公开课/class072/Code03_BestMeetingPoint.java @@ -0,0 +1,50 @@ +package class072; + +// 测试链接 : https://leetcode.com/problems/best-meeting-point/ +// 不过这道题要开通LeetCode会员才能使用 +public class Code03_BestMeetingPoint { + + public static int minTotalDistance(int[][] grid) { + int N = grid.length; + int M = grid[0].length; + int[] iOnes = new int[N]; + int[] jOnes = new int[M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (grid[i][j] == 1) { + iOnes[i]++; + jOnes[j]++; + } + } + } + int total = 0; + int i = 0; + int j = N - 1; + int iRest = 0; + int jRest = 0; + while (i < j) { + if (iOnes[i] + iRest <= iOnes[j] + jRest) { + total += iOnes[i] + iRest; + iRest += iOnes[i++]; + } else { + total += iOnes[j] + jRest; + jRest += iOnes[j--]; + } + } + i = 0; + j = M - 1; + iRest = 0; + jRest = 0; + while (i < j) { + if (jOnes[i] + iRest <= jOnes[j] + jRest) { + total += jOnes[i] + iRest; + iRest += jOnes[i++]; + } else { + total += jOnes[j] + jRest; + jRest += jOnes[j--]; + } + } + return total; + } + +} diff --git a/公开课/class072/Code04_CombinationSumIV.java b/公开课/class072/Code04_CombinationSumIV.java new file mode 100644 index 0000000..fd577e8 --- /dev/null +++ b/公开课/class072/Code04_CombinationSumIV.java @@ -0,0 +1,47 @@ +package class072; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/combination-sum-iv/ +public class Code04_CombinationSumIV { + + public static int[] dp = new int[1001]; + + public static int combinationSum4(int[] nums, int target) { + Arrays.fill(dp, 0, target + 1, -1); + return process1(nums, target); + } + + public static int process1(int[] nums, int rest) { + if (rest < 0) { + return 0; + } + if (dp[rest] != -1) { + return dp[rest]; + } + int ans = 0; + if (rest == 0) { + ans = 1; + } else { + for (int num : nums) { + ans += process1(nums, rest - num); + } + } + dp[rest] = ans; + return ans; + } + +// // 剪枝 + 严格位置依赖的动态规划 +// public static int combinationSum4(int[] nums, int target) { +// Arrays.sort(nums); +// int[] dp = new int[target + 1]; +// dp[0] = 1; +// for (int rest = 1; rest <= target; rest++) { +// for (int i = 0; i < nums.length && nums[i] <= rest; i++) { +// dp[rest] += dp[rest - nums[i]]; +// } +// } +// return dp[target]; +// } + +} diff --git a/公开课/class073/Code01_CoverMax.java b/公开课/class073/Code01_CoverMax.java new file mode 100644 index 0000000..6e743fe --- /dev/null +++ b/公开课/class073/Code01_CoverMax.java @@ -0,0 +1,155 @@ +package class073; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code01_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // 小根堆,每一条线段的结尾数值,使用默认的 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // 和maxCover2过程是一样的 + // 只是代码更短 + // 不使用类定义的写法 + public static int maxCover3(int[][] m) { + // m是二维数组,可以认为m内部是一个一个的一维数组 + // 每一个一维数组就是一个对象,也就是线段 + // 如下的code,就是根据每一个线段的开始位置排序 + // 比如, m = { {5,7}, {1,4}, {2,6} } 跑完如下的code之后变成:{ {1,4}, {2,6}, {5,7} } + Arrays.sort(m, (a, b) -> (a[0] - b[0])); + // 准备好小根堆,和课堂的说法一样 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int[] line : m) { + while (!heap.isEmpty() && heap.peek() <= line[0]) { + heap.poll(); + } + heap.add(line[1]); + max = Math.max(max, heap.size()); + } + return max; + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + int ans3 = maxCover3(lines); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class073/Code02_4KeysKeyboard.java b/公开课/class073/Code02_4KeysKeyboard.java new file mode 100644 index 0000000..f6fd74e --- /dev/null +++ b/公开课/class073/Code02_4KeysKeyboard.java @@ -0,0 +1,38 @@ +package class073; + +// 测试链接 : https://leetcode.com/problems/4-keys-keyboard/ +public class Code02_4KeysKeyboard { + + // 可以证明: + // 来到i的时候,包括i在内最多有连续4次粘贴行为 + // 不可能更多,如果有连续5次粘贴,一定就不再是最优解 + // 假设开始时,A的数量为S,看如下的变化过程,我们称这是行为一: + // 开始 全选 复制(粘贴板S个A) 粘贴 粘贴 粘贴 粘贴 粘贴 + // S S S 2*S 3*S 4*S 5*S 6*S + // 但是,注意看如下的行为二: + // 开始 全选 复制(粘贴板S个A) 粘贴 全选 复制(粘贴板2S个A) 粘贴 粘贴 + // S S S 2*S 2*S 2*S 4*S 6*S + // 行为一,经历8步,最后是6*S个A + // 行为二,经历8步,最后是6*S个A + // 但是行为二在粘贴板上有2S个A,而行为一在粘贴板上有S个A + // 所以行为一没有行为二优 + // 以此说明:来到i的时候,包括i在内最多有连续4次粘贴行为 + // 那么就尝试:连续1次、连续2次、连续3次、连续4次粘贴行为即可 + public static int maxA(int n) { + // dp[0] 1步以内的最优解 + // dp[1] 2步以内的最优解 + // dp[2] 3步以内的最优解 + // dp[i] i+1步以内的最优解 + int[] dp = new int[n]; + for (int i = 0; i < 6 && i < n; i++) { + dp[i] = i + 1; + } + for (int i = 6; i < n; i++) { + dp[i] = Math.max( + Math.max(dp[i - 3] * 2, dp[i - 4] * 3), + Math.max(dp[i - 5] * 4, dp[i - 6] * 5)); + } + return dp[n - 1]; + } + +} diff --git a/公开课/class073/Code03_MinContinuousFragment.java b/公开课/class073/Code03_MinContinuousFragment.java new file mode 100644 index 0000000..2434720 --- /dev/null +++ b/公开课/class073/Code03_MinContinuousFragment.java @@ -0,0 +1,162 @@ +package class073; + +// 来自CMU入学申请考试 +// 给定一个长度为 N 的字符串 S,由字符'a'和'b'组成,空隙由 '?' 表示 +// 你的任务是用a字符或b字符替换每个间隙, +// 替换完成后想让连续出现同一种字符的最长子串尽可能短 +// 例如,S = "aa??bbb", +// 如果将"??"替换为"aa" ,即"aaaabbb",则由相等字符组成的最长子串长度为4 +// 如果将"??"替换为"ba" ,即"aababbb",则由相等字符组成的最长子串长度为3 +// 那么方案二是更好的结果,返回3 +// S的长度 <= 10^6 +public class Code03_MinContinuousFragment { + + // 暴力方法 + // 为了验证 + public static int minContinuous1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return process1(str, 0); + } + + public static int process1(char[] str, int index) { + if (index == str.length) { + return maxLen(str); + } else { + if (str[index] != '?') { + return process1(str, index + 1); + } else { + str[index] = 'a'; + int p1 = process1(str, index + 1); + str[index] = 'b'; + int p2 = process1(str, index + 1); + str[index] = '?'; + return Math.min(p1, p2); + } + } + } + + // 正式方法 + // 时间复杂度O(N) + public static int minContinuous2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int L = 0; + int R = -1; + for (int i = 0; i < N; i++) { + if (str[i] != '?') { + set(str, L, R); + L = i + 1; + R = i; + } else { + R++; + } + } + set(str, L, R); + // 下面的for循环,是单独处理,条件5) + for (int i = 1; i < N; i++) { + if (str[i] == '?') { + // baaaa?bbbbbbbba + for (L = i - 1; L >= 0 && str[L] == str[i - 1]; L--) + ; + for (R = i + 1; R < N && str[R] == str[i + 1]; R++) + ; + L = i - L - 1; + R = R - i - 1; + if (L <= R) { + str[i] = str[i - 1]; + } else { + str[i] = str[i + 1]; + } + } + } + return maxLen(str); + } + + // L...R 都是? + // 如果这一坨问号,满足1)2)3)4)中的一种,就填好 + // 如果满足5),就不填!a?b + public static void set(char[] str, int L, int R) { + int N = str.length; + if (L > R) { + return; + } + if (L == 0 && R == N - 1) { + for (int i = 0; i < N; i++) { + str[i] = (i & 1) == 0 ? 'a' : 'b'; + } + } else if (L == 0) { + for (int i = R; i >= 0; i--) { + str[i] = str[i + 1] == 'a' ? 'b' : 'a'; + } + } else if (R == N - 1) { + for (int i = L; i < str.length; i++) { + str[i] = str[i - 1] == 'a' ? 'b' : 'a'; + } + } else { + if (str[L - 1] == str[R + 1] || L != R) { + for (; L <= R; L++, R--) { + str[L] = str[L - 1] == 'a' ? 'b' : 'a'; + str[R] = str[R + 1] == 'a' ? 'b' : 'a'; + } + } + } + } + + public static int maxLen(char[] str) { + int ans = 1; + int cur = 1; + for (int i = 1; i < str.length; i++) { + if (str[i] != str[i - 1]) { + ans = Math.max(ans, cur); + cur = 1; + } else { + cur++; + } + } + ans = Math.max(ans, cur); + return ans; + } + + public static char[] arr = { 'a', 'b', '?' }; + + public static String randomString(int len) { + int N = (int) (Math.random() * (len + 1)); + char[] str = new char[N]; + for (int i = 0; i < N; i++) { + str[i] = arr[(int) (Math.random() * 3)]; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int len = 35; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String s = randomString(len); + int ans1 = minContinuous1(s); + int ans2 = minContinuous2(s); + if (ans1 != ans2) { + System.out.println(s); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + + len = 10000000; + String s = randomString(len); + long start = System.currentTimeMillis(); + minContinuous2(s); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒):" + (end - start)); + + } + +} diff --git a/公开课/class073/Code04_MaxGap.java b/公开课/class073/Code04_MaxGap.java new file mode 100644 index 0000000..3e92bbc --- /dev/null +++ b/公开课/class073/Code04_MaxGap.java @@ -0,0 +1,101 @@ +package class073; + +import java.util.Arrays; + +public class Code04_MaxGap { + + public static int maxGap(int[] nums) { + if (nums == null || nums.length < 2) { + return 0; + } + int len = nums.length; + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < len; i++) { + min = Math.min(min, nums[i]); + max = Math.max(max, nums[i]); + } + if (min == max) { + return 0; + } + // 不止一种数,min~max一定有range, len个数据,准备len+1个桶 + boolean[] hasNum = new boolean[len + 1]; // hasNum[i] i号桶是否进来过数字 + int[] maxs = new int[len + 1]; // maxs[i] i号桶收集的所有数字的最大值 + int[] mins = new int[len + 1]; // mins[i] i号桶收集的所有数字的最小值 + int bid = 0; // 桶号 + for (int i = 0; i < len; i++) { + bid = bucket(nums[i], len, min, max); + mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i]; + maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i]; + hasNum[bid] = true; + } + int res = 0; + int lastMax = maxs[0]; // 上一个非空桶的最大值 + int i = 1; + for (; i <= len; i++) { + if (hasNum[i]) { + res = Math.max(res, mins[i] - lastMax); + lastMax = maxs[i]; + } + } + return res; + } + + // 如果当前的数是num,整个范围是min~max,分成了len + 1份 + // 返回num该进第几号桶 + public static int bucket(long num, long len, long min, long max) { + return (int) ((num - min) * len / (max - min)); + } + + // for test + public static int comparator(int[] nums) { + if (nums == null || nums.length < 2) { + return 0; + } + Arrays.sort(nums); + int gap = Integer.MIN_VALUE; + for (int i = 1; i < nums.length; i++) { + gap = Math.max(nums[i] - nums[i - 1], gap); + } + return gap; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + if (maxGap(arr1) != comparator(arr2)) { + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/公开课/class074/Code01_Mod3Max.java b/公开课/class074/Code01_Mod3Max.java new file mode 100644 index 0000000..3d093fd --- /dev/null +++ b/公开课/class074/Code01_Mod3Max.java @@ -0,0 +1,247 @@ +package class074; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.TreeSet; + +// 来自去哪儿网 +// 给定一个arr,里面的数字都是0~9 +// 你可以随意使用arr中的数字,哪怕打乱顺序也行 +// 请拼出一个能被3整除的,最大的数字,用str形式返回 +public class Code01_Mod3Max { + + public static String max1(int[] arr) { + Arrays.sort(arr); + for (int l = 0, r = arr.length - 1; l < r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + StringBuilder builder = new StringBuilder(); + TreeSet set = new TreeSet<>((a, b) -> Integer.valueOf(b).compareTo(Integer.valueOf(a))); + process1(arr, 0, builder, set); + return set.isEmpty() ? "" : set.first(); + } + + public static void process1(int[] arr, int index, StringBuilder builder, TreeSet set) { + if (index == arr.length) { + if (builder.length() != 0 && Integer.valueOf(builder.toString()) % 3 == 0) { + set.add(builder.toString()); + } + } else { + process1(arr, index + 1, builder, set); + builder.append(arr[index]); + process1(arr, index + 1, builder, set); + builder.deleteCharAt(builder.length() - 1); + } + } + + public static String max2(int[] arr) { + if (arr == null || arr.length == 0) { + return ""; + } + Arrays.sort(arr); + for (int l = 0, r = arr.length - 1; l < r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + if (arr[0] == 0) { + return "0"; + } + String ans = process2(arr, 0, 0); + String res = ans.replaceAll("^(0+)", ""); + if (!res.equals("")) { + return res; + } + return ans.equals("") ? ans : "0"; + } + + // arr中的数字一定是0~9 + // arr是经过排序的,并且是从大到小排序,比如[9,8,7,7,7,3,1]等 + // 这个递归函数的含义 : + // 在arr[index...一直到最后]上做选择,arr[0...index-1]就当不存在 + // 每个位置的字符可以要、也可以不要,但是!选出来的数字拼完之后的结果,在%3之后,余数一定要是mod! + // 返回在上面设定的情况下,最大的数是多少? + // 如果存在这样的数,返回字符串的形式 + // 如果不存在这样的数,返回特殊字符串,比如"$",代表不可能 + // 这个递归函数可以很轻易的改出动态规划 + public static String process2(int[] arr, int index, int mod) { + if (index == arr.length) { + return mod == 0 ? "" : "$"; + } + String p1 = "$"; + int nextMod = nextMod(mod, arr[index] % 3); + String next = process2(arr, index + 1, nextMod); + if (!next.equals("$")) { + p1 = String.valueOf(arr[index]) + next; + } + String p2 = process2(arr, index + 1, mod); + if (p1.equals("$") && p2.equals("$")) { + return "$"; + } + if (!p1.equals("$") && !p2.equals("$")) { + return smaller(p1, p2) ? p2 : p1; + } + return p1.equals("$") ? p2 : p1; + } + + public static int nextMod(int require, int current) { + if (require == 0) { + if (current == 0) { + return 0; + } else if (current == 1) { + return 2; + } else { + return 1; + } + } else if (require == 1) { + if (current == 0) { + return 1; + } else if (current == 1) { + return 0; + } else { + return 2; + } + } else { // require == 2 + if (current == 0) { + return 2; + } else if (current == 1) { + return 1; + } else { + return 0; + } + } + } + + public static boolean smaller(String p1, String p2) { + if (p1.length() != p2.length()) { + return p1.length() < p2.length(); + } + return p1.compareTo(p2) < 0; + } + + // 贪心的思路解法 : + // 先得到数组的累加和,记为sum + // 1) 如果sum%3==0,说明所有数从大到小拼起来就可以了 + // 2) 如果sum%3==1,说明多了一个余数1, + // 只需要删掉一个最小的数(该数是%3==1的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==2的数); + // 3) 如果sum%3==2,说明多了一个余数2, + // 只需要删掉一个最小的数(该数是%3==2的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==1的数); + // 如果上面都做不到,说明拼不成 + public static String max3(int[] A) { + if (A == null || A.length == 0) { + return ""; + } + int mod = 0; + ArrayList arr = new ArrayList<>(); + for (int num : A) { + arr.add(num); + mod += num; + mod %= 3; + } + if ((mod == 1 || mod == 2) && !remove(arr, mod, 3 - mod)) { + return ""; + } + if (arr.isEmpty()) { + return ""; + } + arr.sort((a, b) -> b - a); + if (arr.get(0) == 0) { + return "0"; + } + StringBuilder builder = new StringBuilder(); + for (int num : arr) { + builder.append(num); + } + return builder.toString(); + } + + // 在arr中,要么删掉最小的一个、且%3之后余数是first的数 + // 如果做不到,删掉最小的两个、且%3之后余数是second的数 + // 如果能做到返回true,不能做到返回false + public static boolean remove(ArrayList arr, int first, int second) { + if (arr.size() == 0) { + return false; + } + arr.sort((a, b) -> compare(a, b, first, second)); + int size = arr.size(); + if (arr.get(size - 1) % 3 == first) { + arr.remove(size - 1); + return true; + } else if (size > 1 && arr.get(size - 1) % 3 == second && arr.get(size - 2) % 3 == second) { + arr.remove(size - 1); + arr.remove(size - 2); + return true; + } else { + return false; + } + } + + // a和b比较: + // 如果余数一样,谁大谁放前面 + // 如果余数不一样,余数是0的放最前面、余数是s的放中间、余数是f的放最后 + public static int compare(int a, int b, int f, int s) { + int ma = a % 3; + int mb = b % 3; + if (ma == mb) { + return b - a; + } else { + if (ma == 0 || mb == 0) { + return ma == 0 ? -1 : 1; + } else { + return ma == s ? -1 : 1; + } + } + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * 10); + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * N); + int[] arr1 = randomArray(len); + int[] arr2 = copyArray(arr1); + int[] arr3 = copyArray(arr1); + String ans1 = max1(arr1); + String ans2 = max2(arr2); + String ans3 = max3(arr3); + if (!ans1.equals(ans2) || !ans1.equals(ans3)) { + System.out.println("出错了!"); + for (int num : arr3) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class074/Code02_LetASorted.java b/公开课/class074/Code02_LetASorted.java new file mode 100644 index 0000000..942d4fa --- /dev/null +++ b/公开课/class074/Code02_LetASorted.java @@ -0,0 +1,97 @@ +package class074; + +// 给定两个数组A和B,长度都是N +// A[i]不可以在A中和其他数交换,只可以选择和B[i]交换(0<=i A[index] ? false : (process(A, B, index + 1, A[index])); + // 2) 交换 + boolean p2 = pre > B[index] ? false : (process(A, B, index + 1, B[index])); + return p1 | p2; + } + + public static boolean canSorted2(int[] A, int[] B) { + if (A == null || A.length < 2) { + return true; + } + // 长度>=2 + // 0 1.. + return zuo(A, B, 1, false) || zuo(A, B, 1, true); + } + + public static boolean zuo(int[] A, int[] B, int index, boolean swap) { + if (index == A.length) { + return true; + } + int pre = swap ? B[index - 1] : A[index - 1]; + // index还没到终止位置! + // 1) 不交换 A[index] + boolean p1 = pre > A[index] ? false : (zuo(A, B, index + 1, false)); + // 2) 交换 + boolean p2 = pre > B[index] ? false : (zuo(A, B, index + 1, true)); + return p1 | p2; + + } + +// +// public static boolean letASorted(int[] A, int[] B) { +// return process(A, B, 0, Integer.MIN_VALUE); +// } +// +// // 当前推进到了i位置,对于A和B都是i位置 +// // A[i]前一个数字,lastA +// // 能否通过题意中的操作,A[i] B[i] 让A整体有序 +// public static boolean process(int[] A, int[] B, int i, int lastA) { +// if (i == A.length) { +// return true; +// } +// // 第一种选择 : A[i]不和B[i]交换 +// if (A[i] >= lastA && process(A, B, i + 1, A[i])) { +// return true; +// } +// // 第一种选择 : A[i]和B[i]交换 +// if (B[i] >= lastA && process(A, B, i + 1, B[i])) { +// return true; +// } +// return false; +// } +// +// public static boolean process2(int[] A, int[] B, int i, int lastA) { +// if (i == A.length) { +// return true; +// } +// // 第一种选择 : A[i]不和B[i]交换 +// if (A[i] <= lastA && process2(A, B, i + 1, A[i])) { +// return true; +// } +// // 第一种选择 : A[i]和B[i]交换 +// if (B[i] <= lastA && process2(A, B, i + 1, B[i])) { +// return true; +// } +// return false; +// } + + // A B 操作 : A[i] 与 B[i] 交换! + // 目的 : 让A和B都有序,能不能做到 +// public static boolean process3(int[] A, int[] B, int i, int lastA, int lastB) { +// +// } + +} diff --git a/公开课/class074/Code03_AwayFromBlackHole.java b/公开课/class074/Code03_AwayFromBlackHole.java new file mode 100644 index 0000000..145369c --- /dev/null +++ b/公开课/class074/Code03_AwayFromBlackHole.java @@ -0,0 +1,122 @@ +package class074; + +// 来自美团 +// 所有黑洞的中心点记录在holes数组里 +// 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9) +// 并且所有黑洞的中心点都在左下角(0,0),右上角(x,y)的区域里 +// 飞船一旦开始进入黑洞,就会被吸进黑洞里 +// 返回: +// 如果统一所有黑洞的半径,最大半径是多少,依然能保证飞船从(0,0)能到达(x,y) +// 1000 1000*1000 10^6 * 二分 +public class Code03_AwayFromBlackHole { + + public static int maxRadius(int[][] holes, int x, int y) { + int L = 1; + int R = Math.max(x, y); + int ans = 0; + while (L <= R) { + int M = (L + R) / 2; + if (ok(holes, M, x, y)) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return ans; + } + + public static boolean ok(int[][] holes, int r, int x, int y) { + int n = holes.length; + UnionFind uf = new UnionFind(holes, n, r); + for (int i = 0; i < n; i++) { + for (int j = i; j < n; j++) { + if (touch(holes[i][0], holes[i][1], holes[j][0], holes[j][1], r)) { + uf.union(i, j); + } + if (uf.block(i, x, y)) { + return false; + } + } + } + return true; + } + + public static boolean touch(int x1, int y1, int x2, int y2, int r) { + return (r << 1) >= Math.sqrt((Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2))); + } + + public static class UnionFind { + public int[] father; + public int[] size; + public int[] xmin; + public int[] xmax; + public int[] ymin; + public int[] ymax; + public int[] help; + + public UnionFind(int[][] holes, int n, int r) { + father = new int[n]; + size = new int[n]; + xmin = new int[n]; + xmax = new int[n]; + ymin = new int[n]; + ymax = new int[n]; + help = new int[n]; + for (int i = 0; i < n; i++) { + father[i] = i; + size[i] = 1; + xmin[i] = holes[i][0] - r; + xmax[i] = holes[i][0] + r; + ymin[i] = holes[i][1] - r; + ymax[i] = holes[i][1] + r; + } + } + + private int find(int i) { + int hi = 0; + while (i != father[i]) { + help[hi++] = i; + i = father[i]; + } + for (hi--; hi >= 0; hi--) { + father[help[hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int fatheri = find(i); + int fatherj = find(j); + if (fatheri != fatherj) { + int sizei = size[fatheri]; + int sizej = size[fatherj]; + int big = sizei >= sizej ? fatheri : fatherj; + int small = big == fatheri ? fatherj : fatheri; + father[small] = big; + size[big] = sizei + sizej; + xmin[big] = Math.min(xmin[big], xmin[small]); + xmax[big] = Math.max(xmax[big], xmax[small]); + ymin[big] = Math.min(ymin[big], ymin[small]); + ymax[big] = Math.max(ymax[big], ymax[small]); + } + } + + public boolean block(int i, int x, int y) { + i = find(i); + return (xmin[i] <= 0 && xmax[i] >= x) + || (ymin[i] <= 0 && ymax[i] >= y) + || (xmin[i] <= 0 && ymin[i] <= 0) + || (xmax[i] >= x && ymax[i] >= y); + } + + } + + public static void main(String[] args) { + int[][] holes = { { 1, 2 }, { 4, 4 }, { 3, 0 }, { 5, 2 } }; + int x = 4; + int y = 6; + System.out.println(maxRadius(holes, x, y)); + } + +} diff --git a/公开课/class074/Code04_AllSame.java b/公开课/class074/Code04_AllSame.java new file mode 100644 index 0000000..9ce0c34 --- /dev/null +++ b/公开课/class074/Code04_AllSame.java @@ -0,0 +1,65 @@ +package class074; + +// 来自腾讯 +// 比如arr = {3,1,2,4} +// 下标对应是:0 1 2 3 +// 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作 +// 比如你选定1下标,1下标上面的数字是1,你可以选择变化这个数字,比如你让这个数字变成2 +// 那么arr = {3,2,2,4} +// 下标对应是:0 1 2 3 +// 因为你最开始确定了1这个下标,所以你以后都只能对这个下标进行操作, +// 但是,和你此时下标上的数字一样的、且位置连成一片的数字,会跟着一起变 +// 比如你选择让此时下标1的数字2变成3, +// 那么arr = {3,3,3,4} 可以看到下标1和下标2的数字一起变成3,这是规则!一定会一起变 +// 下标对应是:0 1 2 3 +// 接下来,你还是只能对1下标进行操作,那么数字一样的、且位置连成一片的数字(arr[0~2]这个范围)都会一起变 +// 决定变成4 +// 那么arr = {4,4,4,4} +// 下标对应是:0 1 2 3 +// 至此,所有数都成一样的了,你在下标1上做了3个决定(第一次变成2,第二次变成3,第三次变成4), +// 因为联动规则,arr全刷成一种数字了 +// 给定一个数组arr,最开始选择哪个下标,你随意 +// 你的目的是一定要让arr都成为一种数字,注意联动效果会一直生效 +// 返回最小的变化数 +// arr长度 <= 200, arr中的值 <= 10^6 +public class Code04_AllSame { + + public static int allSame(int[] arr) { + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + ans = Math.min(ans, process(arr, i - 1, arr[i], i + 1)); + } + return ans; + } + + // 左边arr[0..left],如果left == -1,说明没有左边了 + // 右边arr[right...n-1],如果right == n,说明没有右边了 + // 中间的值是midV,中间的值代表中间一整个部分的值,中间部分有可能是一个数,也可能是一堆数,但是值都为midV + // 返回arr都刷成一样的,最小代价是多少 + // left 可能性 : N + // right 可能性 : N + // midV 可能性 : arr中的最大值! + public static int process(int[] arr, int left, int midV, int right) { + for (; left >= 0 && arr[left] == midV;) { + left--; + } + for (; right < arr.length && arr[right] == midV;) { + right++; + } + if (left == -1 && right == arr.length) { + return 0; + } + int p1 = Integer.MAX_VALUE; + if (left >= 0) { + p1 = process(arr, left - 1, arr[left], right) + 1; + } + int p2 = Integer.MAX_VALUE; + if (right < arr.length) { + p2 = process(arr, left, arr[right], right + 1) + 1; + } + return Math.min(p1, p2); + } + + // 如上的递归,请改动态规划,具体参考体系学习班,动态规划大章节! + +} diff --git a/公开课/class075/Code01_BattleshipsInABoard.java b/公开课/class075/Code01_BattleshipsInABoard.java new file mode 100644 index 0000000..2b010b3 --- /dev/null +++ b/公开课/class075/Code01_BattleshipsInABoard.java @@ -0,0 +1,21 @@ +package class075; + +// 来自米哈游 +// 测试链接 : https://leetcode.com/problems/battleships-in-a-board/ +public class Code01_BattleshipsInABoard { + + public static int countBattleships(char[][] m) { + int ans = 0; + for (int i = 0; i < m.length; i++) { + for (int j = 0; j < m[0].length; j++) { + if ((m[i][j] == 'X') + && (i == 0 || m[i - 1][j] != 'X') + && (j == 0 || m[i][j - 1] != 'X')) { + ans++; + } + } + } + return ans; + } + +} diff --git a/公开课/class075/Code02_ShortestSubarrayWithSumAtLeastK.java b/公开课/class075/Code02_ShortestSubarrayWithSumAtLeastK.java new file mode 100644 index 0000000..e797f0b --- /dev/null +++ b/公开课/class075/Code02_ShortestSubarrayWithSumAtLeastK.java @@ -0,0 +1,74 @@ +package class075; + +// 来自字节跳动 +// 给定一个数组arr,其中的值有可能正、负、0 +// 给定一个正数k +// 返回累加和>=k的所有子数组中,最短的子数组长度 +// 本题测试链接 : https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/ +public class Code02_ShortestSubarrayWithSumAtLeastK { + + public static int shortestSubarray1(int[] arr, int k) { + if (arr == null || arr.length < 1) { + return -1; + } + int n = arr.length + 1; + long[] sum = new long[n]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i - 1]; + } + int[] stack = new int[n]; + int size = 1; + int ans = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + int mostRight = mostRight(sum, stack, size, sum[i] - k); + ans = Math.min(ans, mostRight == -1 ? Integer.MAX_VALUE : (i - mostRight)); + while (size > 0 && sum[stack[size - 1]] >= sum[i]) { + size--; + } + stack[size++] = i; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int mostRight(long[] sum, int[] stack, int size, long aim) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sum[stack[m]] <= aim) { + ans = stack[m]; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int shortestSubarray2(int[] arr, int K) { + int N = arr.length; + long[] sum = new long[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + int ans = Integer.MAX_VALUE; + int[] dq = new int[N + 1]; + int l = 0; + int r = 0; + for (int i = 0; i < N + 1; i++) { + // 头部开始,符合条件的,从头部弹出! + while (l != r && sum[i] - sum[dq[l]] >= K) { + ans = Math.min(ans, i - dq[l++]); + } + // 尾部开始,前缀和比当前的前缀和大于等于的,从尾部弹出! + while (l != r && sum[dq[r - 1]] >= sum[i]) { + r--; + } + dq[r++] = i; + } + return ans != Integer.MAX_VALUE ? ans : -1; + } + +} diff --git a/公开课/class075/Code03_BuyThingsAboutCollocation.java b/公开课/class075/Code03_BuyThingsAboutCollocation.java new file mode 100644 index 0000000..62110ee --- /dev/null +++ b/公开课/class075/Code03_BuyThingsAboutCollocation.java @@ -0,0 +1,103 @@ +package class075; + +// things是一个N*3的二维数组,商品有N件,商品编号从1~N +// 比如things[3] = [300, 2, 6] +// 代表第3号商品:价格300,重要度2,它是6号商品的附属商品 +// 再比如things[6] = [500, 3, 0] +// 代表第6号商品:价格500,重要度3,它不是任何附属,它是主商品 +// 每件商品的收益是价格*重要度,花费就是价格 +// 如果一个商品是附属品,那么只有它附属的主商品购买了,它才能被购买 +// 任何一个附属商品,只会有1个主商品 +// 任何一个主商品的附属商品数量,不会超过2件 +// 主商品和附属商品的层级最多有2层 +// 给定二维数组things、钱数money,返回整体花费不超过money的情况下,最大的收益总和 +// 测试链接 : https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4 +// 请把如下的代码的主类名改为"Main", 可以直接通过 +import java.util.ArrayList; +import java.util.Scanner; + +public class Code03_BuyThingsAboutCollocation { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int money = sc.nextInt(); + int size = sc.nextInt(); + ArrayList> things = new ArrayList<>(); + things.add(new ArrayList<>()); + for (int i = 0; i < size; i++) { + ArrayList cur = new ArrayList<>(); + cur.add(new int[] { sc.nextInt(), sc.nextInt(), sc.nextInt() }); + things.add(cur); + } + int n = clean(things, size); + int ans = maxScore(things, n, money); + System.out.println(ans); + } + sc.close(); + } + + public static int clean(ArrayList> things, int size) { + for (int i = 1; i <= size; i++) { + int[] cur = things.get(i).get(0); + if (cur[2] != 0) { + things.get(i).clear(); + things.get(cur[2]).add(cur); + } + } + int n = 0; + for (int i = 0; i <= size; i++) { + if (!things.get(i).isEmpty()) { + things.set(n++, things.get(i)); + } + } + return n; + } + + public static int maxScore(ArrayList> things, int n, int money) { + int[][] dp = new int[n][money + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= money; j++) { + dp[i][j] = -2; + } + } + return process(things, n, 0, money, dp); + } + + public static int process(ArrayList> things, int n, int index, int rest, int[][] dp) { + if (rest < 0) { + return -1; + } + if (index == n) { + return 0; + } + if (dp[index][rest] != -2) { + return dp[index][rest]; + } + ArrayList project = things.get(index); + int[] a = project.get(0); + int[] b = project.size() > 1 ? project.get(1) : null; + int[] c = project.size() > 2 ? project.get(2) : null; + int p1 = process(things, n, index + 1, rest, dp); + int p2 = process(things, n, index + 1, rest - a[0], dp); + if (p2 != -1) { + p2 += a[0] * a[1]; + } + int p3 = b != null ? process(things, n, index + 1, rest - a[0] - b[0], dp) : -1; + if (p3 != -1) { + p3 += a[0] * a[1] + b[0] * b[1]; + } + int p4 = c != null ? process(things, n, index + 1, rest - a[0] - c[0], dp) : -1; + if (p4 != -1) { + p4 += a[0] * a[1] + c[0] * c[1]; + } + int p5 = c != null ? process(things, n, index + 1, rest - a[0] - b[0] - c[0], dp) : -1; + if (p5 != -1) { + p5 += a[0] * a[1] + b[0] * b[1] + c[0] * c[1]; + } + int ans = Math.max(Math.max(Math.max(p1, p2), Math.max(p3, p4)), p5); + dp[index][rest] = ans; + return ans; + } + +} diff --git a/公开课/class075/Code04_BrickAll.java b/公开课/class075/Code04_BrickAll.java new file mode 100644 index 0000000..be4bac2 --- /dev/null +++ b/公开课/class075/Code04_BrickAll.java @@ -0,0 +1,50 @@ +package class075; + +import java.util.Arrays; + +// 来自华为 +// 给定一个正数数组arr,其中每个值代表砖块长度 +// 所有砖块等高等宽,只有长度有区别 +// 每一层可以用1块或者2块砖来摆 +// 要求每一层的长度一样 +// 要求必须使用所有的砖块 +// 请问最多摆几层 +// 如果无法做到,返回-1 +public class Code04_BrickAll { + + public static int maxLevels(int[] arr) { + if (arr == null) { + return 0; + } + int n = arr.length; + if (n < 2) { + return n; + } + Arrays.sort(arr); + int p1 = levels(arr, arr[n - 1]); + int p2 = levels(arr, arr[n - 1] + arr[0]); + return Math.max(p1, p2); + } + + // 要求所有砖必须都使用,并且每一层最多两块砖,并且每一层长度都是len + // 返回能摆几层,如果无法做到返回-1 + public static int levels(int[] arr, int len) { + int ans = 0; + int L = 0; + int R = arr.length - 1; + while (L <= R) { + if (arr[R] == len) { + R--; + ans++; + } else if (L < R && arr[L] + arr[R] == len) { + L++; + R--; + ans++; + } else { + return -1; + } + } + return ans; + } + +} diff --git a/公开课/class076/Code01_SetAll.java b/公开课/class076/Code01_SetAll.java new file mode 100644 index 0000000..2179b51 --- /dev/null +++ b/公开课/class076/Code01_SetAll.java @@ -0,0 +1,48 @@ +package class076; + +import java.util.HashMap; + +public class Code01_SetAll { + + public static class MyValue { + public V value; + public long time; + + public MyValue(V v, long t) { + value = v; + time = t; + } + } + + public static class MyHashMap { + private HashMap> map; + private long time; + private MyValue setAll; + + public MyHashMap() { + map = new HashMap<>(); + time = 0; + setAll = new MyValue(null, -1); + } + + public void put(K key, V value) { + map.put(key, new MyValue(value, time++)); + } + + public void setAll(V value) { + setAll = new MyValue(value, time++); + } + + public V get(K key) { + if (!map.containsKey(key)) { + return null; + } + if (map.get(key).time > setAll.time) { + return map.get(key).value; + } else { + return setAll.value; + } + } + } + +} diff --git a/公开课/class076/Code02_4KeysKeyboard.java b/公开课/class076/Code02_4KeysKeyboard.java new file mode 100644 index 0000000..46ce34c --- /dev/null +++ b/公开课/class076/Code02_4KeysKeyboard.java @@ -0,0 +1,24 @@ +package class076; + +// 测试链接 : https://leetcode.com/problems/4-keys-keyboard/ +public class Code02_4KeysKeyboard { + + public static int maxA(int n) { + int[] dp = new int[n + 1]; + // dp[1] : 1 + // dp[2] : 2 + // dp[5] : 5 + for (int i = 1; i <= 5 && i <= n; i++) { + dp[i] = i; + } + // i + //-1 -1 -1 粘贴 + for (int i = 6; i <= n; i++) { + dp[i] = Math.max( + Math.max(dp[i - 3] * 2, dp[i - 4] * 3), + Math.max(dp[i - 5] * 4, dp[i - 6] * 5)); + } + return dp[n]; + } + +} diff --git a/公开课/class076/Code03_BrickAll.java b/公开课/class076/Code03_BrickAll.java new file mode 100644 index 0000000..32e98d2 --- /dev/null +++ b/公开课/class076/Code03_BrickAll.java @@ -0,0 +1,50 @@ +package class076; + +import java.util.Arrays; + +// 来自华为 +// 给定一个正数数组arr,其中每个值代表砖块长度 +// 所有砖块等高等宽,只有长度有区别 +// 每一层可以用1块或者2块砖来摆 +// 要求每一层的长度一样 +// 要求必须使用所有的砖块 +// 请问最多摆几层 +// 如果无法做到,返回-1 +public class Code03_BrickAll { + + public static int maxLevels(int[] arr) { + if (arr == null) { + return 0; + } + int n = arr.length; + if (n < 2) { + return n; + } + Arrays.sort(arr); + int p1 = levels(arr, arr[n - 1]); + int p2 = levels(arr, arr[n - 1] + arr[0]); + return Math.max(p1, p2); + } + + // 要求所有砖必须都使用,并且每一层最多两块砖,并且每一层长度都是len + // 返回能摆几层,如果无法做到返回-1 + public static int levels(int[] arr, int len) { + int ans = 0; + int L = 0; + int R = arr.length - 1; + while (L <= R) { + if (arr[R] == len) { + R--; + ans++; + } else if (L < R && arr[L] + arr[R] == len) { + L++; + R--; + ans++; + } else { + return -1; + } + } + return ans; + } + +} diff --git a/公开课/class076/Code04_InsertDeleteGetRandom.java b/公开课/class076/Code04_InsertDeleteGetRandom.java new file mode 100644 index 0000000..141551d --- /dev/null +++ b/公开课/class076/Code04_InsertDeleteGetRandom.java @@ -0,0 +1,51 @@ +package class076; + +import java.util.HashMap; + +// 测试链接 : https://leetcode.com/problems/insert-delete-getrandom-o1/ +public class Code04_InsertDeleteGetRandom { + + public class RandomizedSet { + private HashMap keyIndexMap; + private HashMap indexKeyMap; + private int size; + + public RandomizedSet() { + keyIndexMap = new HashMap(); + indexKeyMap = new HashMap(); + size = 0; + } + + public boolean insert(int key) { + if (!keyIndexMap.containsKey(key)) { + keyIndexMap.put(key, size); + indexKeyMap.put(size++, key); + return true; + } + return false; + } + + public boolean remove(int val) { + if (keyIndexMap.containsKey(val)) { + int deleteIndex = keyIndexMap.get(val); + int lastIndex = --size; + int lastKey = indexKeyMap.get(lastIndex); + keyIndexMap.put(lastKey, deleteIndex); + indexKeyMap.put(deleteIndex, lastKey); + keyIndexMap.remove(val); + indexKeyMap.remove(lastIndex); + return true; + } + return false; + } + + public int getRandom() { + if (size == 0) { + return -1; + } + int randomIndex = (int) (Math.random() * size); + return indexKeyMap.get(randomIndex); + } + } + +} diff --git a/公开课/class076/Code05_SplitApples.java b/公开课/class076/Code05_SplitApples.java new file mode 100644 index 0000000..a08fc8f --- /dev/null +++ b/公开课/class076/Code05_SplitApples.java @@ -0,0 +1,161 @@ +package class076; + +//有m个同样的苹果,认为苹果之间无差别 +//有n个同样的盘子,认为盘子之间也无差别 +//还有,比如5个苹果如果放进3个盘子, +//那么1、3、1和1、1、3和3、1、1的放置方法,也认为是一种方法 +//如上的设定下,返回有多少种放置方法 +//测试链接 : https://www.nowcoder.com/practice/bfd8234bb5e84be0b493656e390bdebf +//提交以下的code,提交时请把类名改成"Main" +import java.util.Arrays; +import java.util.Scanner; + +public class Code05_SplitApples { + + public static int test(int apples, int plates) { + // 1000 + // 1000 + int[][] dp = new int[apples + 1][plates + 1]; + for (int i = 0; i <= apples; i++) { + for (int j = 0; j <= plates; j++) { + dp[i][j] = -1; + } + } + return f(apples, plates, dp); + } + + // 摆法要符合题目的意思:排完序之后,如果数字分布一样,就认为是同一种摆法! + // 给你苹果数量apples,给你盘子数量plates + // 返回,几种摆法! + // 1000个 + // 1000个 + // 1000 * 1000 + public static int f(int apples, int plates, int[][] dp) { + if (dp[apples][plates] != -1) { + return dp[apples][plates]; + } + int ans = 0; + if (apples == 0) { + ans = 1; + } else if (plates == 0) { + ans = 0; + } else { + if (plates > apples) { + ans = f(apples, apples, dp); + } else { // apples >= plates; + ans = f(apples, plates - 1, dp) + f(apples - plates, plates, dp); + } + } + dp[apples][plates] = ans; + return ans; + } + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int m = sc.nextInt(); + int n = sc.nextInt(); + int ways = ways3(m, n); + System.out.println(ways); + } + sc.close(); + } + + // 思路来自于分裂数问题 + // 体系学习班代码第22节,题目3,split number问题 + public static int ways1(int apples, int plates) { + return process1(1, apples, plates); + } + + // pre : 上一个盘子分到的苹果数量,当前的盘子分到的数量不能小于pre + // apples : 剩余的苹果数量 + // plates : 剩余的盘子数量 + // 在盘子够用的情况下,把苹果分完,有几种方法 + public static int process1(int pre, int apples, int plates) { + if (apples == 0) { + return 1; + } + // apples != 0 + if (plates == 0) { + return 0; + } + // apples != 0 && plates != 0 + if (pre > apples) { + return 0; + } + // apples != 0 && plates != 0 && pre <= apples + int way = 0; + // 之前的盘子分了3个苹果,现在还剩下8个苹果 + // 当前的盘子,可以装几个苹果:3、4、5、6、7、8 + for (int cur = pre; cur <= apples; cur++) { + way += process1(cur, apples - cur, plates - 1); + } + return way; + } + + // 新的尝试,最优解 + // 苹果有apples个,盘子有plates个 + // 返回有几种摆法 + // 如果苹果数为0,有1种摆法:什么也不摆 + // 如果苹果数不为0,但是盘子数为0,有0种摆法(做不到) + // 如果苹果数不为0,盘子数也不为0,进行如下的情况讨论: + // 假设苹果数为apples,盘子数为plates + // 可能性 1) apples < plates + // 这种情况下,一定有多余的盘子,这些盘子完全没用,所以砍掉 + // 后续是f(apples, apples) + // 可能性 2) apples >= plates + // 在可能性2)下,讨论摆法,有如下两种选择 + // 选择a) 不是所有的盘子都使用 + // 选择b) 就是所有的盘子都使用 + // 对于选择a),既然不是所有盘子都使用,那么后续就是f(apples, plates - 1) + // 意思是:既然不是所有盘子都使用,那盘子减少一个,然后继续讨论吧! + // 对于选择b),既然就是所有的盘子都使用,那么先把所有盘子都摆上1个苹果。 + // 剩余苹果数 = apples - plates + // 然后继续讨论,剩下的这些苹果,怎么摆进plates个盘子里, + // 所以后续是f(apples - plates, plates) + public static int ways2(int apples, int plates) { + if (apples == 0) { + return 1; + } + if (plates == 0) { + return 0; + } + if (plates > apples) { + return ways2(apples, apples); + } else { // apples >= plates; + return ways2(apples, plates - 1) + ways2(apples - plates, plates); + } + } + + // 上面最优解尝试的记忆化搜索版本 + public static int[][] dp = null; + + public static int ways3(int apples, int plates) { + if (dp == null) { + dp = new int[11][11]; + for (int i = 0; i <= 10; i++) { + Arrays.fill(dp[i], -1); + } + } + return process3(apples, plates, dp); + } + + public static int process3(int apples, int plates, int[][] dp) { + if (dp[apples][plates] != -1) { + return dp[apples][plates]; + } + int ans = 0; + if (apples == 0) { + ans = 1; + } else if (plates == 0) { + ans = 0; + } else if (plates > apples) { + ans = process3(apples, apples, dp); + } else { + ans = process3(apples, plates - 1, dp) + process3(apples - plates, plates, dp); + } + dp[apples][plates] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/公开课/class076/Code06_SplitStrings.java b/公开课/class076/Code06_SplitStrings.java new file mode 100644 index 0000000..8bcd267 --- /dev/null +++ b/公开课/class076/Code06_SplitStrings.java @@ -0,0 +1,11 @@ +package class076; + +public class Code06_SplitStrings { +// 题目6 +// 给定一个非常大的List list +// 每一个字符串类似 : "hello,world,have,hello,world" +// 这一个字符串中,有2个hello,2个world,1个have +// 请设计一种多线程处理方案,统计list中每一个字符串, +// 切分出来的单词数量,并且汇总 +// 最终返回一个HashMap表示每个字符串出现几次 +} diff --git a/公开课/class076/Code07_ShortestSubarrayWithSumAtLeastK.java b/公开课/class076/Code07_ShortestSubarrayWithSumAtLeastK.java new file mode 100644 index 0000000..719aabf --- /dev/null +++ b/公开课/class076/Code07_ShortestSubarrayWithSumAtLeastK.java @@ -0,0 +1,74 @@ +package class076; + +// 来自字节跳动 +// 给定一个数组arr,其中的值有可能正、负、0 +// 给定一个正数k +// 返回累加和>=k的所有子数组中,最短的子数组长度 +// 本题测试链接 : https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/ +public class Code07_ShortestSubarrayWithSumAtLeastK { + + public static int shortestSubarray1(int[] arr, int k) { + if (arr == null || arr.length < 1) { + return -1; + } + int n = arr.length + 1; + long[] sum = new long[n]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i - 1]; + } + int[] stack = new int[n]; + int size = 1; + int ans = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + int mostRight = mostRight(sum, stack, size, sum[i] - k); + ans = Math.min(ans, mostRight == -1 ? Integer.MAX_VALUE : (i - mostRight)); + while (size > 0 && sum[stack[size - 1]] >= sum[i]) { + size--; + } + stack[size++] = i; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int mostRight(long[] sum, int[] stack, int size, long aim) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sum[stack[m]] <= aim) { + ans = stack[m]; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int shortestSubarray2(int[] arr, int K) { + int N = arr.length; + long[] sum = new long[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + int ans = Integer.MAX_VALUE; + int[] dq = new int[N + 1]; + int l = 0; + int r = 0; + for (int i = 0; i < N + 1; i++) { + // 头部开始,符合条件的,从头部弹出! + while (l != r && sum[i] - sum[dq[l]] >= K) { + ans = Math.min(ans, i - dq[l++]); + } + // 尾部开始,前缀和比当前的前缀和大于等于的,从尾部弹出! + while (l != r && sum[dq[r - 1]] >= sum[i]) { + r--; + } + dq[r++] = i; + } + return ans != Integer.MAX_VALUE ? ans : -1; + } + +} diff --git a/公开课/class077/Code01_MonotonousStack.java b/公开课/class077/Code01_MonotonousStack.java new file mode 100644 index 0000000..35e3fb2 --- /dev/null +++ b/公开课/class077/Code01_MonotonousStack.java @@ -0,0 +1,178 @@ +package class077; + +import java.util.List; +import java.util.ArrayList; +import java.util.Stack; + +public class Code01_MonotonousStack { + + // arr = [ 3, 1, 2, 3] + // 0 1 2 3 + // [ + // 0 : [-1, 1] + // 1 : [-1, -1] + // 2 : [ 1, -1] + // 3 : [ 2, -1] + // ] + public static int[][] getNearLessNoRepeat(int[] arr) { + int[][] res = new int[arr.length][2]; + // 只存位置! + Stack stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // 当遍历到i位置的数,arr[i] + while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = i; + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = -1; + } + return res; + } + + + // arr中可能有重复值,[3,2,3,3,1] + // 0 1 2 3 4 + // + // 0 -> 3 : -1 1->2 + // 0 : [-1, 1] + // 1 : [-1, 4] + // 2 : [ 1, 4] + // n-1 : [ a, b] + public static int[][] getNearLess(int[] arr) { + int[][] res = new int[arr.length][2]; + Stack< List > stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈 + // i -> arr[i] + while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() + ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = i; + } + } + // 1) 弹出完之后,顶部有东西,和当前值一样 + if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) { + stack.peek().add(Integer.valueOf(i)); + } else { // 2) 栈为空 or 栈顶值 < 当前值 + ArrayList list = new ArrayList<>(); + list.add(i); + stack.push(list); + } + } + // 单独清算栈里的东西! + while (!stack.isEmpty()) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = -1; + } + } + return res; + } + + // for test + public static int[] getRandomArrayNoRepeat(int size) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + for (int i = 0; i < arr.length; i++) { + int swapIndex = (int) (Math.random() * arr.length); + int tmp = arr[swapIndex]; + arr[swapIndex] = arr[i]; + arr[i] = tmp; + } + return arr; + } + + // for test + public static int[] getRandomArray(int size, int max) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // for test + public static int[][] rightWay(int[] arr) { + int[][] res = new int[arr.length][2]; + for (int i = 0; i < arr.length; i++) { + int leftLessIndex = -1; + int rightLessIndex = -1; + int cur = i - 1; + while (cur >= 0) { + if (arr[cur] < arr[i]) { + leftLessIndex = cur; + break; + } + cur--; + } + cur = i + 1; + while (cur < arr.length) { + if (arr[cur] < arr[i]) { + rightLessIndex = cur; + break; + } + cur++; + } + res[i][0] = leftLessIndex; + res[i][1] = rightLessIndex; + } + return res; + } + + // for test + public static boolean isEqual(int[][] res1, int[][] res2) { + if (res1.length != res2.length) { + return false; + } + for (int i = 0; i < res1.length; i++) { + if (res1[i][0] != res2[i][0] || res1[i][1] != res2[i][1]) { + return false; + } + } + + return true; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int size = 10; + int max = 20; + int testTimes = 2000000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr1 = getRandomArrayNoRepeat(size); + int[] arr2 = getRandomArray(size, max); + if (!isEqual(getNearLessNoRepeat(arr1), rightWay(arr1))) { + System.out.println("Oops!"); + printArray(arr1); + break; + } + if (!isEqual(getNearLess(arr2), rightWay(arr2))) { + System.out.println("Oops!"); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } +} diff --git a/公开课/class077/Code02_AllTimesMinToMax.java b/公开课/class077/Code02_AllTimesMinToMax.java new file mode 100644 index 0000000..d927278 --- /dev/null +++ b/公开课/class077/Code02_AllTimesMinToMax.java @@ -0,0 +1,101 @@ +package class077; + +import java.util.Stack; + +// 给定一个只包含正数的数组arr,arr中任何一个子数组sub, +// 一定都可以算出(sub累加和 )* (sub中的最小值)是什么, +// 那么所有子数组中,这个值最大是多少? +public class Code02_AllTimesMinToMax { + + public static int max1(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + int minNum = Integer.MAX_VALUE; + int sum = 0; + for (int k = i; k <= j; k++) { + sum += arr[k]; + minNum = Math.min(minNum, arr[k]); + } + max = Math.max(max, minNum * sum); + } + } + return max; + } + + public static int max2(int[] arr) { + int size = arr.length; + int[] sums = new int[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + int max = Integer.MIN_VALUE; + Stack stack = new Stack(); + for (int i = 0; i < size; i++) { + while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) { + int j = stack.pop(); + max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]); + } + return max; + } + + public static int[] gerenareRondomArray() { + int[] arr = new int[(int) (Math.random() * 20) + 10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * 101); + } + return arr; + } + + public static void main(String[] args) { + int testTimes = 2000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = gerenareRondomArray(); + if (max1(arr) != max2(arr)) { + System.out.println("FUCK!"); + break; + } + } + System.out.println("test finish"); + } + + // 本题可以在leetcode上找到原题 + // 测试链接 : https://leetcode.com/problems/maximum-subarray-min-product/ + // 注意测试题目数量大,要取模,但是思路和课上讲的是完全一样的 + // 注意溢出的处理即可,也就是用long类型来表示累加和 + // 还有优化就是,你可以用自己手写的数组栈,来替代系统实现的栈,也会快很多 + public static int maxSumMinProduct(int[] arr) { + int size = arr.length; + long[] sums = new long[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + long max = Long.MIN_VALUE; + int[] stack = new int[size]; + int stackSize = 0; + for (int i = 0; i < size; i++) { + while (stackSize != 0 && arr[stack[stackSize - 1]] >= arr[i]) { + int j = stack[--stackSize]; + max = Math.max(max, + (stackSize == 0 ? sums[i - 1] : (sums[i - 1] - sums[stack[stackSize - 1]])) * arr[j]); + } + stack[stackSize++] = i; + } + while (stackSize != 0) { + int j = stack[--stackSize]; + max = Math.max(max, + (stackSize == 0 ? sums[size - 1] : (sums[size - 1] - sums[stack[stackSize - 1]])) * arr[j]); + } + return (int) (max % 1000000007); + } + +} diff --git a/公开课/class077/Code03_LargestRectangleInHistogram.java b/公开课/class077/Code03_LargestRectangleInHistogram.java new file mode 100644 index 0000000..45b11cd --- /dev/null +++ b/公开课/class077/Code03_LargestRectangleInHistogram.java @@ -0,0 +1,58 @@ +package class077; + +import java.util.Stack; + +// 测试链接:https://leetcode.com/problems/largest-rectangle-in-histogram +public class Code03_LargestRectangleInHistogram { + + public static int largestRectangleArea1(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int maxArea = 0; + Stack stack = new Stack(); + for (int i = 0; i < height.length; i++) { + while (!stack.isEmpty() && height[i] <= height[stack.peek()]) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + + public static int largestRectangleArea2(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int N = height.length; + int[] stack = new int[N]; + int si = -1; + int maxArea = 0; + for (int i = 0; i < height.length; i++) { + while (si != -1 && height[i] <= height[stack[si]]) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack[++si] = i; + } + while (si != -1) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + +} diff --git a/公开课/class077/Code04_ValidSequence.java b/公开课/class077/Code04_ValidSequence.java new file mode 100644 index 0000000..b21c73a --- /dev/null +++ b/公开课/class077/Code04_ValidSequence.java @@ -0,0 +1,113 @@ +package class077; + +// 来自腾讯 +// 给定一个长度为n的数组arr,求有多少个子数组满足 : +// 子数组两端的值,是这个子数组的最小值和次小值,最小值和次小值谁在最左和最右无所谓 +// n<=100000(10^5) n*logn O(N) +public class Code04_ValidSequence { + + + public static int nums(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int n = arr.length; + int[] values = new int[n]; + int[] times = new int[n]; + int size = 0; + int ans = 0; + for (int i = 0; i < arr.length; i++) { + while (size != 0 && values[size - 1] > arr[i]) { + size--; + ans += times[size] + cn2(times[size]); + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + while (size != 0) { + ans += cn2(times[--size]); + } + for (int i = arr.length - 1; i >= 0; i--) { + while (size != 0 && values[size - 1] > arr[i]) { + ans += times[--size]; + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + return ans; + } + + public static int cn2(int n) { + return (n * (n - 1)) >> 1; + } + + // 为了测试 + // 暴力方法 + public static int test(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int ans = 0; + for (int s = 0; s < arr.length; s++) { + for (int e = s + 1; e < arr.length; e++) { + int max = Math.max(arr[s], arr[e]); + boolean valid = true; + for (int i = s + 1; i < e; i++) { + if (arr[i] < max) { + valid = false; + break; + } + } + ans += valid ? 1 : 0; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int n = 30; + int v = 30; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * n); + int[] arr = randomArray(m, v); + int ans1 = nums(arr); + int ans2 = test(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class078/Code01_SplitSameNumberWays.java b/公开课/class078/Code01_SplitSameNumberWays.java new file mode 100644 index 0000000..bde57ca --- /dev/null +++ b/公开课/class078/Code01_SplitSameNumberWays.java @@ -0,0 +1,36 @@ +package class078; + +// 来自微软 +// 比如,str = "ayxbx" +// 有以下4种切法 : a | yxbx、ay | xbx、ayx | bx、ayxb | x +// 其中第1、3、4种切法符合:x和y的个数,至少在左右两块中的一块里有相同的数量 +// 所以返回3 +// 给定一个字符串str,长度为N +// 你有N-1种划分方法,把str切成左右两半,返回有几种切法满足: +// x和y的个数,至少在左右两块中的一块里有相同的数量 +public class Code01_SplitSameNumberWays { + + public static int splitSameNumberWays(char[] str) { + if (str == null || str.length == 0) { + return 0; + } + int xAll = 0; + int yAll = 0; + for (char c : str) { + xAll += c == 'x' ? 1 : 0; + yAll += c == 'y' ? 1 : 0; + } + int leftX = str[0] == 'x' ? 1 : 0; + int leftY = str[0] == 'y' ? 1 : 0; + int ans = 0; + for (int i = 1; i < str.length; i++) { + if (leftX == leftY || (xAll - leftX) == (yAll - leftY)) { + ans++; + } + leftX += str[i] == 'x' ? 1 : 0; + leftY += str[i] == 'y' ? 1 : 0; + } + return ans; + } + +} diff --git a/公开课/class078/Code02_NearBiggerNoSameNeighbour.java b/公开课/class078/Code02_NearBiggerNoSameNeighbour.java new file mode 100644 index 0000000..af3ac1e --- /dev/null +++ b/公开课/class078/Code02_NearBiggerNoSameNeighbour.java @@ -0,0 +1,67 @@ +package class078; + +// 来自微软 +// 给定一个正数num,要返回一个大于num的数,并且每一位和相邻位的数字不能相等 +// 返回达标的数字中,最小的那个 +// 10^9 +public class Code02_NearBiggerNoSameNeighbour { + + public static int near(int num) { + // "99998" + // [0, 9, 9, 9, 9, 9] + char[] raw = ("0" + String.valueOf(num + 1)).toCharArray(); + process(raw); + return Integer.valueOf(String.valueOf(raw)); + } + + public static void process(char[] raw) { + // 0 1 -> + // 1 2 -> + for (int i = 1; i < raw.length; i++) { + if (raw[i - 1] == raw[i]) { + addOne(raw, i); + for (int j = i + 1; j < raw.length; j++) { + raw[j] = '0'; + } + process(raw); + return; + } + } + } + + // 99..... + // +1 + //100 + public static void addOne(char[] r, int i) { + boolean up = true; + while (up && r[i] == '9') { + r[i--] = '0'; + } + r[i]++; + } + + public static void main(String[] args) { + char[] test = new char[] { '0', '1', '2', '3' }; + + System.out.println(Integer.valueOf(String.valueOf(test))); + + int num1 = 55; + System.out.println(near(num1)); + + int num2 = 1765; + System.out.println(near(num2)); + + int num3 = 98; + System.out.println(near(num3)); + + int num4 = 44432; + System.out.println(near(num4)); + + int num5 = 3298; + System.out.println(near(num5)); + + int num6 = 9999998; + System.out.println(near(num6)); + } + +} diff --git a/公开课/class078/Code03_MaxTeamNumber.java b/公开课/class078/Code03_MaxTeamNumber.java new file mode 100644 index 0000000..970be2f --- /dev/null +++ b/公开课/class078/Code03_MaxTeamNumber.java @@ -0,0 +1,129 @@ +package class078; + +import java.util.Arrays; + +// 来自微软 +// 给定一个数组arr,一个正数num,一个正数k +// 可以把arr中的某些数字拿出来组成一组,要求该组中的最大值减去最小值<=num +// 且该组数字的个数一定要正好等于k +// 每个数字只能选择进某一组,不能进多个组 +// 返回arr中最多有多少组 +public class Code03_MaxTeamNumber { + + public static int maxSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int[] dp = new int[n]; + dp[0] = arr[0]; + int ans = dp[0]; + for (int i = 1; i < n; i++) { + int p1 = arr[i]; + int p2 = arr[i] + dp[i-1]; + dp[i] = Math.max(p1, p2); + ans = Math.max(ans, dp[i]); + } + return ans; + } + + + + public static int maxSum2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int lastStepAns = arr[0]; + int ans = lastStepAns; + for (int i = 1; i < n; i++) { + lastStepAns = Math.max(arr[i], arr[i] + lastStepAns); + ans = Math.max(ans, lastStepAns); + } + return ans; + } + + + + // 对数器方法 + public static int maxTeams1(int[] arr, int num, int k) { + Arrays.sort(arr); + return process1(arr, 0, new int[arr.length], 0, num, k); + } + + public static int process1(int[] arr, int index, int[] path, int size, int num, int k) { + if (index == arr.length) { + if (size % k != 0) { + return 0; + } else { + for (int start = 0; start < size; start += k) { + if (path[start + k - 1] - path[start] > num) { + return 0; + } + } + return size / k; + } + } else { + int p1 = process1(arr, index + 1, path, size, num, k); + path[size] = arr[index]; + int p2 = process1(arr, index + 1, path, size + 1, num, k); + return Math.max(p1, p2); + } + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int maxTeams2(int[] arr, int num, int k) { + int n = arr.length; + if (k > n) { + return 0; + } + Arrays.sort(arr); + int[] dp = new int[n]; + dp[k - 1] = arr[k - 1] - arr[0] <= num ? 1 : 0; + for (int i = k; i < n; i++) { + int p1 = dp[i - 1]; + int p2 = (arr[i] - arr[i - k + 1] <= num ? 1 : 0) + dp[i - k]; + dp[i] = Math.max(p1, p2); + } + return dp[n - 1]; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 18; + int v = 50; + int testTimes = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int num = (int) (Math.random() * v) + 1; + int k = (int) (Math.random() * len) + 1; + int ans1 = maxTeams1(arr, num, k); + int ans2 = maxTeams2(arr, num, k); + if (ans1 != ans2) { + for (int number : arr) { + System.out.print(number + " "); + } + System.out.println(); + System.out.println("num : " + num); + System.out.println("k : " + k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class078/Code04_MinimumNumberOfDaysToEatNOranges.java b/公开课/class078/Code04_MinimumNumberOfDaysToEatNOranges.java new file mode 100644 index 0000000..5c6733a --- /dev/null +++ b/公开课/class078/Code04_MinimumNumberOfDaysToEatNOranges.java @@ -0,0 +1,59 @@ +package class078; + +import java.util.HashMap; + +// 来自学员看到的腾讯面试 +// 测试链接 : https://leetcode.com/problems/minimum-number-of-days-to-eat-n-oranges/ +public class Code04_MinimumNumberOfDaysToEatNOranges { + + public static int zuo(int n) { + // 17 -> 5 + HashMap cache = new HashMap<>(); + return minDay(n, cache); + } + + public static int minDay(int n, HashMap cache) { + if (cache.containsKey(n)) { + return cache.get(n); + } + int ans = 0; + if (n == 0) { + ans = 0; + } else if (n == 1) { + ans = 1; + } else { + int p1 = n % 2 + 1 + minDay(n / 2, cache); + int p2 = n % 3 + 1 + minDay(n / 3, cache); + ans = Math.min(p1, p2); + } + cache.put(n, ans); + return ans; + } + + // 所有的答案都填在这个表里 + // 这个表对所有的过程共用 + public static HashMap dp = new HashMap<>(); + + public static int minDays(int n) { + if (n <= 1) { + return n; + } + if (dp.containsKey(n)) { + return dp.get(n); + } + // 1) 吃掉一个橘子 + // 2) 如果n能被2整除,吃掉一半的橘子,剩下一半 + // 3) 如果n能被3正数,吃掉三分之二的橘子,剩下三分之一 + // 因为方法2)和3),是按比例吃橘子,所以必然会非常快 + // 所以,决策如下: + // 可能性1:为了使用2)方法,先把橘子吃成2的整数倍,然后直接干掉一半,剩下的n/2调用递归 + // 即,n % 2 + 1 + minDays(n/2) + // 可能性2:为了使用3)方法,先把橘子吃成3的整数倍,然后直接干掉三分之二,剩下的n/3调用递归 + // 即,n % 3 + 1 + minDays(n/3) + // 至于方法1),完全是为了这两种可能性服务的,因为能按比例吃,肯定比一个一个吃快(显而易见的贪心) + int ans = Math.min(n % 2 + 1 + minDays(n / 2), n % 3 + 1 + minDays(n / 3)); + dp.put(n, ans); + return ans; + } + +} diff --git a/公开课/class078/Code05_MaxKLenSequence.java b/公开课/class078/Code05_MaxKLenSequence.java new file mode 100644 index 0000000..56e1fad --- /dev/null +++ b/公开课/class078/Code05_MaxKLenSequence.java @@ -0,0 +1,92 @@ +package class078; + +import java.util.TreeSet; + +// 来自腾讯 +// 给定一个字符串str,和一个正数k +// 返回长度为k的所有子序列中,字典序最大的子序列 +public class Code05_MaxKLenSequence { + + public static String maxString(String s, int k) { + if (k <= 0 || s.length() < k) { + return ""; + } + char[] str = s.toCharArray(); + int n = str.length; + // 栈,栈大小 size 栈顶stack[size-1] + char[] stack = new char[n]; + int size = 0; + for (int i = 0; i < n; i++) { + // 当前字符 str[i] + while (size > 0 && stack[size - 1] < str[i] && size + n - i > k) { + size--; + } + if (size + n - i == k) { + return String.valueOf(stack, 0, size) + s.substring(i); + } + stack[size++] = str[i]; + } + return String.valueOf(stack, 0, k); + } + + // 为了测试 + // 纯暴力,我把str,所有的、长度为k的,子序列全生成了!找出一个最大的! + public static String test(String str, int k) { + if (k <= 0 || str.length() < k) { + return ""; + } + TreeSet ans = new TreeSet<>(); + process(0, 0, str.toCharArray(), new char[k], ans); + return ans.last(); + } + + // 为了测试 + public static void process(int si, int pi, char[] str, char[] path, TreeSet ans) { + if (si == str.length) { + if (pi == path.length) { + ans.add(String.valueOf(path)); + } + } else { + process(si + 1, pi, str, path, ans); + if (pi < path.length) { + path[pi] = str[si]; + process(si + 1, pi + 1, str, path, ans); + } + } + } + + // 为了测试 + public static String randomString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + System.out.println(maxString("ccfaa", 3)); + + int n = 12; + int r = 5; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * (n + 1)); + String str = randomString(len, r); + int k = (int) (Math.random() * (str.length() + 1)); + String ans1 = maxString(str, k); + String ans2 = test(str, k); + if (!ans1.equals(ans2)) { + System.out.println("出错了!"); + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class078/Code06_StringCheck.java b/公开课/class078/Code06_StringCheck.java new file mode 100644 index 0000000..aad2291 --- /dev/null +++ b/公开课/class078/Code06_StringCheck.java @@ -0,0 +1,65 @@ +package class078; + +import java.util.Arrays; + +// 来自字节飞书团队 +// 小歪每次会给你两个字符串: +// 笔记s1和关键词s2,请你写一个函数 +// 判断s2的排列之一是否是s1的子串 +// 如果是,返回true +// 否则,返回false +public class Code06_StringCheck { + + public static boolean check1(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + Arrays.sort(str2); + s2 = String.valueOf(str2); + for (int L = 0; L < s1.length(); L++) { + for (int R = L; R < s1.length(); R++) { + char[] cur = s1.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(s2)) { + return true; + } + } + } + return false; + } + + public static boolean check2(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + int[] count = new int[256]; + for (int i = 0; i < str2.length; i++) { + count[str2[i]]++; + } + int M = str2.length; + char[] st1 = s1.toCharArray(); + int inValidTimes = 0; + int R = 0; + for (; R < M; R++) { + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + } + for (; R < st1.length; R++) { + if (inValidTimes == 0) { + return true; + } + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + if (count[st1[R - M]]++ < 0) { + inValidTimes--; + } + } + return inValidTimes == 0; + } + +} diff --git a/公开课/class078/Code07_ShortestSubarrayWithSumAtLeastK.java b/公开课/class078/Code07_ShortestSubarrayWithSumAtLeastK.java new file mode 100644 index 0000000..5a1c651 --- /dev/null +++ b/公开课/class078/Code07_ShortestSubarrayWithSumAtLeastK.java @@ -0,0 +1,74 @@ +package class078; + +// 来自字节跳动 +// 给定一个数组arr,其中的值有可能正、负、0 +// 给定一个正数k +// 返回累加和>=k的所有子数组中,最短的子数组长度 +// 本题测试链接 : https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/ +public class Code07_ShortestSubarrayWithSumAtLeastK { + + public static int shortestSubarray1(int[] arr, int k) { + if (arr == null || arr.length < 1) { + return -1; + } + int n = arr.length + 1; + long[] sum = new long[n]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i - 1]; + } + int[] stack = new int[n]; + int size = 1; + int ans = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + int mostRight = mostRight(sum, stack, size, sum[i] - k); + ans = Math.min(ans, mostRight == -1 ? Integer.MAX_VALUE : (i - mostRight)); + while (size > 0 && sum[stack[size - 1]] >= sum[i]) { + size--; + } + stack[size++] = i; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int mostRight(long[] sum, int[] stack, int size, long aim) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sum[stack[m]] <= aim) { + ans = stack[m]; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int shortestSubarray2(int[] arr, int K) { + int N = arr.length; + long[] sum = new long[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + int ans = Integer.MAX_VALUE; + int[] dq = new int[N + 1]; + int l = 0; + int r = 0; + for (int i = 0; i < N + 1; i++) { + // 头部开始,符合条件的,从头部弹出! + while (l != r && sum[i] - sum[dq[l]] >= K) { + ans = Math.min(ans, i - dq[l++]); + } + // 尾部开始,前缀和比当前的前缀和大于等于的,从尾部弹出! + while (l != r && sum[dq[r - 1]] >= sum[i]) { + r--; + } + dq[r++] = i; + } + return ans != Integer.MAX_VALUE ? ans : -1; + } + +} diff --git a/公开课/class079/Code01_StringCheck.java b/公开课/class079/Code01_StringCheck.java new file mode 100644 index 0000000..5833669 --- /dev/null +++ b/公开课/class079/Code01_StringCheck.java @@ -0,0 +1,65 @@ +package class079; + +import java.util.Arrays; + +// 来自字节飞书团队 +// 小歪每次会给你两个字符串: +// 笔记s1和关键词s2,请你写一个函数 +// 判断s2的排列之一是否是s1的子串 +// 如果是,返回true +// 否则,返回false +public class Code01_StringCheck { + + public static boolean check1(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + Arrays.sort(str2); + s2 = String.valueOf(str2); + for (int L = 0; L < s1.length(); L++) { + for (int R = L; R < s1.length(); R++) { + char[] cur = s1.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(s2)) { + return true; + } + } + } + return false; + } + + public static boolean check2(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + int[] count = new int[256]; + for (int i = 0; i < str2.length; i++) { + count[str2[i]]++; + } + int M = str2.length; + char[] st1 = s1.toCharArray(); + int inValidTimes = 0; + int R = 0; + for (; R < M; R++) { + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + } + for (; R < st1.length; R++) { + if (inValidTimes == 0) { + return true; + } + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + if (count[st1[R - M]]++ < 0) { + inValidTimes--; + } + } + return inValidTimes == 0; + } + +} diff --git a/公开课/class079/Code02_NumberOfDivisibleByM.java b/公开课/class079/Code02_NumberOfDivisibleByM.java new file mode 100644 index 0000000..f454bc3 --- /dev/null +++ b/公开课/class079/Code02_NumberOfDivisibleByM.java @@ -0,0 +1,69 @@ +package class079; + +// 来自微软 +// 给定一个数组arr,给定一个正数M +// 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对 +// 返回arr中M整除对的总数量 +public class Code02_NumberOfDivisibleByM { + + public static int num1(int[] arr, int m) { + int n = arr.length; + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (Math.abs(arr[i] + arr[j]) % m == 0) { + ans++; + } + } + } + return ans; + } + + public static int num2(int[] arr, int m) { + int n = arr.length; + int[] cnts = new int[m]; + int ans = 0; + for (int i = n - 1; i >= 0; i--) { + int cur = (arr[i] % m + m) % m; + ans += cnts[(m - cur) % m]; + cnts[cur]++; + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + public static void main(String[] args) { + int len = 50; + int value = 50; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * value) + 1; + int ans1 = num1(arr, m); + int ans2 = num2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m = " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class079/Code03_MinWaitingTime.java b/公开课/class079/Code03_MinWaitingTime.java new file mode 100644 index 0000000..85d9923 --- /dev/null +++ b/公开课/class079/Code03_MinWaitingTime.java @@ -0,0 +1,95 @@ +package class079; + +import java.util.PriorityQueue; + +// 来自谷歌 +// 给定一个数组arr,长度为n +// 表示n个服务员,每个人服务一个人的时间 +// 给定一个正数m,表示有m个人等位 +// 如果你是刚来的人,请问你需要等多久? +// 假设:m远远大于n,比如n<=1000, m <= 10的9次方,该怎么做? +public class Code03_MinWaitingTime { + + public static int minWaitingTime1(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] - b[0])); + int n = arr.length; + for (int i = 0; i < n; i++) { + heap.add(new int[] { 0, arr[i] }); + } + for (int i = 0; i < m; i++) { + int[] cur = heap.poll(); + cur[0] += cur[1]; + heap.add(cur); + } + return heap.peek()[0]; + } + + public static int minWaitingTime2(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + int best = Integer.MAX_VALUE; + for (int num : arr) { + best = Math.min(best, num); + } + int left = 0; + int right = best * m; + int mid = 0; + int near = 0; + while (left <= right) { + mid = (left + right) / 2; + int cover = 0; + for (int num : arr) { + cover += (mid / num) + 1; + } + if (cover >= m + 1) { + near = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 30; + int mMax = 3000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * mMax); + int ans1 = minWaitingTime1(arr, m); + int ans2 = minWaitingTime2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m : " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class079/Code04_LongestUncontinuousSet.java b/公开课/class079/Code04_LongestUncontinuousSet.java new file mode 100644 index 0000000..9bca43f --- /dev/null +++ b/公开课/class079/Code04_LongestUncontinuousSet.java @@ -0,0 +1,39 @@ +package class079; + +import java.util.Arrays; + +// 来自美团 +// 给定一个数组arr,你可以随意挑选其中的数字 +// 但是你挑选的数中,任何两个数a和b,不能让Math.abs(a - b) <= 1 +// 返回你最多能挑选几个数 +public class Code04_LongestUncontinuousSet { + + public static int longestUncontinuous(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int n = arr.length; + int size = 1; + for (int i = 1; i < n; i++) { + if (arr[i] != arr[size - 1]) { + arr[size++] = arr[i]; + } + } + int[] dp = new int[size]; + dp[0] = 1; + int ans = 1; + for (int i = 1; i < size; i++) { + dp[i] = 1; + if (arr[i] - arr[i - 1] > 1) { + dp[i] = 1 + dp[i - 1]; + } + if (i - 2 >= 0 && arr[i] - arr[i - 2] > 1) { + dp[i] = Math.max(dp[i], 1 + dp[i - 2]); + } + ans = Math.max(ans, dp[i]); + } + return ans; + } + +} diff --git a/公开课/class079/Code05_CutDouFu.java b/公开课/class079/Code05_CutDouFu.java new file mode 100644 index 0000000..eaa1c52 --- /dev/null +++ b/公开课/class079/Code05_CutDouFu.java @@ -0,0 +1,52 @@ +package class079; + +import java.util.Arrays; + +// 来自美团 +// 有一块10000 * 10000 * 10000的立方体豆腐 +// 豆腐的前左下角放在(0,0,0)点,豆腐的后右上角放在(10000,10000,10000)点 +// 下面给出切法的数据结构 +// [a,b] +// a = 1,表示x = b处,一把无穷大的刀平行于yz面贯穿豆腐切过去 +// a = 2,表示y = b处,一把无穷大的刀平行于xz面贯穿豆腐切过去 +// a = 3,表示z = b处,一把无穷大的刀平行于xy面贯穿豆腐切过去 +// a = 1 or 2 or 3,0<=b<=10000 +// 给定一个n*2的二维数组,表示切了n刀 +// 返回豆腐中最大的一块体积是多少 +public class Code05_CutDouFu { + + public static long maxCut(int[][] cuts) { + if (cuts == null || cuts.length == 0) { + return 10000L * 10000L * 10000L; + } + Arrays.sort(cuts, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] - b[1])); + int n = cuts.length; + int i = 0; + int xMaxDiff = 0; + int pre = 0; + while (i < n && cuts[i][0] == 1) { + xMaxDiff = Math.max(xMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + xMaxDiff = Math.max(xMaxDiff, 10000 - pre); + int yMaxDiff = 0; + pre = 0; + while (i < n && cuts[i][0] == 2) { + yMaxDiff = Math.max(yMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + yMaxDiff = Math.max(yMaxDiff, 10000 - pre); + int zMaxDiff = 0; + pre = 0; + while (i < n && cuts[i][0] == 3) { + zMaxDiff = Math.max(zMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + zMaxDiff = Math.max(zMaxDiff, 10000 - pre); + return (long) xMaxDiff * (long) yMaxDiff * (long) zMaxDiff; + } + +} diff --git a/公开课/class080/Code01_FinancialProduct.java b/公开课/class080/Code01_FinancialProduct.java new file mode 100644 index 0000000..eab80db --- /dev/null +++ b/公开课/class080/Code01_FinancialProduct.java @@ -0,0 +1,51 @@ +package class080; + +import java.util.Arrays; + +// 来自银联编程比赛,前500名获得内推资格 +// 某公司计划推出一批投资项目。 product[i] = price 表示第 i 个理财项目的投资金额 price 。 +// 客户在按需投资时,需要遵循以下规则: +// 客户在首次对项目 product[i] 投资时,需要投入金额 price +// 对已完成首次投资的项目 product[i] 可继续追加投入, +// 但追加投入的金额需小于上一次对该项目的投入(追加投入为大于 0 的整数) +// 为控制市场稳定,每人交易次数不得大于 limit。(首次投资和追加投入均记作 1 次交易) +// 若对所有理财项目中最多进行 limit 次交易,使得投入金额总和最大,请返回这个最大值的总和。 +// 测试链接 : https://leetcode-cn.com/contest/cnunionpay-2022spring/problems/I4mOGz/ +public class Code01_FinancialProduct { + + public static long mod = 1000000007L; + + public int maxInvestment(int[] arr, int limit) { + Arrays.sort(arr); + int n = arr.length; + long ans = 0; + int r = n - 1; + int l = r; + while (limit > 0 && r != -1) { + while (l >= 0 && arr[l] == arr[r]) { + l--; + } + int big = arr[r]; + int small = l == -1 ? 0 : arr[l]; + int teams = n - l - 1; + int all = (big - small) * teams; + if (limit >= all) { + ans += get(big, small + 1, teams); + ans %= mod; + limit -= all; + } else { + int a = limit / teams; + ans += get(big, big - a + 1, teams) + (long) (big - a) * (long) (limit - a * teams); + ans %= mod; + limit = 0; + } + r = l; + } + return (int) (ans % mod); + } + + public static long get(long up, long down, long num) { + return num * ((up + down) * (up - down + 1) / 2); + } + +} diff --git a/公开课/class080/Code02_EatFish.java b/公开课/class080/Code02_EatFish.java new file mode 100644 index 0000000..f49d5c3 --- /dev/null +++ b/公开课/class080/Code02_EatFish.java @@ -0,0 +1,106 @@ +package class080; + +// 来自bilibili +// 现在有N条鱼,每条鱼的体积为Ai,从左到右排列,数组arr给出 +// 每一轮,左边的大鱼一定会吃掉右边比自己小的第一条鱼, +// 并且每条鱼吃比自己小的鱼的事件是同时发生的。 +// 返回多少轮之后,鱼的数量会稳定 +// 注意:6 6 3 3 +// 第一轮过后 : +// 对于两个6来说,右边比自己小的第一条鱼都是第1个3,所以只有这个3被吃掉, +// 数组变成 : 6 6 3(第2个3) +// 第二轮过后 : 6 6 +// 返回2 +public class Code02_EatFish { + + public static int minTurns1(int[] arr) { + int ans = 0; + for (;; ans++) { + int[] rest = eatRest(arr); + if (arr.length == rest.length) { + break; + } + arr = rest; + } + return ans; + } + + public static int[] eatRest(int[] arr) { + if (arr.length == 0) { + return new int[0]; + } + int n = arr.length; + boolean[] delete = new boolean[n]; + int len = n; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (arr[i] > arr[j]) { + if (!delete[j]) { + delete[j] = true; + len--; + } + break; + } + } + } + int[] rest = new int[len]; + for (int i = 0, j = 0; i < n; i++) { + if (!delete[i]) { + rest[j++] = arr[i]; + } + } + return rest; + } + + public static int minTurns2(int[] arr) { + int n = arr.length; + int[][] stack = new int[n][2]; + int stackSize = 0; + int ans = 0; + for (int i = n - 1; i >= 0; i--) { + int curAns = 0; + while (stackSize > 0 && stack[stackSize - 1][0] < arr[i]) { + curAns = Math.max(curAns + 1, stack[--stackSize][1]); + } + stack[stackSize][0] = arr[i]; + stack[stackSize++][1] = curAns; + ans = Math.max(ans, curAns); + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = minTurns1(arr); + int ans2 = minTurns2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class080/Code03_BuyGoodsHaveDiscount.java b/公开课/class080/Code03_BuyGoodsHaveDiscount.java new file mode 100644 index 0000000..359288f --- /dev/null +++ b/公开课/class080/Code03_BuyGoodsHaveDiscount.java @@ -0,0 +1,89 @@ +package class080; + +// 来自字节内部训练营 +// 某公司游戏平台的夏季特惠开始了,你决定入手一些游戏。现在你一共有X元的预算。 +// 该平台上所有的 n 个游戏均有折扣,标号为 i 的游戏的原价a_i元,现价只要b_i元 +// 也就是说该游戏可以优惠 a_i - b_i,并且你购买该游戏能获得快乐值为 w_i +// 由于优惠的存在,你可能做出一些冲动消费导致最终买游戏的总费用超过预算, +// 只要满足 : 获得的总优惠金额不低于超过预算的总金额 +// 那在心理上就不会觉得吃亏。 +// 现在你希望在心理上不觉得吃亏的前提下,获得尽可能多的快乐值。 +// 测试链接 : https://leetcode-cn.com/problems/tJau2o/ +// 提交以下的code,将主类名字改成"Main" +// 可以直接通过 +import java.util.Scanner; + +public class Code03_BuyGoodsHaveDiscount { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int money = sc.nextInt(); + int[] costs = new int[n]; + long[] values = new long[n]; + int size = 0; + long ans = 0; + for (int i = 0; i < n; i++) { + // 打折前 + int pre = sc.nextInt(); + // 打折后 + int pos = sc.nextInt(); + // 满足度 + int happy = sc.nextInt(); + // 节省的钱(save) = 打折前(pre) - 打折后(pos) + int save = pre - pos; + // 带来的好处(well) = 节省的钱 - 打折后(pos) + int well = save - pos; + // 比如,一件"一定要买的商品": + // 预算 = 100,商品原价 = 10,打折后 = 3 + // 那么好处 = (10 - 3) - 3 = 4 + // 所以,这件商品把预算增加到了104,一定要买 + // 接下来,比如一件"需要考虑的商品",预算 = 104,商品原价 = 10,打折后 = 8 + // 那么好处 = (10 - 8) - 8 = -6 + // 这件商品,就花掉6元! + // 也就是说,以后花的不是打折后的值,是"坏处" + int cost = -well; + if (well >= 0) { + money += well; + ans += happy; + } else { + costs[size] = cost; + values[size++] = happy; + } + + } + long[][] dp = new long[size + 1][money + 1]; + for (int a = 0; a <= size; a++) { + for (int b = 0; b <= money; b++) { + dp[a][b] = -2; + } + } + ans += process(costs, values, size, 0, money, dp); + System.out.println(ans); + } + sc.close(); + } + + public static long process(int[] costs, long[] values, int size, int i, int money, long[][] dp) { + if (money < 0) { + return -1; + } + if (i == size) { + return 0; + } + if (dp[i][money] != -2) { + return dp[i][money]; + } + long p1 = process(costs, values, size, i + 1, money, dp); + long p2 = -1; + long next = process(costs, values, size, i + 1, money - costs[i], dp); + if (next != -1) { + p2 = values[i] + next; + } + long ans = Math.max(p1, p2); + dp[i][money] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/公开课/class080/Code04_JumpToTargets.java b/公开课/class080/Code04_JumpToTargets.java new file mode 100644 index 0000000..3051e0d --- /dev/null +++ b/公开课/class080/Code04_JumpToTargets.java @@ -0,0 +1,56 @@ +package class080; + +// 来自字节 +// 一开始在0位置,每一次都可以向左或者向右跳 +// 第i次能向左或者向右跳严格的i步 +// 请问从0到x位置,至少跳几次可以到达 +// 字节考的问题其实就是这个问题 +// 找到了测试链接 : https://www.luogu.com.cn/problem/CF11B +// 提交以下所有代码,把主类名改成"Main",可以直接通过 +import java.util.Scanner; + +public class Code04_JumpToTargets { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int x = Math.abs(sc.nextInt()); + System.out.println(minTimes(x)); + } + sc.close(); + } + + public static long minTimes(long x) { + if (x == 0) { + return 0; + } + long l = 0; + long r = x; + long m = 0; + long near = 0; + while (l <= r) { + m = (l + r) / 2; + if (sum(m) >= x) { + near = m; + r = m - 1; + } else { + l = m + 1; + } + } + if (sum(near) == x) { + return near; + } + if (((sum(near) - x) & 1) == 1) { + near++; + } + if (((sum(near) - x) & 1) == 1) { + near++; + } + return near; + } + + public static long sum(long n) { + return (n * (n + 1)) / 2; + } + +} diff --git a/公开课/class081/Code01_ArrangeJob.java b/公开课/class081/Code01_ArrangeJob.java new file mode 100644 index 0000000..03130d7 --- /dev/null +++ b/公开课/class081/Code01_ArrangeJob.java @@ -0,0 +1,62 @@ +package class081; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自华为面试 +// 给定一个n*2的二维数组,表示有n个任务 +// 一个信息是任务能够开始做的时间,另一个信息是任务的结束期限,后者一定大于前者,且数值上都是正数 +// 你作为单线程的人,不能并行处理任务,但是每个任务都只需要一个单位时间完成 +// 你需要将所有任务的执行时间,位于开始做的时间和最后期限之间 +// 返回你能否做到这一点 +public class Code01_ArrangeJob { + + public static class TimePoint { + // 时间 + public int time; + public int end; + // add = true time 任务的添加时间 + // add = false time 任务的结束时间 + public boolean add; + + public TimePoint(int t, int e, boolean a) { + time = t; + end = e; + add = a; + } + + } + + public static boolean canDo(int[][] jobs) { + if (jobs == null || jobs.length < 2) { + return true; + } + int n = jobs.length; + TimePoint[] arr = new TimePoint[n << 1]; + for (int i = 0; i < n; i++) { + arr[i] = new TimePoint(jobs[i][0], jobs[i][1], true); + arr[i + n] = new TimePoint(jobs[i][1], jobs[i][1], false); + } + Arrays.sort(arr, (a, b) -> a.time - b.time); + PriorityQueue heap = new PriorityQueue<>(); + for (int i = 0, lastTime = arr[0].time; i < arr.length; i++) { + if (arr[i].add) { + heap.add(arr[i].end); + } else { // 检查时间 + int curTime = arr[i].time; + for (int j = lastTime; j < curTime; j++) { + if (heap.isEmpty()) { + break; + } + heap.poll(); + } + if (heap.peek() <= curTime) { + return false; + } + lastTime = curTime; + } + } + return true; + } + +} diff --git a/公开课/class081/Code02_MinTowNumberSumABS.java b/公开课/class081/Code02_MinTowNumberSumABS.java new file mode 100644 index 0000000..b07e161 --- /dev/null +++ b/公开课/class081/Code02_MinTowNumberSumABS.java @@ -0,0 +1,159 @@ +package class081; + +import java.util.Arrays; + +// 来自华为面试 +// 给定一个数组arr,可能有正、有负、有0,无序 +// 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小 +// 返回可能的最小的值 +public class Code02_MinTowNumberSumABS { + + public static int minSumABS1(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + ans = Math.min(ans, Math.abs(arr[i] + arr[j])); + } + } + return ans; + } + + public static int minSumABS2(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + Arrays.sort(arr); + int n = arr.length; + int split = -1; + for (int i = 0; i < n; i++) { + if (arr[i] >= 0) { + split = i; + break; + } + } + if (split == 0) { + return arr[0] + arr[1]; + } + if (split == -1) { + return -arr[n - 2] - arr[n - 1]; + } + int ans = Integer.MAX_VALUE; + if (split + 1 < n) { + ans = arr[split] + arr[split + 1]; + } + if (split - 2 >= 0) { + ans = Math.min(ans, -arr[split - 1] - arr[split - 2]); + } + for (int i = 0; i < split; i++) { + ans = Math.min(ans, Math.abs(arr[i] + near(arr, split, -arr[i]))); + } + return ans; + } + + // arr[start...]是有序的 + // 返回离num最近的数字 + public static int near(int[] arr, int start, int num) { + int l = start; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= num) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + if (ans == -1) { + return arr[start]; + } else { + if (ans == arr.length - 1) { + return arr[arr.length - 1]; + } else { + if (Math.abs(arr[ans] - num) <= Math.abs(arr[ans + 1] - num)) { + return arr[ans]; + } else { + return arr[ans + 1]; + } + } + } + } + + public static int minSumABS3(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + Arrays.sort(arr); + int n = arr.length; + int split = -1; + for (int i = 0; i < n; i++) { + if (arr[i] >= 0) { + split = i; + break; + } + } + if (split == 0) { + return arr[0] + arr[1]; + } + if (split == -1) { + return -arr[n - 2] - arr[n - 1]; + } + int ans = Integer.MAX_VALUE; + if (split + 1 < n) { + ans = arr[split] + arr[split + 1]; + } + if (split - 2 >= 0) { + ans = Math.min(ans, -arr[split - 1] - arr[split - 2]); + } + int r = n - 1; + for (int l = 0; l < split; l++) { + ans = Math.min(ans, Math.abs(arr[l] + arr[r])); + while (r - 1 >= split && Math.abs(arr[l] + arr[r]) >= Math.abs(arr[l] + arr[r - 1])) { + ans = Math.min(ans, Math.abs(arr[l] + arr[--r])); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 500; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = minSumABS1(arr); + int ans2 = minSumABS2(arr); + int ans3 = minSumABS3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class081/Code03_HowManyWaysFromBottomToTop.java b/公开课/class081/Code03_HowManyWaysFromBottomToTop.java new file mode 100644 index 0000000..c585fa3 --- /dev/null +++ b/公开课/class081/Code03_HowManyWaysFromBottomToTop.java @@ -0,0 +1,81 @@ +package class081; + +// 来自理想汽车 +// a -> b,代表a在食物链中被b捕食 +// 给定一个有向无环图,返回 +// 这个图中从最初级动物到最顶级捕食者的食物链有几条 +// 线上测试链接 : https://www.luogu.com.cn/problem/P4017 +// 以下代码都提交,提交时把主类名改成"Main"即可 +// 注意:洛谷测试平台对java提交非常不友好,空间限制可能会卡住,C++的提交就完全不会 +// 所以提交时如果显示失败,就多提交几次,一定是能成功的 +// 这道题本身就是用到拓扑排序,没什么特别的 +// 但是为了提交能通过,逼迫我在空间上做的优化值得好好说一下,可以推广到其他题目 +import java.util.Arrays; +import java.util.Scanner; + +public class Code03_HowManyWaysFromBottomToTop { + + public static int[] in = new int[5001]; + public static boolean[] out = new boolean[5001]; + public static int[] lines = new int[5001]; + public static int[] headEdge = new int[5001]; + public static int[] queue = new int[5001]; + public static int mod = 80112002; + public static int n = 0; + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + Arrays.fill(in, 0); + Arrays.fill(out, false); + Arrays.fill(lines, 0); + Arrays.fill(headEdge, 0); + n = sc.nextInt(); + int m = sc.nextInt(); + int[] preEdge = new int[m + 1]; + int[] edgesTo = new int[m + 1]; + for (int i = 1; i <= m; i++) { + int from = sc.nextInt(); + int to = sc.nextInt(); + edgesTo[i] = to; + preEdge[i] = headEdge[from]; + headEdge[from] = i; + out[from] = true; + in[to]++; + } + System.out.println(howManyWays(preEdge, edgesTo)); + } + sc.close(); + } + + public static int howManyWays(int[] preEdge, int[] edgesTo) { + int ql = 0; + int qr = 0; + for (int i = 1; i <= n; i++) { + if (in[i] == 0) { + queue[qr++] = i; + lines[i] = 1; + } + } + while (ql < qr) { + int cur = queue[ql++]; + int edge = headEdge[cur]; + while (edge != 0) { + int next = edgesTo[edge]; + lines[next] = (lines[next] + lines[cur]) % mod; + if (--in[next] == 0) { + queue[qr++] = next; + } + edge = preEdge[edge]; + } + } + int ans = 0; + for (int i = 1; i <= n; i++) { + if (!out[i]) { + ans = (ans + lines[i]) % mod; + } + } + return ans; + } + +} diff --git a/公开课/class082/Code01_JumpToTargets.java b/公开课/class082/Code01_JumpToTargets.java new file mode 100644 index 0000000..b2a485b --- /dev/null +++ b/公开课/class082/Code01_JumpToTargets.java @@ -0,0 +1,45 @@ +package class082; + +// 来自字节 +// 一开始在0位置,每一次都可以向左或者向右跳 +// 第i次能向左或者向右跳严格的i步 +// 请问从0到x位置,至少跳几次可以到达 +// 字节考的问题其实就是这个问题 +// leetcode测试链接 : https://leetcode.com/problems/reach-a-number/ +public class Code01_JumpToTargets { + + public static int reachNumber(long target) { + if (target == 0) { + return 0; + } + target = Math.abs(target); + long l = 0; + long r = target; + long m = 0; + long near = 0; + while (l <= r) { + m = (l + r) / 2; + if (sum(m) >= target) { + near = m; + r = m - 1; + } else { + l = m + 1; + } + } + if (sum(near) == target) { + return (int)near; + } + if (((sum(near) - target) & 1) == 1) { + near++; + } + if (((sum(near) - target) & 1) == 1) { + near++; + } + return (int)near; + } + + public static long sum(long n) { + return (n * (n + 1)) / 2; + } + +} diff --git a/公开课/class082/Code02_ArrangeMeetingPosCancelPre.java b/公开课/class082/Code02_ArrangeMeetingPosCancelPre.java new file mode 100644 index 0000000..38d212f --- /dev/null +++ b/公开课/class082/Code02_ArrangeMeetingPosCancelPre.java @@ -0,0 +1,210 @@ +package class082; + +import java.util.ArrayList; +import java.util.Arrays; + +// 来自通维数码 +// 每个会议给定开始和结束时间 +// 后面的会议如果跟前面的会议有任何冲突,完全取消冲突的、之前的会议,安排当前的 +// 给定一个会议数组,返回安排的会议列表 +public class Code02_ArrangeMeetingPosCancelPre { + + // 彻底暴力做的! + // 对数器! + public static ArrayList arrange1(int[][] meetings) { + int max = 0; + for (int[] meeting : meetings) { + max = Math.max(max, meeting[1]); + } + boolean[] occupy = new boolean[max + 1]; + ArrayList ans = new ArrayList<>(); + for (int i = meetings.length - 1; i >= 0; i--) { + int[] cur = meetings[i]; + boolean add = true; + for (int j = cur[0]; j < cur[1]; j++) { + if (occupy[j]) { + add = false; + break; + } + } + if (add) { + ans.add(cur); + } + for (int j = cur[0]; j < cur[1]; j++) { + occupy[j] = true; + } + } + return ans; + } + + public static ArrayList arrange2(int[][] meetings) { + int n = meetings.length; + int[] rank = new int[n << 1]; + for (int i = 0; i < meetings.length; i++) { + rank[i] = meetings[i][0]; + rank[i + n] = meetings[i][1] - 1; + } + Arrays.sort(rank); + SegmentTree st = new SegmentTree(n << 1); + ArrayList ans = new ArrayList<>(); + for (int i = meetings.length - 1; i >= 0; i--) { + int[] cur = meetings[i]; + int from = rank(rank, cur[0]); + int to = rank(rank, cur[1] - 1); + if (st.sum(from, to) == 0) { + ans.add(cur); + } + st.add(from, to, 1); + } + return ans; + } + + public static int rank(int[] rank, int num) { + int l = 0; + int r = rank.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (rank[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] sum; + private int[] lazy; + + public SegmentTree(int size) { + n = size + 1; + sum = new int[n << 2]; + lazy = new int[n << 2]; + n--; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void add(int L, int R, int C) { + add(L, R, C, 1, n, 1); + } + + private void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int sum(int L, int R) { + return query(L, R, 1, n, 1); + } + + private int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + // 为了测试 + public static int[][] randomMeeting(int len, int time) { + int[][] meetings = new int[len][2]; + for (int i = 0; i < len; i++) { + int a = (int) (Math.random() * (time + 1)); + int b = (int) (Math.random() * (time + 1)); + if (a == b) { + b++; + } + meetings[i][0] = Math.min(a, b); + meetings[i][1] = Math.max(a, b); + } + return meetings; + } + + // 为了测试 + public static int[][] copyMeetings(int[][] meetings) { + int len = meetings.length; + int[][] ans = new int[len][2]; + for (int i = 0; i < len; i++) { + ans[i][0] = meetings[i][0]; + ans[i][1] = meetings[i][1]; + } + return ans; + } + + // 为了测试 + public static boolean equal(ArrayList arr1, ArrayList arr2) { + if (arr1.size() != arr2.size()) { + return false; + } + for (int i = 0; i < arr1.size(); i++) { + int[] a = arr1.get(i); + int[] b = arr2.get(i); + if (a[0] != b[0] || a[1] != b[1]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int n = 100; + int t = 5000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[][] meetings1 = randomMeeting(len, t); + int[][] meetings2 = copyMeetings(meetings1); + ArrayList ans1 = arrange1(meetings1); + ArrayList ans2 = arrange2(meetings2); + if (!equal(ans1, ans2)) { + System.out.println("出错了!"); + System.out.println(ans1.size()); + System.out.println(ans2.size()); + System.out.println("===="); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class082/Code03_MaxScoreMoveInBoard.java b/公开课/class082/Code03_MaxScoreMoveInBoard.java new file mode 100644 index 0000000..c750516 --- /dev/null +++ b/公开课/class082/Code03_MaxScoreMoveInBoard.java @@ -0,0 +1,71 @@ +package class082; + +// 来自小红书 +// 小红书第一题: +// 薯队长从北向南穿过一片红薯地(南北长M,东西宽N),红薯地被划分为1x1的方格, +// 他可以从北边的任何一个格子出发,到达南边的任何一个格子, +// 但每一步只能走到东南、正南、西南方向的三个格子之一, +// 而且不能跨出红薯地,他可以获得经过的格子上的所有红薯,请问他可以获得最多的红薯个数。 +public class Code03_MaxScoreMoveInBoard { + + public static int maxScore(int[][] map) { + int ans = 0; + for (int col = 0; col < map[0].length; col++) { + ans = Math.max(ans, process(map, 0, col)); + } + return ans; + } + + public static int process(int[][] map, int row, int col) { + if (col < 0 || col == map[0].length) { + return -1; + } + if (row == map.length - 1) { + return map[row][col]; + } + int cur = map[row][col]; + int next1 = process(map, row + 1, col - 1); + int next2 = process(map, row + 1, col); + int next3 = process(map, row + 1, col + 1); + return cur + Math.max(Math.max(next1, next2), next3); + } + + public static int maxScore2(int[][] map) { + int ans = 0; + int n = map.length; + int m = map[0].length; + int[][] dp = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + dp[i][j] = -2; // -2表示,这个格子没算过! + } + } + for (int col = 0; col < map[0].length; col++) { + ans = Math.max(ans, process2(map, 0, col, dp)); + } + return ans; + } + + public static int process2(int[][] map, int row, int col, int[][] dp) { + if (col < 0 || col == map[0].length) { + return -1; + } + if (dp[row][col] != -2) { + return dp[row][col]; + } + // 继续算! + int ans = 0; + if (row == map.length - 1) { + ans = map[row][col]; + } else { + int cur = map[row][col]; + int next1 = process2(map, row + 1, col - 1, dp); + int next2 = process2(map, row + 1, col, dp); + int next3 = process2(map, row + 1, col + 1, dp); + ans = cur + Math.max(Math.max(next1, next2), next3); + } + dp[row][col] = ans; + return ans; + } + +} diff --git a/公开课/class082/Code04_FourNumbersMinusOne.java b/公开课/class082/Code04_FourNumbersMinusOne.java new file mode 100644 index 0000000..b2b2cf0 --- /dev/null +++ b/公开课/class082/Code04_FourNumbersMinusOne.java @@ -0,0 +1,58 @@ +package class082; + +import java.util.Arrays; + +// 来自阿里笔试 +// 牛牛今年上幼儿园了,老师叫他学习减法 +// 老师给了他5个数字,他每次操作可以选择其中的4个数字减1 +// 减一之后的数字不能小于0,因为幼儿园的牛牛还没有接触过负数 +// 现在牛牛想知道,自己最多可以进行多少次这样的操作 +// 扩展问题来自leetcode 2141,掌握了这个题原始问题就非常简单了 +// leetcode测试链接 : https://leetcode.com/problems/maximum-running-time-of-n-computers/ +public class Code04_FourNumbersMinusOne { + + public static long maxRunTime(int n, int[] arr) { + Arrays.sort(arr); + int size = arr.length; + long[] sums = new long[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + long l = 0; + long m = 0; + long r = sums[size - 1] / n; + long ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (ok(arr, sums, m, n)) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static boolean ok(int[] arr, long[] sum, long time, int num) { + int l = 0; + int m = 0; + int r = arr.length - 1; + int left = arr.length; + // >= time 最左 + while (l <= r) { + m = (l + r) / 2; + if (arr[m] >= time) { + left = m; + r = m - 1; + } else { + l = m + 1; + } + } + num -= arr.length - left; + long rest = left == 0 ? 0 : sum[left - 1]; + return time * (long) num <= rest; + } + +} diff --git a/公开课/class082/Code05_MaxSumOnReverseArray.java b/公开课/class082/Code05_MaxSumOnReverseArray.java new file mode 100644 index 0000000..d8b0043 --- /dev/null +++ b/公开课/class082/Code05_MaxSumOnReverseArray.java @@ -0,0 +1,105 @@ +package class082; + +// 来自美团 +// 最大子段和是 +// 一个经典问题,即对于一个数组找出其和最大的子数组。 +// 现在允许你在求解该问题之前翻转这个数組的连续一段 +// 如翻转(1,2,3,4,5,6)的第三个到第五个元素組成的子数组得到的是(1,2,5,4,3,6), +// 则翻转后该数组的最大子段和最大能达到多少? +// 来自字节 +// 几乎一样的题,来自字节笔试第4题 +// 给定两个数組values和numbers, +// values[i]表示i号宝石的单品价值 +// numbers[i]表示i号宝石的数量 +// i号宝石的总价值 = values[i] * numbers[i] +// 如果有一种魔法,可以翻转任何区间L...R的宝石,也就是改变L..R的宝石排列,变成逆序的 +// 求在允许用一次魔法的情况下,任取一段连续区间,能达到的最大价值 +// 这两个问法解法都几乎一样,区别无非是: +// 美团的: 可进行一次翻转情况下,子数组最大累加和 +// 字节的: 可进行一次翻转情况下,子数组最大价值和 +public class Code05_MaxSumOnReverseArray { + + public static int maxSumReverse1(int[] arr) { + int ans = Integer.MIN_VALUE; + for (int L = 0; L < arr.length; L++) { + for (int R = L; R < arr.length; R++) { + reverse(arr, L, R); + ans = Math.max(ans, maxSum(arr)); + reverse(arr, L, R); + } + } + return ans; + } + + public static void reverse(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int maxSum(int[] arr) { + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], arr[i] + pre); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSumReverse2(int[] arr) { + int n = arr.length; + int[] prefix = new int[n]; + prefix[n - 1] = arr[n - 1]; + for (int i = n - 2; i >= 0; i--) { + prefix[i] = arr[i] + Math.max(0, prefix[i + 1]); + } + int ans = prefix[0]; + int suffix = arr[0]; + int maxSuffix = suffix; + for (int i = 1; i < n; i++) { + ans = Math.max(ans, maxSuffix + prefix[i]); + suffix = arr[i] + Math.max(0, suffix); + maxSuffix = Math.max(maxSuffix, suffix); + } + ans = Math.max(ans, maxSuffix); + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSumReverse1(arr); + int ans2 = maxSumReverse2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class083/Code01_MaxOneNumbers.java b/公开课/class083/Code01_MaxOneNumbers.java new file mode 100644 index 0000000..c2de66e --- /dev/null +++ b/公开课/class083/Code01_MaxOneNumbers.java @@ -0,0 +1,114 @@ +package class083; + +// 小红书 +// 3.13 笔试 +// 数组里有0和1,一定要翻转一个区间,翻转:0变1,1变0 +// 请问翻转后可以使得1的个数最多是多少? +public class Code01_MaxOneNumbers { + + // arr中,子数组的最大累加和,返回 + public static int maxSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int[] dp = new int[n]; + dp[0] = arr[0]; + int max = dp[0]; + for (int i = 1; i < n; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1] + arr[i]; + dp[i] = Math.max(p1, p2); + max = Math.max(max, dp[i]); + } + return max; + } + + public static int maxSum2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int pre = arr[0]; // pre -> dp[0]; + int max = pre; + for (int i = 1; i < n; i++) { + pre = Math.max(arr[i], pre + arr[i]); + max = Math.max(max, pre); + } + return max; + } + + + + + public static int maxOneNumbers1(int[] arr) { + int ans = 0; + for (int l = 0; l < arr.length; l++) { + for (int r = l; r < arr.length; r++) { + reverse(arr, l, r); + ans = Math.max(ans, oneNumbers(arr)); + reverse(arr, l, r); + } + } + return ans; + } + + public static void reverse(int[] arr, int l, int r) { + for (int i = l; i <= r; i++) { + arr[i] ^= 1; + } + } + + public static int oneNumbers(int[] arr) { + int ans = 0; + for (int num : arr) { + ans += num; + } + return ans; + } + + public static int maxOneNumbers2(int[] arr) { + int ans = 0; + for (int num : arr) { + ans += num; + } + for (int i = 0; i < arr.length; i++) { + arr[i] = arr[i] == 0 ? 1 : -1; + } + int max = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return ans + max; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * 2); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n); + int ans1 = maxOneNumbers1(arr); + int ans2 = maxOneNumbers2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class083/Code02_PerfectPairNumber.java b/公开课/class083/Code02_PerfectPairNumber.java new file mode 100644 index 0000000..88afae6 --- /dev/null +++ b/公开课/class083/Code02_PerfectPairNumber.java @@ -0,0 +1,52 @@ +package class083; + +// 来自阿里 +// x = { a, b, c, d } +// y = { e, f, g, h } +// x、y两个小数组长度都是4 +// 如果有: a + e = b + f = c + g = d + h +// 那么说x和y是一个完美对 +// 题目给定N个小数组,每个小数组长度都是K +// 返回这N个小数组中,有多少完美对 +// 本题测试链接 : https://www.nowcoder.com/practice/f5a3b5ab02ed4202a8b54dfb76ad035e +// 提交如下代码,把主类名改成Main +// 可以直接通过 +import java.util.HashMap; +import java.util.Scanner; + +public class Code02_PerfectPairNumber { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int m = sc.nextInt(); + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = sc.nextInt(); + } + } + long ans = perfectPairs(matrix); + System.out.println(ans); + } + sc.close(); + } + + public static long perfectPairs(int[][] matrix) { + long ans = 0; + HashMap counts = new HashMap<>(); + for (int[] arr : matrix) { + StringBuilder self = new StringBuilder(); + StringBuilder minus = new StringBuilder(); + for (int i = 1; i < arr.length; i++) { + self.append("_" + (arr[i] - arr[i - 1])); + minus.append("_" + (arr[i - 1] - arr[i])); + } + ans += counts.getOrDefault(minus.toString(), 0); + counts.put(self.toString(), counts.getOrDefault(self.toString(), 0) + 1); + } + return ans; + } + +} \ No newline at end of file diff --git a/公开课/class083/Code03_TopMinSubsquenceSum.java b/公开课/class083/Code03_TopMinSubsquenceSum.java new file mode 100644 index 0000000..98488a6 --- /dev/null +++ b/公开课/class083/Code03_TopMinSubsquenceSum.java @@ -0,0 +1,114 @@ +package class083; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 给定一个数组arr,含有n个数字,都是非负数 +// 给定一个正数k +// 返回所有子序列中,累加和最小的前k个子序列累加和 +// 假设K不大,怎么算最快? +public class Code03_TopMinSubsquenceSum { + + public static int[] topMinSum1(int[] arr, int k) { + ArrayList allAns = new ArrayList<>(); + process(arr, 0, 0, allAns); + allAns.sort((a, b) -> a.compareTo(b)); + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + ans[i] = allAns.get(i); + } + return ans; + } + + public static void process(int[] arr, int index, int sum, ArrayList ans) { + if (index == arr.length) { + ans.add(sum); + } else { + process(arr, index + 1, sum, ans); + process(arr, index + 1, sum + arr[index], ans); + } + } + + public static int[] topMinSum2(int[] arr, int k) { + Arrays.sort(arr); + // (最右的下标,集合的累加和) + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { 0, arr[0] }); + int[] ans = new int[k]; + // ans[0] = 0 + // 0 1 2 k-1 + // k个! + for (int i = 1; i < k; i++) { + int[] cur = heap.poll(); + // (7, 100) + // 左 :8, 100 - arr[7] + arr[8] + // 右 :8, 100 + arr[8] + int last = cur[0]; + int sum = cur[1]; + ans[i] = sum; + if (last + 1 < arr.length) { + heap.add(new int[] { last + 1, sum - arr[last] + arr[last + 1] }); + heap.add(new int[] { last + 1, sum + arr[last + 1] }); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static boolean equals(int[] ans1, int[] ans2) { + if (ans1.length != ans2.length) { + return false; + } + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 40; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int k = (int) (Math.random() * ((1 << len) - 1)) + 1; + int[] ans1 = topMinSum1(arr, k); + int[] ans2 = topMinSum2(arr, k); + if (!equals(ans1, ans2)) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class083/Code04_TopMaxSubsquenceSum.java b/公开课/class083/Code04_TopMaxSubsquenceSum.java new file mode 100644 index 0000000..87bc403 --- /dev/null +++ b/公开课/class083/Code04_TopMaxSubsquenceSum.java @@ -0,0 +1,124 @@ +package class083; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自Amazon +// 给定一个数组arr,含有n个数字,可能有正、有负、有0 +// 给定一个正数k +// 返回所有子序列中,累加和最大的前k个子序列累加和 +// 假设K不大,怎么算最快? +public class Code04_TopMaxSubsquenceSum { + + public static int[] topMaxSum1(int[] arr, int k) { + ArrayList allAns = new ArrayList<>(); + process(arr, 0, 0, allAns); + allAns.sort((a, b) -> a.compareTo(b)); + int[] ans = new int[k]; + for (int i = allAns.size() - 1, j = 0; j < k; i--, j++) { + ans[j] = allAns.get(i); + } + return ans; + } + + public static void process(int[] arr, int index, int sum, ArrayList ans) { + if (index == arr.length) { + ans.add(sum); + } else { + process(arr, index + 1, sum, ans); + process(arr, index + 1, sum + arr[index], ans); + } + } + + public static int[] topMaxSum2(int[] arr, int k) { + int sum = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] >= 0) { + sum += arr[i]; + } else { + arr[i] = -arr[i]; + } + } + int[] ans = topMinSum(arr, k); + for (int i = 0; i < ans.length; i++) { + ans[i] = sum - ans[i]; + } + return ans; + } + + public static int[] topMinSum(int[] arr, int k) { + Arrays.sort(arr); + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { 0, arr[0] }); + int[] ans = new int[k]; + for (int i = 1; i < k; i++) { + int[] cur = heap.poll(); + int last = cur[0]; + int sum = cur[1]; + ans[i] = sum; + if (last + 1 < arr.length) { + heap.add(new int[] { last + 1, sum - arr[last] + arr[last + 1] }); + heap.add(new int[] { last + 1, sum + arr[last + 1] }); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static boolean equals(int[] ans1, int[] ans2) { + if (ans1.length != ans2.length) { + return false; + } + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 40; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int k = (int) (Math.random() * ((1 << len) - 1)) + 1; + int[] ans1 = topMaxSum1(arr, k); + int[] ans2 = topMaxSum2(arr, k); + if (!equals(ans1, ans2)) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class084/Code01_MinDepth.java b/公开课/class084/Code01_MinDepth.java new file mode 100644 index 0000000..c4e5f98 --- /dev/null +++ b/公开课/class084/Code01_MinDepth.java @@ -0,0 +1,116 @@ +package class084; + +// 美团面试题 +// 来自学员反馈的一面题目 +// 求二叉树最小深度 +// 本题测试链接 : https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ +public class Code01_MinDepth { + + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int x) { + val = x; + } + } + + // head为头的二叉树,最小深度返回 + public static int ans = Integer.MAX_VALUE; + + public static int minDepth(TreeNode head) { + if(head == null) { + return 0; + } + ans = Integer.MAX_VALUE; + zuo(head, 1); + return ans; + } + + public static void zuo(TreeNode x, int level) { + if (x.left == null && x.right == null) { + ans = Math.min(ans, level); + } else { + if (x.left != null) { + zuo(x.left, level + 1); + } + if (x.right != null) { + zuo(x.right, level + 1); + } + } + } + + // 下面的方法是一般解 + public static int minDepth1(TreeNode head) { + if (head == null) { + return 0; + } + return p(head); + } + + // 返回x为头的树,最小深度是多少 + public static int p(TreeNode x) { + if (x.left == null && x.right == null) { + return 1; + } + // 左右子树起码有一个不为空 + int leftH = Integer.MAX_VALUE; + if (x.left != null) { + leftH = p(x.left); + } + int rightH = Integer.MAX_VALUE; + if (x.right != null) { + rightH = p(x.right); + } + return 1 + Math.min(leftH, rightH); + } + + // 下面的方法是morris遍历的解 + public static int minDepth2(TreeNode head) { + if (head == null) { + return 0; + } + TreeNode cur = head; + TreeNode mostRight = null; + int curLevel = 0; + int minHeight = Integer.MAX_VALUE; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + int rightBoardSize = 1; + while (mostRight.right != null && mostRight.right != cur) { + rightBoardSize++; + mostRight = mostRight.right; + } + if (mostRight.right == null) { // 第一次到达 + curLevel++; + mostRight.right = cur; + cur = cur.left; + continue; + } else { // 第二次到达 + if (mostRight.left == null) { + minHeight = Math.min(minHeight, curLevel); + } + curLevel -= rightBoardSize; + mostRight.right = null; + } + } else { // 只有一次到达 + curLevel++; + } + cur = cur.right; + } + int finalRight = 1; + cur = head; + while (cur.right != null) { + finalRight++; + cur = cur.right; + } + if (cur.left == null && cur.right == null) { + minHeight = Math.min(minHeight, finalRight); + } + return minHeight; + } + +} diff --git a/公开课/class084/Code02_DiameterOfBinarytree.java b/公开课/class084/Code02_DiameterOfBinarytree.java new file mode 100644 index 0000000..495d414 --- /dev/null +++ b/公开课/class084/Code02_DiameterOfBinarytree.java @@ -0,0 +1,70 @@ +package class084; + +// 来自学员反馈 +// 最近字节的面试题 +// 二叉树直径 +// 测试链接 : https://leetcode.com/problems/diameter-of-binary-tree/ +public class Code02_DiameterOfBinarytree { + + // 不要提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public static int diameterOfBinaryTree(TreeNode root) { + return zuo(root).maxDistance; + } + + public static class Info { + public int maxDistance; + public int height; + + public Info(int m, int h) { + maxDistance = m; + height = h; + } + } + + public static Info zuo(TreeNode x) { + if (x == null) { + return new Info(0, 0); + } + Info leftInfo = zuo(x.left); + Info rightInfo = zuo(x.right); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + int p1 = Math.max(leftInfo.maxDistance, rightInfo.maxDistance); + int p2 = leftInfo.height + rightInfo.height; + int maxDistance = Math.max(p1, p2); + return new Info(maxDistance, height); + } + +// public static int diameterOfBinaryTree(TreeNode root) { +// return process(root).maxDistance; +// } +// +// public static class Info { +// public int maxDistance; +// public int height; +// +// public Info(int m, int h) { +// maxDistance = m; +// height = h; +// } +// } +// +// public static Info process(TreeNode x) { +// if (x == null) { +// return new Info(0, 0); +// } +// Info leftInfo = process(x.left); +// Info rightInfo = process(x.right); +// int maxDistance = Math.max( +// Math.max(leftInfo.maxDistance, rightInfo.maxDistance), +// leftInfo.height + rightInfo.height); +// int height = Math.max(leftInfo.height, rightInfo.height) + 1; +// return new Info(maxDistance, height); +// } + +} diff --git a/公开课/class084/Code03_AllJobFinishTime.java b/公开课/class084/Code03_AllJobFinishTime.java new file mode 100644 index 0000000..75b292d --- /dev/null +++ b/公开课/class084/Code03_AllJobFinishTime.java @@ -0,0 +1,51 @@ +package class084; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +// 来自美团 +// 3.26笔试 +// 给定一个正数n, 表示有0~n-1号任务 +// 给定一个长度为n的数组time,time[i]表示i号任务做完的时间 +// 给定一个二维数组matrix +// matrix[j] = {a, b} 代表:a任务想要开始,依赖b任务的完成 +// 只要能并行的任务都可以并行,但是任何任务只有依赖的任务完成,才能开始 +// 返回一个长度为n的数组ans,表示每个任务完成的时间 +// 输入可以保证没有循环依赖 +public class Code03_AllJobFinishTime { + + // n : 0 ~ n-1 + // time : time[i]是i号任务自己完成需要的时间 + // matrix : 依赖关系 + public static int[] finishTime(int n, int[] time, int[][] matrix) { + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nexts.add(new ArrayList<>()); + } + int[] in = new int[n]; + for (int[] line : matrix) { + nexts.get(line[1]).add(line[0]); + in[line[0]]++; + } + Queue zeroInQueue = new LinkedList<>(); + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + if (in[i] == 0) { + zeroInQueue.add(i); + } + } + while (!zeroInQueue.isEmpty()) { + int cur = zeroInQueue.poll(); + ans[cur] += time[cur]; + for (int next : nexts.get(cur)) { + ans[next] = Math.max(ans[next], ans[cur]); + if (--in[next] == 0) { + zeroInQueue.add(next); + } + } + } + return ans; + } + +} diff --git a/公开课/class084/Code04_ClassicDP.java b/公开课/class084/Code04_ClassicDP.java new file mode 100644 index 0000000..ca4d82b --- /dev/null +++ b/公开课/class084/Code04_ClassicDP.java @@ -0,0 +1,56 @@ +package class084; + +// 来自阿里 +// 测试链接 : https://www.nowcoder.com/practice/237ae40ea1e84d8980c1d5666d1c53bc +// 提交以下的代码,把类名改成Main +// 可以直接通过 +import java.util.Scanner; + +public class Code04_ClassicDP { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int m = sc.nextInt(); + int[] cost = new int[n]; + int[] value = new int[n]; + for (int i = 0; i < n; i++) { + cost[i] = sc.nextInt(); + value[i] = sc.nextInt(); + } + int[][] dp = dp(cost, value, m); + int ans = 0; + for (int j = 0; j <= m; j++) { + ans = Math.max(ans, dp[n - 1][j]); + } + System.out.println(ans); + System.out.println(dp[n - 1][m] == -1 ? 0 : dp[n - 1][m]); + } + sc.close(); + } + + public static int[][] dp(int[] cost, int[] value, int m) { + int n = cost.length; + // dp[i][j] : 背包重量严格等于j,货物在0...i上随意选,最大价值是多少 + int[][] dp = new int[n][m + 1]; + for (int i = 0; i < n; i++) { + for (int j = 1; j <= m; j++) { + dp[i][j] = -1; + } + } + for (int k = 1; cost[0] * k <= m; k++) { + dp[0][cost[0] * k] = k * value[0]; + } + for (int i = 1; i < n; i++) { + for (int j = 1; j <= m; j++) { + dp[i][j] = dp[i - 1][j]; + if (j - cost[i] >= 0 && dp[i][j - cost[i]] != -1) { + dp[i][j] = Math.max(dp[i][j], dp[i][j - cost[i]] + value[i]); + } + } + } + return dp; + } + +} diff --git a/公开课/class085/Code01_WhoWin21Balls.java b/公开课/class085/Code01_WhoWin21Balls.java new file mode 100644 index 0000000..36465be --- /dev/null +++ b/公开课/class085/Code01_WhoWin21Balls.java @@ -0,0 +1,100 @@ +package class085; + +// 来自微众 +// 人工智能岗 +// 一开始有21个球,甲和乙轮流拿球,甲先、乙后 +// 每个人在自己的回合,一定要拿不超过3个球,不能不拿 +// 最终谁的总球数为偶数,谁赢 +// 请问谁有必胜策略 +public class Code01_WhoWin21Balls { + + // 球数balls,值一定要是奇数! + // 返回String,"甲","乙" + public static String whoWin(int balls) { + return process(0, balls, 0, 0); + } + + // turn,谁的回合,turn = 0 甲回合,turn = 1 乙回合 + // 还剩多少球,rest + // 之前甲已经拿了几个球,jia + // 之前乙已经拿了几个球,yi + // 之前,甲拿了jia个球,乙拿了yi个球 + // 当前,是谁的回合,turn告诉你! + // 还剩几个球呢?rest告诉你 + // 返回,最终谁赢! + public static String process(int turn, int rest, int jia, int yi) { + if (rest == 0) { + return jia % 2 == 0 ? "甲" : "乙"; + } + // rest > 0 还有球剩下 + if (turn == 0) { // 甲的回合 + // pick 当前甲要拿几个球 + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + // pick 是甲当前的选择 + String next = process(1, rest - pick, jia + pick, yi); + if(next.equals("甲")) { + return "甲"; + } + } + return "乙"; + } else { // 乙的回合 + // pick 当前乙要拿几个球 + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + // pick 是乙当前的选择 + String next = process(0, rest - pick, jia, yi + pick); + if(next.equals("乙")) { + return "乙"; + } + } + return "甲"; + } + } + +// // balls = 21 +// // ball是奇数 +// public static String win(int balls) { +// return process(0, balls, 0, 0); +// } +// +// // 憋递归! +// // turn 谁的回合! +// // turn == 0 甲回合 +// // turn == 1 乙回合 +// // rest剩余球的数量 +// // 之前,jiaBalls、yiBalls告诉你! +// // 当前,根据turn,知道是谁的回合! +// // 当前,还剩多少球,rest +// // 返回:谁会赢! +// public static String process(int turn, int rest, int jia, int yi) { +// if (rest == 0) { +// return (jia & 1) == 0 ? "甲" : "乙"; +// } +// // rest > 0, 还剩下球! +// if (turn == 0) { // 甲的回合! +// // 甲,自己赢!甲赢! +// for (int pick = 1; pick <= Math.min(rest, 3); pick++) { +// // pick 甲当前做的选择 +// if (process(1, rest - pick, jia + pick, yi).equals("甲")) { +// return "甲"; +// } +// } +// return "乙"; +// } else { +// for (int pick = 1; pick <= Math.min(rest, 3); pick++) { +// // pick 甲当前做的选择 +// if (process(0, rest - pick, jia, yi + pick).equals("乙")) { +// return "乙"; +// } +// } +// return "甲"; +// } +// } + + // 为了测试 + public static void main(String[] args) { + for (int balls = 1; balls <= 500; balls += 2) { + System.out.println("球数为 " + balls + " 时 , 赢的是 " + whoWin(balls)); + } + } + +} \ No newline at end of file diff --git a/公开课/class085/Code02_ShortestSubarrayWithSumAtLeastK.java b/公开课/class085/Code02_ShortestSubarrayWithSumAtLeastK.java new file mode 100644 index 0000000..99f411d --- /dev/null +++ b/公开课/class085/Code02_ShortestSubarrayWithSumAtLeastK.java @@ -0,0 +1,122 @@ +package class085; + +// 来自字节跳动 +// 给定一个数组arr,其中的值有可能正、负、0 +// 给定一个正数k +// 返回累加和>=k的所有子数组中,最短的子数组长度 +// 本题测试链接 : https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/ +public class Code02_ShortestSubarrayWithSumAtLeastK { + +// public static int shortestLenSumNolessK(int[] arr, int k) { +// int ans = Integer.MAX_VALUE; +// Box box = new Box(arr.length); +// box.add(0, -1); +// int sum = 0; +// for (int i = 0; i < arr.length; i++) { +// sum += arr[i]; +// // sum : 0...i 整体累加和! +// int find = sum - k; +// // box中 <= find 的最右位置! +// int right = box.find(find); +// if(right != -2) { +// ans = Math.min(ans, i - right); +// } +// box.add(sum, i); +// } +// return ans == Integer.MAX_VALUE ? -1 : ans; +// } +// +// public static class Box { +// +// public Node[] help;// 搞长一些,n长度 +// int size; +// public Box(int n) { +// help = new Node[n]; +// size = 0; +// } +// +// // 0...right sum +// // 保持单调性去更新 +// public void add(int sum, int right) { +// +// } +// +// // 在box中<=aim的最右位置,返回 +// // 如果不存在,返回-2 +// // 二分去找! +// public int find(int aim) { +// +// } +// +// } +// +// public static class Node{ +// public int sum; +// public int right; +// } + + public static int shortestSubarray1(int[] arr, int k) { + if (arr == null || arr.length < 1) { + return -1; + } + int n = arr.length + 1; + long[] sum = new long[n]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i - 1]; + } + int[] stack = new int[n]; + int size = 1; + int ans = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + int mostRight = mostRight(sum, stack, size, sum[i] - k); + ans = Math.min(ans, mostRight == -1 ? Integer.MAX_VALUE : (i - mostRight)); + while (size > 0 && sum[stack[size - 1]] >= sum[i]) { + size--; + } + stack[size++] = i; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int mostRight(long[] sum, int[] stack, int size, long aim) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sum[stack[m]] <= aim) { + ans = stack[m]; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int shortestSubarray2(int[] arr, int K) { + int N = arr.length; + long[] preSums = new long[N + 1]; + for (int i = 0; i < N; i++) { + preSums[i + 1] = preSums[i] + arr[i]; + } + int ans = Integer.MAX_VALUE; + int[] dq = new int[N + 1]; + int l = 0; + int r = 0; + for (int i = 0; i < N + 1; i++) { + // 从尾巴干掉,box里的记录! + while (l != r && preSums[dq[r - 1]] >= preSums[i]) { + r--; + } + // 从头部干掉,box里的记录 + while (l != r && preSums[i] - preSums[dq[l]] >= K) { + ans = Math.min(ans, i - dq[l++]); + } + dq[r++] = i; + } + return ans != Integer.MAX_VALUE ? ans : -1; + } + +} diff --git a/公开课/class086/Code01_TwoObjectMaxValue.java b/公开课/class086/Code01_TwoObjectMaxValue.java new file mode 100644 index 0000000..6d2c9b2 --- /dev/null +++ b/公开课/class086/Code01_TwoObjectMaxValue.java @@ -0,0 +1,225 @@ +package class086; + +import java.util.Arrays; + +// 来自字节 +// 5.6笔试 +// 给定N件物品,每个物品有重量(w[i])、有价值(v[i]) +// 只能最多选两件商品,重量不超过bag,返回价值最大能是多少? +// N <= 10^5, w[i] <= 10^5, v[i] <= 10^5, bag <= 10^5 +// 本题的关键点:什么数据范围都很大,唯独只需要最多选两件商品,这个可以利用一下 +public class Code01_TwoObjectMaxValue { + + // 暴力方法 + // 为了验证而写的方法 + public static int max1(int[] w, int[] v, int bag) { + return process1(w, v, 0, 2, bag); + } + + public static int process1(int[] w, int[] v, int index, int restNumber, int restWeight) { + if (restNumber < 0 || restWeight < 0) { + return -1; + } + if (index == w.length) { + return 0; + } + int p1 = process1(w, v, index + 1, restNumber, restWeight); + int p2 = -1; + int next = process1(w, v, index + 1, restNumber - 1, restWeight - w[index]); + if (next != -1) { + p2 = v[index] + next; + } + return Math.max(p1, p2); + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int max2(int[] w, int[] v, int bag) { + int n = w.length; + int[][] arr = new int[n][2]; + // [ [20,6], [14,8] , [3, 2] ] + for (int i = 0; i < n; i++) { + arr[i][0] = w[i]; + arr[i][1] = v[i]; + } + // 根据重量排序 + // [ [20,6], [14,8] , [3, 2] ] + // [ [3,2] 、[14,8] 、 [20,6] ] + Arrays.sort(arr, (a, b) -> (a[0] - b[0])); + // arr是二维数组,但是,只使用价值,不使用重量! + RMQ rmq = new RMQ(arr); + int ans = 0; + for (int i = 0, j = 1; i < n && arr[i][0] <= bag; i++, j++) { + int right = right(arr, bag - arr[i][0]) + 1; + int rest = 0; + // right:查询的和你搭配的货,的范围! + // j + if (right == j) { + rest = rmq.max(1, right - 1); + } else if (right < j) { + rest = rmq.max(1, right); + } else { + rest = Math.max(rmq.max(1, j - 1), rmq.max(j + 1, right)); + } + ans = Math.max(ans, arr[i][1] + rest); + } + return ans; + } + + public static int right(int[][] arr, int limit) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m][0] <= limit) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static class RMQ { + public int[][] max; + + public RMQ(int[][] arr) { + // n = 16个数 + int n = arr.length; + // k = 4 + int k = power2(n); + // 行:1~n + // 列:0~4 + // 请注意: + // 数组来说,下标从0开始 + // 对于RMQ来说,下标从1开始 + max = new int[n + 1][k + 1]; + for (int i = 1; i <= n; i++) { + max[i][0] = arr[i - 1][1]; + } + for (int j = 1; (1 << j) <= n; j++) { + for (int i = 1; i + (1 << j) - 1 <= n; i++) { + max[i][j] = Math.max(max[i][j - 1], max[i + (1 << (j - 1))][j - 1]); + } + } + } + + public int max(int l, int r) { + if (r < l) { + return 0; + } + int k = power2(r - l + 1); + return Math.max(max[l][k], max[r - (1 << k) + 1][k]); + } + + // 求2的几次方,离m最近,又不超过m + private int power2(int m) { + int ans = 0; + while ((1 << ans) <= (m >> 1)) { + ans++; + } + return ans; + } + + } + + + + public static class RMQZuo { + public int[][] max;// 如果传入的数组长度是N,max大小:N*logN大小 + + public RMQZuo(int[] arr) { + // n = 16个数 + int n = arr.length; + // k = 4 + int k = power2(n); + // 行:1~n + // 列:0~4 + // 请注意: + // 数组来说,下标从0开始 + // 对于RMQ来说,下标从1开始 + max = new int[n + 1][k + 1]; + for (int i = 1; i <= n; i++) { + max[i][0] = arr[i-1]; + } + for (int j = 1; (1 << j) <= n; j++) { + for (int i = 1; i + (1 << j) - 1 <= n; i++) { + // 10....8(2的3次方) + // 10...4 14...4 + + + // max[10][3] = ? + // max[10][2] 10...2的2次方个数(4个) + // i + (1 << (j - 1)) -> 10 + 4 -> 14...2的2次方个数(4个) + + max[i][j] = + Math.max(max[i][j - 1], + max[i + (1 << (j - 1))][j - 1]); + } + } + } + + // O(1) + public int max(int l, int r) { + if (r < l) { + return 0; + } + int k = power2(r - l + 1); + // l....r + // r - l + 1有几个数?2的k次方,离这个个数,最近,且不超过 + // l.... ?....r + // l...r -> 3...11 + // 11 - 3 + 1 = 9 + // 2的3次方 = 8 <= 9 + // k = 3 + // l...2的3次方个数: max[l][k] + // r - (1 << k) + 1 + // 11 - 8 + 1 -> 4 + return Math.max(max[l][k], max[r - (1 << k) + 1][k]); + } + + // 求2的几次方,离m最近,又不超过m + private int power2(int m) { + int ans = 0; + while ((1 << ans) <= (m >> 1)) { + ans++; + } + return ans; + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 12; + int V = 20; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] w = randomArray(n, V); + int[] v = randomArray(n, V); + int bag = (int) (Math.random() * V * 3); + int ans1 = max1(w, v, bag); + int ans2 = max2(w, v, bag); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class086/Code02_ModifyOneNumberModXWays.java b/公开课/class086/Code02_ModifyOneNumberModXWays.java new file mode 100644 index 0000000..b8fa3ea --- /dev/null +++ b/公开课/class086/Code02_ModifyOneNumberModXWays.java @@ -0,0 +1,84 @@ +package class086; + +// 来自网易 +// 小红拿到了一个长度为N的数组arr,她准备只进行一次修改 +// 可以将数组中任意一个数arr[i],修改为不大于P的正数(修改后的数必须和原数不同) +// 并使得所有数之和为X的倍数 +// 小红想知道,一共有多少种不同的修改方案 +// 1 <= N, X <= 10^5 +// 1 <= arr[i], P <= 10^9 +public class Code02_ModifyOneNumberModXWays { + + public static int ways1(int[] arr, int p, int x) { + long sum = 0; + for (int num : arr) { + sum += num; + } + int ans = 0; + for (int num : arr) { + sum -= num; + for (int v = 1; v <= p; v++) { + if (v != num) { + if ((sum + v) % x == 0) { + ans++; + } + } + } + sum += num; + } + return ans; + } + + public static int ways2(int[] arr, int p, int x) { + long sum = 0; + for (int num : arr) { + sum += num; + } + int ans = 0; + for (int num : arr) { + ans += cnt(p, x, num, (x - (int) ((sum - num) % x)) % x); + } + return ans; + } + + // 当前数字num + // 1~p以内,不能是num的情况下,% x == mod的数字有几个 + // O(1) + public static int cnt(int p, int x, int num, int mod) { + // p/x 至少有几个 + // (p % x) >= mod ? 1 : 0 + // 在不考虑变出来的数,是不是num的情况下,算一下有几个数,符合要求 + int ans = (p / x) + ((p % x) >= mod ? 1 : 0) - (mod == 0 ? 1 : 0); + // 不能等于num! + return ans - ((num <= p && num % x == mod) ? 1 : 0); + } + + // 为了测试 + 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 len = 100; + int value = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int p = (int) (Math.random() * value) + 1; + int x = (int) (Math.random() * value) + 1; + int ans1 = ways1(arr, p, x); + int ans2 = ways2(arr, p, x); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class086/Code03_BattleshipsInABoard.java b/公开课/class086/Code03_BattleshipsInABoard.java new file mode 100644 index 0000000..a96ff73 --- /dev/null +++ b/公开课/class086/Code03_BattleshipsInABoard.java @@ -0,0 +1,21 @@ +package class086; + +// 来自米哈游 +// 测试链接 : https://leetcode.com/problems/battleships-in-a-board/ +public class Code03_BattleshipsInABoard { + + public static int countBattleships(char[][] m) { + int ans = 0; + for (int i = 0; i < m.length; i++) { + for (int j = 0; j < m[0].length; j++) { + if ((m[i][j] == 'X') + && (i == 0 || m[i - 1][j] != 'X') + && (j == 0 || m[i][j - 1] != 'X')) { + ans++; + } + } + } + return ans; + } + +} diff --git a/公开课/class086/Code04_BrickAll.java b/公开课/class086/Code04_BrickAll.java new file mode 100644 index 0000000..9c5c62a --- /dev/null +++ b/公开课/class086/Code04_BrickAll.java @@ -0,0 +1,50 @@ +package class086; + +import java.util.Arrays; + +// 来自华为 +// 给定一个正数数组arr,其中每个值代表砖块长度 +// 所有砖块等高等宽,只有长度有区别 +// 每一层可以用1块或者2块砖来摆 +// 要求每一层的长度一样 +// 要求必须使用所有的砖块 +// 请问最多摆几层 +// 如果无法做到,返回-1 +public class Code04_BrickAll { + + public static int maxLevels(int[] arr) { + if (arr == null) { + return 0; + } + int n = arr.length; + if (n < 2) { + return n; + } + Arrays.sort(arr); + int p1 = levels(arr, arr[n - 1]); + int p2 = levels(arr, arr[n - 1] + arr[0]); + return Math.max(p1, p2); + } + + // 要求所有砖必须都使用,并且每一层最多两块砖,并且每一层长度都是len + // 返回能摆几层,如果无法做到返回-1 + public static int levels(int[] arr, int len) { + int ans = 0; + int L = 0; + int R = arr.length - 1; + while (L <= R) { + if (arr[R] == len) { + R--; + ans++; + } else if (L < R && arr[L] + arr[R] == len) { + L++; + R--; + ans++; + } else { + return -1; + } + } + return ans; + } + +} diff --git a/公开课/class087/Code01_RedAndWhiteSquares.java b/公开课/class087/Code01_RedAndWhiteSquares.java new file mode 100644 index 0000000..18294d6 --- /dev/null +++ b/公开课/class087/Code01_RedAndWhiteSquares.java @@ -0,0 +1,211 @@ +package class087; + +// 来自网易 +// 小红拿到了一个大立方体,该大立方体由1*1*1的小方块拼成,初始每个小方块都是白色。 +// 小红可以每次选择一个小方块染成红色 +// 每次小红可能选择同一个小方块重复染色 +// 每次染色以后,你需要帮小红回答出当前的白色连通块数 +// 如果两个小方块共用同一个面,且颜色相同,则它们是连通的 +// 给定n、m、h,表示大立方体的长、宽、高 +// 给定k次操作,每一次操作用(a, b, c)表示在大立方体的该位置进行染色 +// 返回长度为k的数组,表示每一次操作后,白色方块的连通块数 +// n * m * h <= 10 ^ 5,k <= 10 ^ 5 +public class Code01_RedAndWhiteSquares { + + // 暴力方法 + // 时间复杂度(k * n * m * h); + public static int[] blocks1(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] cube = new int[n][m][h]; + int value = 1; + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + cube[ops[i][0]][ops[i][1]][ops[i][2]] = -1; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (cube[x][y][z] != -1 && cube[x][y][z] != value) { + ans[i]++; + infect(cube, x, y, z, value); + } + } + } + } + value++; + } + return ans; + } + + public static void infect(int[][][] cube, int a, int b, int c, int change) { + if (a < 0 || a == cube.length || b < 0 || b == cube[0].length || c < 0 || c == cube[0][0].length + || cube[a][b][c] == -1 || cube[a][b][c] == change) { + return; + } + cube[a][b][c] = change; + infect(cube, a - 1, b, c, change); + infect(cube, a + 1, b, c, change); + infect(cube, a, b - 1, c, change); + infect(cube, a, b + 1, c, change); + infect(cube, a, b, c - 1, change); + infect(cube, a, b, c + 1, change); + } + + // 最优解 + // O(k + n * m * h) + public static int[] blocks2(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] red = new int[n][m][h]; + for (int[] op : ops) { + red[op[0]][op[1]][op[2]]++; + } + UnionFind uf = new UnionFind(n, m, h, red); + int[] ans = new int[k]; + for (int i = k - 1; i >= 0; i--) { + ans[i] = uf.sets; + int x = ops[i][0]; + int y = ops[i][1]; + int z = ops[i][2]; + if (--red[x][y][z] == 0) { + // x, y ,z 这个格子,变白,建立自己的小集合 + // 然后6个方向,集合该合并合并 + uf.finger(x, y, z); + } + } + return ans; + } + + public static class UnionFind { + public int n; + public int m; + public int h; + public int[] father; + public int[] size; + public int[] help; + public int sets; + + public UnionFind(int a, int b, int c, int[][][] red) { + n = a; + m = b; + h = c; + int len = n * m * h; + father = new int[len]; + size = new int[len]; + help = new int[len]; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (red[x][y][z] == 0) { + finger(x, y, z); + } + } + } + } + } + + public void finger(int x, int y, int z) { + // x,y,z + // 一维数值 + int i = index(x, y, z); + father[i] = i; + size[i] = 1; + sets++; + union(i, x - 1, y, z); + union(i, x + 1, y, z); + union(i, x, y - 1, z); + union(i, x, y + 1, z); + union(i, x, y, z - 1); + union(i, x, y, z + 1); + } + + private int index(int x, int y, int z) { + return z * n * m + y * n + x; + } + + private void union(int i, int x, int y, int z) { + if (x < 0 || x == n || y < 0 || y == m || z < 0 || z == h) { + return; + } + int j = index(x, y, z); + if (size[j] == 0) { + return; + } + i = find(i); + j = find(j); + if (i != j) { + if (size[i] >= size[j]) { + father[j] = i; + size[i] += size[j]; + } else { + father[i] = j; + size[j] += size[i]; + } + sets--; + } + } + + private int find(int i) { + int s = 0; + while (i != father[i]) { + help[s++] = i; + i = father[i]; + } + while (s > 0) { + father[help[--s]] = i; + } + return i; + } + + } + + // 为了测试 + public static int[][] randomOps(int n, int m, int h) { + int size = (int) (Math.random() * (n * m * h)) + 1; + int[][] ans = new int[size][3]; + for (int i = 0; i < size; i++) { + ans[i][0] = (int) (Math.random() * n); + ans[i][1] = (int) (Math.random() * m); + ans[i][2] = (int) (Math.random() * h); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int size = 10; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * size) + 1; + int m = (int) (Math.random() * size) + 1; + int h = (int) (Math.random() * size) + 1; + int[][] ops = randomOps(n, m, h); + int[] ans1 = blocks1(n, m, h, ops); + int[] ans2 = blocks2(n, m, h, ops); + for (int j = 0; j < ops.length; j++) { + if (ans1[j] != ans2[j]) { + System.out.println("出错了!"); + } + } + } + System.out.println("测试结束"); + + // 立方体达到10^6规模 + int n = 100; + int m = 100; + int h = 100; + int len = n * m * h; + // 操作条数达到10^6规模 + int[][] ops = new int[len][3]; + for (int i = 0; i < len; i++) { + ops[i][0] = (int) (Math.random() * n); + ops[i][1] = (int) (Math.random() * m); + ops[i][2] = (int) (Math.random() * h); + } + long start = System.currentTimeMillis(); + blocks2(n, m, h, ops); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + + } + +} diff --git a/公开课/class087/Code02_MaxNumberUnderLimit.java b/公开课/class087/Code02_MaxNumberUnderLimit.java new file mode 100644 index 0000000..9d113e1 --- /dev/null +++ b/公开课/class087/Code02_MaxNumberUnderLimit.java @@ -0,0 +1,203 @@ +package class087; + +import java.util.Arrays; + +// 来自字节 +// 输入: +// 去重数组arr,里面的数只包含0~9 +// limit,一个数字 +// 返回: +// 要求比limit小的情况下,能够用arr拼出来的最大数字 +public class Code02_MaxNumberUnderLimit { + + public static int tmp = 0; + + // 暴力尝试的方法 + public static int maxNumber1(int[] arr, int limit) { + tmp = 0; + Arrays.sort(arr); + limit--; + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + process1(arr, 0, offset, limit); + if (tmp == 0) { + int rest = 0; + offset /= 10; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + return tmp; + } + + public static void process1(int[] arr, int num, int offset, int limit) { + if (offset == 0) { + if (num <= limit) { + tmp = Math.max(tmp, num); + } + } else { + for (int cur : arr) { + process1(arr, num * 10 + cur, offset / 10, limit); + } + } + } + + // 正式方法 + public static int maxNumber2(int[] arr, int limit) { + // arr里面是不重复的数字,且只包含0~9 + // 5, 8 , 2 + // 2, 5, 8 + Arrays.sort(arr); + limit--; + // <= limit 且最大的数字 + // 68886 + // 10000 + // 为了取数而设计的! + // 457 + // 100 + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + // arr, 2 5 8 + // limit--, 试图去追平的数字 + // offset, 扣出limit中每一位的数字! + // 可以使用arr中的数字,期望得到尽可能接近limit的结果,并且位数一样 + // 能做到,返回的结果就是答案 + // 如果做不到和limit位数一样!返回-1 + int ans = process2(arr, limit, offset); + if (ans != -1) { + return ans; + } else { // ans = -1 一定要减少位数,才能做到 + offset /= 10; + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + } + + // 可以选哪些数字,都在arr里,arr是有序的,[2,5,8] + // limit : <= limit 且尽量的大! + // offset : 为limit服务的,就是为了提取limit的每一位数字 + public static int process2(int[] arr, int limit, int offset) { + // offset 1000 100 10 1 0 + // offset == 0,意味着,之前的数字,全都可以追平,返回limit + // 之前的数字和limit完全一样,且limit所有数字都一样 + if (offset == 0) { + return limit; + } + // limit中还有数字,没遍历到, + // (limit / offset) % 10 + // 当前数字是谁,提取出来 + int cur = (limit / offset) % 10; + + // 2 5 8 + // <=4 + // 2 ....8888 + int near = near(arr, cur); + if (near == -1) { + return -1; + } else if (arr[near] == cur) { // 找出来的数字,真的和当前数字cur一样! + // 当前位,追平了! + // 看看后续能不能走的通! + int ans = process2(arr, limit, offset / 10); + // 1) 走通了! + // 2) 走不通, + // A :当前位还有下降的空间! + // B :当前位没有下降的空间! + if (ans != -1) { + return ans; + } else if (near > 0) { // 虽然后续没成功,但是我自己还能下降!还能选更小的数字 + near--; + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } else { // 后续没成功,我自己也不能再下降了!宣告失败,往上返回! + return -1; + } + } else { // arr[near] < cur + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } + } + + // 比如offset = 100 + // 一共3位数 + // 那么就把arr中最大的数字x,拼成xxx,返回 + // 比如offset = 10000 + // 一共5位数 + // 那么就把arr中最大的数字x,拼成xxxxx,返回 + public static int rest(int[] arr, int offset) { + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + + // 在有序数组arr中,找到<=num,且最大的数字,在arr中的位置返回 + // 如果所有数字都大于num,返回-1 + // [3,6,9] num = 4 3 + // [5,7,9] num = 4 -1 + public static int near(int[] arr, int num) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int near = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= num) { + near = m; + l = m + 1; + } else { + r = m - 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray() { + int[] arr = new int[(int) (Math.random() * 10) + 1]; + boolean[] cnt = new boolean[10]; + for (int i = 0; i < arr.length; i++) { + do { + arr[i] = (int) (Math.random() * 10); + } while (cnt[arr[i]]); + cnt[arr[i]] = true; + } + return arr; + } + + public static void main(String[] args) { + int max = 3000; + int testTime = 100; + System.out.println("测试开始"); + for (int i = 0; i < max; i++) { + int[] arr = randomArray(); + for (int j = 0; j < testTime; j++) { + int ans1 = maxNumber1(arr, i); + int ans2 = maxNumber2(arr, i); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println("数组为 :"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("数字为 :" + i); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class087/Code03_NumberOfCannon.java b/公开课/class087/Code03_NumberOfCannon.java new file mode 100644 index 0000000..0be0f9e --- /dev/null +++ b/公开课/class087/Code03_NumberOfCannon.java @@ -0,0 +1,104 @@ +package class087; + +import java.util.TreeMap; + +// 来自学员问题 +// 给定一个数组arr,表示从早到晚,依次会出现的导弹的高度 +// 大炮打导弹的时候,如果一旦大炮定了某个高度去打,那么这个大炮每次打的高度都必须下降一点 +// 1) 如果只有一个大炮,返回最多能拦截多少导弹 +// 2) 如果所有的导弹都必须拦截,返回最少的大炮数量 +public class Code03_NumberOfCannon { + + public static int numOfCannon(int[] arr) { + // key : 某个大炮打的结尾数值 + // value : 有多少个大炮有同样的结尾数值 + // 比如: + // 一共有A、B、C三个大炮 + // 如果A大炮此时打的高度是17,B大炮此时打的高度是7,C大炮此时打的高度是13 + // 那么在表中: + // 7, 1 + // 13, 1 + // 17, 1 + // 如果A大炮此时打的高度是13,B大炮此时打的高度是7,C大炮此时打的高度是13 + // 那么在表中: + // 7, 1 + // 13, 2 + TreeMap ends = new TreeMap<>(); + for (int num : arr) { + if (ends.ceilingKey(num + 1) == null) { + ends.put(Integer.MAX_VALUE, 1); + } + int ceilKey = ends.ceilingKey(num + 1); + if (ends.get(ceilKey) > 1) { + ends.put(ceilKey, ends.get(ceilKey) - 1); + } else { + ends.remove(ceilKey); + } + ends.put(num, ends.getOrDefault(num, 0) + 1); + } + int ans = 0; + for (int value : ends.values()) { + ans += value; + } + return ans; + } + + public static void main(String[] args) { + + // orderedMap C++ + TreeMap map = new TreeMap<>(); + map.put(1, "我是1"); + map.put(5, "我是5"); + map.put(50, "我是50"); + map.remove(5); + + System.out.println(map.get(5)); + + // 查 >= 15 , 并且离15最近的key + System.out.println(map.ceilingKey(55)); + + // 查 <= 15 + System.out.println(map.floorKey(15)); + + // put、get、remove、ceilingKey、floorKey + // 时间复杂度O(log N) + // TreeMap,key是Integer,去重的! + + // key : 大炮来到的高度,<=key以下高度的导弹 + // value : 这样高度的大炮有几门 + TreeMap cannonMap = new TreeMap(); + + // A : 80 B :80 C : 80 D : 20 + // 80 : 3 + // 20 : 1 + // 假设map中有一些大炮, + int h = 100; + // >= 100,的key存在? + if (cannonMap.ceilingKey(h) == null) { // 没有能打100高度的 + // 新开一门炮,100消灭!99 + if (cannonMap.containsKey(h - 1)) { + cannonMap.put(h - 1, cannonMap.get(h - 1) + 1); + } else { + cannonMap.put(h - 1, 1); + } + } else { // 有能打100高度的 + // 100 110 99 + int cannonH = cannonMap.ceilingKey(h); + if(cannonMap.get(cannonH) == 1) { + cannonMap.remove(cannonH); + }else { + cannonMap.put(cannonH , cannonMap.get(cannonH) - 1); + } + if (cannonMap.containsKey(h - 1)) { + cannonMap.put(h - 1, cannonMap.get(h - 1) + 1); + } else { + cannonMap.put(h - 1, 1); + } + } + + + // map ,values 累加 返回~ + + } + +} diff --git a/公开课/class088/Code01_FourNumbersMinusOne.java b/公开课/class088/Code01_FourNumbersMinusOne.java new file mode 100644 index 0000000..3f84bb4 --- /dev/null +++ b/公开课/class088/Code01_FourNumbersMinusOne.java @@ -0,0 +1,74 @@ +package class088; + +import java.util.Arrays; + +// 来自阿里笔试 +// 牛牛今年上幼儿园了,老师叫他学习减法 +// 老师给了他5个数字,他每次操作可以选择其中的4个数字减1 +// 减一之后的数字不能小于0,因为幼儿园的牛牛还没有接触过负数 +// 现在牛牛想知道,自己最多可以进行多少次这样的操作 +// 扩展问题来自leetcode 2141,掌握了这个题原始问题就非常简单了 +// leetcode测试链接 : https://leetcode.com/problems/maximum-running-time-of-n-computers/ +public class Code01_FourNumbersMinusOne { + + // n,电脑台数 + // arr,所有的电池 + public static long maxRunTime(int n, int[] arr) { + // 电量排个序 + // 2, 3, 5, 15, 23... + // 2, 5,10, 25, 48 + Arrays.sort(arr); + int size = arr.length; + long[] sums = new long[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + // 答案的范围,定好,因为要二分,最大流程! + // 所有电量加起来,1000,电脑有10台。 + // 0 ~ 100 + long l = 0; + long m = 0; + long r = sums[size - 1] / n; + long ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (ok(arr, sums, m, n)) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + // 电池数组arr,前缀和数组sum + // 撑够的分钟数:time + // 几台电脑:num + // 返回:能不能做到! + public static boolean ok(int[] arr, long[] sum, long time, int num) { + int l = 0; + int m = 0; + int r = arr.length - 1; + int left = arr.length; + // 20分钟 + // arr,有序的! + // >=20分钟,有几块电池!7块 + // 电脑有10台,7台,3台(碎片电池去搞定!< 20) + // >= time 最左 + while (l <= r) { + m = (l + r) / 2; + if (arr[m] >= time) { + left = m; + r = m - 1; + } else { + l = m + 1; + } + } + num -= arr.length - left; + long rest = left == 0 ? 0 : sum[left - 1]; + return time * (long) num <= rest; + } + +} diff --git a/公开课/class088/Code02_MinWaitingTime.java b/公开课/class088/Code02_MinWaitingTime.java new file mode 100644 index 0000000..275b8f0 --- /dev/null +++ b/公开课/class088/Code02_MinWaitingTime.java @@ -0,0 +1,97 @@ +package class088; + +import java.util.PriorityQueue; + +// 来自谷歌 +// 给定一个数组arr,长度为n +// 表示n个服务员,每个人服务一个人的时间 +// 给定一个正数m,表示有m个人等位 +// 如果你是刚来的人,请问你需要等多久? +// 假设:m远远大于n,比如n<=1000, m <= 10的9次方,该怎么做? +public class Code02_MinWaitingTime { + + // 堆!堆模拟!方法1! + public static int minWaitingTime1(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] - b[0])); + int n = arr.length; + for (int i = 0; i < n; i++) { + heap.add(new int[] { 0, arr[i] }); + } + for (int i = 0; i < m; i++) { + int[] cur = heap.poll(); + cur[0] += cur[1]; + heap.add(cur); + } + return heap.peek()[0]; + } + + // 二分答案! + public static int minWaitingTime2(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + int best = Integer.MAX_VALUE; + for (int num : arr) { + best = Math.min(best, num); + } + int left = 0; + int right = best * m; + int mid = 0; + int near = 0; + while (left <= right) { + mid = (left + right) / 2; + int cover = 0; + for (int num : arr) { + cover += (mid / num) + 1; + } + if (cover >= m + 1) { + near = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 30; + int mMax = 3000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * mMax); + int ans1 = minWaitingTime1(arr, m); + int ans2 = minWaitingTime2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m : " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class088/Code03_UniqueSubstringsInWraparoundString.java b/公开课/class088/Code03_UniqueSubstringsInWraparoundString.java new file mode 100644 index 0000000..e456add --- /dev/null +++ b/公开课/class088/Code03_UniqueSubstringsInWraparoundString.java @@ -0,0 +1,34 @@ +package class088; + +// 把字符串 s 看作 "abcdefghijklmnopqrstuvwxyz" 的无限环绕字符串, +// 所以 s 看起来是这样的: +// ...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.... +// 现在给定另一个字符串 p 。返回 s 中 不同 的 p 的 非空子串 的数量 +// 测试链接 : https://leetcode.com/problems/unique-substrings-in-wraparound-string/ +public class Code03_UniqueSubstringsInWraparoundString { + + public int findSubstringInWraproundString(String s) { + char[] str = s.toCharArray(); + int n = str.length; + int ans = 0; + int len = 1; + // 256 0~255 + int[] max = new int[256]; + max[str[0]]++; + for (int i = 1; i < n; i++) { + char cur = str[i]; + char pre = str[i - 1]; + if ((pre == 'z' && cur == 'a') || pre + 1 == cur) { + len++; + } else { + len = 1; + } + max[cur] = Math.max(max[cur], len); + } + for (int i = 0; i < 256; i++) { + ans += max[i]; + } + return ans; + } + +} diff --git a/公开课/class089/Code01_MostStonesRemovedWithSameRowOrColumn.java b/公开课/class089/Code01_MostStonesRemovedWithSameRowOrColumn.java new file mode 100644 index 0000000..2de73c5 --- /dev/null +++ b/公开课/class089/Code01_MostStonesRemovedWithSameRowOrColumn.java @@ -0,0 +1,95 @@ +package class089; + +import java.util.HashMap; + +// n块石头放置在二维平面中的一些整数坐标点上 +// 每个坐标点上最多只能有一块石头 +// 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。 +// 给你一个长度为 n 的数组 stones , +// 其中 stones[i] = [xi, yi] 表示第 i 块石头的位置, +// 返回 可以移除的石子 的最大数量。 +// 测试链接 : https://leetcode.com/problems/most-stones-removed-with-same-row-or-column/ +public class Code01_MostStonesRemovedWithSameRowOrColumn { + + // stones[i] = [a,b] i号石子,行所在a行,列所在b列 + public static int removeStones(int[][] stones) { + // n,石子数量 + int n = stones.length; + // 100万行,A石子,第17个石子 + // 100万(key) 17 + HashMap rowPre = new HashMap(); + // 200万列 R石子,第105个石子 + // 200万(key) 105 + HashMap colPre = new HashMap(); + // 并查集,n个石子,一开始都在各自的集合里! + UnionFind uf = new UnionFind(n); + for (int i = 0; i < n; i++) { // 遍历石子 + // 当前石子,i号,行 x 列 y + int x = stones[i][0]; + int y = stones[i][1]; + if (!rowPre.containsKey(x)) { + rowPre.put(x, i); + } else { + uf.union(i, rowPre.get(x)); + } + if (!colPre.containsKey(y)) { + colPre.put(y, i); + } else { + uf.union(i, colPre.get(y)); + } + } + return n - 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; + } + + } + +} diff --git a/公开课/class089/Code02_ReachingPoints.java b/公开课/class089/Code02_ReachingPoints.java new file mode 100644 index 0000000..7e501d9 --- /dev/null +++ b/公开课/class089/Code02_ReachingPoints.java @@ -0,0 +1,49 @@ +package class089; + +// 测试链接 : https://leetcode.com/problems/reaching-points/ +public class Code02_ReachingPoints { + + // 会超时,但是揭示了大思路 + public static boolean reachingPoints1(int sx, int sy, int tx, int ty) { + while (tx != ty) { + if (tx < ty) { + ty -= tx; + } else { + tx -= ty; + } + if (sx == tx && sy == ty) { + return true; + } + } + return false; + } + + // 对大体思路的优化 + // s ( 5, 10) + // t (100, 65) + public static boolean reachingPoints2(int sx, int sy, int tx, int ty) { + // sx 出发点的x + // sy 出发点的y + // tx 目标点的x + // ty 目标点的y + // sx < tx 同时 sy < ty + // x和y,出发点都小才跳! + while (sx < tx && sy < ty) { + // tx ty 谁大,谁去%,然后用余数替换大数! + if (tx < ty) { + ty %= tx; + } else { + tx %= ty; + } + } + // 1) sx == tx 跳出来! + // 2) sy == ty 跳出来! + // 除此之外的所有,都认为返回false + // 什么情况下,会返回true + // 1) (sx == tx && sy <= ty && (ty - sy) % sx == 0) + // 2) (sy == ty && sx <= tx && (tx - sx) % sy == 0) + // false! + return (sx == tx && sy <= ty && (ty - sy) % sx == 0) || (sy == ty && sx <= tx && (tx - sx) % sy == 0); + } + +} diff --git a/公开课/class089/Code03_NumberOfDifferentSubsequencesGCDs.java b/公开课/class089/Code03_NumberOfDifferentSubsequencesGCDs.java new file mode 100644 index 0000000..daf0d8f --- /dev/null +++ b/公开课/class089/Code03_NumberOfDifferentSubsequencesGCDs.java @@ -0,0 +1,64 @@ +package class089; + +// 给你一个由正整数组成的数组 nums 。 +// 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。 +// 例如,序列 [4,6,16] 的最大公约数是 2 。 +// 数组的一个 子序列 本质是一个序列,可以通过删除数组中的某些元素(或者不删除)得到。 +// 例如,[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。 +// 计算并返回 nums 的所有 非空 子序列中 不同 最大公约数的 数目 。 +// 测试链接 : https://leetcode.com/problems/number-of-different-subsequences-gcds/ +public class Code03_NumberOfDifferentSubsequencesGCDs { + + // n不是数字的个数,是数组中的最大值 + // 体系学习班, + // 根据数据量猜解法, + // 要想通过测试,一定要让计算量不超过10的7次方~10的8次方 + // n/1 + n/2 + n/3 + n/4 + ... + n/n -> O(N * logN) + public static int countDifferentSubsequenceGCDs(int[] nums) { + // 找到数组中的最大数!max + int max = Integer.MIN_VALUE; + for (int num : nums) { + max = Math.max(max, num); + } + // 1~max,哪个数有哪个数没有 + boolean[] set = new boolean[max + 1]; + for (int num : nums) { + set[num] = true; + } + // 最终要返回的答案,所有子序列的不同最大公约数个数! + int ans = 0; + // a是当前想确定,是不是某个子序列的最大公约数,有a! + // 1 2 3 4 5 ... max + for (int a = 1; a <= max; a++) { + // a + // 1)找到,离a最近的,a的倍数!在数组中存在的! + int g = a; + for (; g <= max; g += a) { + if (set[g]) { + break; + } + } + // 2) 找到了离a最近的、a的倍数!是g + // g g(存在!) 最大公约数,是不是a,是,结束了!ans+1 + // g g + a(存在!) 最大公约数,是不是a,是,结束了!ans+1 + // g g + 2a(存在!) 最大公约数,是不是a,是,结束了!ans+1 + // g g + 3a(存在!) 最大公约数,是不是a,是,结束了!ans+1 + // g g + 4a(存在!) 最大公约数,是不是a,是,结束了!ans+1 + for (int b = g; b <= max; b += a) { + if (set[b]) { + g = gcd(g, b); + if (g == a) { + ans++; + break; + } + } + } + } + return ans; + } + + public static int gcd(int m, int n) { + return n == 0 ? m : gcd(n, m % n); + } + +} diff --git a/公开课/class090/Code01_LIS.java b/公开课/class090/Code01_LIS.java new file mode 100644 index 0000000..15fa4ac --- /dev/null +++ b/公开课/class090/Code01_LIS.java @@ -0,0 +1,94 @@ +package class090; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code01_LIS { + +// // O(N^2) +// // 不够好 +// public static int lisMaxLen1(int[] arr) { +// if (arr == null || arr.length == 0) { +// return 0; +// } +// int n = arr.length; +// int[] dp = new int[n]; +// dp[0] = 1; +// int max = dp[0]; +// for (int i = 1; i < n; i++) { +// int pre = 0; +// for (int j = 0; j < i; j++) { +// if (arr[j] < arr[i]) { +// pre = Math.max(pre, dp[j]); +// } +// } +// dp[i] = pre + 1; +// max = Math.max(max, dp[i]); +// } +// return max; +// } +// +// public static int lisMaxLen2(int[] arr) { +// if (arr == null || arr.length == 0) { +// return 0; +// } +// int n = arr.length; +// int[] dp = new int[n]; +// int[] ends = new int[n]; +// dp[0] = 1; +// ends[0] = arr[0]; +// // ends数组,有效区 +// // ends = 1.... +// // 0...validSize-1 范围上去二分! +// int validSize = 1; +// int max = dp[0]; +// for (int i = 1; i < n; i++) { +// int cur = arr[i]; +// int endsi = find(ends, validSize, cur); +// if(endsi == -1) { // ends数组有效区里,都是 < num +// ends[validSize++] = cur; +// dp[i] = validSize; +// } else { // 查找>=num最左的位置,找到了! +// ends[endsi] = cur; +// dp[i] = endsi + 1; +// } +// max = Math.max(max, dp[i]); +// } +// return max; +// } +// +// // 在ends数组有效区里,查找>=num最左的位置 +// // 如果没有>=num最左的位置,返回-1 +// public static int find(int[] ends, int size, int num) { +// +// } + + // 最优解! + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + int max = 1; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/公开课/class090/Code02_MaxAnimalNumber.java b/公开课/class090/Code02_MaxAnimalNumber.java new file mode 100644 index 0000000..5080c7e --- /dev/null +++ b/公开课/class090/Code02_MaxAnimalNumber.java @@ -0,0 +1,116 @@ +package class090; + +// 有n个动物重量分别是a1、a2、a3.....an, +// 这群动物一起玩叠罗汉游戏, +// 规定从左往右选择动物,每只动物左边动物的总重量不能超过自己的重量 +// 返回最多能选多少个动物,求一个高效的算法。 +// 比如有7个动物,从左往右重量依次为:1,3,5,7,9,11,21 +// 则最多能选5个动物:1,3,5,9,21 +// 注意本题给的例子是有序的,但是实际给定的动物数组,可能是无序的, +// 要求从左往右选动物,且不能打乱原始数组 +public class Code02_MaxAnimalNumber { + + // 普通动态规划 + // 非常一般的方法 + // 来自背包的思路 + public static int maxAnimals1(int[] arr) { + int sum = 0; + for (int num : arr) { + sum += num; + } + int[][] dp = new int[arr.length][sum + 1]; + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j <= sum; j++) { + dp[i][j] = -1; + } + } + return process1(arr, 0, 0, dp); + } + + public static int process1(int[] arr, int index, int pre, int[][] dp) { + if (index == arr.length) { + return 0; + } + if (dp[index][pre] != -1) { + return dp[index][pre]; + } + int p1 = process1(arr, index + 1, pre, dp); + int p2 = 0; + if (arr[index] >= pre) { + p2 = 1 + process1(arr, index + 1, pre + arr[index], dp); + } + int ans = Math.max(p1, p2); + dp[index][pre] = ans; + return ans; + } + + // 最优解 + // 如果arr长度为N,时间复杂度O(N*logN) + public static int maxAnimals2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length + 1]; + ends[0] = 0; + int endsSize = 1; + int max = 1; + for (int i = 0; i < arr.length; i++) { + int l = 0; + int r = endsSize - 1; + int m = 0; + int find = 0; + while (l <= r) { + m = (l + r) / 2; + if (ends[m] <= arr[i]) { + find = m; + l = m + 1; + } else { + r = m - 1; + } + } + if (find == endsSize - 1) { // 能扩充有效区! + ends[endsSize] = ends[endsSize - 1] + arr[i]; + endsSize++; + } else { // 不能扩充有效区 + if (ends[find + 1] > ends[find] + arr[i]) { + ends[find + 1] = ends[find] + arr[i]; + } + } + max = Math.max(max, find + 1); + } + return max; + } + + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + public static void main(String[] args) { + int N = 100; + int V = 1000; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int ans1 = maxAnimals1(arr); + int ans2 = maxAnimals2(arr); + if (ans1 != ans2) { + System.out.println("出错了"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class090/Code03_EnvelopesProblem.java b/公开课/class090/Code03_EnvelopesProblem.java new file mode 100644 index 0000000..c2d1d31 --- /dev/null +++ b/公开课/class090/Code03_EnvelopesProblem.java @@ -0,0 +1,61 @@ +package class090; + +import java.util.Arrays; +import java.util.Comparator; + +// 本题测试链接 : https://leetcode.com/problems/russian-doll-envelopes/ +public class Code03_EnvelopesProblem { + + public static int maxEnvelopes(int[][] matrix) { + // 生成信封,根据宽度从小到大,宽度一样的,高度从大到小 + Envelope[] arr = sort(matrix); + int[] ends = new int[matrix.length]; + ends[0] = arr[0].h; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i].h > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i].h; + } + return right + 1; + } + + public static class Envelope { + public int l; + public int h; + + public Envelope(int weight, int hight) { + l = weight; + h = hight; + } + } + + public static class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.l != o2.l ? o1.l - o2.l : o2.h - o1.h; + } + } + + public static Envelope[] sort(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} diff --git a/公开课/class091/Code01_SetAll.java b/公开课/class091/Code01_SetAll.java new file mode 100644 index 0000000..2de8ab7 --- /dev/null +++ b/公开课/class091/Code01_SetAll.java @@ -0,0 +1,57 @@ +package class091; + +import java.util.HashMap; + +// 设计有一个带有setAll功能的哈希表,有如下功能 +// 1) put(x, y) : 新增或者更新,key为x,value是y +// 2) get(x) : 返回key为x的时候,value是多少 +// 3) setAll(y) : 把调用这个方法之前所有的key,value都设置成y +// 要求三个方法的时间复杂度都是O(1) +public class Code01_SetAll { + + public static class MyValue { + public V value; + public long time; + + public MyValue(V v, long t) { + value = v; + time = t; + } + } + + public static class MyHashMap { + // key : 原始的key + // value : 封装好的一个MyValue结构:原始的value、时间戳 + private HashMap> map; + // 全局时间点,每一次put、或者setAll都会让时间点 + 1 + private long time; + // setAll区域:setAllValue,setAllTime + private MyValue setAll; + + public MyHashMap() { + map = new HashMap<>(); + time = 0; + setAll = new MyValue(null, -1); + } + + public void put(K key, V value) { + map.put(key, new MyValue(value, time++)); + } + + public void setAll(V value) { + setAll = new MyValue(value, time++); + } + + public V get(K key) { + if (!map.containsKey(key)) { + return null; + } + if (map.get(key).time > setAll.time) { + return map.get(key).value; + } else { + return setAll.value; + } + } + } + +} diff --git a/公开课/class091/Code02_Heaters.java b/公开课/class091/Code02_Heaters.java new file mode 100644 index 0000000..a71ef8f --- /dev/null +++ b/公开课/class091/Code02_Heaters.java @@ -0,0 +1,132 @@ +package class091; + +import java.util.Arrays; + +// 你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。 +// 在加热器的加热半径范围内的每个房屋都可以获得供暖。 +// 现在,给出位于一条水平线上的房屋 houses 和供暖器 heaters 的位置, +// 请你找出并返回可以覆盖所有房屋的最小加热半径。 +// 说明:所有供暖器都遵循你的半径标准,加热的半径都一样。 +// 测试链接 :https://leetcode.cn/problems/heaters +public class Code02_Heaters { + + // 比如地点是7, 9, 14 + // 供暖点的位置: 1 3 4 5 13 15 17 + // 先看地点7 + // 由1供暖,半径是6 + // 由3供暖,半径是4 + // 由4供暖,半径是3 + // 由5供暖,半径是2 + // 由13供暖,半径是6 + // 由此可知,地点7应该由供暖点5来供暖,半径是2 + // 再看地点9 + // 供暖点不回退 + // 由5供暖,半径是4 + // 由13供暖,半径是4 + // 由15供暖,半径是6 + // 由此可知,地点9应该由供暖点13来供暖,半径是4 + // 为什么是13而不是5?因为接下来的地点都会更靠右,所以半径一样的时候,就应该选更右的供暖点 + // 再看地点14 + // 供暖点不回退 + // 由13供暖,半径是1 + // 由15供暖,半径是1 + // 由17供暖,半径是3 + // 由此可知,地点14应该由供暖点15来供暖,半径是1 + // 以此类推 + public static int findRadius(int[] houses, int[] heaters) { + // 排序 + // 时间复杂度O(N*logN) + Arrays.sort(houses); + Arrays.sort(heaters); + // 至少的供暖半径 + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + // 3 9 13 17 + // 0 1 2 3 + // i就是房子的编号,houses[i]房子所在的位置 + // j火炉的编号 + // 2 5 8 13 16 + // 0 1 2 3 4 + // best : i号房,由j号火炉给其供暖,是不是最优的! + while (!best(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + // 这个函数含义: + // 当前的地点houses[i]由heaters[j]来供暖是最优的吗? + // 当前的地点houses[i]由heaters[j]来供暖,产生的半径是a + // 当前的地点houses[i]由heaters[j + 1]来供暖,产生的半径是b + // 如果a < b, 说明是最优,供暖不应该跳下一个位置 + // 如果a >= b, 说明不是最优,应该跳下一个位置 + public static boolean best(int[] houses, int[] heaters, int i, int j) { + return + // 已经来到最后的火炉了!一定是最优! + j == heaters.length - 1 || + // 当前的火炉到当前的房子距离 < 下一个火炉到当前的房子距离 + Math.abs(heaters[j] - houses[i]) < Math.abs(heaters[j + 1] - houses[i]); + } + + // 下面这个方法不对,你能找出原因嘛?^_^ + public static int findRadius2(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best2(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + public static boolean best2(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 || Math.abs(heaters[j] - houses[i]) <= Math.abs(heaters[j + 1] - houses[i]); + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 5; + int v = 10; + int testTime = 10000; + for (int i = 0; i < testTime; i++) { + int[] a = randomArray(len, v); + int[] b = randomArray(len, v); + int ans1 = findRadius(a, b); + int ans2 = findRadius2(a, b); + if (ans1 != ans2) { + System.out.println("A : "); + for (int num : a) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("B : "); + for (int num : b) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + } + +} diff --git a/公开课/class091/Code03_TrappingRainWater.java b/公开课/class091/Code03_TrappingRainWater.java new file mode 100644 index 0000000..c3c016c --- /dev/null +++ b/公开课/class091/Code03_TrappingRainWater.java @@ -0,0 +1,116 @@ +package class091; + +// 给定一个正整数数组arr,把arr想象成一个直方图。 +// 返回这个直方图如果装水,能装下几格水? +// 测试链接 : https://leetcode.cn/problems/trapping-rain-water +public class Code03_TrappingRainWater { + + public static int trap(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int n = arr.length; + int maxLeft = arr[0]; + int maxRight = arr[n - 1]; + int L = 1; + int R = n - 2; + int ans = 0; + while (L <= R) { + // 左边部分最大值 < 右边部分最大值,让左侧动 + // 左边部分最大值 == 右边部分最大值,也让左侧动 + if (maxLeft <= maxRight) { + ans += Math.max(maxLeft - arr[L], 0); + maxLeft = Math.max(maxLeft, arr[L++]); + } else { // 左边部分最大值 > 右边部分最大值,让右侧动 + ans += Math.max(maxRight - arr[R], 0); + maxRight = Math.max(maxRight, arr[R--]); + } + } + return ans; + } + + public static int trap1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int water = 0; + for (int i = 1; i < N - 1; i++) { + int leftMax = Integer.MIN_VALUE; + for (int j = 0; j < i; j++) { + leftMax = Math.max(leftMax, arr[j]); + } + int rightMax = Integer.MIN_VALUE; + for (int j = i + 1; j < N; j++) { + rightMax = Math.max(rightMax, arr[j]); + } + water += Math.max(Math.min(leftMax, rightMax) - arr[i], 0); + } + return water; + } + + public static int trap2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] leftMaxs = new int[N]; + leftMaxs[0] = arr[0]; + for (int i = 1; i < N; i++) { + leftMaxs[i] = Math.max(leftMaxs[i - 1], arr[i]); + } + + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMaxs[i - 1], rightMaxs[i + 1]) - arr[i], 0); + } + return water; + } + + public static int trap3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] rightMaxs = new int[N]; + rightMaxs[N - 1] = arr[N - 1]; + for (int i = N - 2; i >= 0; i--) { + rightMaxs[i] = Math.max(rightMaxs[i + 1], arr[i]); + } + int water = 0; + int leftMax = arr[0]; + for (int i = 1; i < N - 1; i++) { + water += Math.max(Math.min(leftMax, rightMaxs[i + 1]) - arr[i], 0); + leftMax = Math.max(leftMax, arr[i]); + } + return water; + } + + public static int trap4(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + +} diff --git a/公开课/class091/Code04_SplitApples.java b/公开课/class091/Code04_SplitApples.java new file mode 100644 index 0000000..b3debb4 --- /dev/null +++ b/公开课/class091/Code04_SplitApples.java @@ -0,0 +1,124 @@ +package class091; + +// 有m个同样的苹果,认为苹果之间无差别 +// 有n个同样的盘子,认为盘子之间也无差别 +// 比如有3个苹果,有2个盘子 +// 方法有两种分别为:1、2 ; 3 +// 1、2和2、1的放置认为是一种方法 +// 返回有多少种放置苹果的方法 +// 测试链接 : https://www.nowcoder.com/practice/bfd8234bb5e84be0b493656e390bdebf +// 提交以下的code,提交时请把类名改成"Main" +import java.util.Arrays; +import java.util.Scanner; + +public class Code04_SplitApples { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int m = sc.nextInt(); + int n = sc.nextInt(); + int ways = ways3(m, n); + System.out.println(ways); + } + sc.close(); + } + + // 思路来自于分裂数问题 + // 体系学习班代码第22节,题目3,split number问题 + public static int ways1(int apples, int plates) { + return process1(1, apples, plates); + } + + // pre : 上一个盘子分到的苹果数量,当前的盘子分到的数量不能小于pre + // apples : 剩余的苹果数量 + // plates : 剩余的盘子数量 + // 在盘子够用的情况下,把苹果分完,有几种方法 + public static int process1(int pre, int apples, int plates) { + if (apples == 0) { + return 1; + } + // apples != 0 + if (plates == 0) { + return 0; + } + // apples != 0 && plates != 0 + if (pre > apples) { + return 0; + } + // apples != 0 && plates != 0 && pre <= apples + int way = 0; + // 之前的盘子分了3个苹果,现在还剩下8个苹果 + // 当前的盘子,可以装几个苹果:3、4、5、6、7、8 + for (int cur = pre; cur <= apples; cur++) { + way += process1(cur, apples - cur, plates - 1); + } + return way; + } + + // 新的尝试,最优解 + // 苹果有apples个,盘子有plates个 + // 返回有几种摆法 + // 如果苹果数为0,有1种摆法:什么也不摆 + // 如果苹果数不为0,但是盘子数为0,有0种摆法(做不到) + // 如果苹果数不为0,盘子数也不为0,进行如下的情况讨论: + // 假设苹果数为apples,盘子数为plates + // 可能性 1) apples < plates + // 这种情况下,一定有多余的盘子,这些盘子完全没用,所以砍掉 + // 后续是f(apples, apples) + // 可能性 2) apples >= plates + // 在可能性2)下,讨论摆法,有如下两种选择 + // 选择a) 不是所有的盘子都使用 + // 选择b) 就是所有的盘子都使用 + // 对于选择a),既然不是所有盘子都使用,那么后续就是f(apples, plates - 1) + // 意思是:既然不是所有盘子都使用,那盘子减少一个,然后继续讨论吧! + // 对于选择b),既然就是所有的盘子都使用,那么先把所有盘子都摆上1个苹果。 + // 剩余苹果数 = apples - plates + // 然后继续讨论,剩下的这些苹果,怎么摆进plates个盘子里, + // 所以后续是f(apples - plates, plates) + public static int ways2(int apples, int plates) { + if (apples == 0) { + return 1; + } + if (plates == 0) { + return 0; + } + if (plates > apples) { + return ways2(apples, apples); + } else { // apples >= plates; + return ways2(apples, plates - 1) + ways2(apples - plates, plates); + } + } + + // 上面最优解尝试的记忆化搜索版本 + public static int[][] dp = null; + + public static int ways3(int apples, int plates) { + if (dp == null) { + dp = new int[11][11]; + for (int i = 0; i <= 10; i++) { + Arrays.fill(dp[i], -1); + } + } + return process3(apples, plates, dp); + } + + public static int process3(int apples, int plates, int[][] dp) { + if (dp[apples][plates] != -1) { + return dp[apples][plates]; + } + int ans = 0; + if (apples == 0) { + ans = 1; + } else if (plates == 0) { + ans = 0; + } else if (plates > apples) { + ans = process3(apples, apples, dp); + } else { + ans = process3(apples, plates - 1, dp) + process3(apples - plates, plates, dp); + } + dp[apples][plates] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/公开课/class092/Code01_ValidParentheses.java b/公开课/class092/Code01_ValidParentheses.java new file mode 100644 index 0000000..8ea317d --- /dev/null +++ b/公开课/class092/Code01_ValidParentheses.java @@ -0,0 +1,102 @@ +package class092; + +// () +// ()() +// (()())(()) +// (() +// (())()...... +// +// cnt = 0 +// ( ++ +// ) -- +// ( ) ) ( +// 1 0 -1 false +// ( ( ( ) ) ) ( ) ( ( ( ) +// 1 2 3 2 1 0 1 0 1 2 3 2 false +// 前缀 : ( ( ) ( ( ) ) ) +// ()) +public class Code01_ValidParentheses { + + // 检查有效性,整体有效性! + public static boolean valid(String s) { + char[] str = s.toCharArray(); + int count = 0; + for (int i = 0; i < str.length; i++) { + count += str[i] == '(' ? 1 : -1; + if (count < 0) { + return false; + } + } + return count == 0; + } + + // )( -> 2个 + // ...... -> ? 能变合法! + // + // cnt = 0 + // add = 3 + // ( ++ + // ) -- + // 遍历的过程中,cnt < 0时刻! + // ( ) ) ) ( ( ) ) ) ( ( ( ) + // 1 0 0 0 1 2 1 0 0 1 2 3 2 + // cnt = -1 添加 ( , 使之平衡!cnt = 0 + // 到最后 add += cnt + public static int needParentheses(String s) { + char[] str = s.toCharArray(); + int count = 0; + int add = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == '(') { + count++; + } else { // 遇到的是')' + if (count == 0) { + add++; + } else { + count--; + } + } + } + return count + add; + } + + public static boolean isValid(char[] str) { + if (str == null || str.length == 0) { + return false; + } + int status = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] != ')' && str[i] != '(') { + return false; + } + if (str[i] == ')' && --status < 0) { + return false; + } + if (str[i] == '(') { + status++; + } + } + return status == 0; + } + + + + // 4 + // ( () () ) ( ( ( ) ) ) ( ( ) ( ) ( ( ) ) ( )( ) ( ( ( ) ( ) ) ) ) + // cnt ( ++ + // cnt ) -- + public static int deep(String s) { + char[] str = s.toCharArray(); + if (!isValid(str)) { + return 0; + } + int count = 0; + int max = 0; + for (int i = 0; i < str.length; i++) { + count += str[i] == '(' ? 1 : -1; + max = Math.max(max, count); + } + return max; + } + +} diff --git a/公开课/class092/Code02_LongestParentheses.java b/公开课/class092/Code02_LongestParentheses.java new file mode 100644 index 0000000..6549115 --- /dev/null +++ b/公开课/class092/Code02_LongestParentheses.java @@ -0,0 +1,84 @@ +package class092; + +// s只由(和)组成 +// 求最长有效括号子串长度 +// 本题测试链接 : https://leetcode.com/problems/longest-valid-parentheses/ +public class Code02_LongestParentheses { + + // ( ) ) ) ( ( ) ) ( ) ( ( + // 0 1 2 3 4 5 6 7 8 9 10 11 + // 最长有效括号子串 + // dp[] 长度11, + // dp[i] : 子串必须以i位置结尾的情况下!向左最长能延伸多长!是有效的串! + // ( ) ) ) ( ( ) ) ( ) ( ( + // 0 1 2 3 4 5 6 7 8 9 10 11 + // dp 0 2 0 0 ................. + // 0 1 2 3 + // + // dp[i] : 重点关注 dp[i-1] + // 1) [i] == (, dp[i] = 0 + // ( + // i + // 2) [i] == ) + // ) ( ) ( ) ) + // i-1 i + // 4 0 + + // ( ( ) ( ) ) + // i-1 i + // 4 至少6 + // + // ( ) ( ) ( ( ) ( ) ) + // 0 1 2 3 4 5 6 7 8 9 + // 4 10 + // ( ) ( ) ( ) ( ( ) ) ( ( ( ) ) ) + // a b c d e f g h i j k l m n o p + // 0 2 0 4 0 6 0 0 2 10 + public static int longestValidParentheses(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int[] dp = new int[str.length]; + int pre = 0; + int ans = 0; + // dp[0] = 0 + for (int i = 1; i < str.length; i++) { + if (str[i] == ')') { + // pre 找到和当前 ) 括号配的位置! + // ? ( ( ) ) ) + // 4 5 6 7 8 9(i) + // 4 + // + // ? ( ) ) + // 4 5 6 7 8 9(i) + // 2 + // + // + // ( ) + // 4 5 6 7 8 9(i) + // 0 + // ? ) + // pre i + pre = i - dp[i - 1] - 1; + // pre < 0 + // ( ) ( ) ) + // 0 1 2 3 4 + // 0 + // ( ) + // pre i + // 0 + if (pre >= 0 && str[pre] == '(') { + // ( ........ ) + // pre-1 pre i + dp[i] = dp[i - 1] + 2 + (pre > 0 ? dp[pre - 1] : 0); + } + // dp[i] = 0 + } + // ( dp[i] = 0 + ans = Math.max(ans, dp[i]); + } + return ans; + } + +} diff --git a/公开课/class092/Code03_CompleteTreeNodeNumber.java b/公开课/class092/Code03_CompleteTreeNodeNumber.java new file mode 100644 index 0000000..cf57943 --- /dev/null +++ b/公开课/class092/Code03_CompleteTreeNodeNumber.java @@ -0,0 +1,105 @@ +package class092; + +// 本题测试链接 : https://leetcode.cn/problems/count-complete-tree-nodes/ +public class Code03_CompleteTreeNodeNumber { + + // 提交时不要提交这个类 + public class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + // 1 + // 2 3 + // 4 5 6 7 + // .... + // 遍历做法!不行!所有节点要走一遍!O(N), N是节点个数! + // 1 1 + // 2 3 2 + // 4 5 6 7 3 + // 8 9 a b c d e f 4 + // g h i j k l 5 + // + // 先从1出发,看1的右树的最左节点,到了哪一层! + // c 没到 最深! 说明,1的右树是满的!高度是3,节点数: 7个 + // 1自己,1个节点! 1 + 1的右树 = 8个 + // 整棵树的所有节点: 8个 + 1的左树节点数(递归!) + // + // 再从2出发,看2的右树的最左节点,到了哪一层! + // k 到了最深! 说明,2的左树是满的!左树高度3,2的左树 = 7个 + // 2自己,1个节点! 2 + 2的左树 = 8个 + // 2这棵树所有的节点 = 8个节点 + 2的右树节点个数(递归!) + // + // 继续从5出发,看5的右树的最左节点,到了哪一层! + // b 没到 最深!说明,5的右树是满的!高度是1,节点数: 1个 + // 5自己,1个节点! 5 + 5的右树 = 2个 + // 5这棵树所有的节点 = 2个 + 5的左树节点数(递归!) + // + // 从a出发!看a的右树的最左节点,到了哪一层! + // + + + + // 最深到了4层!这个结论记录! + // + // 满二叉树,高度是h,满二叉树的节点数 2^h - 1 + // 提交如下的方法 + public static int countNodes(TreeNode head) { + if (head == null) { + return 0; + } + // 当前 几层的节点 最深层是几层 + return bs(head, 1, mostLeftLevel(head, 1)); + } + + // 当前来到node节点,node节点在level层,总层数是h + // 返回node为头的子树(必是完全二叉树),有多少个节点 + public static int bs(TreeNode node, int Level, int h) { + if (Level == h) { + return 1; + } + if (mostLeftLevel(node.right, Level + 1) == h) { + // node的右树的最左节点,到了最后一层! + // 说明node的左树,是满的! + // 左树的节点数 + 1个(node自己) + 右树去递归! + // 2^(h - level) -> 1 << (h - level) + // node 5(level) + // 6 + // 7 + // 8(最深h) + return (1 << (h - Level)) + bs(node.right, Level + 1, h); + } else { + // node的右树的最左节点,没到最后一层! + // 说明node的右树,是满的!-1 + return (1 << (h - Level - 1)) + bs(node.left, Level + 1, h); + } + } + + // 时间复杂度 + // 完全二叉树的节点数是N, 完全二叉树的高度 h -> log N + // + // x 去x右树上走个高度 + // a 去a右树上走个高度 + // d 去d右树上走个高度 + // f 去f右树上走个高度 + // y 去y右树上走个高度 + // h O(h^2) h logN + // O( (logN)平方 ) < O(N) + // 如果node在第level层, + // 求以node为头的子树,最大深度是多少 + // node为头的子树,一定是完全二叉树 + // x 19 + // a 20 + // b 21 + // c 22 + // null 23 + public static int mostLeftLevel(TreeNode node, int level) { + while (node != null) { + level++; + node = node.left; + } + return level - 1; + } + +} diff --git a/公开课/class093/Code01_FindDuplicateOnlyOne.java b/公开课/class093/Code01_FindDuplicateOnlyOne.java new file mode 100644 index 0000000..b7f45d3 --- /dev/null +++ b/公开课/class093/Code01_FindDuplicateOnlyOne.java @@ -0,0 +1,117 @@ +package class093; + +import java.util.Arrays; +import java.util.HashSet; + +// 来自学员问题,阿里面试题 +// 1、2、3...n-1、n、n、n+1、n+2... +// 在这个序列中,只有一个数字有重复(n) +// 这个序列是无序的,找到重复数字n +// 这个序列是有序的,找到重复数字n +public class Code01_FindDuplicateOnlyOne { + + // 为了测试 + // 绝对正确,但是直接遍历+哈希表,没有得分的方法 + public static int right(int[] arr) { + HashSet set = new HashSet<>(); + for (int num : arr) { + if (set.contains(num)) { + return num; + } + set.add(num); + } + return -1; + } + + // 符合题目要求的、无序数组,找重复数 + // 时间复杂度O(N),额外空间复杂度O(1) + public static int findDuplicate(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + // 0位置开始跳,slow == 0,arr[0] + // 0位置开始跳,fast == 0,arr[arr[0]] + int slow = arr[0]; + int fast = arr[arr[0]]; + // slow 和 fast如果没相遇,就一直跳 ! + while (slow != fast) { + slow = arr[slow]; + fast = arr[arr[fast]]; + } + // slow == fast + fast = 0; + while (slow != fast) { + fast = arr[fast]; + slow = arr[slow]; + } + // 再相遇!一个结论 + return slow; + } + + // 符合题目要求的、有序数组,找重复数 + // 时间复杂度O(logN),额外空间复杂度O(1) + public static int findDuplicateSorted(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + int l = 0; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if ((m - 1 >= 0 && arr[m - 1] == arr[m]) || (m + 1 < arr.length && arr[m + 1] == arr[m])) { + ans = arr[m]; + break; + } + if (m - l == arr[m] - arr[l]) { + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n + 1]; + for (int i = 0; i < n; i++) { + ans[i] = i + 1; + } + ans[n] = (int) (Math.random() * n) + 1; + for (int i = n; i > 0; i--) { + int j = (int) (Math.random() * (i + 1)); + int tmp = ans[i]; + ans[i] = ans[j]; + ans[j] = tmp; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray((int) (Math.random() * N) + 1); + if (right(arr) != findDuplicate(arr)) { + System.out.println("未排序情况出错!"); + } + Arrays.sort(arr); + if (right(arr) != findDuplicateSorted(arr)) { + System.out.println("排序情况出错!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(right(arr)); + System.out.println(findDuplicateSorted(arr)); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class093/Code02_SellingPiecesOfWood.java b/公开课/class093/Code02_SellingPiecesOfWood.java new file mode 100644 index 0000000..d974266 --- /dev/null +++ b/公开课/class093/Code02_SellingPiecesOfWood.java @@ -0,0 +1,165 @@ +package class093; + +// 给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽。 +// 同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei]  +// 表示你可以以 pricei 元的价格卖一块高为 hi 宽为 wi 的矩形木块。 +// 每一次操作中,你必须按下述方式之一执行切割操作,以得到两块更小的矩形木块: +// 沿垂直方向按高度 完全 切割木块,或 +// 沿水平方向按宽度 完全 切割木块 +// 在将一块木块切成若干小木块后,你可以根据 prices 卖木块。 +// 你可以卖多块同样尺寸的木块。 +// 你不需要将所有小木块都卖出去。 +// 你 不能 旋转切好后木块的高和宽。 +// 请你返回切割一块大小为 m x n 的木块后,能得到的 最多 钱数。 +// 注意你可以切割木块任意次。 +// 测试链接 : https://leetcode.cn/problems/selling-pieces-of-wood/ +public class Code02_SellingPiecesOfWood { + + public static int zuo(int m, int n, int[][] projects) { + // 50 * 60 + int[][] prices = new int[m + 1][n + 1]; + for (int[] project : projects) { + int row = project[0]; + int col = project[1]; + int value = project[2]; + prices[row][col] = Math.max(prices[row][col], value); + } + return maxMoney(m, n, prices); + } + + // 当前还剩下的木板,规格 : m * n + // 请按照题目说的方式来切割! + // 每一种规格的报价,都在prices里面 + // 比如,想要查 10 * 20规格的木板,的最好报价,prices[10][20] 值! + // 返回最大的钱数! + public static int maxMoney(int m, int n, int[][] prices) { + if (m == 0 || n == 0) { + return 0; + } + // m > 0, n > 0 + // 可能性1 : 当前的木板根本不切,看看有没有报价 + int p1 = prices[m][n]; + // 可能性2 : 当前的木板水平切 + int p2 = 0; + for (int up = 1; up < m; up++) { + int down = m - up; + int upMoney = maxMoney(up, n, prices); + int downMoney = maxMoney(down, n, prices); + int cur = upMoney + downMoney; + p2 = Math.max(p2, cur); + } + // 可能性3 : 当前木板,垂直切 + int p3 = 0; + for (int left = 1; left < n; left++) { + int right = n - left; + int leftMoney = maxMoney(m, left, prices); + int rightMoney = maxMoney(m, right, prices); + int cur = leftMoney + rightMoney; + p3 = Math.max(p3, cur); + } + return Math.max(p1, Math.max(p2, p3)); + } + + // 递归尝试版本 + public static long sellingWood1(int m, int n, int[][] prices) { + // 单一报价 + long[][] values = new long[m + 1][n + 1]; + // 2 * 7 10元 + // 2 * 7 100元 + for (int[] p : prices) { + values[p[0]][p[1]] = Math.max(values[p[0]][p[1]], p[2]); + } + return f1(m, n, values); + } + + public static long f1(int m, int n, long[][] values) { + if (m == 0 || n == 0) { + return 0; + } + long ans = values[m][n]; + for (int split = 1; split < m; split++) { + ans = Math.max(ans, f1(split, n, values) + f1(m - split, n, values)); + } + for (int split = 1; split < n; split++) { + ans = Math.max(ans, f1(m, split, values) + f1(m, n - split, values)); + } + return ans; + } + + // 递归版本 + 记忆化搜索 + public static long sellingWood2(int m, int n, int[][] prices) { + long[][] values = new long[m + 1][n + 1]; + for (int[] p : prices) { + values[p[0]][p[1]] = Math.max(values[p[0]][p[1]], p[2]); + } + long[][] dp = new long[m + 1][n + 1]; + // dp[10][20] :没算过,-1 + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + dp[i][j] = -1; + } + } + return f2(m, n, values, dp); + } + + // m n 一旦确定,返回值,确定! + // dp[m][n] == -1 没算过! + // dp[m][n] != -1 之前算过!结果是什么呢?dp[m][n]的值 + public static long f2(int m, int n, long[][] values, long[][] dp) { + if (m == 0 || n == 0) { + return 0; + } + if (dp[m][n] != -1) { + return dp[m][n]; + } + long ans = values[m][n]; + for (int split = 1; split < m; split++) { + ans = Math.max(ans, f2(split, n, values, dp) + f2(m - split, n, values, dp)); + } + for (int split = 1; split < n; split++) { + ans = Math.max(ans, f2(m, split, values, dp) + f2(m, n - split, values, dp)); + } + dp[m][n] = ans; + return ans; + } + + // 严格位置依赖的动态规划版本 + 优化 + // 优化1 : 递归的形式,改成迭代形式,课上讲了 + // 优化2 : prices中的单块收益直接填入dp表即可,如果有更好的分割方案,更新掉 + // 优化3 : 分割只需要枚举一半即可 + public static long sellingWood3(int m, int n, int[][] prices) { + // dp表! + long[][] dp = new long[m + 1][n + 1]; + for (int[] p : prices) { + // {3, 5, 100} + // 0 1 2 + // dp[3][5] = 100 + dp[p[0]][p[1]] = p[2]; + } + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 垂直分割 + // i * j = 100 * 100 + // dp[100][1] + dp[100][99] + // dp[100][2] + dp[100][98] + // .. + for (int k = 1; k <= (j >> 1); k++) { + dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[i][j - k]); + } + // 水平分割 + // 100 * 100 + // 1) 1 * 100 + 99 * 100 + // 1) 2 * 100 + 98 * 100 + // i * j + // 1) 1 * j + (i - 1) * i; + // 2) 2 * j + (i - 2) * j; + // k) k * j + (i - k) * j; + for (int k = 1; k <= (i >> 1); k++) { + dp[i][j] = Math.max(dp[i][j], dp[k][j] + dp[i - k][j]); + } + } + } + return dp[m][n]; + } + +} diff --git a/公开课/class094/Code01_SortStackUsingRecursive.java b/公开课/class094/Code01_SortStackUsingRecursive.java new file mode 100644 index 0000000..0da47d5 --- /dev/null +++ b/公开课/class094/Code01_SortStackUsingRecursive.java @@ -0,0 +1,205 @@ +package class094; + +import java.util.Stack; + +// 栈只提供push、pop、isEmpty三个方法 +// 请完成无序栈的排序,要求排完序之后,从栈顶到栈底从小到大 +// 只能使用栈提供的push、pop、isEmpty三个方法、以及递归函数 +// 除此之外不能使用任何的容器,任何容器都不许,连数组也不行 +// 也不能自己定义任何结构体 +// 就是只用: +// 1) 栈提供的push、pop、isEmpty三个方法 +// 2) 简单返回值的递归函数 +public class Code01_SortStackUsingRecursive { + + public static void sortStack(Stack stack) { + int deep = deep(stack); + while (deep > 0) { + int max = max(stack, deep); + int k = times(stack, max, deep); + down(stack, deep, max, k); + deep -= k; + } + } + + // 返回栈的深度 + // stack push pop isEmpty + public static int deep(Stack stack) { + if (stack.isEmpty()) { + return 0; + } + int num = stack.pop(); + int deep = deep(stack) + 1; + stack.push(num); + return deep; + } + + // 从栈当前的顶部开始,往下数deep层 + //) 返回这deep层里的最大值 + public static int max(Stack stack, int deep) { + if (deep == 0) { + return Integer.MIN_VALUE; + } + int num = stack.pop(); + int restMax = max(stack, deep - 1); + int max = Math.max(num, restMax); + stack.push(num); + return max; + } + + // 从栈当前的顶部开始,往下数deep层,已知最大值是max了 + // 返回,max出现了几次,不改变栈的数据状况 + public static int times(Stack stack, int max, int deep) { + if (deep == 0) { + return 0; + } + int num = stack.pop(); + int restTimes = times(stack, max, deep - 1); + int times = restTimes + (num == max ? 1 : 0); + stack.push(num); + return times; + } + + // 从栈当前的顶部开始,往下数deep层,已知最大值是max,出现了k次 + // 请把这k个最大值沉底,剩下的数据状况不变 + public static void down(Stack stack, int deep, int max, int k) { + if (deep == 0) { + for (int i = 0; i < k; i++) { + stack.push(max); + } + } else { + int num = stack.pop(); + down(stack, deep - 1, max, k); + if (num != max) { + stack.push(num); + } + } + } + + public static void sort(Stack stack) { + int deep = size(stack); + while (deep > 0) { + int max = findMax(stack, deep); + int k = findMaxTimes(stack, deep, max); + maxDown(stack, deep, max, k); + deep -= k; + } + } + + // 求栈的大小 + // 但是不改变栈的任何数据状况 + public static int size(Stack stack) { + if (stack.isEmpty()) { + return 0; + } + int hold = stack.pop(); + int size = size(stack) + 1; + stack.push(hold); + return size; + } + + // 从stack顶部出发,只往下找deep层 + // 返回最大值 + // 完全不改变stack的任何数据状况 + public static int findMax(Stack stack, int deep) { + if (deep == 0) { + return Integer.MIN_VALUE; + } + int num = stack.pop(); + int restMax = findMax(stack, deep - 1); + int ans = Math.max(num, restMax); + stack.push(num); + return ans; + } + + // 已知从stack顶部出发,只往下找deep层,最大值是max + // 返回这个最大值出现了几次,只找到deep层!再往下不找了! + // 完全不改变stack的任何数据状况 + public static int findMaxTimes(Stack stack, int deep, int max) { + if (deep == 0) { + return 0; + } + int num = stack.pop(); + int times = findMaxTimes(stack, deep - 1, max); + times += num == max ? 1 : 0; + stack.push(num); + return times; + } + + // 已知从stack顶部出发,只往下找deep层,最大值是max + // 并且这个max出现了k次 + // 请把这k个max沉底,不是沉到stack整体的底部,而是到deep层 + // stack改变数据状况,但是只在从顶部到deep层的范围上改变 + public static void maxDown(Stack stack, int deep, int max, int k) { + if (deep == 0) { + for (int i = 0; i < k; i++) { + stack.push(max); + } + } else { + int cur = stack.pop(); + maxDown(stack, deep - 1, max, k); + if (cur < max) { + stack.push(cur); + } + } + } + + // 为了测试 + // 生成随机栈 + public static Stack generateRandomStack(int n, int v) { + Stack ans = new Stack(); + for (int i = 0; i < n; i++) { + ans.add((int) (Math.random() * v)); + } + return ans; + } + + // 为了测试 + // 检测栈是不是有序的 + public static boolean isSorted(Stack stack) { + int step = Integer.MIN_VALUE; + while (!stack.isEmpty()) { + if (step > stack.peek()) { + return false; + } + step = stack.pop(); + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + Stack test = new Stack(); + test.add(7); + test.add(5); + test.add(4); + test.add(5); + test.add(3); + test.add(6); + test.add(3); + test.add(1); + test.add(4); + test.add(9); + // 1 5 4 5 3 2 3 1 4 2 + sortStack(test); + while (!test.isEmpty()) { + System.out.println(test.pop()); + } + + int N = 20; + int V = 20; + int testTimes = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N); + Stack stack = generateRandomStack(n, V); + sort(stack); + if (!isSorted(stack)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class094/Code02_ReversePolishNotation.java b/公开课/class094/Code02_ReversePolishNotation.java new file mode 100644 index 0000000..90cf58d --- /dev/null +++ b/公开课/class094/Code02_ReversePolishNotation.java @@ -0,0 +1,101 @@ +package class094; + +import java.util.Stack; + +// 给定一个逆波兰式 +// 转化成正确的中序表达式 +// 要求只有必要加括号的地方才加括号 +public class Code02_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) { + 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 + // 3 5 + + + // 5 * ( 1 + 2) + // 5 1 2 + * + + // 3*(-5+13)+6/(2-3+2)-4*5*3 + // 24 + 6 - 60 + String rpn = "3 -5 13 + * 6 2 3 - 2 + / + 4 5 3 * * -"; + + System.out.println(getAns(rpn)); + + String ans = convert(rpn); + + System.out.println(ans); + } + +} diff --git a/公开课/class094/Code03_RightView.java b/公开课/class094/Code03_RightView.java new file mode 100644 index 0000000..df65edf --- /dev/null +++ b/公开课/class094/Code03_RightView.java @@ -0,0 +1,97 @@ +package class094; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +// 二叉树右视图, +// 有Leetcode测试链接 : https://leetcode.cn/problems/binary-tree-right-side-view/ +// 提交如下rightSideView方法,可以直接通过 +public class Code03_RightView { + + // 不要提交这个类 + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int v) { + val = v; + } + } + + public static void bfs(TreeNode head) { + Queue queue = new LinkedList<>(); + queue.add(head); + while (!queue.isEmpty()) { + TreeNode cur = queue.poll(); + if (cur.left != null) { + queue.add(cur.left); + } + if (cur.right != null) { + queue.add(cur.right); + } + } + } + + public static void bfs2(TreeNode head) { + Queue queue = new LinkedList<>(); + queue.add(head); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size - 1; i++) { + TreeNode cur = queue.poll(); + if (cur.left != null) { + queue.add(cur.left); + } + if (cur.right != null) { + queue.add(cur.right); + } + } + + TreeNode cur = queue.poll(); + if (cur.left != null) { + queue.add(cur.left); + } + if (cur.right != null) { + queue.add(cur.right); + } + + } + } + + // 只提交下面的方法 + public static List rightSideView(TreeNode root) { + List ans = new ArrayList<>(); + if (root == null) { + return ans; + } + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + int size = queue.size(); + TreeNode cur = queue.poll(); + ans.add(cur.val); + if (cur.right != null) { + queue.add(cur.right); + } + if (cur.left != null) { + queue.add(cur.left); + } + size--; + while (size > 0) { + cur = queue.poll(); + if (cur.right != null) { + queue.add(cur.right); + } + if (cur.left != null) { + queue.add(cur.left); + } + size--; + } + } + return ans; + } + +} diff --git a/公开课/class095/Code01_NumberOfPeopleAwareOfASecret.java b/公开课/class095/Code01_NumberOfPeopleAwareOfASecret.java new file mode 100644 index 0000000..fdf89a5 --- /dev/null +++ b/公开课/class095/Code01_NumberOfPeopleAwareOfASecret.java @@ -0,0 +1,63 @@ +package class095; + +// 在第 1 天,有一个人发现了一个秘密。 +// 给你一个整数 delay ,表示每个人会在发现秘密后的 delay 天之后, +// 每天 给一个新的人 分享 秘密。 +// 同时给你一个整数 forget ,表示每个人在发现秘密 forget 天之后会 忘记 这个秘密。 +// 一个人 不能 在忘记秘密那一天及之后的日子里分享秘密。 +// 给你一个整数 n ,请你返回在第 n 天结束时,知道秘密的人数。 +// 由于答案可能会很大,请你将结果对 109 + 7 取余 后返回。 +// 测试链接 : https://leetcode.cn/problems/number-of-people-aware-of-a-secret/ +public class Code01_NumberOfPeopleAwareOfASecret { + + // 1天 。。。。 n天 + public static int peopleAwareOfSecret(int n, int delay, int forget) { + long mod = 1000000007; + // 从第1天开始,每一天到来的时候,争取把这三个数组正确的人数填对 + // dpKnow[i], 第i天知道秘密的人 + long[] dpKnow = new long[n + 1]; + // dpForget[i], 第i天将要忘记秘密的人 + long[] dpForget = new long[n + 1]; + // dpShare[i], 第i天可以分享秘密的人 + long[] dpShare = new long[n + 1]; + + // 第1天的时候,知道秘密的人1个,A + // 第1天的时候,将要忘记秘密的人0个 + // 第1天的时候,可以分享秘密的人0个 + dpKnow[1] = 1; + if (1 + forget <= n) { + dpForget[1 + forget] = 1; + } + if (1 + delay <= n) { + dpShare[1 + delay] = 1; + } + // 从第2天开始!i + for (int i = 2; i <= n; i++) { + // 第i天 + // dpKnow[i - 1] - dpForget[i] + dpShare[i] + dpKnow[i] = (mod + dpKnow[i - 1] - dpForget[i] + dpShare[i]) % mod; + + + + + // 第i天,新增知道秘密的,dpShare[i] + if (i + forget <= n) { + + // dpShare[i] 是第i天,刚知道秘密的人! + // 这批人,会在i + forget天,都忘了! + dpForget[i + forget] = dpShare[i]; + } + if (i + delay <= n) { + // dpShare[i + delay - 1] + dpShare[i] - dpForget[i + delay] + // i + delay 天 , 100天后,会分享秘密的人 + // 第i天,有一些新人,i + delay天分享,一部分, dpShare[i] + // 第二部分呢?i + delay - 1天,知道秘密并且会散播的人,- dpForget[i + delay] + dpShare[i + delay] = + (mod + dpShare[i + delay - 1] - dpForget[i + delay] + + dpShare[i]) % mod; + } + } + return (int) dpKnow[n]; + } + +} diff --git a/公开课/class095/Code02_MaxNumberUnderLimit.java b/公开课/class095/Code02_MaxNumberUnderLimit.java new file mode 100644 index 0000000..2c6f765 --- /dev/null +++ b/公开课/class095/Code02_MaxNumberUnderLimit.java @@ -0,0 +1,213 @@ +package class095; + +import java.util.Arrays; + +// 来自字节 +// 输入: +// 去重数组arr,里面的数只包含0~9 +// limit,一个数字 +// 返回: +// 要求比limit小的情况下,能够用arr拼出来的最大数字 +public class Code02_MaxNumberUnderLimit { + + public static int tmp = 0; + + // 暴力尝试的方法 limit -1 -2 -3 -4 + public static int maxNumber1(int[] arr, int limit) { + tmp = 0; + Arrays.sort(arr); + limit--; + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + process1(arr, 0, offset, limit); + if (tmp == 0) { + int rest = 0; + offset /= 10; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + return tmp; + } + + public static void process1(int[] arr, int num, int offset, int limit) { + if (offset == 0) { + if (num <= limit) { + tmp = Math.max(tmp, num); + } + } else { + for (int cur : arr) { + process1(arr, num * 10 + cur, offset / 10, limit); + } + } + } + + // 正式方法 + // 用arr中的数字去拼,< limit ,尽量大 + // 能拼出来尽量大的数字,返回 + public static int maxNumber2(int[] arr, int limit) { + // [6,2,8] [8,2,6] [2,6,8] + Arrays.sort(arr); + // limit - 1 + limit--; + // <= limit,尽量大即可 + // limit : 657321 + // offset: 100000 + // 当前数 : (limit / offset) % 10 -> 6 + // 下一个 : + // limit : 657321 + // offset: 10000 + // 当前数 : (limit / offset) % 10 -> 5 + // 下一个 : + // limit : 657321 + // offset: 1000 + // 当前数 : (limit / offset) % 10 -> 7 + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + + // 不要这么写,可能溢出! +// while(offset <= limit) { +// offset *=10; +// } +// offset /=10; + + + // limit : 65431098 + // offset: 10000000 + // arr中的数字,<=limit, offset方便我提取数字用的! + // limit : 65431098 + // process2 : 拼出来的数字,和limit位数一定要一样长!!!! + // 返回尽量大的数字! + // 如果拼出来的数字,无法和limit位数一样长,返回-1 + int ans = process2(arr, limit, offset); + if (ans != -1) { + return ans; + } else { + // limit : 65431098 + // offset: 1000000 + // arr[5] + // 5000000 +// 500000 +// 50000 + offset /= 10; + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + } + + // 可以用arr中的数字! + // 去拼<=limit,尽量大!位数一定要和limit一样长 + // offset是用来取数字的! + // limit = 876530 + // offset= 1000 + // 87这两位,一定做的决定是:追平!而且真的追平了! + public static int process2(int[] arr, int limit, int offset) { + // limit = 876530 + // offset= 0 + if (offset == 0) { + return limit; + } + // limit = 876530 + // offset= 1000 + int cur = (limit / offset) % 10; + // 当前数字已经知道了cur + // 去arr中拿数字试图追平! + // 6 + // 1) 拿到了能追平的数字 + // 2) 没拿到能追平的数字,但又较小的数字 + // 3) <=当前想追平的数字,都不存在 -1 + int near = near(arr, cur); + if (near == -1) { + return -1; + } else if (arr[near] == cur) { // 1) 拿到了能追平的数字 + // 当前位达成了! + int ans = process2(arr, limit, offset / 10); + if (ans != -1) { // 后续计算出了最优结果! + return ans; + } else if (near > 0) { + near--; + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } else { // 后续搞不定!当前位也没有办法再下降了 + return -1; + } + } else { + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } + } + + public static int rest(int[] arr, int offset) { + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + + public static int near(int[] arr, int num) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int near = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= num) { + near = m; + l = m + 1; + } else { + r = m - 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray() { + int[] arr = new int[(int) (Math.random() * 10) + 1]; + boolean[] cnt = new boolean[10]; + for (int i = 0; i < arr.length; i++) { + do { + arr[i] = (int) (Math.random() * 10); + } while (cnt[arr[i]]); + cnt[arr[i]] = true; + } + return arr; + } + + public static void main(String[] args) { + int max = 3000; + int testTime = 100; + System.out.println("测试开始"); + for (int i = 0; i < max; i++) { + int[] arr = randomArray(); + for (int j = 0; j < testTime; j++) { + int ans1 = maxNumber1(arr, i); + int ans2 = maxNumber2(arr, i); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println("数组为 :"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("数字为 :" + i); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + System.out.println("测试结束"); + + } + +} diff --git a/公开课/class095/Code03_SwimInRisingWater.java b/公开课/class095/Code03_SwimInRisingWater.java new file mode 100644 index 0000000..4267297 --- /dev/null +++ b/公开课/class095/Code03_SwimInRisingWater.java @@ -0,0 +1,155 @@ +package class095; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 在一个 n x n 的整数矩阵 grid 中, +// 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。 +// 当开始下雨时,在时间为 t 时,水池中的水位为 t 。 +// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。 +// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。 +// 当然,在你游泳的时候你必须待在坐标方格里面。 +// 你从坐标方格的左上平台 (0,0) 出发。 +// 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。 +// 测试链接 :https://leetcode.cn/problems/swim-in-rising-water +public class Code03_SwimInRisingWater { + + // 并查集的解法 + public static int swimInWater1(int[][] grid) { + // 行号 + int n = grid.length; + // 列号 + int m = grid[0].length; + // [0,0,5] + // [0,1,3].... + int[][] points = new int[n * m][3]; + int pi = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + points[pi][0] = i; + points[pi][1] = j; + points[pi++][2] = grid[i][j]; + } + } + // 所有格子小对象,生成好了! + // 排序![a,b,c] [d,e,f] + Arrays.sort(points, (a, b) -> a[2] - b[2]); + // 生成并查集!n * m + // 初始化的时候,把所有格子独自成一个集合! + UnionFind uf = new UnionFind(n, m); + int ans = 0; + for (int i = 0; i < points.length; i++) { + int r = points[i][0]; + int c = points[i][1]; + int v = points[i][2]; + if (r > 0 && grid[r - 1][c] <= v) { + uf.union(r, c, r - 1, c); + } + if (r < n - 1 && grid[r + 1][c] <= v) { + uf.union(r, c, r + 1, c); + } + if (c > 0 && grid[r][c - 1] <= v) { + uf.union(r, c, r, c - 1); + } + if (c < m - 1 && grid[r][c + 1] <= v) { + uf.union(r, c, r, c + 1); + } + if (uf.isSameSet(0, 0, n - 1, m - 1)) { + ans = v; + break; + } + } + return ans; + } + + public static class UnionFind { + public int col; + public int pointsSize; + public int[] father; + public int[] size; + public int[] help; + + public UnionFind(int n, int m) { + col = m; + pointsSize = n * m; + father = new int[pointsSize]; + size = new int[pointsSize]; + help = new int[pointsSize]; + for (int i = 0; i < pointsSize; i++) { + father[i] = i; + size[i] = 1; + } + } + + 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; + } + + private int index(int i, int j) { + return i * col + j; + } + + public void union(int row1, int col1, int row2, int col2) { + int f1 = find(index(row1, col1)); + int f2 = find(index(row2, col2)); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + father[f2] = f1; + size[f1] += size[f2]; + } else { + father[f1] = f2; + size[f2] += size[f1]; + } + } + } + + public boolean isSameSet(int row1, int col1, int row2, int col2) { + return find(index(row1, col1)) == find(index(row2, col2)); + } + + } + + // Dijkstra算法 + public static int swimInWater2(int[][] grid) { + int n = grid.length; + int m = grid[0].length; + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); + boolean[][] visited = new boolean[n][m]; + heap.add(new int[] { 0, 0, grid[0][0] }); + int ans = 0; + while (!heap.isEmpty()) { + int r = heap.peek()[0]; + int c = heap.peek()[1]; + int v = heap.peek()[2]; + heap.poll(); + if (visited[r][c]) { + continue; + } + visited[r][c] = true; + if (r == n - 1 && c == m - 1) { + ans = v; + break; + } + add(grid, heap, visited, r - 1, c, v); + add(grid, heap, visited, r + 1, c, v); + add(grid, heap, visited, r, c - 1, v); + add(grid, heap, visited, r, c + 1, v); + } + return ans; + } + + public static void add(int[][] grid, PriorityQueue heap, boolean[][] visited, int r, int c, int preV) { + if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length && !visited[r][c]) { + heap.add(new int[] { r, c, preV + Math.max(0, grid[r][c] - preV) }); + } + } + +} diff --git a/公开课/class096/Code01_ParenthesesDye.java b/公开课/class096/Code01_ParenthesesDye.java new file mode 100644 index 0000000..d779ea9 --- /dev/null +++ b/公开课/class096/Code01_ParenthesesDye.java @@ -0,0 +1,169 @@ +package class096; + +// 来自猿辅导 +// 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...]范围上做决定 + // 之前在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; + } + // i.... n-i(10个) ...(11个) + 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/公开课/class096/Code02_ShortestImpossibleSequenceOfRolls.java b/公开课/class096/Code02_ShortestImpossibleSequenceOfRolls.java new file mode 100644 index 0000000..9f02f9e --- /dev/null +++ b/公开课/class096/Code02_ShortestImpossibleSequenceOfRolls.java @@ -0,0 +1,38 @@ +package class096; + +import java.util.Arrays; + +// 给你一个长度为 n 的整数数组 rolls 和一个整数 k 。 +// 你扔一个 k 面的骰子 n 次,骰子的每个面分别是 1 到 k , +// 其中第 i 次扔得到的数字是 rolls[i] 。 +// 请你返回 无法 从 rolls 中得到的 最短 骰子子序列的长度。 +// 扔一个 k 面的骰子 len 次得到的是一个长度为 len 的 骰子子序列 。 +// 注意 ,子序列只需要保持在原数组中的顺序,不需要连续。 +// 测试链接 : https://leetcode.cn/problems/shortest-impossible-sequence-of-rolls/ +public class Code02_ShortestImpossibleSequenceOfRolls { + + // 所有数字1~k + public static int shortestSequence(int[] rolls, int k) { + // 1~k上,某个数字是否收集到了! + // set[i] == true + // set[i] == false + boolean[] set = new boolean[k + 1]; + // 当前这一套,收集了几个数字了? + int size = 0; + // 一共能收集全几套 + int ans = 0; + for (int num : rolls) { + if (!set[num]) { + set[num] = true; + size++; + } + if (size == k) { + ans++; + Arrays.fill(set, false); + size = 0; + } + } + return ans + 1; + } + +} diff --git a/公开课/class096/Code03_SwimInRisingWater.java b/公开课/class096/Code03_SwimInRisingWater.java new file mode 100644 index 0000000..8253f5f --- /dev/null +++ b/公开课/class096/Code03_SwimInRisingWater.java @@ -0,0 +1,155 @@ +package class096; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 在一个 n x n 的整数矩阵 grid 中, +// 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。 +// 当开始下雨时,在时间为 t 时,水池中的水位为 t 。 +// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。 +// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。 +// 当然,在你游泳的时候你必须待在坐标方格里面。 +// 你从坐标方格的左上平台 (0,0) 出发。 +// 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。 +// 测试链接 :https://leetcode.cn/problems/swim-in-rising-water +public class Code03_SwimInRisingWater { + + // 并查集的解法 + public static int swimInWater1(int[][] grid) { + // 行号 + int n = grid.length; + // 列号 + int m = grid[0].length; + // [0,0,5] + // [0,1,3].... + int[][] points = new int[n * m][3]; + int pi = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + points[pi][0] = i; + points[pi][1] = j; + points[pi++][2] = grid[i][j]; + } + } + // 所有格子小对象,生成好了! + // 排序![a,b,c] [d,e,f] + Arrays.sort(points, (a, b) -> a[2] - b[2]); + // 生成并查集!n * m + // 初始化的时候,把所有格子独自成一个集合! + UnionFind uf = new UnionFind(n, m); + int ans = 0; + for (int i = 0; i < points.length; i++) { + int r = points[i][0]; + int c = points[i][1]; + int v = points[i][2]; + if (r > 0 && grid[r - 1][c] <= v) { + uf.union(r, c, r - 1, c); + } + if (r < n - 1 && grid[r + 1][c] <= v) { + uf.union(r, c, r + 1, c); + } + if (c > 0 && grid[r][c - 1] <= v) { + uf.union(r, c, r, c - 1); + } + if (c < m - 1 && grid[r][c + 1] <= v) { + uf.union(r, c, r, c + 1); + } + if (uf.isSameSet(0, 0, n - 1, m - 1)) { + ans = v; + break; + } + } + return ans; + } + + public static class UnionFind { + public int col; + public int pointsSize; + public int[] father; + public int[] size; + public int[] help; + + public UnionFind(int n, int m) { + col = m; + pointsSize = n * m; + father = new int[pointsSize]; + size = new int[pointsSize]; + help = new int[pointsSize]; + for (int i = 0; i < pointsSize; i++) { + father[i] = i; + size[i] = 1; + } + } + + 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; + } + + private int index(int i, int j) { + return i * col + j; + } + + public void union(int row1, int col1, int row2, int col2) { + int f1 = find(index(row1, col1)); + int f2 = find(index(row2, col2)); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + father[f2] = f1; + size[f1] += size[f2]; + } else { + father[f1] = f2; + size[f2] += size[f1]; + } + } + } + + public boolean isSameSet(int row1, int col1, int row2, int col2) { + return find(index(row1, col1)) == find(index(row2, col2)); + } + + } + + // Dijkstra算法 + public static int swimInWater2(int[][] grid) { + int n = grid.length; + int m = grid[0].length; + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); + boolean[][] visited = new boolean[n][m]; + heap.add(new int[] { 0, 0, grid[0][0] }); + int ans = 0; + while (!heap.isEmpty()) { + int r = heap.peek()[0]; + int c = heap.peek()[1]; + int v = heap.peek()[2]; + heap.poll(); + if (visited[r][c]) { + continue; + } + visited[r][c] = true; + if (r == n - 1 && c == m - 1) { + ans = v; + break; + } + add(grid, heap, visited, r - 1, c, v); + add(grid, heap, visited, r + 1, c, v); + add(grid, heap, visited, r, c - 1, v); + add(grid, heap, visited, r, c + 1, v); + } + return ans; + } + + public static void add(int[][] grid, PriorityQueue heap, boolean[][] visited, int r, int c, int preV) { + if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length && !visited[r][c]) { + heap.add(new int[] { r, c, preV + Math.max(0, grid[r][c] - preV) }); + } + } + +} diff --git a/公开课/class096/Code04_LongestOneLetterManyNumberString.java b/公开课/class096/Code04_LongestOneLetterManyNumberString.java new file mode 100644 index 0000000..a3cdb00 --- /dev/null +++ b/公开课/class096/Code04_LongestOneLetterManyNumberString.java @@ -0,0 +1,108 @@ +package class096; + +// 给定一个只由小写字母和数字字符组成的字符串str +// 要求子串必须只含有一个小写字母,数字字符数量随意 +// 求这样的子串最大长度是多少 +public class Code04_LongestOneLetterManyNumberString { + + // 一个绝对正确的暴力方法 + public static int right(String s) { + char[] str = s.toCharArray(); + int ans = 0; + for (int i = 0; i < str.length; i++) { + for (int j = i; j < str.length; j++) { + if (check(str, i, j)) { + ans = Math.max(ans, j - i + 1); + } + } + } + return ans; + } + + public static boolean check(char[] str, int l, int r) { + int letterNumber = 0; + for (int i = l; i <= r; i++) { + if (str[i] >= 'a' && str[i] <= 'z') { + letterNumber++; + } + } + return letterNumber == 1; + } + + // 用窗口 + // 时间复杂度O(N) + public static int zuo(String s) { + char[] str = s.toCharArray(); + int n = str.length; + // 窗口内小写字母的数量 + int letters = 0; + // 右边界 + // 0.....5 6(x) + // [Left, right) + // [Left, right-1] + // [0,0) -> 代表窗口一个数也没有 + int right = 0; + int ans = 0; + // 窗口开始的位置left + // 枚举了窗口每一个开始的位置 + for (int left = 0; left < n; left++) { + // left......right(停!) + while (right < n) { // right不能越界 + if (letters == 1 + && str[right] >= 'a' + && str[right] <= 'z') { + break; + } + // right往右扩! + if (str[right] >= 'a' && str[right] <= 'z') { + letters++; + } + right++; + } + // right已经来到X的位置 + // left......... X + if (letters == 1) { + ans = Math.max(ans, right - left); + } + // left 往右 吐出一个字符 + if (str[left] >= 'a' && str[left] <= 'z') { + letters--; + } + } + return ans; + } + + // 为了测试 + public static char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + + // 为了测试 + public static String randomString(int n) { + char[] str = new char[n]; + for (int i = 0; i < n; i++) { + str[i] = chars[(int) (Math.random() * chars.length)]; + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + String str = randomString(n); + int ans1 = right(str); + int ans2 = zuo(str); + if (ans1 != ans2) { + System.out.println(str); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class097/Code01_Cakes.java b/公开课/class097/Code01_Cakes.java new file mode 100644 index 0000000..79f6635 --- /dev/null +++ b/公开课/class097/Code01_Cakes.java @@ -0,0 +1,116 @@ +package class097; + +// 有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕 +// 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种 +// 拿的数量在1~m之间随意 +// 谁先拿完最后的蛋糕谁赢 +// 返回先手赢还是后手赢 +public class Code01_Cakes { + + // 草莓蛋糕a块 + // 巧克力蛋糕b块 + // 每次可以在任意一种上拿1~m块 + // 返回谁会赢,"先手" or "后手" + public static String[][][] dp = new String[101][101][101]; + + // 暴力方法 + // 为了验证 + public static String whoWin1(int a, int b, int m) { + if (m >= Math.max(a, b)) { // nim博弈 + return a != b ? "先手" : "后手"; + } + if (a == b) { + // 蛋糕一样多 + // 先手必输,因为先手不管拿什么,拿多少 + // 后手都在另一堆上,拿同样多的蛋糕 + // 继续让两堆蛋糕一样多 + // 最终先手必输,后手必赢 + return "后手"; + } + if (dp[a][b][m] != null) { + return dp[a][b][m]; + } + String ans = "后手"; + for (int pick = 1; pick <= Math.min(a, m); pick++) { + if (whoWin1(a - pick, b, m).equals("后手")) { + ans = "先手"; + } + if (ans.equals("先手")) { + break; + } + } + for (int pick = 1; pick <= Math.min(b, m); pick++) { + if (whoWin1(a, b - pick, m).equals("后手")) { + ans = "先手"; + } + if (ans.equals("先手")) { + break; + } + } + dp[a][b][m] = ans; + return ans; + } + + // 正式解法 + // 时间复杂度O(1) + // 先看nim博弈 + public static String whoWin2(int a, int b, int m) { + if (m >= Math.max(a, b)) { // nim博弈 + return a != b ? "先手" : "后手"; + } + if (a == b) { + // 蛋糕一样多 + // 先手必输,因为先手不管拿什么,拿多少 + // 后手都在另一堆上,拿同样多的蛋糕 + // 继续让两堆蛋糕一样多 + // 最终先手必输,后手必赢 + return "后手"; + } + // 如果 a != b + // 关注a和b的差值, + // 谁最先遇到差值为0,谁输 + // 那么这就是巴什博奕 + // 差值蛋糕数量共rest个。 + // 每次从最少取1个,最多取m个,最后取光的人取胜。 + // 如果rest=(m+1)*k + s (s!=0) 那么先手一定必胜 + // 因为第一次取走s个, + // 接下来无论对手怎么取, + // 先手都能保证取到所有(m+1)倍数的点, + // 那么循环下去一定能取到差值最后一个。 + int rest = Math.max(a, b) - Math.min(a, b); + return rest % (m + 1) != 0 ? "先手" : "后手"; + } + + public static void main(String[] args) { + +// int a = 7; +// int b = 5; +// int c = 1; +// // [. . .....] ^ != 0 +// // ^ == 0 +// System.out.println((a ^ b ^ c)); + +// + int V = 100; + System.out.println("测试开始"); + for (int a = 0; a <= V; a++) { + for (int b = 0; b <= V; b++) { + for (int m = 0; m <= V; m++) { + String ans1 = whoWin1(a, b, m); + String ans2 = whoWin2(a, b, m); + if (!ans1.equals(ans2)) { + System.out.println("出错了!"); + System.out.println("a : " + a); + System.out.println("b : " + b); + System.out.println("m : " + m); + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + break; + } + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class097/Code02_MinAddToMatch.java b/公开课/class097/Code02_MinAddToMatch.java new file mode 100644 index 0000000..e3afa3f --- /dev/null +++ b/公开课/class097/Code02_MinAddToMatch.java @@ -0,0 +1,121 @@ +package class097; + +// 测试链接 : https://www.nowcoder.com/practice/e391767d80d942d29e6095a935a5b96b +// 提交如下代码,把主类名改成Main,可以直接通过 +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Code02_MinAddToMatch { + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String line; + while ((line = br.readLine()) != null) { + System.out.println(minAdd(line.toCharArray())); + } + } + + // 主 zuo(str, 0, str.length - 1) + // str[L.....R]这个范围的字符串 + // 整体变成合法的,返回至少添加几个字符,能做到! + public static int zuo(char[] str, int L, int R) { + if (L == R) { + // str[L...R]只剩一个字符了 + // ( or ) or [ or ] + return 1; + } + if (L == R - 1) { + // str[L...R]只剩两个字符了 + // () 0 + // [] 0 + if ((str[L] == '(' && str[R] == ')') || (str[L] == '[' && str[R] == ']')) { + return 0; + } + return 2; + } + // 第一大类 : str[L....R]本身是最大的嵌套! + // 可能性1 : str[L]和str[R],自消化! + int p1LRkill = Integer.MAX_VALUE; + if ((str[L] == '(' && str[R] == ')') || (str[L] == '[' && str[R] == ']')) { + p1LRkill = zuo(str, L + 1, R - 1); + } + // 可能性2 : str[L]和str[R],无法自消化! + int p1lastL = 1 + zuo(str, L + 1, R); + int p1LasrR = zuo(str, L, R - 1) + 1; + + int p1 = Math.max(Math.min(p1LRkill, p1lastL), p1LasrR); + + // 第二大类 : str[L....R] 合法 + 合法,并列关系! + + int p2 = Integer.MAX_VALUE; + for (int m = L; m < R; m++) { + p2 = Math.min(p2, zuo(str, L, m) + zuo(str, m + 1, R)); + } + return Math.min(p1, p2); + } + + public static int minAdd(char[] s) { + int n = s.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 process(s, 0, s.length - 1, dp); + } + + // 让s[l...r]都完美匹配 + // 至少需要加几个字符 + public static int process(char[] s, int l, int r, int[][] dp) { + // 只有一个字符,不管是什么,要想配对,都需要添加一个字符 + if (l == r) { + return 1; + } + // 只有两个字符, + // 如果是()、[],那什么也不需要添加 + // 否则,都需要添加2个字符 + if (l == r - 1) { + if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) { + return 0; + } + return 2; + } + if (dp[l][r] != -1) { + return dp[l][r]; + } + // 重点是如下的过程 + // 可能性1,先搞定l+1...r,然后搞定l + // 比如s[l...r] = ([][] + // 先搞定[][],需要添加0个,然后搞定(,需要添加1个 + // 整体变成([][])搞定 + int p1 = 1 + process(s, l + 1, r, dp); + // 可能性2,先搞定l...r-1,然后搞定r + // 和可能性1同理 + int p2 = 1 + process(s, l, r - 1, dp); + // 可能性3,s[l]和s[r]天然匹配,需要搞定的就是l+1..r-1 + // 比如([[),搞定中间的[[,就是最优解了 + int p3 = Integer.MAX_VALUE; + if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) { + p3 = process(s, l + 1, r - 1, dp); + } + // 可能性后续:可能,最优解并不是l....r整体变成最大的嵌套 + // 而是,并列关系! + // l....split 先变成合法 + // split+1...r 再变成合法 + // 是并列的关系! + // 比如(())[[]] + // l...split : (()) + // split+1...r : [[]] + // 这种并列关系下,有可能出最优解 + // 所以,枚举每一个可能的并列划分点(split) + int ans = Math.min(p1, Math.min(p2, p3)); + for (int split = l; split < r; split++) { + ans = Math.min(ans, process(s, l, split, dp) + process(s, split + 1, r, dp)); + } + dp[l][r] = ans; + return ans; + } + +} diff --git a/公开课/class098/Code01_ChangeToSame.java b/公开课/class098/Code01_ChangeToSame.java new file mode 100644 index 0000000..ba2764f --- /dev/null +++ b/公开课/class098/Code01_ChangeToSame.java @@ -0,0 +1,115 @@ +package class098; + +// 来自美团 +// 8.20笔试 +// 小团生日收到妈妈送的两个一模一样的数列作为礼物! +// 他很开心的把玩,不过不小心没拿稳将数列摔坏了! +// 现在他手上的两个数列分别为A和B,长度分别为n和m。 +// 小团很想再次让这两个数列变得一样。他现在能做两种操作: +// 操作一是将一个选定数列中的某一个数a改成数b,这会花费|b-a|的时间, +// 操作二是选择一个数列中某个数a,将它从数列中丢掉,花费|a|的时间。 +// 小团想知道,他最少能以多少时间将这两个数列变得再次相同! +// 输入描述: +// 第一行两个空格隔开的正整数n和m,分别表示数列A和B的长度。 +// 接下来一行n个整数,分别为A1 A2…An +// 接下来一行m个整数,分别为B1 B2…Bm +// 对于所有数据,1 ≤ n,m ≤ 2000, |Ai|,|Bi| ≤ 10000 +// 输出一行一个整数,表示最少花费时间,来使得两个数列相同。 +public class Code01_ChangeToSame { + + public static int minCost(int[] A, int[] B) { + int n = A.length; + int m = B.length; + int[][] dp = new int[n + 1][m + 1]; + for (int i = 0; i <= n; i++) { + for (int j = 0; j <= m; j++) { + dp[i][j] = -1; + } + } + return change(A, B, 0, 0, dp); + } + + // A, B + // zuo(A,B,0,0) + // A[ai.....] B[bi.....] 请变得一样! + // 返回最少代价! + public static int zuo(int[] A, int[] B, int ai, int bi) { + if (ai == A.length && bi == B.length) { + return 0; + } + if (ai == A.length && bi != B.length) { + return Math.abs(B[bi]) + zuo(A, B, ai, bi + 1); + } + if (ai != A.length && bi == B.length) { + return Math.abs(A[ai]) + zuo(A, B, ai + 1, bi); + } + // A[ai] 有数 + // B[bi] 有数 + // 可能性1 : A[ai]删掉! + int p1 = Math.abs(A[ai]) + zuo(A, B, ai + 1, bi); + // 可能性2 : B[bi]删掉! + int p2 = Math.abs(B[bi]) + zuo(A, B, ai, bi + 1); + // 可能性3 : A[ai]删掉!、B[bi]删掉! + // int p3 = Math.abs(A[ai]) + Math.abs(B[bi]) + zuo(A, B, ai + 1, bi + 1); + // 可能性4 : A[ai] -> B[bi]、B[bi] -> A[ai] + int p4 = Math.abs(A[ai] - B[bi]) + + zuo(A, B, ai + 1, bi + 1); + // 可能性5 : A[ai] == B[bi] +// int p5 = Integer.MAX_VALUE; +// if(A[ai] == B[bi]) { +// p5 = zuo(A, B, ai + 1, bi + 1); +// } + return Math.min(Math.min(p1, p2), p4); + } + + // 暴力递归 + // A[indexA....]和B[indexB....]完全一样 + // 需要付出最少的代价返回 + public static int change(int[] A, int[] B, int indexA, int indexB) { + if (indexA == A.length && indexB == B.length) { + return 0; + } + if (indexA == A.length && indexB != B.length) { + return B[indexB] + change(A, B, indexA, indexB + 1); + } + if (indexA != A.length && indexB == B.length) { + return A[indexA] + change(A, B, indexA + 1, indexB); + } + // indexA、indexB都没到最后 + // 可能性1,丢掉A[indexA] + int p1 = A[indexA] + change(A, B, indexA + 1, indexB); + // 可能性2,丢掉B[indexB] + int p2 = B[indexB] + change(A, B, indexA, indexB + 1); + // 可能性3,同时丢掉A[indexA]、B[indexB] + // 可能性4,把A[indexA]改成B[indexB](也是B[indexB]改成A[indexA],因为代价一样) + // 可能性5,A[indexA]本来就是等于B[indexB]的,改的代价为0 + // 可以分析出可能性3,肯定是不如可能性4、可能性5的 + // 所以舍弃掉可能性3 + int p45 = Math.abs(A[indexA] - B[indexB]) + change(A, B, indexA + 1, indexB + 1); + return Math.min(Math.min(p1, p2), p45); + } + + // 上面的暴力递归方法改动态规划 + public static int change(int[] A, int[] B, int indexA, int indexB, int[][] dp) { + if (indexA == A.length && indexB == B.length) { + return 0; + } + if (dp[indexA][indexB] != -1) { + return dp[indexA][indexB]; + } + int ans = 0; + if (indexA == A.length && indexB != B.length) { + ans = B[indexB] + change(A, B, indexA, indexB + 1); + } else if (indexA != A.length && indexB == B.length) { + ans = A[indexA] + change(A, B, indexA + 1, indexB); + } else { + int p1 = A[indexA] + change(A, B, indexA + 1, indexB); + int p2 = B[indexB] + change(A, B, indexA, indexB + 1); + int p45 = Math.abs(A[indexA] - B[indexB]) + change(A, B, indexA + 1, indexB + 1); + ans = Math.min(Math.min(p1, p2), p45); + } + dp[indexA][indexB] = ans; + return ans; + } + +} diff --git a/公开课/class098/Code02_MinCostMostE.java b/公开课/class098/Code02_MinCostMostE.java new file mode 100644 index 0000000..80d2805 --- /dev/null +++ b/公开课/class098/Code02_MinCostMostE.java @@ -0,0 +1,203 @@ +package class098; + +// 来自网易 +// 小红拿到了一个仅由r、e、d组成的字符串 +// 她定义一个字符e为"好e" : 当且仅当这个e字符和r、d相邻 +// 例如"reeder"只有一个"好e",前两个e都不是"好e",只有第三个e是"好e" +// 小红每次可以将任意字符修改为任意字符,即三种字符可以相互修改 +// 她希望"好e"的数量尽可能多 +// 小红想知道,自己最少要修改多少次 +// 输入一个只有r、e、d三种字符的字符串 +// 长度 <= 2 * 10^5 +// 输出最小修改次数 +public class Code02_MinCostMostE { + +// // int[] arr { d 0 e 1 r 2 } +// // 好e -> 好1 +// // arr[i....] 请整出最多的好1来! +// // 返回1:整出多少个好1 +// // 返回2: 低价是多少? +// // 返回最小代价 +// // prepre pre +// // i-2 i-1 +// +// +// +// // arr[0 0 0] 好1 左右0、2双全 +// // 0 1 2 +// // +// // 1) 0 0 i mostEminCost(arr, 2, 0 , 0) +// // 2) 0 1 i mostEminCost(arr, 2, 0 , 1) +// // 3) 0 2 i +// // 4) 1 0 i +// // 5) 1 1 i +// // +// // i 2 * 10^5 +// // prepre 0 1 2 +// // pre 0 1 2 +// // 9 * 2 * 10^5 +// public static Info mostEminCost(int[] arr, int i, int prepre, int pre) { +// if(i == arr.length) { +// return new Info(0,0); +// } +// // i位置不终止 +// // 可能性1 : [i] -> 0 +// int curCost1 = arr[i] == 0 ? 0 :1; +// int curValue1 = prepre == 2 && pre == 1 ? 1 : 0; +// Info info1 = mostEminCost(arr, i + 1, pre, 0); +// +// +// // 可能性2 : [i] -> 1 +// int curCost2 = arr[i] == 1 ? 0 : 1; +// int curValue2 = 0; +// Info info2 = mostEminCost(arr, i + 1, pre, 1); +// +// +// // 可能性3 : [i] -> 2 +// int curCost3 = arr[i] == 2 ? 0 : 1; +// int curValue3 = prepre == 0 && pre == 1 ? 1 : 0; +// Info info3 = mostEminCost(arr, i + 1, pre, 2); +// +// int p1Value = curValue1 + info1.value; +// int p1Cost = curCost1 + info1.cost; +// int p2Value = curValue2 + info2.value; +// int p2Cost = curCost2 + info2.cost; +// int p3Value = curValue3 + info3.value; +// int p3Cost = curCost3 + info3.cost; +// int bestValue = 0; +// int minCost = Integer.MAX_VALUE; +// +// if(bestValue < p1Value) { +// bestValue = p1Value; +// minCost = p1Cost; +// } else if(bestValue == p1Value) { +// minCost = Math.min(minCost, p1Cost); +// } +// if(bestValue < p2Value) { +// bestValue = p2Value; +// minCost = p2Cost; +// } else if(bestValue == p2Value) { +// minCost = Math.min(minCost, p2Cost); +// } +// if(bestValue < p3Value) { +// bestValue = p3Value; +// minCost = p3Cost; +// } else if(bestValue == p3Value) { +// minCost = Math.min(minCost, p3Cost); +// } +// return new Info(bestValue, minCost); +// } +// +// public static class Info{ +// public int value; +// public int cost; +// +// public Info(int v, int c) { +// value = v; +// cost = c; +// } +// } + + public static int minCost(String str) { + int n = str.length(); + if (n < 3) { + return -1; + } + int[] arr = new int[n]; + // d认为是0,e认为是1,r认为是2 + for (int i = 0; i < n; i++) { + char cur = str.charAt(i); + if (cur == 'd') { + arr[i] = 0; + } else if (cur == 'e') { + arr[i] = 1; + } else { + arr[i] = 2; + } + } + // 通过上面的转化,问题变成了: + // 1的左右,一定要被0和2包围,这个1才是"好1" + // 请让"好1"的尽量多,返回最少的修改代价 + int maxGood = 0; + int minCost = Integer.MAX_VALUE; + for (int prepre = 0; prepre < 3; prepre++) { + for (int pre = 0; pre < 3; pre++) { + int cost = arr[0] == prepre ? 0 : 1; + cost += arr[1] == pre ? 0 : 1; + Info cur = process(arr, 2, prepre, pre); + if (cur.maxGood > maxGood) { + maxGood = cur.maxGood; + minCost = cur.minCost + cost; + } else if (cur.maxGood == maxGood) { + minCost = Math.min(minCost, cur.minCost + cost); + } + } + } + return minCost; + } + + public static class Info { + public int maxGood; + public int minCost; + + public Info(int a, int b) { + maxGood = a; + minCost = b; + } + } + + // 暴力递归 + // 可以自己改成动态规划 + // arr[index-2]位置的数值是prepre + // arr[index-1]位置的数值是pre + // 在这种情况下,请让arr[index...]上的好1尽量多 + // 返回: + // 尽量多的"好1",是多少? + // 得到尽量多的"好1",最小代价是多少? + public static Info process(int[] arr, int index, int prepre, int pre) { + if (index == arr.length) { + return new Info(0, 0); + } + // 可能性1,arr[index],变成0 + int p1Value = prepre == 2 && pre == 1 ? 1 : 0; + int p1Cost = arr[index] == 0 ? 0 : 1; + Info info = process(arr, index + 1, pre, 0); + p1Value += info.maxGood; + p1Cost += info.minCost; + // 可能性2,arr[index],变成1 + int p2Value = 0; + int p2Cost = arr[index] == 1 ? 0 : 1; + info = process(arr, index + 1, pre, 1); + p2Value += info.maxGood; + p2Cost += info.minCost; + // 可能性3,arr[index],变成2 + int p3Value = prepre == 0 && pre == 1 ? 1 : 0; + int p3Cost = arr[index] == 2 ? 0 : 1; + info = process(arr, index + 1, pre, 2); + p3Value += info.maxGood; + p3Cost += info.minCost; + // 开始决策,选出三种可能性中的最优解 + int maxGood = 0; + int minCost = Integer.MAX_VALUE; + if (p1Value > maxGood) { + maxGood = p1Value; + minCost = p1Cost; + } else if (p1Value == maxGood) { + minCost = Math.min(minCost, p1Cost); + } + if (p2Value > maxGood) { + maxGood = p2Value; + minCost = p2Cost; + } else if (p2Value == maxGood) { + minCost = Math.min(minCost, p2Cost); + } + if (p3Value > maxGood) { + maxGood = p3Value; + minCost = p3Cost; + } else if (p3Value == maxGood) { + minCost = Math.min(minCost, p3Cost); + } + return new Info(maxGood, minCost); + } + +} diff --git a/公开课/class098/Code03_TravelMinFuel.java b/公开课/class098/Code03_TravelMinFuel.java new file mode 100644 index 0000000..352da94 --- /dev/null +++ b/公开课/class098/Code03_TravelMinFuel.java @@ -0,0 +1,82 @@ +package class098; + +import java.util.ArrayList; + +// 来自微软 +// 给定两个数组A和B,比如 +// A = { 0, 1, 1 } +// B = { 1, 2, 3 } +// A[0] = 0, B[0] = 1,表示0到1有双向道路 +// A[1] = 1, B[1] = 2,表示1到2有双向道路 +// A[2] = 1, B[2] = 3,表示1到3有双向道路 +// 给定数字N,编号从0~N,所以一共N+1个节点 +// 题目输入一定保证所有节点都联通,并且一定没有环 +// 默认办公室是0节点,其他1~N节点上,每个节点上都有一个居民 +// 每天所有居民都去往0节点上班 +// 所有的居民都有一辆5座的车,也都乐意和别人一起坐车 +// 车不管负重是多少,只要走过一条路,就耗费1的汽油 +// 比如A、B、C的居民,开着自己的车来到D居民的位置,一共耗费3的汽油 +// D居民和E居民之间,假设有一条路 +// 那么D居民可以接上A、B、C,4个人可以用一辆车,去往E的话,就再耗费1的汽油 +// 求所有居民去办公室的路上,最少耗费多少汽油 +public class Code03_TravelMinFuel { + + public static int cnt = 0; + + public static int minFuel(int[] a, int[] b, int n) { + // 先建图 + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + graph.add(new ArrayList<>()); + } + for (int i = 0; i < a.length; i++) { + graph.get(a[i]).add(b[i]); + graph.get(b[i]).add(a[i]); + } + // 建图完毕 + // 根据题目描述,办公室一定是0号点 + // 所有员工一定是往0号点汇聚 + int[] dfn = new int[n + 1]; + int[] size = new int[n + 1]; + int[] cost = new int[n + 1]; + cnt = 0; + dfs(graph, 0, dfn, size, cost); + return cost[0]; + } + + // 图graph + // 当前节点的编号cur + // 以cur为头的整棵树,每个节点,去分配dfn序号! + // 以cur为头的整棵树,每个节点,都去求子树的节点个数,size + // 以cur为头的整棵树,所有节点汇聚到cur,废了多少油,填入到cost + public static void dfs( + ArrayList> graph, + int cur, + int[] dfn, + int[] size, + int[] cost) { + dfn[cur] = ++cnt; + size[cur] = 1; + for (int next : graph.get(cur)) { + if (dfn[next] == 0) { + dfs(graph, next, dfn, size, cost); + size[cur] += size[next]; + cost[cur] += cost[next]; + cost[cur] += (size[next] + 4) / 5; + } + } + } + + public static void main(String[] args) { + int[] a1 = { 0, 1, 1 }; + int[] b1 = { 1, 2, 3 }; + int n1 = 3; + System.out.println(minFuel(a1, b1, n1)); + + int[] a2 = { 1, 1, 1, 9, 9, 9, 9, 7, 8 }; + int[] b2 = { 2, 0, 3, 1, 6, 5, 4, 0, 0 }; + int n2 = 9; + System.out.println(minFuel(a2, b2, n2)); + } + +} diff --git a/公开课/class099/Code01_MatchsticksToSquare.java b/公开课/class099/Code01_MatchsticksToSquare.java new file mode 100644 index 0000000..cd60157 --- /dev/null +++ b/公开课/class099/Code01_MatchsticksToSquare.java @@ -0,0 +1,96 @@ +package class099; + +// 你将得到一个整数数组 matchsticks ,其中 matchsticks[i] 是第 i 个火柴棒的长度。 +// 你要用 所有的火柴棍 拼成一个正方形。 +// 你 不能折断 任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须 使用一次 。 +// 如果你能拼出正方形,则返回 true ,否则返回 false 。 +// 测试链接 : https://leetcode.cn/problems/matchsticks-to-square/ +public class Code01_MatchsticksToSquare { + + public static boolean zuo(int[] arr) { + int sum = 0; + for (int num : arr) { + sum += num; + } + + if (sum % 4 != 0) { + return false; + } + int len = sum / 4; + return f(arr, 0, 0, len, 4); + } + + // status = 000000110111,任何一个下标的火柴用没用 + // arr 0 1 2 3 4 + // y x y y x + // status 01101 + // arr[0] s 1 + // arr[1] s 1 + // + // status : 可变 + // cur : 可变 + // edges : 可变 + // cur 、 edges,被,status决定了! + public static boolean f(int[] arr, int status, int cur, int len, int edges) { + if (edges == 0) { + // 确定所有的火柴是否用光! + return (status == (1 << arr.length) - 1) ? true : false; + } + boolean ans = false; + // 还没都搞定! + // arr中,还没有尝试的火柴!当前全试一遍 + for (int i = 0; i < arr.length; i++) { + // i号火柴,有没有用过呢? + // 2 1 0 + // 00001110111 0 0 1 + if ((status & (1 << i)) == 0) { + if (cur + arr[i] > len) { // 不能用! + continue; + } else if (cur + arr[i] < len) { + ans |= f(arr, status | (1 << i), cur + arr[i], len, edges); + } else { // cur + arr[i] == len + ans |= f(arr, status | (1 << i), 0, len, edges - 1); + } + if (ans) { + break; + } + } + } + return ans; + } + + public static boolean makesquare(int[] matchsticks) { + int sum = 0; + for (int num : matchsticks) { + sum += num; + } + if ((sum & 3) != 0) { + return false; + } + int[] dp = new int[1 << matchsticks.length]; + return process(matchsticks, 0, 0, sum >> 2, 4, dp); + } + + public static boolean process(int[] arr, int status, int cur, int len, int edges, int[] dp) { + if (dp[status] != 0) { + return dp[status] == 1; + } + boolean ans = false; + if (edges == 0) { + ans = (status == (1 << arr.length) - 1) ? true : false; + } else { + for (int i = 0; i < arr.length && !ans; i++) { + if (((1 << i) & status) == 0 && cur + arr[i] <= len) { + if (cur + arr[i] == len) { + ans |= process(arr, status | (1 << i), 0, len, edges - 1, dp); + } else { + ans |= process(arr, status | (1 << i), cur + arr[i], len, edges, dp); + } + } + } + } + dp[status] = ans ? 1 : -1; + return ans; + } + +} diff --git a/公开课/class099/Code02_CutOrPoison.java b/公开课/class099/Code02_CutOrPoison.java new file mode 100644 index 0000000..556be5a --- /dev/null +++ b/公开课/class099/Code02_CutOrPoison.java @@ -0,0 +1,122 @@ +package class099; + +// 来自学员问题 +// 给定怪兽的血量为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 / poisons[0]; + int r = hp + 1; + // 1 ~ hp + 1 二分找答案 + int m = 0; + int ans = Integer.MAX_VALUE; + while (l <= r) { + // m = (l + r) / 2 + 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/公开课/class099/Code03_CorporateFlightBookings.java b/公开课/class099/Code03_CorporateFlightBookings.java new file mode 100644 index 0000000..a099b6e --- /dev/null +++ b/公开课/class099/Code03_CorporateFlightBookings.java @@ -0,0 +1,33 @@ +package class099; + +// 这里有 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 Code03_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/公开课/class100/Code01_EvenTimesMaxSubstring.java b/公开课/class100/Code01_EvenTimesMaxSubstring.java new file mode 100644 index 0000000..5655164 --- /dev/null +++ b/公开课/class100/Code01_EvenTimesMaxSubstring.java @@ -0,0 +1,103 @@ +package class100; + +import java.util.HashMap; + +// 来自微软面试 +// 给定一个字符串s,其中都是英文小写字母 +// 如果s中的子串含有的每种字符都是偶数个 +// 那么这样的子串就是达标子串,子串要求是连续串 +// 返回s中达标子串的最大长度 +// 1 <= s的长度 <= 10^5 +// 字符种类都是英文小写 +public class Code01_EvenTimesMaxSubstring { + + // 为了测试 + // 暴力方法 + public static int maxLen1(String s) { + int n = s.length(); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = n - 1; j >= i; j--) { + if (ok(s, i, j)) { + ans = Math.max(ans, j - i + 1); + break; + } + } + } + return ans; + } + + // 为了测试 + // 暴力方法 + public static boolean ok(String s, int l, int r) { + if (((r - l + 1) & 1) == 1) { + return false; + } + int[] cnts = new int[26]; + for (int i = l; i <= r; i++) { + cnts[s.charAt(i) - 'a']++; + } + for (int cnt : cnts) { + if ((cnt & 1) == 1) { + return false; + } + } + return true; + } + + // 正式方法 + // 时间复杂度O(N) + public static int maxLen2(String s) { + // key : 状态int, 32位的,a~z一共26位,够用 + // value : 该状态最早出现的位置 + HashMap map = new HashMap<>(); + // 00000000..000000 + map.put(0, -1); + // 0...当前字符,总状态! + int status = 0; + int ans = 0; + int n = s.length(); + // ....0 .....1 .....2 .....i ....n-1 + for (int i = 0; i < n; i++) { + // 从开头....i位置的字符 + // 总状态,出来了! + status ^= 1 << (s.charAt(i) - 'a'); + if (map.containsKey(status)) { + ans = Math.max(ans, i - map.get(status)); + } else { + map.put(status, i); + } + } + return ans; + } + + // 为了测试 + public static String randomString(int n, int v) { + char[] s = new char[n]; + for (int i = 0; i < n; i++) { + s[i] = (char) ((int) (Math.random() * v) + 'a'); + } + return String.valueOf(s); + } + + // 为了测试 + public static void main(String[] args) { + int n = 50; + int v = 6; + int testTimes = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + String s = randomString(n, v); + int ans1 = maxLen1(s); + int ans2 = maxLen2(s); + if (ans1 != ans2) { + System.out.println(s); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class100/Code02_MaxLengthSameCharMChanges.java b/公开课/class100/Code02_MaxLengthSameCharMChanges.java new file mode 100644 index 0000000..35fa20e --- /dev/null +++ b/公开课/class100/Code02_MaxLengthSameCharMChanges.java @@ -0,0 +1,124 @@ +package class100; + +// 来自字节笔试 +// 给定一个只由小写字母组成的字符串str,长度为N +// 给定一个只由0、1组成的数组arr,长度为N +// arr[i] == 0表示str中i位置的字符不许修改 +// arr[i] == 1表示str中i位置的字符允许修改 +// 给定一个正数m,表示在任意允许修改的位置,可以把该位置的字符变成a~z中的任何一个 +// 可以修改m次 +// 返回在最多修改m次的情况下,全是一种字符的最长子串是多长 +// 1 <= N, M <= 10^5 +// 所有字符都是小写 +public class Code02_MaxLengthSameCharMChanges { + + // 暴力方法 + // 为了测试 + public static int maxLen1(String str, int[] arr, int m) { + char[] s = str.toCharArray(); + int n = s.length; + int ans = 0; + for (char c = 'a'; c <= 'z'; c++) { + for (int i = 0; i < n; i++) { + for (int j = n - 1; j >= i; j--) { + if (ok(s, i, j, c, arr, m)) { + ans = Math.max(ans, j - i + 1); + break; + } + } + } + } + return ans; + } + + // 为了测试 + public static boolean ok(char[] s, int l, int r, char c, int[] arr, int m) { + for (int i = l; i <= r; i++) { + if (s[i] == c) { + continue; + } + if (arr[i] == 0 || m == 0) { + return false; + } + m--; + } + return true; + } + + // 正式方法 + // 时间复杂度O(N) + public static int maxLen2(String str, int[] arr, int m) { + char[] s = str.toCharArray(); + int n = s.length; + int ans = 0; + for (char c = 'a'; c <= 'z'; c++) { + int r = 0; + int change = 0; + for (int l = 0; l < n; l++) { + // [l..r) + // [l...r-1] r + while (r < n) { + if (s[r] == c) { + r++; + continue; + } + // s[r] != 你的要求 + if (arr[r] == 0 || change == m) { + break; + } + // arr[r] == 1 && change < m + change++; + r++; + } + // [l...r-1] r + ans = Math.max(ans, r - l); + // [l....r-1] [l]吐出来! + if (s[l] != c && arr[l] == 1) { + change--; + } + r = Math.max(r, l + 1); + } + } + return ans; + } + + // 为了测试 + public static String randomString(int n, int r) { + char[] ans = new char[n]; + for (int i = 0; i < n; i++) { + ans[i] = (char) ((int) (Math.random() * r) + 'a'); + } + return String.valueOf(ans); + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * 2); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int R = 5; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * n) + 1; + String str = randomString(n, R); + int[] arr = randomArray(n); + int ans1 = maxLen1(str, arr, m); + int ans2 = maxLen2(str, arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class100/Code03_ExaminationPaperWays.java b/公开课/class100/Code03_ExaminationPaperWays.java new file mode 100644 index 0000000..1ec3f23 --- /dev/null +++ b/公开课/class100/Code03_ExaminationPaperWays.java @@ -0,0 +1,119 @@ +package class100; + +import java.util.Arrays; + +// 来自美团 +// 有三个题库A、B、C,每个题库均有n道题目,且题目都是从1到n进行编号 +// 每个题目都有一个难度值 +// 题库A中第i个题目的难度为ai +// 题库B中第i个题目的难度为bi +// 题库C中第i个题目的难度为ci +// 小美准备组合出一套试题,试题共有三道题, +// 第一题来自题库A,第二题来自题库B,第三题来自题库C +// 试题要求题目难度递增,且梯度不能过大 +// 具体地说,第二题的难度必须大于第一题的难度,但不能大于第一题难度的两倍 +// 第三题的难度必须大于第二题的难度,但不能大于第二题难度的两倍 +// 小美想知道在满足上述要求下,有多少种不同的题目组合 +//(三道题目中只要存在一道题目不同,则两个题目组合就视为不同 +// 输入描述 第一行一个正整数n, 表示每个题库的题目数量 +// 第二行为n个正整数a1, a2,...... an,其中ai表示题库A中第i个题目的难度值 +// 第三行为n个正整数b1, b2,...... bn,其中bi表示题库B中第i个题目的难度值 +// 第四行为n个正整数c1, c2,...... cn,其中ci表示题库C中第i个题目的难度值 +// 1 ≤ n ≤ 20000, 1 ≤ ai, bi, ci ≤ 10^9。 +public class Code03_ExaminationPaperWays { + + // 暴力方法 + // 时间复杂度O(N^3) + // 为了验证 + public static int ways1(int[] a, int[] b, int[] c) { + int n = a.length; + Arrays.sort(a); + Arrays.sort(b); + Arrays.sort(c); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n && b[j] <= a[i] * 2; j++) { + if (b[j] > a[i]) { + for (int k = 0; k < n && c[k] <= b[j] * 2; k++) { + if (c[k] > b[j]) { + ans++; + } + } + } + } + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N) + public static int ways2(int[] a, int[] b, int[] c) { + int n = a.length; + Arrays.sort(a); + Arrays.sort(b); + Arrays.sort(c); + int[] help = new int[n]; + for (int i = 0, l = -1, r = 0; i < n; i++) { + while (l + 1 < n && c[l + 1] <= b[i]) { + l++; + } + while (r < n && c[r] <= b[i] * 2) { + r++; + } + help[i] = Math.max(r - l - 1, 0); + } + for (int i = 1; i < n; i++) { + help[i] += help[i - 1]; + } + int ans = 0; + for (int i = 0, l = -1, r = 0; i < n; i++) { + while (l + 1 < n && b[l + 1] <= a[i]) { + l++; + } + while (r < n && b[r] <= a[i] * 2) { + r++; + } + if (r - l - 1 > 0) { + ans += sum(help, l + 1, r - 1); + } + } + return ans; + } + + public static int sum(int[] help, int l, int r) { + return l == 0 ? help[r] : help[r] - help[l - 1]; + } + + // 为了测试 + 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); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int V = 100; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] a = randomArray(n, V); + int[] b = randomArray(n, V); + int[] c = randomArray(n, V); + int ans1 = ways1(a, b, c); + int ans2 = ways2(a, b, c); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class101/Code01_SlidingWindowMaxArray.java b/公开课/class101/Code01_SlidingWindowMaxArray.java new file mode 100644 index 0000000..7d3ca61 --- /dev/null +++ b/公开课/class101/Code01_SlidingWindowMaxArray.java @@ -0,0 +1,177 @@ +package class101; + + +public class Code01_SlidingWindowMaxArray { + + public static class Window { + + public int[] arr; + + public int[] queue; + public int ql; + public int qr; + + public int l; + public int r; + + public Window(int[] a) { + arr = a; + queue = new int[a.length]; + l = 0; + r = 0; + ql = 0; + qr = 0; + } + + // 右边扩了! + public void add() { + if (r == arr.length) { + return; + } + // [l,r) + // 能扩! + // arr[r] + while (ql != qr && arr[queue[qr - 1]] <= arr[r]) { + qr--; + } + queue[qr++] = r++; + } + + // 左边缩了! + // 必须有窗口才能缩 + public void remove() { + if (l == r) { + return; + } + if (queue[ql] == l) { + ql++; + } + l++; + } + + public int max() { + if (l == r) { + return -1; + } + return arr[queue[ql]]; + } + + } + + public static void main(String[] args) { + int[] arr = { 6, 5, 7, 2, 9, 1, 3 }; + // l..r 无 + Window w = new Window(arr); + + w.add(); + System.out.println(w.max()); + w.add(); + System.out.println(w.max()); + w.add(); + System.out.println(w.max()); + w.add(); + System.out.println(w.max()); + w.remove(); + w.remove(); + w.remove(); + System.out.println(w.max()); + + } + +// +// +// +// +// // 暴力的对数器方法 +// public static int[] right(int[] arr, int w) { +// if (arr == null || w < 1 || arr.length < w) { +// return null; +// } +// int N = arr.length; +// int[] res = new int[N - w + 1]; +// int index = 0; +// int L = 0; +// int R = w - 1; +// while (R < N) { +// int max = arr[L]; +// for (int i = L + 1; i <= R; i++) { +// max = Math.max(max, arr[i]); +// +// } +// res[index++] = max; +// L++; +// R++; +// } +// return res; +// } +// +// public static int[] getMaxWindow(int[] arr, int w) { +// if (arr == null || w < 1 || arr.length < w) { +// return null; +// } +// // qmax 窗口最大值的更新结构 +// // 放下标 +// LinkedList qmax = new LinkedList(); +// int[] res = new int[arr.length - w + 1]; +// int index = 0; +// for (int R = 0; R < arr.length; R++) { +// while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) { +// qmax.pollLast(); +// } +// qmax.addLast(R); +// if (qmax.peekFirst() == R - w) { +// qmax.pollFirst(); +// } +// if (R >= w - 1) { +// res[index++] = arr[qmax.peekFirst()]; +// } +// } +// return res; +// } +// +// // for test +// public static int[] generateRandomArray(int maxSize, int maxValue) { +// int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; +// for (int i = 0; i < arr.length; i++) { +// arr[i] = (int) (Math.random() * (maxValue + 1)); +// } +// return arr; +// } +// +// // for test +// public static boolean isEqual(int[] arr1, int[] arr2) { +// if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { +// return false; +// } +// if (arr1 == null && arr2 == null) { +// return true; +// } +// if (arr1.length != arr2.length) { +// return false; +// } +// for (int i = 0; i < arr1.length; i++) { +// if (arr1[i] != arr2[i]) { +// return false; +// } +// } +// return true; +// } +// +// public static void main(String[] args) { +// int testTime = 100000; +// int maxSize = 100; +// int maxValue = 100; +// System.out.println("test begin"); +// for (int i = 0; i < testTime; i++) { +// int[] arr = generateRandomArray(maxSize, maxValue); +// int w = (int) (Math.random() * (arr.length + 1)); +// int[] ans1 = getMaxWindow(arr, w); +// int[] ans2 = right(arr, w); +// if (!isEqual(ans1, ans2)) { +// System.out.println("Oops!"); +// } +// } +// System.out.println("test finish"); +// } + +} diff --git a/公开课/class101/Code02_WindPrevent.java b/公开课/class101/Code02_WindPrevent.java new file mode 100644 index 0000000..4fbf2cc --- /dev/null +++ b/公开课/class101/Code02_WindPrevent.java @@ -0,0 +1,110 @@ +package class101; + +// 来自真实笔试 +// 给定一个二维数组matrix,数组中的每个元素代表一棵树的高度。 +// 你可以选定连续的若干行组成防风带,防风带每一列的防风高度为这一列的最大值 +// 防风带整体的防风高度为,所有列防风高度的最小值。 +// 比如,假设选定如下三行 +// 1 5 4 +// 7 2 6 +// 2 3 4 +// 1、7、2的列,防风高度为7 +// 5、2、3的列,防风高度为5 +// 4、6、4的列,防风高度为6 +// 防风带整体的防风高度为5,是7、5、6中的最小值 +// 给定一个正数k,k <= matrix的行数,表示可以取连续的k行,这k行一起防风。 +// 求防风带整体的防风高度最大值 +public class Code02_WindPrevent { + + public static int bestHeight1(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int ans = 0; + for (int startRow = 0; startRow < n; startRow++) { + int bottleNeck = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + int height = 0; + for (int endRow = startRow; endRow < n && (endRow - startRow + 1 <= k); endRow++) { + height = Math.max(height, matrix[endRow][col]); + } + bottleNeck = Math.min(bottleNeck, height); + } + ans = Math.max(ans, bottleNeck); + } + return ans; + } + + public static int bestHeight2(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int[][] windowMaxs = new int[m][n]; + int[][] windowLR = new int[m][2]; + for (int i = 0; i < k; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + } + int ans = bottleNeck(matrix, m, windowMaxs, windowLR); + for (int i = k; i < n; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + deleteRow(m, i - k, windowMaxs, windowLR); + ans = Math.max(ans, bottleNeck(matrix, m, windowMaxs, windowLR)); + } + return ans; + } + + public static void addRow(int[][] matrix, int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + while (windowLR[col][0] != windowLR[col][1] + && matrix[windowMaxs[col][windowLR[col][1] - 1]][col] <= matrix[row][col]) { + windowLR[col][1]--; + } + windowMaxs[col][windowLR[col][1]++] = row; + } + } + + public static void deleteRow(int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + if (windowMaxs[col][windowLR[col][0]] == row) { + windowLR[col][0]++; + } + } + } + + public static int bottleNeck(int[][] matrix, int m, int[][] windowMaxs, int[][] windowLR) { + int ans = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + ans = Math.min(ans, matrix[windowMaxs[col][windowLR[col][0]]][col]); + } + return ans; + } + + public static int[][] generateMatrix(int n, int m, int v) { + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * v) + 1; + } + } + return matrix; + } + + public static void main(String[] args) { + int nMax = 100; + int mMax = 100; + int vMax = 100; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * nMax) + 1; + int m = (int) (Math.random() * mMax) + 1; + int[][] matrix = generateMatrix(n, m, vMax); + int k = (int) (Math.random() * n) + 1; + int ans1 = bestHeight1(matrix, k); + int ans2 = bestHeight2(matrix, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class101/Code03_DreamCity.java b/公开课/class101/Code03_DreamCity.java new file mode 100644 index 0000000..8a8a412 --- /dev/null +++ b/公开课/class101/Code03_DreamCity.java @@ -0,0 +1,70 @@ +package class101; + +// 来自学员问题 +// 给定n棵树,和两个长度为n的数组a和b +// i号棵树的初始重量为a[i],i号树每天的增长重量为b[i] +// 你每天最多能砍1棵树,这天收益 = 砍的树初始重量 + 砍的树增长到这天的总增重 +// 给定m,表示你有m天,返回m天内你获得的最大收益 +// 本题测试链接 : https://zoj.pintia.cn/problem-sets/91827364500/problems/91827367873 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交如下方法,把主类名改成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 Code03_DreamCity { + + public static int[][] tree = new int[250][2]; + + public static int[][] dp = new int[250][250]; + + 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)); + in.nextToken(); + int testCases = (int) in.nval; + for (int i = 0; i < testCases; i++) { + in.nextToken(); + int n = (int) in.nval; + in.nextToken(); + int m = (int) in.nval; + for (int j = 0; j < n; j++) { + in.nextToken(); + tree[j][0] = (int) in.nval; + } + for (int j = 0; j < n; j++) { + in.nextToken(); + tree[j][1] = (int) in.nval; + } + out.println(maxWeight(n, m)); + out.flush(); + } + } + + // tree[][] + // i棵树,初始重量 , tree[i][0] + // i棵树,每天的增长重量 ,tree[i][1] + public static int maxWeight(int n, int m) { + Arrays.sort(tree, 0, n, (o1, o2) -> o1[1] - o2[1]); + dp[0][0] = tree[0][0]; + for (int i = 1; i < n; i++) { + dp[i][0] = Math.max(dp[i - 1][0], tree[i][0]); + } + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + tree[0][1]; + } + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + tree[i][0] + tree[i][1] * j); + } + } + return dp[n - 1][m - 1]; + } + +} \ No newline at end of file diff --git a/公开课/class102/Code01_SortGame.java b/公开课/class102/Code01_SortGame.java new file mode 100644 index 0000000..5c970bd --- /dev/null +++ b/公开课/class102/Code01_SortGame.java @@ -0,0 +1,189 @@ +package class102; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeSet; + +// 来自百度 +// 二狗买了一些小兵玩具,和大胖一起玩 +// 一共有n个小兵,这n个小兵拍成一列 +// 第i个小兵战斗力为hi,然后他们两个开始对小兵进行排列 +// 一共进行m次操作,二狗每次操作选择一个数k,将前k个小兵战斗力从小到大排列 +// 大胖每次操作选择一个数k,将前k个小兵战斗力从大到小排列 +// 问所有操作结束后,排列顺序什么样 +// 给定一个长度为n的数组arr,表示每个小兵的战斗力 +// 给定一个长度为m的数组op, +// op[i] = { k , 0 }, 表示对前k个士兵执行从小到大的操作 +// op[i] = { k , 1 }, 表示对前k个士兵执行从大到小的操作 +// 返回数组ans,表示最终的排列 +// 1 <= n, m <= 2 * 10^5 +// - 10^9 <= arr[i] <= + 10^9 +public class Code01_SortGame { + + // 暴力方法 + // 为了验证 + public static int[] game1(int[] arr, int[][] op) { + int n = arr.length; + Integer[] help = new Integer[n]; + for (int i = 0; i < n; i++) { + help[i] = arr[i]; + } + for (int[] o : op) { + if (o[1] == 0) { + Arrays.sort(help, 0, o[0], (a, b) -> a - b); + } else { + Arrays.sort(help, 0, o[0], (a, b) -> b - a); + } + } + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = help[i]; + } + return ans; + } + + // 正式方法 + // 时间复杂度O(M) + O(N*logN) + public static int[] game2(int[] arr, int[][] op) { + int n = arr.length; + int m = op.length; + int[] stack = new int[m]; + int r = 0; + for (int i = 0; i < m; i++) { + while (r != 0 && op[stack[r - 1]][0] <= op[i][0]) { + r--; + } + stack[r++] = i; + } + int[] ans = new int[n]; + int ansi = n - 1; + int l = 0; + for (; ansi >= op[stack[l]][0]; ansi--) { + ans[ansi] = arr[ansi]; + } + TreeSet sorted = new TreeSet<>(new NumberComparator()); + for (int i = 0; i < op[stack[l]][0]; i++) { + sorted.add(new Number(arr[i], i)); + } + while (l != r) { + // 当前处理的指令 + int[] cur = op[stack[l++]]; + if (l != r) { + int[] next = op[stack[l]]; + int num = cur[0] - next[0]; + if (cur[1] == 0) { + for (int i = 0; i < num; i++) { + ans[ansi--] = sorted.pollLast().value; + } + } else { + for (int i = 0; i < num; i++) { + ans[ansi--] = sorted.pollFirst().value; + } + } + } else { + if (cur[1] == 0) { + while (!sorted.isEmpty()) { + ans[ansi--] = sorted.pollLast().value; + } + } else { + while (!sorted.isEmpty()) { + ans[ansi--] = sorted.pollFirst().value; + } + } + } + } + return ans; + } + + public static class Number { + public int value; + public int index; + + public Number(int v, int i) { + value = v; + index = i; + } + } + + public static class NumberComparator implements Comparator { + + @Override + public int compare(Number o1, Number o2) { + if (o1.value != o2.value) { + return o1.value - o2.value; + } else { + return o1.index - o2.index; + } + } + + } + + // 为了测试 + 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 int[][] randomOp(int m, int n) { + int[][] ans = new int[m][2]; + for (int i = 0; i < m; i++) { + ans[i][0] = (int) (Math.random() * (n + 1)); + ans[i][1] = Math.random() < 0.5 ? 0 : 1; + } + return ans; + } + + // 为了测试 + public static boolean isEqual(int[] arr1, int[] arr2) { + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + +// int[] arr = { 3, 3, 7, 7, 7 }; +// +// TreeSet set = new TreeSet<>(new NumberComparator()); +// +// for (int i = 0; i < arr.length; i++) { +// set.add(new Number(arr[i], i)); +// } +// +//// System.out.println(set.size()); +// +// while(!set.isEmpty()) { +// System.out.println(set.pollLast().value); +// } + + int N = 100; + int M = 100; + int V = 200; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[] arr = randomArray(n, V); + int[][] op = randomOp(m, n); + int[] ans1 = game1(arr, op); + int[] ans2 = game2(arr, op); + if (!isEqual(ans1, ans2)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class102/Code02_NLengthMValueLIS3.java b/公开课/class102/Code02_NLengthMValueLIS3.java new file mode 100644 index 0000000..943577a --- /dev/null +++ b/公开课/class102/Code02_NLengthMValueLIS3.java @@ -0,0 +1,176 @@ +package class102; + +// 来自微众银行 +// 给定一个数字n,代表数组的长度 +// 给定一个数字m,代表数组每个位置都可以在1~m之间选择数字 +// 所有长度为n的数组中,最长递增子序列长度为3的数组,叫做达标数组 +// 返回达标数组的数量 +// 1 <= n <= 500 +// 1 <= m <= 10 +// 500 * 10 * 10 * 10 +// 结果对998244353取模 +// 实现的时候没有取模的逻辑,因为非重点 +public class Code02_NLengthMValueLIS3 { + + // 暴力方法 + // 为了验证 + public static int number1(int n, int m) { + return process1(0, n, m, new int[n]); + } + + public static int process1(int i, int n, int m, int[] path) { + if (i == n) { + return lengthOfLIS(path) == 3 ? 1 : 0; + } else { + int ans = 0; + for (int cur = 1; cur <= m; cur++) { + path[i] = cur; + ans += process1(i + 1, n, m, path); + } + return ans; + } + } + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int right = 0; + int max = 1; + for (int i = 1; i < arr.length; i++) { + int l = 0; + int r = right; + while (l <= r) { + int m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + + // i : 目前来到的位置是i + // i f s t 四维参数的递归! + // dp[i][f][s][t] + // 500 * 10 * 10 * 10 = 5 * 10^5 +// public static int zuo(int i, int f, int s, int t, int n, int m) { +// if (i == n) { +// return t != 0 ? 1 : 0; +// } +// // i < n +// // 1 ~ m +// int ans = 0; +// for (int num = 1; num <= m; num++) { +// if (f == 0 || f >= num) { +// ans += zuo(i + 1, num, s, t, n, m); +// } else if (s == 0 || s >= num) { +// ans += zuo(i + 1, f, num, t, n, m); +// } else if (t == 0 || t >= num) { +// ans += zuo(i + 1, f, s, num, n, m); +// } else { // 都没拦住!最长递增子序列的长度将到达4!不合法了! +//// ans += 0; +// break; +// } +// } +// return ans; +// } + + // i : 当前来到的下标 + // f、s、t : ends数组中放置的数字! + // ? == 0,没放! + // n : 一共的长度! + // m : 每一位,都可以在1~m中随意选择数字 + // 返回值:i..... 有几个合法的数组! + public static int zuo(int i, int f, int s, int t, int n, int m) { + if (i == n) { + return f != 0 && s != 0 && t != 0 ? 1 : 0; + } + // i < n + int ans = 0; + for (int cur = 1; cur <= m; cur++) { + if (f == 0 || f >= cur) { + ans += zuo(i + 1, cur, s, t, n, m); + } else if (s == 0 || s >= cur) { + ans += zuo(i + 1, f, cur, t, n, m); + } else if (t == 0 || t >= cur) { + ans += zuo(i + 1, f, s, cur, n, m); + } + } + return ans; + } + + // 正式方法 + // 需要看最长递增子序列! + // 尤其是理解ends数组的意义! + public static int number2(int n, int m) { + int[][][][] dp = new int[n][m + 1][m + 1][m + 1]; + for (int i = 0; i < n; i++) { + for (int f = 0; f <= m; f++) { + for (int s = 0; s <= m; s++) { + for (int t = 0; t <= m; t++) { + dp[i][f][s][t] = -1; + } + } + } + } + return process2(0, 0, 0, 0, n, m, dp); + } + + public static int process2( + int i, int f, int s, int t, + int n, int m, int[][][][] dp) { + if (i == n) { + return f != 0 && s != 0 && t != 0 ? 1 : 0; + } + if (dp[i][f][s][t] != -1) { + return dp[i][f][s][t]; + } + int ans = 0; + for (int cur = 1; cur <= m; cur++) { + if (f == 0 || cur <= f) { + ans += process2(i + 1, cur, s, t, n, m, dp); + } else if (s == 0 || cur <= s) { + ans += process2(i + 1, f, cur, t, n, m, dp); + } else if (t == 0 || cur <= t) { + ans += process2(i + 1, f, s, cur, n, m, dp); + } + } + dp[i][f][s][t] = ans; + return ans; + } + + public static void main(String[] args) { + System.out.println("功能测试开始"); + for (int n = 4; n <= 8; n++) { + for (int m = 1; m <= 5; m++) { + int ans1 = number1(n, m); + int ans2 = number2(n, m); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + } + System.out.println("功能测试结束"); + System.out.println("性能测试开始"); + int n = 2000; + int m = 20; + System.out.println("序列长度 : " + n); + System.out.println("数字范围 : " + m); + long start = System.currentTimeMillis(); + number2(n, m); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + } + +} diff --git a/公开课/class102/Code03_LIS.java b/公开课/class102/Code03_LIS.java new file mode 100644 index 0000000..8320d35 --- /dev/null +++ b/公开课/class102/Code03_LIS.java @@ -0,0 +1,55 @@ +package class102; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code03_LIS { + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // ends数组 + // ends[i]表示 : 目前所有长度为i+1的递增子序列的最小结尾 + int[] ends = new int[arr.length]; + // 根据含义, 一开始ends[0] = arr[0] + ends[0] = arr[0]; + // ends有效区范围是0...right,right往右为无效区 + // 所以一开始right = 0, 表示有效区只有0...0范围 + int right = 0; + // 最长递增子序列的长度 + // 全局变量,抓取每一步的答案,取最大的结果 + int max = 1; + for (int i = 1; i < arr.length; i++) { + int l = 0; + int r = right; + // 在ends[l...r]范围上二分 + // 如果 当前数(arr[i]) > ends[m],砍掉左侧 + // 如果 当前数(arr[i]) <= ends[m],砍掉右侧 + // 整个二分就是在ends里寻找 >= 当前数(arr[i])的最左位置 + // 就是从while里面出来时,l所在的位置。 + // 如果ends中不存在 >= 当前数(arr[i])的情况,将返回有效区的越界位置 + // 也就是从while里面出来时,l所在的位置,是有效区的越界位置 + // 比如 : ends = { 3, 5, 9, 12, 再往右无效} + // 如果当前数为8, 从while里面出来时,l将来到2位置 + // 比如 : ends = { 3, 5, 9, 12, 再往右无效} + // 如果当前数为13, 从while里面出来时,l将来到有效区的越界位置,4位置 + while (l <= r) { + int m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + // 从while里面出来,看l的位置 + // 如果l比right大,说明扩充了有效区,那么right变量要随之变大 + // 如果l不比right大,说明l没有来到有效区的越界位置,right不变 + right = Math.max(right, l); + // l的位置,就是当前数应该填到ends数组里的位置 + ends[l] = arr[i]; + // 更新全局变量 + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/公开课/class103/Code01_SoldierFindEnemy.java b/公开课/class103/Code01_SoldierFindEnemy.java new file mode 100644 index 0000000..fd0af46 --- /dev/null +++ b/公开课/class103/Code01_SoldierFindEnemy.java @@ -0,0 +1,185 @@ +package class103; + +import java.util.PriorityQueue; + +// 来自华为 +// 给定一个N*M的二维矩阵,只由字符'O'、'X'、'S'、'E'组成 +// 'O'表示这个地方是可通行的平地 +// 'X'表示这个地方是不可通行的障碍 +// 'S'表示这个地方有一个士兵,全图保证只有一个士兵 +// 'E'表示这个地方有一个敌人,全图保证只有一个敌人 +// 士兵可以在上、下、左、右四个方向上移动 +// 走到相邻的可通行的平地上,走一步耗费a个时间单位 +// 士兵从初始地点行动时,不管去哪个方向,都不用耗费转向的代价 +// 但是士兵在行动途中,如果需要转向,需要额外再付出b个时间单位 +// 返回士兵找到敌人的最少时间 +// 如果因为障碍怎么都找不到敌人,返回-1 +// 1 <= N,M <= 1000 +// 1 <= a,b <= 100000 +// 只会有一个士兵、一个敌人 +public class Code01_SoldierFindEnemy { + + // 暴力dfs + // 为了验证 + public static int minCost1(char[][] map, int a, int b) { + int n = map.length; + int m = map[0].length; + int si = 0; + int sj = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (map[i][j] == 'S') { + si = i; + sj = j; + } + } + } + boolean[][][] visited = new boolean[n][m][4]; + int p1 = f(map, si, sj, 0, a, b, visited); + int p2 = f(map, si, sj, 1, a, b, visited); + int p3 = f(map, si, sj, 2, a, b, visited); + int p4 = f(map, si, sj, 3, a, b, visited); + int ans = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + return ans == Integer.MAX_VALUE ? -1 : (ans - a); + } + + public static int f(char[][] map, int si, int sj, int d, int a, int b, boolean[][][] visited) { + if (si < 0 || si == map.length || sj < 0 || sj == map[0].length || map[si][sj] == 'X' || visited[si][sj][d]) { + return Integer.MAX_VALUE; + } + if (map[si][sj] == 'E') { + return a; + } + visited[si][sj][d] = true; + int p0 = f(map, si - 1, sj, 0, a, b, visited); + int p1 = f(map, si + 1, sj, 1, a, b, visited); + int p2 = f(map, si, sj - 1, 2, a, b, visited); + int p3 = f(map, si, sj + 1, 3, a, b, visited); + if (d != 0 && p0 != Integer.MAX_VALUE) { + p0 += b; + } + if (d != 1 && p1 != Integer.MAX_VALUE) { + p1 += b; + } + if (d != 2 && p2 != Integer.MAX_VALUE) { + p2 += b; + } + if (d != 3 && p3 != Integer.MAX_VALUE) { + p3 += b; + } + int ans = Math.min(Math.min(p0, p1), Math.min(p2, p3)); + ans = ans == Integer.MAX_VALUE ? ans : (ans + a); + visited[si][sj][d] = false; + return ans; + } + + // 正式方法 + // Dijkstra算法 + public static int minCost2(char[][] map, int a, int b) { + int n = map.length; + int m = map[0].length; + int si = 0; + int sj = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (map[i][j] == 'S') { + si = i; + sj = j; + } + } + } + PriorityQueue heap = new PriorityQueue<>((x, y) -> x[3] - y[3]); + heap.add(new int[] { si, sj, 0, 0 }); + heap.add(new int[] { si, sj, 1, 0 }); + heap.add(new int[] { si, sj, 2, 0 }); + heap.add(new int[] { si, sj, 3, 0 }); + boolean[][][] visited = new boolean[n][m][4]; + int ans = -1; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + if (visited[cur[0]][cur[1]][cur[2]]) { + continue; + } + if (map[cur[0]][cur[1]] == 'E') { + ans = cur[3]; + break; + } + visited[cur[0]][cur[1]][cur[2]] = true; + add(cur[0] - 1, cur[1], 0, cur[2], cur[3], a, b, map, visited, heap); + add(cur[0] + 1, cur[1], 1, cur[2], cur[3], a, b, map, visited, heap); + add(cur[0], cur[1] - 1, 2, cur[2], cur[3], a, b, map, visited, heap); + add(cur[0], cur[1] + 1, 3, cur[2], cur[3], a, b, map, visited, heap); + } + return ans; + } + + public static void add(int i, int j, int d, int preD, int preC, int a, int b, char[][] map, boolean[][][] visited, + PriorityQueue heap) { + if (i < 0 || i == map.length || j < 0 || j == map[0].length || map[i][j] == 'X' || visited[i][j][d]) { + return; + } + int cost = preC + a; + if (d != preD) { + cost += b; + } + heap.add(new int[] { i, j, d, cost }); + } + + // 为了测试 + public static char[][] randomMap(int n, int m) { + char[][] map = new char[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + map[i][j] = Math.random() < 0.5 ? 'O' : 'X'; + } + } + int si = (int) (Math.random() * n); + int sj = (int) (Math.random() * m); + map[si][sj] = 'S'; + int ei, ej; + do { + ei = (int) (Math.random() * n); + ej = (int) (Math.random() * m); + } while (ei == si && ej == sj); + map[ei][ej] = 'E'; + return map; + } + + public static void main(String[] args) { + int n = 3; + int m = 4; + int v = 10; + System.out.println("功能测试开始"); + for (int i = 0; i < 2000; i++) { + char[][] map = randomMap(n, m); + int a = (int) (Math.random() * v) + 1; + int b = (int) (Math.random() * v) + 1; + int ans1 = minCost1(map, a, b); + int ans2 = minCost2(map, a, b); + if (ans1 != ans2) { + System.out.println("出错了"); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + n = 1000; + m = 1000; + v = 100000; + int a = (int) (Math.random() * v) + 1; + int b = (int) (Math.random() * v) + 1; + char[][] map = randomMap(n, m); + System.out.println("数据规模 : " + n + " * " + m); + System.out.println("通行代价 : " + a); + System.out.println("转向代价 : " + b); + long start = System.currentTimeMillis(); + minCost2(map, a, b); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + "毫秒"); + System.out.println("功能测试结束"); + + } + +} diff --git a/公开课/class103/Code02_RedPalindromeGoodStrings.java b/公开课/class103/Code02_RedPalindromeGoodStrings.java new file mode 100644 index 0000000..1e6edbc --- /dev/null +++ b/公开课/class103/Code02_RedPalindromeGoodStrings.java @@ -0,0 +1,93 @@ +package class103; + +// 来自阿里 +// 小红定义一个仅有r、e、d三种字符的字符串中 +// 如果仅有一个长度不小于2的回文子串,那么这个字符串定义为"好串" +// 给定一个正整数n,输出长度为n的好串有多少个 +// 结果对10^9 + 7取模, 1 <= n <= 10^9 +// 示例: +// n = 1, 输出0 +// n = 2, 输出3 +// n = 3, 输出18 +public class Code02_RedPalindromeGoodStrings { + + // 暴力方法 + // 为了观察规律 + // 根据好串的定义 + // 长度为n的时候,返回好串的数量 + public static int goods(int n) { + char[] path = new char[n]; + return process(0, n, path); + } + + // 长度为n所有可能的字符串,请都得到! + public static int process(int i, int n, char[] path) { + if (i == n) { + return ok(path) ? 1 : 0; + } else { + int ans = 0; + path[i] = 'r'; + ans += process(i + 1, n, path); + path[i] = 'e'; + ans += process(i + 1, n, path); + path[i] = 'd'; + ans += process(i + 1, n, path); + return ans; + } + } + + // 如果是好串,返回true;不是好串,返回false + public static boolean ok(char[] path) { + int count = 0; + for (int l = 0; l < path.length; l++) { + for (int r = l + 1; r < path.length; r++) { + if (isP(path, l, r)) { + count++; + } + if (count > 1) { + return false; + } + } + } + return count == 1; + } + + // l.... r + // ? ? ? ? + public static boolean isP(char[] path, int l, int r) { + while (l < r) { + if (path[l] != path[r]) { + return false; + } + l++; + r--; + } + return true; + } + + // 正式方法 + // 观察规律之后,把规律变成代码 + public static int num2(int n) { + if (n == 1) { + return 0; + } + if (n == 2) { + return 3; + } + if (n == 3) { + return 18; + } + return 6 * (n + 1); + } + + public static void main(String[] args) { + System.out.println(goods(1)); + System.out.println(goods(2)); + System.out.println(goods(3)); + System.out.println(goods(4)); + System.out.println(goods(5)); + System.out.println(goods(6)); + System.out.println(goods(7)); + } + +} diff --git a/公开课/class104/Code01_RedPalindromeGoodStrings.java b/公开课/class104/Code01_RedPalindromeGoodStrings.java new file mode 100644 index 0000000..d54a8dd --- /dev/null +++ b/公开课/class104/Code01_RedPalindromeGoodStrings.java @@ -0,0 +1,134 @@ +package class104; + +// 来自阿里 +// 小红定义一个仅有r、e、d三种字符的字符串中 +// 如果仅有一个长度不小于2的回文子串,那么这个字符串定义为"好串" +// 给定一个正整数n,输出长度为n的好串有多少个 +// 结果对10^9 + 7取模, 1 <= n <= 10^9 +// 示例: +// n = 1, 输出0 +// n = 2, 输出3 +// n = 3, 输出18 +public class Code01_RedPalindromeGoodStrings { + + public static int cnt = 0; + + public static int num1(int n) { + cnt = 0; + process(new char[n], 0, n); + return cnt; + } + + // path = {r,r,e,d } .. n + public static void process(char[] path, int i, int n) { + if (i == n) { + if (isGood(path)) { + cnt++; + } + } else { + path[i] = 'r'; + process(path, i + 1, n); + path[i] = 'e'; + process(path, i + 1, n); + path[i] = 'd'; + process(path, i + 1, n); + } + } + + // 验证path,是不是好串 + // 好串:只有一个回文子串长度>=2 + public static boolean isGood(char[] path) { + int ans = 0; + for (int l = 0; l < path.length; l++) { + for (int r = l + 1; r < path.length; r++) { + // path[l...r]是不是回文串! + if (isP(path, l, r)) { + ans++; + } + if (ans > 1) { + return false; + } + } + } + return ans == 1; + } + + // 暴力方法 + // 为了观察规律 + // 根据好串的定义 + // 长度为n的时候,返回好串的数量 + public static int goods(int n) { + char[] path = new char[n]; + return process(0, n, path); + } + + // 长度为n所有可能的字符串,请都得到! + public static int process(int i, int n, char[] path) { + if (i == n) { + return ok(path) ? 1 : 0; + } else { + int ans = 0; + path[i] = 'r'; + ans += process(i + 1, n, path); + path[i] = 'e'; + ans += process(i + 1, n, path); + path[i] = 'd'; + ans += process(i + 1, n, path); + return ans; + } + } + + // 如果是好串,返回true;不是好串,返回false + public static boolean ok(char[] path) { + int count = 0; + for (int l = 0; l < path.length; l++) { + for (int r = l + 1; r < path.length; r++) { + if (isP(path, l, r)) { + count++; + } + if (count > 1) { + return false; + } + } + } + return count == 1; + } + + public static boolean isP(char[] path, int l, int r) { + while (l < r) { + if (path[l] != path[r]) { + return false; + } + l++; + r--; + } + return true; + } + + // 正式方法 + // 观察规律之后,把规律变成代码 + public static int num2(int n) { + if (n == 1) { + return 0; + } + if (n == 2) { + return 3; + } + if (n == 3) { + return 18; + } + return 6 * (n + 1); + } + + public static void main(String[] args) { + System.out.println(num1(1)); + System.out.println(num1(2)); + System.out.println(num1(3)); + System.out.println(num1(4)); + System.out.println(num1(5)); + System.out.println(num1(6)); + System.out.println(num1(7)); + System.out.println(num1(8)); + } + +} diff --git a/公开课/class104/Code02_MaxSumOnReverseArray.java b/公开课/class104/Code02_MaxSumOnReverseArray.java new file mode 100644 index 0000000..f8226a7 --- /dev/null +++ b/公开课/class104/Code02_MaxSumOnReverseArray.java @@ -0,0 +1,96 @@ +package class104; + +// 来自美团 +// 最大子段和是 +// 一个经典问题,即对于一个数组找出其和最大的子数组。 +// 现在允许你在求解该问题之前翻转这个数組的连续一段 +// 如翻转(1,2,3,4,5,6)的第三个到第五个元素組成的子数组得到的是(1,2,5,4,3,6), +// 则翻转后该数组的最大子段和最大能达到多少? +public class Code02_MaxSumOnReverseArray { + + // 暴力解 + // 翻转每一个子数组,全求一遍 + public static int maxSumReverse1(int[] arr) { + int ans = Integer.MIN_VALUE; + for (int L = 0; L < arr.length; L++) { + for (int R = L; R < arr.length; R++) { + reverse(arr, L, R); + ans = Math.max(ans, maxSum(arr)); + reverse(arr, L, R); + } + } + return ans; + } + + public static void reverse(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int maxSum(int[] arr) { + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], arr[i] + pre); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSumReverse2(int[] arr) { + int n = arr.length; + int[] prefix = new int[n]; + prefix[n - 1] = arr[n - 1]; + for (int i = n - 2; i >= 0; i--) { + prefix[i] = arr[i] + Math.max(0, prefix[i + 1]); + } + int ans = prefix[0]; + int suffix = arr[0]; + int maxSuffix = suffix; + for (int i = 1; i < n; i++) { + ans = Math.max(ans, maxSuffix + prefix[i]); + suffix = arr[i] + Math.max(0, suffix); + maxSuffix = Math.max(maxSuffix, suffix); + } + ans = Math.max(ans, maxSuffix); + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSumReverse1(arr); + int ans2 = maxSumReverse2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class104/Code03_ExaminationPaperWays.java b/公开课/class104/Code03_ExaminationPaperWays.java new file mode 100644 index 0000000..575acdb --- /dev/null +++ b/公开课/class104/Code03_ExaminationPaperWays.java @@ -0,0 +1,120 @@ +package class104; + +import java.util.Arrays; + +// 来自美团 +// 有三个题库A、B、C,每个题库均有n道题目,且题目都是从1到n进行编号 +// 每个题目都有一个难度值 +// 题库A中第i个题目的难度为ai +// 题库B中第i个题目的难度为bi +// 题库C中第i个题目的难度为ci +// 小美准备组合出一套试题,试题共有三道题, +// 第一题来自题库A,第二题来自题库B,第三题来自题库C +// 试题要求题目难度递增,且梯度不能过大 +// 具体地说,第二题的难度必须大于第一题的难度,但不能大于第一题难度的两倍 +// 第三题的难度必须大于第二题的难度,但不能大于第二题难度的两倍 +// 小美想知道在满足上述要求下,有多少种不同的题目组合 +//(三道题目中只要存在一道题目不同,则两个题目组合就视为不同 +// 输入描述 第一行一个正整数n, 表示每个题库的题目数量 +// 第二行为n个正整数a1, a2,...... an,其中ai表示题库A中第i个题目的难度值 +// 第三行为n个正整数b1, b2,...... bn,其中bi表示题库B中第i个题目的难度值 +// 第四行为n个正整数c1, c2,...... cn,其中ci表示题库C中第i个题目的难度值 +// 1 <= n <= 20000, 1 <= ai, bi, ci <= 10^9。 +public class Code03_ExaminationPaperWays { + + // 暴力方法 + // 时间复杂度O(N^3) + // 为了验证 + public static int ways1(int[] a, int[] b, int[] c) { + int n = a.length; + Arrays.sort(a); + Arrays.sort(b); + Arrays.sort(c); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n && b[j] <= a[i] * 2; j++) { + if (b[j] > a[i]) { + for (int k = 0; k < n && c[k] <= b[j] * 2; k++) { + if (c[k] > b[j]) { + ans++; + } + } + } + } + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int ways2(int[] a, int[] b, int[] c) { + int n = a.length; + Arrays.sort(a); + Arrays.sort(b); + Arrays.sort(c); + // B里面的记录 + int[] help = new int[n]; + for (int i = 0, l = -1, r = 0; i < n; i++) { + while (l + 1 < n && c[l + 1] <= b[i]) { + l++; + } + while (r < n && c[r] <= b[i] * 2) { + r++; + } + help[i] = Math.max(r - l - 1, 0); + } + for (int i = 1; i < n; i++) { + help[i] += help[i - 1]; + } + int ans = 0; + for (int i = 0, l = -1, r = 0; i < n; i++) { + while (l + 1 < n && b[l + 1] <= a[i]) { + l++; + } + while (r < n && b[r] <= a[i] * 2) { + r++; + } + if (r - l - 1 > 0) { + ans += sum(help, l + 1, r - 1); + } + } + return ans; + } + + public static int sum(int[] help, int l, int r) { + return l == 0 ? help[r] : help[r] - help[l - 1]; + } + + // 为了测试 + 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); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int V = 100; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] a = randomArray(n, V); + int[] b = randomArray(n, V); + int[] c = randomArray(n, V); + int ans1 = ways1(a, b, c); + int ans2 = ways2(a, b, c); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class104/Code04_TopMinSubsquenceSum.java b/公开课/class104/Code04_TopMinSubsquenceSum.java new file mode 100644 index 0000000..9093050 --- /dev/null +++ b/公开课/class104/Code04_TopMinSubsquenceSum.java @@ -0,0 +1,116 @@ +package class104; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自亚马逊 +// 给定一个数组arr,含有n个数字,都是非负数 +// 给定一个正数k +// 返回所有子序列中,累加和最小的前k个子序列累加和 +// 假设K不大,怎么算最快? +public class Code04_TopMinSubsquenceSum { + + public static int[] topMinSum1(int[] arr, int k) { + ArrayList allAns = new ArrayList<>(); + process(arr, 0, 0, allAns); + allAns.sort((a, b) -> a.compareTo(b)); + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + ans[i] = allAns.get(i); + } + return ans; + } + + public static void process(int[] arr, int index, int sum, ArrayList ans) { + if (index == arr.length) { + ans.add(sum); + } else { + process(arr, index + 1, sum, ans); + process(arr, index + 1, sum + arr[index], ans); + } + } + + public static int[] topMinSum2(int[] arr, int k) { + Arrays.sort(arr); + // (最右的下标,集合的累加和) + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { 0, arr[0] }); + int[] ans = new int[k]; + // ans[0] = 0 + // 0 1 2 k-1 + // k个! + for (int i = 1; i < k; i++) { + int[] cur = heap.poll(); + // (7, 100) + // 左 :8, 100 - arr[7] + arr[8] + // 右 :8, 100 + arr[8] + int last = cur[0]; + int sum = cur[1]; + ans[i] = sum; + if (last + 1 < arr.length) { + heap.add(new int[] { last + 1, sum - arr[last] + + arr[last + 1] }); + heap.add(new int[] { last + 1, sum + arr[last + 1] }); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static boolean equals(int[] ans1, int[] ans2) { + if (ans1.length != ans2.length) { + return false; + } + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 40; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int k = (int) (Math.random() * ((1 << len) - 1)) + 1; + int[] ans1 = topMinSum1(arr, k); + int[] ans2 = topMinSum2(arr, k); + if (!equals(ans1, ans2)) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class104/Code05_MaxLengthSameCharMChanges.java b/公开课/class104/Code05_MaxLengthSameCharMChanges.java new file mode 100644 index 0000000..db2d00a --- /dev/null +++ b/公开课/class104/Code05_MaxLengthSameCharMChanges.java @@ -0,0 +1,130 @@ +package class104; + +// 来自字节 +// 给定一个只由小写字母组成的字符串str,长度为N +// 给定一个只由0、1组成的数组arr,长度为N +// arr[i] == 0表示str中i位置的字符不许修改 +// arr[i] == 1表示str中i位置的字符允许修改 +// 给定一个正数m,表示在任意允许修改的位置 +// 可以把该位置的字符变成a~z中的任何一个 +// 可以修改m次 +// 返回在最多修改m次的情况下,全是一种字符的最长子串是多长 +// 1 <= N, M <= 10^5 +// 所有字符都是小写 +public class Code05_MaxLengthSameCharMChanges { + + // 暴力方法 + // 为了测试 + public static int maxLen1(String str, int[] arr, int m) { + char[] s = str.toCharArray(); + int n = s.length; + int ans = 0; + for (char c = 'a'; c <= 'z'; c++) { + for (int i = 0; i < n; i++) { + for (int j = n - 1; j >= i; j--) { + if (ok(s, i, j, c, arr, m)) { + ans = Math.max(ans, j - i + 1); + break; + } + } + } + } + return ans; + } + + // 为了测试 + public static boolean ok(char[] s, int l, int r, char c, int[] arr, int m) { + for (int i = l; i <= r; i++) { + if (s[i] == c) { + continue; + } + if (arr[i] == 0 || m == 0) { + return false; + } + m--; + } + return true; + } + + // 正式方法 + public static int maxLen2(String str, int[] arr, int m) { + char[] s = str.toCharArray(); + int n = s.length; + int ans = 0; + for (char aim = 'a'; aim <= 'z'; aim++) { + // 右边界 + // [l..r) + int r = 0; + // 用了几次修改了 + // change == m 用完的时候 + int change = 0; + for (int l = 0; l < n; l++) { + // l......r -> + while (r < n) { + if (s[r] == aim) { + r++; + continue; + } + // s[r] != aim + if (arr[r] == 0 || change == m) { + break; + } + // s[r] != aim && arr[r] == 1 && change < m + change++; + r++; + } + // l....r-1 r + // X l+1 + ans = Math.max(ans, r - l); + if (s[l] != aim && arr[l] == 1) { + change--; + } + // r r + // l l + // X + r = Math.max(r, l + 1); + } + } + return ans; + } + + // 为了测试 + public static String randomString(int n, int r) { + char[] ans = new char[n]; + for (int i = 0; i < n; i++) { + ans[i] = (char) ((int) (Math.random() * r) + 'a'); + } + return String.valueOf(ans); + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * 2); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int R = 5; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * n) + 1; + String str = randomString(n, R); + int[] arr = randomArray(n); + int ans1 = maxLen1(str, arr, m); + int ans2 = maxLen2(str, arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class104/Code06_EvenTimesMaxSubstring.java b/公开课/class104/Code06_EvenTimesMaxSubstring.java new file mode 100644 index 0000000..bf1d97b --- /dev/null +++ b/公开课/class104/Code06_EvenTimesMaxSubstring.java @@ -0,0 +1,118 @@ +package class104; + +import java.util.HashMap; + +// 来自微软 +// 给定一个字符串s,其中都是英文小写字母 +// 如果s中的子串含有的每种字符都是偶数个 +// 那么这样的子串就是达标子串,子串要求是连续串 +// 返回s中达标子串的最大长度 +// 1 <= s的长度 <= 10^5 +// 字符种类都是英文小写 +public class Code06_EvenTimesMaxSubstring { + + // 一个经典问题 + // 累加和等于k的最长子数组是多长 + public static int maxLength(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return 0; + } + // key : 某个前缀和 + // value : 最早出现的位置 + HashMap map = new HashMap(); + map.put(0, -1); + int len = 0; + int x = 0; + for (int i = 0; i < arr.length; i++) { + x += arr[i]; + if (map.containsKey(x - k)) { + len = Math.max(i - map.get(x - k), len); + } + if (!map.containsKey(x)) { + map.put(x, i); + } + } + return len; + } + + // 为了测试 + // 暴力方法 + public static int maxLen1(String s) { + int n = s.length(); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = n - 1; j >= i; j--) { + if (ok(s, i, j)) { + ans = Math.max(ans, j - i + 1); + break; + } + } + } + return ans; + } + + // 为了测试 + // 暴力方法 + public static boolean ok(String s, int l, int r) { + if (((r - l + 1) & 1) == 1) { + return false; + } + int[] cnts = new int[26]; + for (int i = l; i <= r; i++) { + cnts[s.charAt(i) - 'a']++; + } + for (int cnt : cnts) { + if ((cnt & 1) == 1) { + return false; + } + } + return true; + } + + public static int maxLen2(String s) { + HashMap map = new HashMap<>(); + map.put(0, -1); + int status = 0; + int ans = 0; + int n = s.length(); + for (int i = 0; i < n; i++) { + status ^= 1 << (s.charAt(i) - 'a'); + if (map.containsKey(status)) { + ans = Math.max(ans, i - map.get(status)); + } else { + map.put(status, i); + } + } + return ans; + } + + // 为了测试 + public static String randomString(int n, int v) { + char[] s = new char[n]; + for (int i = 0; i < n; i++) { + s[i] = (char) ((int) (Math.random() * v) + 'a'); + } + return String.valueOf(s); + } + + // 为了测试 + public static void main(String[] args) { + int n = 50; + int v = 6; + int testTimes = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + String s = randomString(n, v); + int ans1 = maxLen1(s); + int ans2 = maxLen2(s); + if (ans1 != ans2) { + System.out.println(s); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class104/Code07_SortGame.java b/公开课/class104/Code07_SortGame.java new file mode 100644 index 0000000..b1dc592 --- /dev/null +++ b/公开课/class104/Code07_SortGame.java @@ -0,0 +1,189 @@ +package class104; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeSet; + +// 来自百度 +// 二狗买了一些小兵玩具,和大胖一起玩 +// 一共有n个小兵,这n个小兵拍成一列 +// 第i个小兵战斗力为hi,然后他们两个开始对小兵进行排列 +// 一共进行m次操作,二狗每次操作选择一个数k,将前k个小兵战斗力从小到大排列 +// 大胖每次操作选择一个数k,将前k个小兵战斗力从大到小排列 +// 问所有操作结束后,排列顺序什么样 +// 给定一个长度为n的数组arr,表示每个小兵的战斗力 +// 给定一个长度为m的数组op, +// op[i] = { k , 0 }, 表示对前k个士兵执行从小到大的操作 +// op[i] = { k , 1 }, 表示对前k个士兵执行从大到小的操作 +// 返回数组ans,表示最终的排列 +// 1 <= n, m <= 2 * 10^5 +// - 10^9 <= arr[i] <= + 10^9 +public class Code07_SortGame { + + // 暴力方法 + // 为了验证 + public static int[] game1(int[] arr, int[][] op) { + int n = arr.length; + Integer[] help = new Integer[n]; + for (int i = 0; i < n; i++) { + help[i] = arr[i]; + } + for (int[] o : op) { + if (o[1] == 0) { + Arrays.sort(help, 0, o[0], (a, b) -> a - b); + } else { + Arrays.sort(help, 0, o[0], (a, b) -> b - a); + } + } + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = help[i]; + } + return ans; + } + + // 正式方法 + // 时间复杂度O(M) + O(N*logN) + public static int[] game2(int[] arr, int[][] op) { + int n = arr.length; + int m = op.length; + int[] stack = new int[m]; + int r = 0; + for (int i = 0; i < m; i++) { + while (r != 0 && op[stack[r - 1]][0] <= op[i][0]) { + r--; + } + stack[r++] = i; + } + int[] ans = new int[n]; + int ansi = n - 1; + int l = 0; + for (; ansi >= op[stack[l]][0]; ansi--) { + ans[ansi] = arr[ansi]; + } + TreeSet sorted = new TreeSet<>(new NumberComparator()); + for (int i = 0; i < op[stack[l]][0]; i++) { + sorted.add(new Number(arr[i], i)); + } + while (l != r) { + // 当前处理的指令 + int[] cur = op[stack[l++]]; + if (l != r) { + int[] next = op[stack[l]]; + int num = cur[0] - next[0]; + if (cur[1] == 0) { + for (int i = 0; i < num; i++) { + ans[ansi--] = sorted.pollLast().value; + } + } else { + for (int i = 0; i < num; i++) { + ans[ansi--] = sorted.pollFirst().value; + } + } + } else { + if (cur[1] == 0) { + while (!sorted.isEmpty()) { + ans[ansi--] = sorted.pollLast().value; + } + } else { + while (!sorted.isEmpty()) { + ans[ansi--] = sorted.pollFirst().value; + } + } + } + } + return ans; + } + + public static class Number { + public int value; + public int index; + + public Number(int v, int i) { + value = v; + index = i; + } + } + + public static class NumberComparator implements Comparator { + + @Override + public int compare(Number o1, Number o2) { + if (o1.value != o2.value) { + return o1.value - o2.value; + } else { + return o1.index - o2.index; + } + } + + } + + // 为了测试 + 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 int[][] randomOp(int m, int n) { + int[][] ans = new int[m][2]; + for (int i = 0; i < m; i++) { + ans[i][0] = (int) (Math.random() * (n + 1)); + ans[i][1] = Math.random() < 0.5 ? 0 : 1; + } + return ans; + } + + // 为了测试 + public static boolean isEqual(int[] arr1, int[] arr2) { + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + +// int[] arr = { 3, 3, 7, 7, 7 }; +// +// TreeSet set = new TreeSet<>(new NumberComparator()); +// +// for (int i = 0; i < arr.length; i++) { +// set.add(new Number(arr[i], i)); +// } +// +//// System.out.println(set.size()); +// +// while(!set.isEmpty()) { +// System.out.println(set.pollLast().value); +// } + + int N = 100; + int M = 100; + int V = 200; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[] arr = randomArray(n, V); + int[][] op = randomOp(m, n); + int[] ans1 = game1(arr, op); + int[] ans2 = game2(arr, op); + if (!isEqual(ans1, ans2)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class105/Code01_EveryQueryUsers.java b/公开课/class105/Code01_EveryQueryUsers.java new file mode 100644 index 0000000..e9c17e2 --- /dev/null +++ b/公开课/class105/Code01_EveryQueryUsers.java @@ -0,0 +1,213 @@ +package class105; + +import java.util.HashMap; +import java.util.HashSet; + +// 来自字节 +// 给定正数N,表示用户数量,用户编号从0~N-1 +// 给定正数M,表示实验数量,实验编号从0~M-1 +// 给定长度为N的二维数组A, +// A[i] = { a, b, c }表示,用户i报名参加了a号、b号、c号实验 +// 给定正数Q,表示查询的条数 +// 给定长度为Q的二维数组B, +// B[i] = { e, f }表示,第i条查询想知道e号、f号实验,一共有多少人(去重统计) +// 返回每一条查询的结果数组 +// 数据描述 : +// 1 <= N <= 10^5 +// 1 <= M <= 10^2 +// 所有查询所列出的所有实验编号数量 <= 10^5 +public class Code01_EveryQueryUsers { + +// public static void main(String[] args) { +//// int a = 11; +//// int b = 10; +//// // a / b向上取整 : +//// System.out.println((a + b - 1) / b); +//// +//// System.out.println(100000 / 32); +//// // 0 ~ 49999 +//// int n = 50000; +//// int[] sets = new int[(n + 31) / 32]; +//// int i = 34601; +//// // 34601 / 32 +//// // sets[0] : 0 ~ 31 +//// // sets[1] : 32 ~ 63 +//// // sets[2] : 64 ~ +//// int from = i / 32; +//// // 34601 % 32 +//// sets[from] |= 1 << (i % 32); +// +// int n = 100000; +// int m = 100; +// int parts = (n + 31) / 32; +// int[][] sets = new int[m][parts]; +// int[][] A = { { 3, 6, 9 }, // 0 +// { 5, 17, 23 }, // 1 +// { 89, 73, 13 }, // 2 +// +// }; +// +// for (int i = 0; i < A.length; i++) { +// for (int exp : A[i]) { +// int[] set = sets[exp]; +// set[i / 32] |= 1 << (1 % 32); +// } +// } +// +// int[][] B = { +// { 4, 17, 23 }, // 第0条查询 +// { 3, 4, 41 }, // 第1条查询 +// }; +// +// int[] ans = new int[B.length]; +// +// for (int i = 0; i < B.length; i++) { +// int ones = 0; +// // 4 : 0 1 ... parts-1 +// //17 : 0 1 ... parts-1 +// //23 : 0 1 ... parts-1 +// for (int j = 0; j < parts; j++) { +// int count = 0; +// for(int exp : B[i]) { +// count |= sets[exp][j]; +// } +// ones += countOnes(count); +// } +// ans[i] = ones; +// } +// +//// System.out.println(countOnes(set)); +// +// } + + // 暴力方法 + // 为了验证 + public static int[] record1(int n, int m, int q, int[][] A, int[][] B) { + HashMap> expUsersMap = new HashMap<>(); + for (int i = 0; i < m; i++) { + expUsersMap.put(i, new HashSet<>()); + } + for (int i = 0; i < n; i++) { + for (int exp : A[i]) { + expUsersMap.get(exp).add(i); + } + } + int[] ans = new int[q]; + HashSet help = new HashSet<>(); + for (int i = 0; i < q; i++) { + help.clear(); + for (int exp : B[i]) { + for (int user : expUsersMap.get(exp)) { + help.add(user); + } + } + ans[i] = help.size(); + } + return ans; + } + + // 正式方法 + public static int[] record2(int n, int m, int q, int[][] A, int[][] B) { + // n 一共有多少人 + // 任何一个实验,需要几个整数,能表示所有人谁出现谁没出现? + int parts = (n + 31) / 32; + // m 0 ~ m -1 + // [i] [.........] + int[][] bitMap = new int[m][parts]; + for (int i = 0; i < n; i++) { + // i 人的编号 : a b c + for (int exp : A[i]) { + bitMap[exp][i / 32] |= 1 << (i % 32); + } + } + int[] ans = new int[q]; + for (int i = 0; i < q; i++) { + // i号查询 : a、c、e,一共有多少去重的人 + // a[0] | c[0] | e[0] -> 几个1 + // a[1] | c[1] | e[1] -> 几个1 + int all = 0; + for (int j = 0; j < parts; j++) { + int status = 0; + for (int exp : B[i]) { + status |= bitMap[exp][j]; + } + all += countOnes(status); + } + ans[i] = all; + } + return ans; + } + + // 大厂刷题班,32节, + // leetcode专题 : https://leetcode.com/problems/number-of-1-bits/ + // 一个32位整数,求里面有几个1 + public static int countOnes(int n) { + n = (n & 0x55555555) + ((n >>> 1) & 0x55555555); + n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); + n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f); + n = (n & 0x00ff00ff) + ((n >>> 8) & 0x00ff00ff); + n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff); + return n; + } + + // 为了测试 + public static int[][] randomMatrix(int n, int m, int v) { + int[][] ans = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + ans[i][j] = (int) (Math.random() * v); + } + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int M = 20; + int Q = 50; + int testTime = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int[][] A = randomMatrix(n, (int) (Math.random() * m) + 1, m); + int q = (int) (Math.random() * Q) + 1; + int[][] B = randomMatrix(q, (int) (Math.random() * m) + 1, m); + int[] ans1 = record1(n, m, q, A, B); + int[] ans2 = record2(n, m, q, A, B); + boolean pass = true; + for (int j = 0; j < q; j++) { + if (ans1[j] != ans2[j]) { + pass = false; + break; + } + } + if (!pass) { + System.out.println("出错了!"); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 100000; + int m = 100; + int[][] A = randomMatrix(n, m, m); + int q = 1000; + int c = 100; + int[][] B = randomMatrix(q, c, m); + System.out.println("用户数量 : " + n); + System.out.println("实验数量 : " + m); + System.out.println("用户参加的实验数量总和 : " + n * m); + System.out.println("查询条数 : " + q); + System.out.println("每条查询的实验数量 : " + c); + System.out.println("所有查询所列出的所有实验编号数量 : " + q * c); + long start = System.currentTimeMillis(); + record2(n, m, q, A, B); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + } + +} diff --git a/公开课/class105/Code02_TwoTeamsSortedMinSwap.java b/公开课/class105/Code02_TwoTeamsSortedMinSwap.java new file mode 100644 index 0000000..fa8658d --- /dev/null +++ b/公开课/class105/Code02_TwoTeamsSortedMinSwap.java @@ -0,0 +1,146 @@ +package class105; + +import java.util.HashMap; + +// 来自美团 +// 两种颜色的球,蓝色和红色,都按1~n编号,共计2n个 +// 为方便放在一个数组中,红球编号取负,篮球不变,并打乱顺序, +// 要求同一种颜色的球按编号升序排列,可以进行如下操作: +// 交换相邻两个球,求最少操作次数。 +// [3,-3,1,-4,2,-2,-1,4] +// 最终交换结果为 +// [1,2,3,-1,-2,-3,-4,4] +// 最少交换次数为10 +// n <= 1000 +public class Code02_TwoTeamsSortedMinSwap { + +// public static int zuo(int lastA, int lastB, int[] arr) { +// if (lastA == 0 && lastB == 0) { +// return 0; +// } +// // lastA != 0 || lastB != 0 +// if (lastA == 0) { +// // lastB +// int curCost = lastB来到此时的终止位置的代价; +// int next = zuo(lastA, lastB - 1, arr); +// return curCost + next; +// } +// if(lastB == 0) { +// int curCost = lastA来到此时的终止位置的代价; +// int next = zuo(lastA - 1, lastB, arr); +// return curCost + next; +// } +// // lastA, lastB +// // 让lastA来到最后 +// int p1 = Integer.MAX_VALUE; +// int lastAComeCost = lastA来到此时的终止位置的代价; +// int next1 = zuo(lastA - 1, lastB, arr); +// p1 = lastAComeCost + next1; +// // 让lastB来到最后 +// int p2 = Integer.MAX_VALUE; +// int lastBComeCost = lastB来到此时的终止位置的代价; +// int next2 = zuo(lastA, lastB - 1, arr); +// p2 = lastBComeCost + next2; +// return Math.min(p1, p2); +// } + + // [3,-3,1,-4,2,-2,-1,4] + // -3 -4 -2 -1 -> -1 -2 -3 -4 + // 3 1 2 4 -> 1 2 3 4 + + // 这个题写对数器太麻烦了 + // 所以这就是正式解 + public static int minSwaps(int[] arr) { + int n = arr.length; + HashMap map = new HashMap<>(); + int topA = 0; + int topB = 0; + for (int i = 0; i < n; i++) { + if (arr[i] > 0) { + topA = Math.max(topA, arr[i]); + } else { + topB = Math.max(topB, Math.abs(arr[i])); + } + map.put(arr[i], i); + } + IndexTree it = new IndexTree(n); + for (int i = 0; i < n; i++) { + it.add(i, 1); + } + return f(topA, topB, it, n - 1, map); + } + + // 可以改二维动态规划! + // 因为it的状态,只由topA和topB决定 + // 所以it的状态不用作为可变参数! + public static int f(int lastA, int lastB, + IndexTree it, // 支持快速的距离计算 + int end, // 不变!永远n-1! + HashMap map // 位置表 + ) { + if (lastA == 0 && lastB == 0) { + return 0; + } + int p1 = Integer.MAX_VALUE; + int p2 = Integer.MAX_VALUE; + int index, cost, next; + if (lastA != 0) { + // 查出原数组中,lastA在哪? + index = map.get(lastA); + // + cost = it.sum(index, end) - 1; + // 既然搞定了lastA,indexTree + it.add(index, -1); + next = f(lastA - 1, lastB, it, end, map); + it.add(index, 1); + p1 = cost + next; + } + if (lastB != 0) { + index = map.get(-lastB); + cost = it.sum(index, end) - 1; + it.add(index, -1); + next = f(lastA, lastB - 1, it, end, map); + it.add(index, 1); + p2 = cost + next; + } + return Math.min(p1, p2); + } + + public static class IndexTree { + private int[] tree; + private int N; + + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + public void add(int i, int v) { + i++; + while (i <= N) { + tree[i] += v; + i += i & -i; + } + } + + public int sum(int l, int r) { + return l == 0 ? sum(r + 1) : (sum(r + 1) - sum(l)); + } + + private int sum(int index) { + int ans = 0; + while (index > 0) { + ans += tree[index]; + index -= index & -index; + } + return ans; + } + + } + + public static void main(String[] args) { + int[] arr = { 3, -3, 1, -4, 2, -2, -1, 4 }; + System.out.println(minSwaps(arr)); + } + +} diff --git a/公开课/class106/Code01_MaximumWidthRamp.java b/公开课/class106/Code01_MaximumWidthRamp.java new file mode 100644 index 0000000..42fa302 --- /dev/null +++ b/公开课/class106/Code01_MaximumWidthRamp.java @@ -0,0 +1,50 @@ +package class106; + +// 给定一个整数数组 A,坡是元组 (i, j),其中  i < j 且 A[i] <= A[j] +// 这样的坡的宽度为 j - i +// 找出 A 中的坡的最大宽度,如果不存在,返回 0 +// 示例 1: +// 输入:[6,0,8,2,1,5] +// 输出:4 +// 解释: +// 最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5 +// 示例 2: +// 输入:[9,8,1,0,1,9,4,0,4,1] +// 输出:7 +// 解释: +// 最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1 +// 测试链接 : https://leetcode.cn/problems/maximum-width-ramp/ +public class Code01_MaximumWidthRamp { + + public static int maxWidthRamp(int[] arr) { + // 5 7 4 6 4 3.... + // 0 1 2 3 4 5.... + int n = arr.length; + int[] stack = new int[n]; + int r = 0; + for (int i = 0; i < n; i++) { + // 5 7 4 6 4 3.... + // 0 1 2 3 4 5.... + // i + if (r == 0 || arr[stack[r - 1]] > arr[i]) { + stack[r++] = i; + } + } + int ans = 0; + for (int j = n - 1; j >= 0; j--) { + // ? 34 + // 18 19 + // 13 -> 3 X + // 9 -> 7 X + // 3 -> 50 停 + // 2 -> 55 + // 0 -> 100 + while (r != 0 && arr[stack[r - 1]] <= arr[j]) { + int i = stack[--r]; + ans = Math.max(ans, j - i); + } + } + return ans; + } + +} diff --git a/公开课/class106/Code02_MakeASortedMinSwaps.java b/公开课/class106/Code02_MakeASortedMinSwaps.java new file mode 100644 index 0000000..287214e --- /dev/null +++ b/公开课/class106/Code02_MakeASortedMinSwaps.java @@ -0,0 +1,169 @@ +package class106; + +import java.util.Arrays; + +// 来自学员问题 +// 商场中有一展柜A,其大小固定,现已被不同的商品摆满 +// 商家提供了一些新商品B,需要对A中的部分商品进行更新替换 +// B中的商品可以自由使用,也就是可以用B中的任何商品替换A中的任何商品 +// A中的商品一旦被替换,就认为消失了!而不是回到了B中! +// 要求更新过后的展柜中,商品严格按照价格由低到高进行排列 +// 不能有相邻商品价格相等的情况 +// A[i]为展柜中第i个位置商品的价格,B[i]为各个新商品的价格 +// 求能够满足A中商品价格严格递增的最小操作次数,若无法满足则返回-1 +// A数组长度、B数组长度 <= 1000 +public class Code02_MakeASortedMinSwaps { + +// public static int minSwapTimes(int[] A, int[] B) { +// int ans = zuo(A, B, 0, 0, 0); +// return ans == Integer.MAX_VALUE ? -1 : ans; +// } +// +// // B数组有序的! +// // A[ai.....] 都递增 +// // A[0...ai-1] 已经做到都递增了!可能用过B里的商品替换! +// // pre == 0,代表前一个数没换过! +// // pre == 1,代表前一个数换过! +// // 当前可以使用B[bi....]商品进行替换 +// // B[0..bi-1]这些商品认为,不能再用了! +// // 在这种情况下,做到 A[ai.....] 都递增,至少还要换几件?返回 +// public static int zuo(int[] A, int[] B, int ai, int bi, int pre) { +// if (ai == A.length) { +// return 0; +// } +// // A还有商品, 当前商品是[ai] +// // 前一件商品的价格,lastPrice +// int lastPrice = 0; +// if (ai == 0) { +// lastPrice = Integer.MIN_VALUE; +// } else { // 不是A里最左的商品 +// if (pre == 0) { +// lastPrice = A[ai - 1]; +// } else { // pre == 1 +// lastPrice = B[bi - 1]; +// } +// } +// // 当前商品 +// // 可能性1,不换! +// int p1 = Integer.MAX_VALUE; +// if (lastPrice < A[ai]) { +// p1 = zuo(A, B, ai + 1, bi, 0); +// } +// // 可能性2, 换! +// int p2 = Integer.MAX_VALUE; +// int findChangeIndex = findMostLeft(B, bi, lastPrice); +// if (findChangeIndex != -1) { +// int next2 = zuo(A, B, ai + 1, findChangeIndex + 1, 1); +// if (next2 != Integer.MAX_VALUE) { +// p2 = 1 + next2; +// } +// } +// return Math.min(p1, p2); +// } +// +// // B[bi.....] 找 >num 尽量左的位置返回!返回的是位置! +// // 如果B[bi.....] 没有 >num的数,返回-1 +// // 有的话,返回 >num, 最左的位置 +// // 二分 +// public static int findMostLeft(int[] B, int bi, int num) { +// +// } + + // 可以用B里的数字,替换A里的数字,想让A严格递增 + // 返回至少换几个数字 + public static int minSwaps(int[] A, int[] B) { + // 根据题意,B里的数字随意拿 + // 所以B里的数字排序,不会影响拿 + // 同时,A如果从左往右考虑,依次被B替换上去的数字,肯定是从小到大的 + // 这是一定的!比如B = {5,3,2,9} + // 可能先用5替换A的某个左边的数,再用2替换A的某个右边的数吗?不可能 + // 所以将B排序 + Arrays.sort(B); + int ans = process(A, B, 0, 0, 0); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 参数解释: + // A[0...ai-1]范围上已经做到升序了 + // 接下来请让A[ai....]范围上的数字做到升序 + // 之前的过程中,B里可能已经拿过一些数字了 + // 拿过的数字都在B[0...bi-1]范围上,不一定都拿了 + // 但是最后拿的数字一定是B[bi-1] + // 如果想用B里的数字替换当前的A[ai],请在B[bi....]范围上考虑拿数字 + // pre : 表示之前的A[ai-1]被替换了没有, + // 如果pre==0,表示A[ai-1]没被替换 + // 如果pre==1,表示A[ai-1]被替换了,被谁替换的?被B[bi-1]替换的! + // 返回值:让A[ai....]范围上的数字做到升序,最少还要在B[bi...]上拿几个数字 + // 如果返回值是Integer.MAX_VALUE,表示怎么都做不到 + // 这就是一个三维动态规划,自己改! + // ai 是N范围 + // bi 是M范围 + // pre 只有0、1两种值 + // 所以时间复杂度O(N*M*logM) + // 这个logM怎么来的,二分来的,看代码! + // ai 0...N + // bi 0...M + // pre 0、1 + // N*M*2 + public static int process(int[] A, int[] B, int ai, int bi, int pre) { + if (ai == A.length) { + return 0; + } + // 之前的数是什么 + int preNum = 0; + if (ai == 0) { + preNum = Integer.MIN_VALUE; + } else { + if (pre == 0) { + preNum = A[ai - 1]; + } else { + preNum = B[bi - 1]; + } + } + // 可能性1,搞定当前的A[ai],不依靠交换 + int p1 = preNum < A[ai] ? process(A, B, ai + 1, bi, 0) : Integer.MAX_VALUE; + // 可能性2,搞定当前的A[ai],依靠交换 + int p2 = Integer.MAX_VALUE; + // 在B[bi....]这个范围上,找到>preNum,最左的位置 + // 这一步是可以二分的!因为B整体有序 + int nearMoreIndex = bs(B, bi, preNum); + if (nearMoreIndex != -1) { + int next2 = process(A, B, ai + 1, nearMoreIndex + 1, 1); + if (next2 != Integer.MAX_VALUE) { + p2 = 1 + next2; + } + } + return Math.min(p1, p2); + } + + // 在B[start....]这个范围上,找到>num,最左的位置 + public static int bs(int[] B, int start, int num) { + int ans = -1; + int l = start; + int r = B.length - 1; + int m = 0; + while (l <= r) { + m = (l + r) / 2; + if (B[m] > num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static void main(String[] args) { + int[] A1 = { 1, 8, 3, 6, 9 }; + int[] B1 = { 1, 3, 2, 4 }; + System.out.println(minSwaps(A1, B1)); + int[] A2 = { 4, 8, 3, 10, 5 }; + int[] B2 = { 1, 3, 2, 4 }; + System.out.println(minSwaps(A2, B2)); + int[] A3 = { 1, 8, 3, 6, 9 }; + int[] B3 = { 4, 3, 1 }; + System.out.println(minSwaps(A3, B3)); + } + +} diff --git a/公开课/class107/Code01_ScoreAllMatrix.java b/公开课/class107/Code01_ScoreAllMatrix.java new file mode 100644 index 0000000..b1ce961 --- /dev/null +++ b/公开课/class107/Code01_ScoreAllMatrix.java @@ -0,0 +1,75 @@ +package class107; + +// 来自蚂蚁金服 +// 得分的定义 : +// 含有大小2*2的矩阵,要么: +// 1 0 +// 0 1 可以得1分 +// 要么 +// 0 1 +// 1 0 可以得1分 +// 那么一个任意大小的矩阵就有若干得分点,比如 +// 0 1 0 +// 1 0 1 +// 这个矩阵就有2个得分点 +// 给定正数N,正数M,求所有可能的情况里,所有的得分点总和 +// 1 <= N、M <= 10^9 +public class Code01_ScoreAllMatrix { + + public static int score1(int n, int m) { + if (n < 2 || m < 2) { + return 0; + } + int[][] matrix = new int[n][m]; + return process(matrix, 0, 0, n, m); + } + + public static int process(int[][] matrix, int i, int j, int n, int m) { + if (i == n) { + int score = 0; + for (int r = 1; r < n; r++) { + for (int c = 1; c < m; c++) { + if (check(matrix, r, c)) { + score++; + } + } + } + return score; + } + if (j == m) { + return process(matrix, i + 1, 0, n, m); + } + int score = 0; + matrix[i][j] = 1; + score += process(matrix, i, j + 1, n, m); + matrix[i][j] = 0; + score += process(matrix, i, j + 1, n, m); + return score; + } + + public static boolean check(int[][] m, int r, int c) { + return (m[r - 1][c - 1] == 0 && m[r][c - 1] == 1 && m[r - 1][c] == 1 && m[r][c] == 0) + || (m[r - 1][c - 1] == 1 && m[r][c - 1] == 0 && m[r - 1][c] == 0 && m[r][c] == 1); + } + + // 原始题目有取mod + // 本方法没有取mod,只展示大思路 + public static int score2(int n, int m) { + if (n < 2 || m < 2) { + return 0; + } + // 注意!这里要求掌握一个技巧点 + // 算一个数的k次方,怎么最快! + // 每算一次,都需要取mod! + // 快速幂来计算 + return (n * m - m - n + 1) * (1 << (n * m - 3)); + } + + public static void main(String[] args) { + int n = 4; + int m = 6; + System.out.println(score1(n, m)); + System.out.println(score2(n, m)); + } + +} diff --git a/公开课/class107/Code02_HappyLimitLessGap.java b/公开课/class107/Code02_HappyLimitLessGap.java new file mode 100644 index 0000000..9b33445 --- /dev/null +++ b/公开课/class107/Code02_HappyLimitLessGap.java @@ -0,0 +1,124 @@ +package class107; + +import java.util.Arrays; + +// 来自蚂蚁金服 +// 小红有n个朋友, 她准备开个宴会,邀请一些朋友 +// i号朋友的愉悦值为a[i],财富值为b[i] +// 如果两个朋友同时参加宴会,这两个朋友之间的隔阂是其财富值差值的绝对值 +// 宴会的隔阂值,是财富差距最大的两人产生的财富值差值的绝对值 +// 宴会的愉悦值,是所有参加宴会朋友的愉悦值总和 +// 小红希望宴会的愉悦值不能小于k的情况下, 宴会的隔阂值能最小是多少 +// 如果做不到,返回-1 +// 1 <= n <= 2 * 10^5 +// 1 <= 愉悦值、财富值 <= 10^9 +// 1 <= k <= 10^14 +public class Code02_HappyLimitLessGap { + + // 暴力方法 + // 为了验证 + public static int lessGap1(int[] a, int[] b, int k) { + long ans = process(a, b, 0, k, Integer.MAX_VALUE, Integer.MIN_VALUE); + return ans < Integer.MAX_VALUE ? (int) ans : -1; + } + + // 暴力方法 + // 为了验证 + public static long process(int[] a, int[] b, int i, int rest, int min, int max) { + if (rest <= 0) { + return (long) max - (long) min; + } + if (i == a.length) { + return (long) Integer.MAX_VALUE; + } + long p1 = process(a, b, i + 1, rest, min, max); + long p2 = process(a, b, i + 1, rest - a[i], Math.min(min, b[i]), Math.max(max, b[i])); + return Math.min(p1, p2); + } + + // 正式方法 + // 二分答案 + public static int lessGap2(int[] a, int[] b, long k) { + int n = a.length; + int[][] f = new int[n][2]; + int min = b[0]; + int max = b[0]; + // a : 30 13 + // b : 10 50 + // 0 1 .... + // [30,10] [13,50] [...] + // 财富排序 + for (int i = 0; i < n; i++) { + f[i][0] = a[i]; + f[i][1] = b[i]; + min = Math.min(min, b[i]); + max = Math.max(max, b[i]); + } + // 财富排序 + Arrays.sort(f, (x, y) -> x[1] - y[1]); + // 0 ~ max - min + int l = 0; + int r = max - min; + int m = 0; + int ans = -1; + while (l <= r) { + // 隔阂阈值 + m = (l + r) / 2; + if (maxHappy(f, m) >= k) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 所有的人,财富、愉悦 + // 隔阂阈值要求 limit + // 返回,最大宴会愉悦度 + public static long maxHappy(int[][] f, int limit) { + int n = f.length; + long sum = 0; + long ans = 0; + for (int l = 0, r = 0; l < n; l++) { + while (r < n && f[r][1] - f[l][1] <= limit) { + sum += f[r++][0]; + } + ans = Math.max(ans, sum); + sum -= f[l][0]; + r = Math.max(r, l + 1); + } + return ans; + } + + // 为了验证 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了验证 + public static void main(String[] args) { + int N = 15; + int V = 20; + int testTime = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] a = randomArray(n, V); + int[] b = randomArray(n, V); + int k = (int) (Math.random() * n * V) + 1; + int ans1 = lessGap1(a, b, k); + int ans2 = lessGap2(a, b, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + } + +} diff --git a/公开课/class107/Code03_RobotAndClothes.java b/公开课/class107/Code03_RobotAndClothes.java new file mode 100644 index 0000000..07f9344 --- /dev/null +++ b/公开课/class107/Code03_RobotAndClothes.java @@ -0,0 +1,304 @@ +package class107; + +import java.util.Arrays; + +// 来自美团 +// 给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 +// 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 +// 给定两个长度为n的数组,powers和rates +// powers[i]表示i位置的机器人的启动电量 +// rates[i]表示i位置的机器人收起1件衣服的时间 +// 使用每个机器人只需要付出启动电量 +// 当i位置的机器人收起i位置的衣服,它会继续尝试往右收起i+1位置衣服 +// 如果i+1位置的衣服已经被其他机器人收了或者其他机器人正在收 +// 这个机器人就会停机, 不再收衣服。 +// 不过如果它不停机,它会同样以rates[i]的时间来收起这件i+1位置的衣服 +// 也就是收衣服的时间为每个机器人的固定属性,当它收起i+1位置的衣服, +// 它会继续检查i+2位置...一直到它停机或者右边没有衣服可以收了 +// 形象的来说,机器人会一直尝试往右边收衣服,收k件的话就耗费k * rates[i]的时间 +// 但是当它遇见其他机器人工作的痕迹,就会认为后面的事情它不用管了,进入停机状态 +// 你手里总共有电量b,准备在0时刻将所有想启动的机器人全部一起启动 +// 过后不再启动新的机器人,并且启动机器人的电量之和不能大于b +// 返回在最佳选择下,假快多久能收完所有衣服 +// 如果无论如何都收不完所有衣服,返回-1 +// 给定数据: int n, int b, int[] powers, int[] rates +// 数据范围: +// powers长度 == rates长度 == n <= 1000 +// 1 <= b <= 10^5 +// 1 <= powers[i]、rates[i] <= 10^5 +public class Code03_RobotAndClothes { + + // 通过不了的简单动态规划方法 + // 只是为了对数器验证 + public static int fast1(int n, int b, int[] powers, int[] rates) { + int[][] dp = new int[n][b + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= b; j++) { + dp[i][j] = -1; + } + } + int ans = process1(powers, rates, n, 0, b, dp); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // i....这些衣服 + // 由i....这些机器人负责 + // 在剩余电量还有rest的情况下 + // 收完i....这些衣服最少时间是多少 + // 如果怎么都收不完 + // 返回Integer.MAX_VALUE + public static int process1(int[] powers, int[] rates, int n, int i, int rest, int[][] dp) { + if (i == n) { + return 0; + } + if (powers[i] > rest) { + return Integer.MAX_VALUE; + } + if (dp[i][rest] != -1) { + return dp[i][rest]; + } + int ans = Integer.MAX_VALUE; + for (int j = i; j < n; j++) { + int curCost = (j - i + 1) * rates[i]; + int nextCost = process1(powers, rates, n, j + 1, rest - powers[i], dp); + int curAns = Math.max(curCost, nextCost); + ans = Math.min(ans, curAns); + } + dp[i][rest] = ans; + return ans; + } + + // 正式方法 + // 时间复杂度O( N^2 * log(rates[0] * n)) + // 揭示了大的思路,可以继续用线段树优化枚举,详情看fast3 + // 解题思路: + // 二分答案 + // 定义函数minPower: + // 如果一定要在time时间内捡完所有衣服,请返回使用最少的电量 + // 如果minPower,这个函数能实现 + // 那么只要二分出最小的答案即可 + public static int fast2(int n, int b, int[] powers, int[] rates) { + if (n == 0) { + return 0; + } + if (b == 0 || powers[0] > b) { + return -1; + } + // 最小时间只可能在[1, rates[0] * n]范围上 + int l = 1; + int r = rates[0] * n; + int m = 0; + int ans = -1; + // 二分答案 + // 规定的时间就是m + // minPower(powers, rates, m): + // 如果一定要在time时间内捡完所有衣服,返回最小电量 + // 如果这个最小电量 <= 总电量,说明m时间可行,左侧继续二分答案 + // 如果这个最小电量 > 总电量,说明m时间不可行,右侧继续二分答案 + while (l <= r) { + m = (l + r) / 2; + if (minPower2(powers, rates, m) <= b) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 给定所有机器人的启动电量 powers[] + // 给定所有机器人的收一件衣服的时间 rates[] + // 一定要在time时间内,收完所有衣服! + // 返回 : 至少需要的电量! + public static int minPower2(int[] powers, int[] rates, int time) { + int[] dp = new int[powers.length]; + Arrays.fill(dp, -1); + return process2(powers, rates, 0, time, dp); + } + + // i....这么多的衣服 + // 在time时间内一定要收完 + // 返回最小电量 + // 如果怎么都收不完,返回系统最大值 + // N^2 + public static int process2(int[] powers, int[] rates, int i, int time, int[] dp) { + int n = powers.length; + if (i == n) { + return 0; + } + if (dp[i] != -1) { + return dp[i]; + } + // i..... + // 收当前i位置这一件衣服的时间 + int usedTime = rates[i]; + int nextMinPower = Integer.MAX_VALUE; + for (int j = i; j < n && usedTime <= time; j++) { + // i...i i+1.... + // i......i+1 i+2... + // i...........i+2 i+3... + // i....j j+1.... + nextMinPower = Math.min(nextMinPower, process2(powers, rates, j + 1, time, dp)); + usedTime += rates[i]; + } + int ans = nextMinPower == Integer.MAX_VALUE ? nextMinPower : (powers[i] + nextMinPower); + dp[i] = ans; + return ans; + } + + // fast2的思路 + 线段树优化枚举 + // 时间复杂度O(N * logN * log(rates[0] * N)) + public static int fast3(int n, int b, int[] powers, int[] rates) { + if (n == 0) { + return 0; + } + if (b == 0 || powers[0] > b) { + return -1; + } + int l = 1; + int r = rates[0] * n; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (minPower3(powers, rates, m) <= b) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static int minPower3(int[] powers, int[] rates, int time) { + int n = powers.length; + int[] dp = new int[n + 1]; + // dp[n-1] dp[n] + // n-1 n + SegmentTree st = new SegmentTree(n + 1); + st.update(n, 0); + for (int i = n - 1; i >= 0; i--) { + if (rates[i] > time) { + dp[i] = Integer.MAX_VALUE; + } else { + int j = Math.min(i + (time / rates[i]) - 1, n - 1); + // for.... logN + int next = st.min(i + 1, j + 1); + int ans = next == Integer.MAX_VALUE ? next : (powers[i] + next); + dp[i] = ans; + } + st.update(i, dp[i]); + } + return dp[0]; + } + + public static class SegmentTree { + private int n; + private int[] min; + + public SegmentTree(int size) { + n = size + 1; + min = new int[n << 2]; + Arrays.fill(min, Integer.MAX_VALUE); + } + + private void pushUp(int rt) { + min[rt] = Math.min(min[rt << 1], min[rt << 1 | 1]); + } + + public void update(int i, int v) { + update(i + 1, i + 1, v, 1, n, 1); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + min[rt] = C; + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int min(int l, int r) { + return min(l + 1, r + 1, 1, n, 1); + } + + private int min(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return min[rt]; + } + int mid = (l + r) >> 1; + int left = Integer.MAX_VALUE; + int right = Integer.MAX_VALUE; + if (L <= mid) { + left = min(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = min(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.min(left, right); + } + + } + + // 为了测试 + 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 = 200; + int B = 100; + int P = 20; + int R = 10; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int b = (int) (Math.random() * B) + 1; + int[] powers = randomArray(n, P); + int[] rates = randomArray(n, R); + int ans1 = fast1(n, b, powers, rates); + int ans2 = fast2(n, b, powers, rates); + int ans3 = fast3(n, b, powers, rates); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 10000; + int b = 100000; + int[] powers = randomArray(n, b); + int[] rates = randomArray(n, b); + System.out.println("衣服规模 : " + n); + System.out.println("电量规模 : " + b); + System.out.println("机器人启动费用取值规模 : " + b); + System.out.println("机器人工作速度取值规模 : " + b); + long start = System.currentTimeMillis(); + fast3(n, b, powers, rates); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/公开课/class108/Code01_CordCoverMaxPoint.java b/公开课/class108/Code01_CordCoverMaxPoint.java new file mode 100644 index 0000000..7ee5359 --- /dev/null +++ b/公开课/class108/Code01_CordCoverMaxPoint.java @@ -0,0 +1,92 @@ +package class108; + +import java.util.Arrays; + +// 最简单的窗口题 +// 给定一个有序数组arr,代表坐落在X轴上的点 +// 给定一个正数K,代表绳子的长度 +// 返回绳子最多压中几个点 +// 即使绳子边缘处盖住点也算盖住 +public class Code01_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + + } + +} diff --git a/公开课/class108/Code02_LongestSumSubArrayLengthInPositiveArray.java b/公开课/class108/Code02_LongestSumSubArrayLengthInPositiveArray.java new file mode 100644 index 0000000..8b01807 --- /dev/null +++ b/公开课/class108/Code02_LongestSumSubArrayLengthInPositiveArray.java @@ -0,0 +1,94 @@ +package class108; + +// 给定一个正整数组成的无序数组arr,给定一个正整数值K +// 找到arr的所有子数组里,哪个子数组的累加和等于K +// 并且是长度最大的,返回其长度 +public class Code02_LongestSumSubArrayLengthInPositiveArray { + + public static int getMaxLength(int[] arr, int K) { + if (arr == null || arr.length == 0 || K <= 0) { + return 0; + } + int left = 0; + int right = 0; + int sum = arr[0]; + int len = 0; + while (right < arr.length) { + if (sum == K) { + len = Math.max(len, right - left + 1); + sum -= arr[left++]; + } else if (sum < K) { + right++; + if (right == arr.length) { + break; + } + sum += arr[right]; + } else { + sum -= arr[left++]; + } + } + return len; + } + + // for test + public static int right(int[] arr, int K) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + if (valid(arr, i, j, K)) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + // for test + public static boolean valid(int[] arr, int L, int R, int K) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + return sum == K; + } + + // for test + public static int[] generatePositiveArray(int size, int value) { + int[] ans = new int[size]; + for (int i = 0; i != size; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 500000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generatePositiveArray(len, value); + int K = (int) (Math.random() * value) + 1; + int ans1 = getMaxLength(arr, K); + int ans2 = right(arr, K); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println("K : " + K); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/公开课/class108/Code03_LongestSumSubArrayLength.java b/公开课/class108/Code03_LongestSumSubArrayLength.java new file mode 100644 index 0000000..ce49858 --- /dev/null +++ b/公开课/class108/Code03_LongestSumSubArrayLength.java @@ -0,0 +1,104 @@ +package class108; + +import java.util.HashMap; + +// 给定一个整数组成的无序数组arr,值可能正、可能负、可能0 +// 给定一个整数值K, 找到arr的所有子数组里,哪个子数组的累加和等于K +// 并且是长度最大的,返回其长度 +public class Code03_LongestSumSubArrayLength { + + // arr中,有正、有负、有0,都可能 + // 累加和 == k,且最长是多长? + public static int maxLength(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return 0; + } + // key:前缀和 + // value : key这个前缀和,最早出现的位置 + HashMap map = new HashMap(); + map.put(0, -1); // important + int len = 0; + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + if (map.containsKey(sum - k)) { + // 0..................100 + // 1000 + // k = 300 + // 700 + // 0...10 + // 11.....100 + len = Math.max(i - map.get(sum - k), len); + } + // sum + if (!map.containsKey(sum)) { + map.put(sum, i); + } + } + return len; + } + + // for test + public static int right(int[] arr, int K) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + if (valid(arr, i, j, K)) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + // for test + public static boolean valid(int[] arr, int L, int R, int K) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + return sum == K; + } + + // for test + public static int[] generateRandomArray(int size, int value) { + int[] ans = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 500000; + + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len, value); + int K = (int) (Math.random() * value) - (int) (Math.random() * value); + int ans1 = maxLength(arr, K); + int ans2 = right(arr, K); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println("K : " + K); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test end"); + + } + +} diff --git a/公开课/class108/Code04_SlidingWindowMaxArray.java b/公开课/class108/Code04_SlidingWindowMaxArray.java new file mode 100644 index 0000000..be38b8b --- /dev/null +++ b/公开课/class108/Code04_SlidingWindowMaxArray.java @@ -0,0 +1,145 @@ +package class108; + +import java.util.LinkedList; + +// 给你一个整数数组 nums,有一个大小为 k 的滑动窗口 +// 从数组的最左侧移动到数组的最右侧 +// 你只可以看到在滑动窗口内的 k 个数字 +// 滑动窗口每次只向右移动一位。 +// 返回每一步 滑动窗口中的最大值 +// 测试链接 : https://leetcode.cn/problems/sliding-window-maximum/ +public class Code04_SlidingWindowMaxArray { + +// public static class Window { +// +// // 一些结构 +// +// public int[] help; +// +// public Window(int[] arr) { +// help = arr; +// } +// +// public void addNumberFromRight() { +// +// } +// +// public void deleteNumberFromLeft() { +// +// } +// +// public int max() { +// +// } +// +// } +// +// public static void main(String[] args) { +// int[] arr = { 3, 5, 7, 2, 4, 2, 6 }; +// // [ 7, 2] +// Window w = new Window(arr); +// w.addNumberFromRight(); +// w.addNumberFromRight(); +// w.addNumberFromRight(); +// System.out.println(w.max()); +// w.deleteNumberFromLeft(); +// w.deleteNumberFromLeft(); +// w.addNumberFromRight(); +// System.out.println(w.max()); +// } + + + + // 暴力的对数器方法 + public static int[] right(int[] arr, int w) { + if (arr == null || w < 1 || arr.length < w) { + return null; + } + int N = arr.length; + int[] res = new int[N - w + 1]; + int index = 0; + int L = 0; + int R = w - 1; + while (R < N) { + int max = arr[L]; + for (int i = L + 1; i <= R; i++) { + max = Math.max(max, arr[i]); + + } + res[index++] = max; + L++; + R++; + } + return res; + } + + public static int[] getMaxWindow(int[] arr, int w) { + if (arr == null || w < 1 || arr.length < w) { + return null; + } + // qmax 窗口最大值的更新结构 + // 放下标 + LinkedList qmax = new LinkedList(); + int[] res = new int[arr.length - w + 1]; + int index = 0; + for (int R = 0; R < arr.length; R++) { + while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) { + qmax.pollLast(); + } + qmax.addLast(R); + if (qmax.peekFirst() == R - w) { + qmax.pollFirst(); + } + if (R >= w - 1) { + res[index++] = arr[qmax.peekFirst()]; + } + } + return res; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int testTime = 100000; + int maxSize = 100; + int maxValue = 100; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int w = (int) (Math.random() * (arr.length + 1)); + int[] ans1 = getMaxWindow(arr, w); + int[] ans2 = right(arr, w); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/公开课/class108/Code05_WindPrevent.java b/公开课/class108/Code05_WindPrevent.java new file mode 100644 index 0000000..4c86b02 --- /dev/null +++ b/公开课/class108/Code05_WindPrevent.java @@ -0,0 +1,149 @@ +package class108; + +// 来自真实笔试 +// 给定一个二维数组matrix,数组中的每个元素代表一棵树的高度。 +// 你可以选定连续的若干行组成防风带,防风带每一列的防风高度为这一列的最大值 +// 防风带整体的防风高度为,所有列防风高度的最小值。 +// 比如,假设选定如下三行 +// 1 5 4 +// 7 2 6 +// 2 3 4 +// 1、7、2的列,防风高度为7 +// 5、2、3的列,防风高度为5 +// 4、6、4的列,防风高度为6 +// 防风带整体的防风高度为5,是7、5、6中的最小值 +// 给定一个正数k,k <= matrix的行数,表示可以取连续的k行,这k行一起防风。 +// 求防风带整体的防风高度最大值 +public class Code05_WindPrevent { + + public static int bestHeight1(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int ans = 0; + for (int startRow = 0; startRow < n; startRow++) { + int bottleNeck = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + int height = 0; + for (int endRow = startRow; endRow < n && (endRow - startRow + 1 <= k); endRow++) { + height = Math.max(height, matrix[endRow][col]); + } + bottleNeck = Math.min(bottleNeck, height); + } + ans = Math.max(ans, bottleNeck); + } + return ans; + } + +// public static class WindowManager { +// +// // 建立出m个窗口! +// public WindowManager(int m) { +// +// } +// +// public void addRow(int[][] matrix, int row) { +// +// } +// +// public void deleteRow(int[][] matrix, int row) { +// +// } +// +// public int getAllWindowMaxMin() { +// return 100; +// } +// +// } +// +// public static int bestWindHeight(int[][] matrix, int k) { +// int n = matrix.length; +// int m = matrix[0].length; +// k = Math.min(k, n); +// WindowManager windowManager = new WindowManager(m); +// for (int i = 0; i < k - 1; i++) { +// windowManager.addRow(matrix, i); +// } +// int ans = 0; +// for (int i = k - 1; i < n; i++) { +// windowManager.addRow(matrix, i); +// int cur = windowManager.getAllWindowMaxMin(); +// ans = Math.max(ans, cur); +// windowManager.deleteRow(matrix, i - k + 1); +// } +// return ans; +// } + + public static int bestHeight2(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int[][] windowMaxs = new int[m][n]; + int[][] windowLR = new int[m][2]; + for (int i = 0; i < k; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + } + int ans = bottleNeck(matrix, m, windowMaxs, windowLR); + for (int i = k; i < n; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + deleteRow(m, i - k, windowMaxs, windowLR); + ans = Math.max(ans, bottleNeck(matrix, m, windowMaxs, windowLR)); + } + return ans; + } + + public static void addRow(int[][] matrix, int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + while (windowLR[col][0] != windowLR[col][1] + && matrix[windowMaxs[col][windowLR[col][1] - 1]][col] <= matrix[row][col]) { + windowLR[col][1]--; + } + windowMaxs[col][windowLR[col][1]++] = row; + } + } + + public static void deleteRow(int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + if (windowMaxs[col][windowLR[col][0]] == row) { + windowLR[col][0]++; + } + } + } + + public static int bottleNeck(int[][] matrix, int m, int[][] windowMaxs, int[][] windowLR) { + int ans = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + ans = Math.min(ans, matrix[windowMaxs[col][windowLR[col][0]]][col]); + } + return ans; + } + + public static int[][] generateMatrix(int n, int m, int v) { + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * v) + 1; + } + } + return matrix; + } + + public static void main(String[] args) { + int nMax = 10; + int mMax = 10; + int vMax = 50; + int testTimes = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * nMax) + 1; + int m = (int) (Math.random() * mMax) + 1; + int[][] matrix = generateMatrix(n, m, vMax); + int k = (int) (Math.random() * n) + 1; + int ans1 = bestHeight1(matrix, k); + int ans2 = bestHeight2(matrix, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class108/Code06_RemoveMostKContinuousSameLongest.java b/公开课/class108/Code06_RemoveMostKContinuousSameLongest.java new file mode 100644 index 0000000..349b01f --- /dev/null +++ b/公开课/class108/Code06_RemoveMostKContinuousSameLongest.java @@ -0,0 +1,105 @@ +package class108; + +import java.util.HashMap; +import java.util.LinkedList; + +// 来自亚马逊 +// 给定一个数组arr,和一个正数k +// 你可以随意删除arr中的数字,最多删除k个 +// 目的是让连续出现一种数字的长度尽量长 +// 返回这个尽量长的长度 +// 比如数组arr = { 3, -2, 3, 3, 5, 6, 3, -2 }, k = 3 +// 你可以删掉-2、5、6(最多3个),这样数组arr = { 3, 3, 3, 3, -2 } +// 可以看到连续出现3的长度为4 +// 这是所有删除方法里的最长结果,所以返回4 +// 1 <= arr长度 <= 3 * 10^5 +// -10^9 <= arr中的数值 <= 10^9 +// 0 <= k <= 3 * 10^5 +public class Code06_RemoveMostKContinuousSameLongest { + + // 暴力方法 + // 为了测试 + public static int longest1(int[] arr, int k) { + return process1(arr, 0, new int[arr.length], 0, k); + } + + public static int process1(int[] arr, int i, int[] path, int size, int k) { + if (k < 0) { + return 0; + } + if (i == arr.length) { + if (size == 0) { + return 0; + } + int ans = 0; + int cnt = 1; + for (int j = 1; j < size; j++) { + if (path[j - 1] != path[j]) { + ans = Math.max(ans, cnt); + cnt = 1; + } else { + cnt++; + } + } + ans = Math.max(ans, cnt); + return ans; + } else { + path[size] = arr[i]; + int p1 = process1(arr, i + 1, path, size + 1, k); + int p2 = process1(arr, i + 1, path, size, k - 1); + return Math.max(p1, p2); + } + } + + // 正式方法 + // 时间复杂度O(N) + public static int longest2(int[] arr, int k) { + HashMap> valueIndies = new HashMap<>(); + int ans = 1; + for (int i = 0; i < arr.length; i++) { + int value = arr[i]; + if (!valueIndies.containsKey(value)) { + valueIndies.put(value, new LinkedList<>()); + } + LinkedList indies = valueIndies.get(value); + while (!indies.isEmpty() && i - indies.peekFirst() - indies.size() > k) { + indies.pollFirst(); + } + indies.addLast(i); + ans = Math.max(ans, indies.size()); + } + return ans; + } + + // 为了测试 + // 生成长度为n的数组 + // 值在[-v,v]之间等概率随机 + 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() * (2 * v + 1)) - v; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 20; + int V = 10; + int K = 5; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int k = (int) (Math.random() * K); + int ans1 = longest1(arr, k); + int ans2 = longest2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class108/Code07_RangesHasDominateNumber.java b/公开课/class108/Code07_RangesHasDominateNumber.java new file mode 100644 index 0000000..8b166a0 --- /dev/null +++ b/公开课/class108/Code07_RangesHasDominateNumber.java @@ -0,0 +1,88 @@ +package class108; + +import java.util.HashMap; + +// 来自小红书 +// 小A认为如果在数组中有一个数出现了至少k次 +// 且这个数是该数组的众数,即出现次数最多的数之一 +// 那么这个数组被该数所支配 +// 显然当k比较大的时候,有些数组不被任何数所支配 +// 现在小A拥有一个长度为n的数组,她想知道内部有多少个区间是被某个数支配的 +// 2 <= k <= n <= 100000 +// 1 <= 数组的值 <= n +public class Code07_RangesHasDominateNumber { + + // 暴力方法 + // 为了验证 + // 时间复杂度O(N^3) + public static int dominates1(int[] arr, int k) { + int n = arr.length; + int ans = 0; + for (int l = 0; l < n; l++) { + for (int r = l; r < n; r++) { + if (ok(arr, l, r, k)) { + ans++; + } + } + } + return ans; + } + + public static boolean ok(int[] arr, int l, int r, int k) { + HashMap map = new HashMap(); + for (int i = l; i <= r; i++) { + map.put(arr[i], map.getOrDefault(arr[i], 0) + 1); + } + for (int times : map.values()) { + if (times >= k) { + return true; + } + } + return false; + } + + // 正式方法 + // 时间复杂度O(N) + public static int dominates2(int[] arr, int k) { + int n = arr.length; + int all = n * (n + 1) / 2; + int except = 0; + int[] cnt = new int[n + 1]; + for (int l = 0, r = 0; l < n; l++) { + while (r < n && cnt[arr[r]] + 1 < k) { + cnt[arr[r++]]++; + } + except += r - l; + cnt[arr[l]]--; + } + return all - except; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * n) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n); + int k = (int) (Math.random() * n) + 1; + int ans1 = dominates1(arr, k); + int ans2 = dominates2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class108/Code08_LastStoneWeightII.java b/公开课/class108/Code08_LastStoneWeightII.java new file mode 100644 index 0000000..92830c9 --- /dev/null +++ b/公开课/class108/Code08_LastStoneWeightII.java @@ -0,0 +1,40 @@ +package class108; + +// 来自字节 +// 11.02笔试 +// leetcode原题 +// 有一堆石头,用整数数组 stones 表示 +// 其中 stones[i] 表示第 i 块石头的重量。 +// 每一回合,从中选出任意两块石头,然后将它们一起粉碎 +// 假设石头的重量分别为 x 和 y,且 x <= y +// 那么粉碎的可能结果如下: +// 如果 x == y,那么两块石头都会被完全粉碎; +// 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 +// 最后,最多只会剩下一块 石头 +// 返回此石头 最小的可能重量 +// 如果没有石头剩下,就返回 0 +// 测试链接 : https://leetcode.cn/problems/last-stone-weight-ii/ +public class Code08_LastStoneWeightII { + + public int lastStoneWeightII(int[] arr) { + int n = arr.length; + int sum = 0; + for (int num : arr) { + sum += num; + } + int half = sum / 2; + int[][] dp = new int[n + 1][half + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int rest = 0; rest <= half; rest++) { + int p1 = dp[i + 1][rest]; + int p2 = 0; + if (arr[i] <= rest) { + p2 = arr[i] + dp[i + 1][rest - arr[i]]; + } + dp[i][rest] = Math.max(p1, p2); + } + } + return Math.abs(sum - dp[0][half] - dp[0][half]); + } + +} diff --git a/公开课/class108/Code09_ScoreAllMatrix.java b/公开课/class108/Code09_ScoreAllMatrix.java new file mode 100644 index 0000000..3422620 --- /dev/null +++ b/公开课/class108/Code09_ScoreAllMatrix.java @@ -0,0 +1,78 @@ +package class108; + +// 来自蚂蚁金服 +// 得分的定义 : +// 含有大小2*2的矩阵,要么: +// 1 0 +// 0 1 可以得1分 +// 要么 +// 0 1 +// 1 0 可以得1分 +// 那么一个任意大小的矩阵就有若干得分点,比如 +// 0 1 0 +// 1 0 1 +// 这个矩阵就有2个得分点 +// 给定正数N,正数M,求所有可能的情况里,所有的得分点总和 +// 1 <= N、M <= 10^9 +public class Code09_ScoreAllMatrix { + + public static int score1(int n, int m) { + if (n < 2 || m < 2) { + return 0; + } + int[][] matrix = new int[n][m]; + return process(matrix, 0, 0, n, m); + } + + public static int process(int[][] matrix, int i, int j, int n, int m) { + if (i == n) { + int score = 0; + for (int r = 1; r < n; r++) { + for (int c = 1; c < m; c++) { + if (check(matrix, r, c)) { + score++; + } + } + } + return score; + } + if (j == m) { + return process(matrix, i + 1, 0, n, m); + } + int score = 0; + matrix[i][j] = 1; + score += process(matrix, i, j + 1, n, m); + matrix[i][j] = 0; + score += process(matrix, i, j + 1, n, m); + return score; + } + + public static boolean check(int[][] m, int r, int c) { + return (m[r - 1][c - 1] == 0 && m[r][c - 1] == 1 && m[r - 1][c] == 1 && m[r][c] == 0) + || (m[r - 1][c - 1] == 1 && m[r][c - 1] == 0 && m[r - 1][c] == 0 && m[r][c] == 1); + } + + public static int score2(int n, int m) { + if (n < 2 || m < 2) { + return 0; + } + // n <= 10^9 + // m <= 10^9 + // 取mod + // (n * m - m - n + 1) -> O(1) + // 2^(n * m - 3) ??? + // 真实的笔试场景下: + // 算2^(k)次方的 + // 体系学习班,章节27,学习快速幂 + // 本代码,不处理mod + return (n * m - m - n + 1) * (1 << (n * m - 3)); + } + + public static void main(String[] args) { + int n = 3; + int m = 4; + System.out.println(score1(n, m)); + System.out.println(score2(n, m)); + } + +} diff --git a/公开课/class109/Code01_ThreeEqualParts.java b/公开课/class109/Code01_ThreeEqualParts.java new file mode 100644 index 0000000..f2d3bcb --- /dev/null +++ b/公开课/class109/Code01_ThreeEqualParts.java @@ -0,0 +1,80 @@ +package class109; + +// 给定一个由 0 和 1 组成的数组 arr ,将数组分成 3 个非空的部分 +// 使得所有这些部分表示相同的二进制值。 +// 如果可以做到,请返回任何 [i, j],其中 i+1 < j,这样一来 +// arr[0], arr[1], ..., arr[i] 为第一部分 +// arr[i + 1], arr[i + 2], ..., arr[j - 1] 为第二部分 +// arr[j], arr[j + 1], ..., arr[arr.length - 1] 为第三部分 +// 这三个部分所表示的二进制值相等 +// 如果无法做到,就返回 [-1, -1] +// 注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体 +// 例如,[1,1,0] 表示十进制中的 6,而不会是 3。此外,前导零也是被允许的 +// 所以 [0,1,1] 和 [1,1] 表示相同的值。 +// 测试链接 : https://leetcode.cn/problems/three-equal-parts/ +public class Code01_ThreeEqualParts { + + public static int[] threeEqualParts(int[] arr) { + // 计算arr中1的数量 + int ones = 0; + for (int num : arr) { + ones += num == 1 ? 1 : 0; + } + // 如果1的数量不能被3整除,肯定不存在方案 + if (ones % 3 != 0) { + return new int[] { -1, -1 }; + } + int n = arr.length; + // 如果1的数量是0,怎么划分都可以了,因为全是0 + if (ones == 0) { + return new int[] { 0, n - 1 }; + } + // 接下来的过程 + // 因为1的数量能被3整除,比如一共有12个1 + // 那么第一段肯定含有第1个1~第4个1 + // 那么第二段肯定含有第5个1~第8个1 + // 那么第三段肯定含有第9个1~第12个1 + // 所以把第1个1,当做第一段的开头,start1 + // 所以把第5个1,当做第二段的开头,start2 + // 所以把第9个1,当做第三段的开头,start3 + int part = ones / 3; + int start1 = -1; + int start2 = -1; + int start3 = -1; + int cnt = 0; + // 1个数21个 + // part = 7 + // 1 8 + for (int i = 0; i < n; i++) { + if (arr[i] == 1) { + cnt++; + if (start1 == -1 && cnt == 1) { + start1 = i; + } + if (start2 == -1 && cnt == part + 1) { + start2 = i; + } + if (start3 == -1 && cnt == 2 * part + 1) { + start3 = i; + } + } + } + // 第一段的开头往下的部分 + // 第二段的开头往下的部分 + // 第三段的开头往下的部分 + // 要都一样,这三段的状态才是一样的 + // 所以接下来就验证这一点,是不是每一步都一样 + while (start3 < n) { + if (arr[start1] != arr[start2] || arr[start1] != arr[start3]) { + // 一旦不一样,肯定没方案了 + return new int[] { -1, -1 }; + } + start1++; + start2++; + start3++; + } + // 如果验证通过,返回断点即可 + return new int[] { start1 - 1, start2 }; + } + +} diff --git a/公开课/class109/Code02_LongestSumSubArrayLength.java b/公开课/class109/Code02_LongestSumSubArrayLength.java new file mode 100644 index 0000000..9db0082 --- /dev/null +++ b/公开课/class109/Code02_LongestSumSubArrayLength.java @@ -0,0 +1,114 @@ +package class109; + +import java.util.HashMap; + +public class Code02_LongestSumSubArrayLength { + + // 该代码不得分! +// public static int targetSumMaxLen(int[] arr, int target) { +// int ans = 0; +// for(int 开头 = 0; 开头 < arr.length;开头++) { +// for(int 结尾 = 开头; 结尾 <= arr.length; 结尾++) { +// // arr[开头....结尾] +// // 统计一下arr[开头....结尾]累加和 +// // == target +// int cur = 结尾 - 开头 + 1; +// ans = Math.max(ans, cur); +// } +// } +// return ans; +// } + + public static int maxLength(int[] arr, int target) { + if (arr == null || arr.length == 0) { + return 0; + } + // key:前缀和 + // value : 0~value这个前缀和是最早出现key这个值的 + HashMap map = new HashMap(); + map.put(0, -1); // important + int len = 0; + int sum = 0; + // O(N) + for (int i = 0; i < arr.length; i++) { + // 0...i整体前缀和 + sum += arr[i]; + if (map.containsKey(sum - target)) { + // 0.....17 1000 target == 300 + // sum - target + // 0...4 700 + // 5.....17 300 + // 17 - 4 + len = Math.max(i - map.get(sum - target), len); + } + if (!map.containsKey(sum)) { + map.put(sum, i); + } + } + return len; + } + + // for test + public static int right(int[] arr, int K) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + if (valid(arr, i, j, K)) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + // for test + public static boolean valid(int[] arr, int L, int R, int K) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + return sum == K; + } + + // for test + public static int[] generateRandomArray(int size, int value) { + int[] ans = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 500000; + + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len, value); + int K = (int) (Math.random() * value) - (int) (Math.random() * value); + int ans1 = maxLength(arr, K); + int ans2 = right(arr, K); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println("K : " + K); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test end"); + + } + +} diff --git a/公开课/class109/Code03_EvenTimesMaxSubstring.java b/公开课/class109/Code03_EvenTimesMaxSubstring.java new file mode 100644 index 0000000..777bc91 --- /dev/null +++ b/公开课/class109/Code03_EvenTimesMaxSubstring.java @@ -0,0 +1,103 @@ +package class109; + +import java.util.HashMap; + +// 来自微软面试 +// 给定一个字符串s,其中都是英文小写字母 +// 如果s中的子串含有的每种字符都是偶数个 +// 那么这样的子串就是达标子串,子串要求是连续串 +// 返回s中达标子串的最大长度 +// 1 <= s的长度 <= 10^5 +// 字符种类都是英文小写 +public class Code03_EvenTimesMaxSubstring { + + // 为了测试 + // 暴力方法 + public static int maxLen1(String s) { + int n = s.length(); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = n - 1; j >= i; j--) { + if (ok(s, i, j)) { + ans = Math.max(ans, j - i + 1); + break; + } + } + } + return ans; + } + + // 为了测试 + // 暴力方法 + public static boolean ok(String s, int l, int r) { + if (((r - l + 1) & 1) == 1) { + return false; + } + int[] cnts = new int[26]; + for (int i = l; i <= r; i++) { + cnts[s.charAt(i) - 'a']++; + } + for (int cnt : cnts) { + if ((cnt & 1) == 1) { + return false; + } + } + return true; + } + + // 正式方法 + // 时间复杂度O(N) + public static int maxLen2(String s) { + // key : 状态int, 32位的,a~z一共26位,够用 + // value : 该状态最早出现的位置 + HashMap map = new HashMap<>(); + // 00000000..000000 + map.put(0, -1); + // 0...当前字符,总状态! + int status = 0; + int ans = 0; + int n = s.length(); + // ....0 .....1 .....2 .....i ....n-1 + for (int i = 0; i < n; i++) { + // 从开头....i位置的字符 + // 总状态,出来了! + status ^= 1 << (s.charAt(i) - 'a'); + if (map.containsKey(status)) { + ans = Math.max(ans, i - map.get(status)); + } else { + map.put(status, i); + } + } + return ans; + } + + // 为了测试 + public static String randomString(int n, int v) { + char[] s = new char[n]; + for (int i = 0; i < n; i++) { + s[i] = (char) ((int) (Math.random() * v) + 'a'); + } + return String.valueOf(s); + } + + // 为了测试 + public static void main(String[] args) { + int n = 50; + int v = 6; + int testTimes = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + String s = randomString(n, v); + int ans1 = maxLen1(s); + int ans2 = maxLen2(s); + if (ans1 != ans2) { + System.out.println(s); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class109/Code04_RemoveMostKContinuousSameLongest.java b/公开课/class109/Code04_RemoveMostKContinuousSameLongest.java new file mode 100644 index 0000000..e28ee0c --- /dev/null +++ b/公开课/class109/Code04_RemoveMostKContinuousSameLongest.java @@ -0,0 +1,113 @@ +package class109; + +import java.util.HashMap; +import java.util.LinkedList; + +// 来自亚马逊 +// 给定一个数组arr,和一个正数k +// 你可以随意删除arr中的数字,最多删除k个 +// 目的是让连续出现一种数字的长度尽量长 +// 返回这个尽量长的长度 +// 比如数组arr = { 3, -2, 3, 3, 5, 6, 3, -2 }, k = 3 +// 你可以删掉-2、5、6(最多3个),这样数组arr = { 3, 3, 3, 3, -2 } +// 可以看到连续出现3的长度为4 +// 这是所有删除方法里的最长结果,所以返回4 +// 1 <= arr长度 <= 3 * 10^5 +// -10^9 <= arr中的数值 <= 10^9 +// 0 <= k <= 3 * 10^5 +public class Code04_RemoveMostKContinuousSameLongest { + + // 暴力方法 + // 为了测试 + public static int longest1(int[] arr, int k) { + return process1(arr, 0, new int[arr.length], 0, k); + } + + public static int process1(int[] arr, int i, int[] path, int size, int k) { + if (k < 0) { + return 0; + } + if (i == arr.length) { + if (size == 0) { + return 0; + } + int ans = 0; + int cnt = 1; + for (int j = 1; j < size; j++) { + if (path[j - 1] != path[j]) { + ans = Math.max(ans, cnt); + cnt = 1; + } else { + cnt++; + } + } + ans = Math.max(ans, cnt); + return ans; + } else { + path[size] = arr[i]; + int p1 = process1(arr, i + 1, path, size + 1, k); + int p2 = process1(arr, i + 1, path, size, k - 1); + return Math.max(p1, p2); + } + } + + // 正式方法 + // 时间复杂度O(N) + public static int longest2(int[] arr, int k) { + // key : 某个数值 + // value : 双端列表 ,头部或者尾部,进入或者弹出,都是非常方便的 + // 哪些下标拥有这个数值 + HashMap> valueIndies = new HashMap<>(); + int ans = 1; + for (int i = 0; i < arr.length; i++) { + int value = arr[i]; + if (!valueIndies.containsKey(value)) { + valueIndies.put(value, new LinkedList<>()); + } + LinkedList indies = valueIndies.get(value); + // 依次考察开头是否能和当前i位置,连起来 + // i - indies.peekFirst() : 一共有几个数 + // 6 10 13 15 18 == 18 - 6 + // indies.size() 6 ~ 17 有几个当前数! + // 一共有几个数 - 6 ~ 17 有几个当前数 = 需要使用几次删除才能让i和开头连起来! + while (!indies.isEmpty() && i - indies.peekFirst() - indies.size() > k) { + indies.pollFirst(); + } + indies.addLast(i); + ans = Math.max(ans, indies.size()); + } + return ans; + } + + // 为了测试 + // 生成长度为n的数组 + // 值在[-v,v]之间等概率随机 + 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() * (2 * v + 1)) - v; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 20; + int V = 10; + int K = 5; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int k = (int) (Math.random() * K); + int ans1 = longest1(arr, k); + int ans2 = longest2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class110/Code01_MoveCityGetMoney.java b/公开课/class110/Code01_MoveCityGetMoney.java new file mode 100644 index 0000000..595eef8 --- /dev/null +++ b/公开课/class110/Code01_MoveCityGetMoney.java @@ -0,0 +1,236 @@ +package class110; + +import java.util.Arrays; + +// 来自美团 +// 有n个城市,城市从0到n-1进行编号。小美最初住在k号城市中 +// 在接下来的m天里,小美每天会收到一个任务 +// 她可以选择完成当天的任务或者放弃该任务 +// 第i天的任务需要在ci号城市完成,如果她选择完成这个任务 +// 若任务开始前她恰好在ci号城市,则会获得ai的收益 +// 若她不在ci号城市,她会前往ci号城市,获得bi的收益 +// 当天的任务她都会当天完成 +// 任务完成后,她会留在该任务所在的ci号城市直到接受下一个任务 +// 如果她选择放弃任务,她会停留原地,且不会获得收益 +// 小美想知道,如果她合理地完成任务,最大能获得多少收益 +// 输入描述: 第一行三个正整数n, m和k,表示城市数量,总天数,初始所在城市 +// 第二行为m个整数c1, c2,...... cm,其中ci表示第i天的任务所在地点为ci +// 第三行为m个整数a1, a2,...... am,其中ai表示完成第i天任务且地点不变的收益 +// 第四行为m个整数b1, b2,...... bm,其中bi表示完成第i天的任务且地点改变的收益 +// 0 <= k, ci <= n <= 30000 +// 1 <= m <= 30000 +// 0 <= ai, bi <= 10^9 +// 输出描述 输出一个整数,表示小美合理完成任务能得到的最大收益 +public class Code01_MoveCityGetMoney { + + public static int max(int n, int m, int k, int[] c, int[] a, int[] b) { + return zuo(k, 0, c, a, b); + } + + // 假设,小美目前身在curCity + // 还有i....m-1这么多任务可以去选择 + // 返回最大的收益 + // curCity -> n种 + // i -> m种 + // n * m二维表 -> 9 * (10 ^ 8) + public static int zuo(int curCity, int i, int[] c, int[] a, int[] b) { + if (i == c.length) { + return 0; + } + // 任务没结束 i号任务 有 + // 可能性1,彻底放弃当前i任务 + int p1 = zuo(curCity, i + 1, c, a, b); + // 可能性2,要做当前任务 + // 小美在哪:curCity + // i号任务在哪:北京 + // 如果身在北京 : 50 a[i] + // 如果不在北京 : 20 b[i] + int comeCity = c[i]; + int p2 = curCity == comeCity ? a[i] : b[i]; + p2 += zuo(comeCity, i + 1, c, a, b); + return Math.max(p1, p2); + } + + // 暴力方法 + // 时间复杂度O(N^2) + // 为了验证 + public static int maxPorfit1(int n, int m, int k, int[] c, int[] a, int[] b) { + int[][] dp = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + dp[i][j] = -1; + } + } + return process1(k, 0, m, c, a, b, dp); + } + + // cur : 小美当前在哪! + // i : 当前面临的是任务编号! + // m : 一共有多少任务,固定 + // c[i] : 第i号任务要在哪个城里完成 + // a[i] : 恰好在!收益 + // b[i] : 赶过去!收益 + // 返回 : i....... 小美获得的最大收益 + public static int process1(int cur, int i, int m, int[] c, int[] a, int[] b, int[][] dp) { + if (i == m) { + return 0; + } + if (dp[cur][i] != -1) { + return dp[cur][i]; + } + // 可能性1 : 不做任务,彻底放弃,留在原地 + int p1 = process1(cur, i + 1, m, c, a, b, dp); + // 可能性2 : 做任务,要看看cur在哪,来获得收益 + int p2 = 0; + if (cur == c[i]) { + p2 = a[i] + process1(c[i], i + 1, m, c, a, b, dp); + } else { + p2 = b[i] + process1(c[i], i + 1, m, c, a, b, dp); + } + int ans = Math.max(p1, p2); + dp[cur][i] = ans; + return ans; + } + + // 正式方法 + // 时间复杂度O(N*logN) + public static int maxPorfit2(int n, int m, int k, int[] c, int[] a, int[] b) { + SegmentTree st = new SegmentTree(n); + + // st : + // s s s s 0 s s s + // 0 1 2 3 4 5 6 7 + // k + st.update(k, 0); +// int ans = 0; + for (int i = 0; i < m; i++) { + // c[i] : 3 + // 外地 : 0 ~ 2 4 ~ 7 + // 0~2 max 4 ~ 7 max -> max + int curAns = Math.max( + // 可能性1,从外地赶过来 + Math.max(st.max(0, c[i] - 1), + st.max(c[i] + 1, n - 1)) + b[i], + // 可能性2,原地 + st.max(c[i], c[i]) + a[i]); +// ans = Math.max(ans, curAns); + st.update(c[i], curAns); + } + return st.max(0, n-1); + } + + public static class SegmentTree { + private int n; + private int[] max; + + public SegmentTree(int N) { + n = N; + max = new int[(n + 1) << 2]; + Arrays.fill(max, Integer.MIN_VALUE); + } + + public int max(int l, int r) { + l++; + r++; + if (l > r) { + return Integer.MIN_VALUE; + } + return max(l, r, 1, n, 1); + } + + public void update(int i, int v) { + i++; + update(i, i, v, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = Math.max(max[rt], C); + return; + } + int mid = (l + r) >> 1; + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + int left = Integer.MIN_VALUE; + int right = Integer.MIN_VALUE; + if (L <= mid) { + left = max(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = max(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.max(left, right); + } + + } + + // 为了测试 + 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); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int M = 100; + int V = 10000; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int k = (int) (Math.random() * n); + int[] c = randomArray(m, n); + int[] a = randomArray(m, V); + int[] b = randomArray(m, V); + int ans1 = maxPorfit1(n, m, k, c, a, b); + int ans2 = maxPorfit2(n, m, k, c, a, b); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + int n = 100000; + int m = 100000; + int v = 1000000; + int k = (int) (Math.random() * n); + int[] c = randomArray(m, n); + int[] a = randomArray(m, v); + int[] b = randomArray(m, v); + System.out.println("城市数量 : " + n); + System.out.println("任务天数 : " + m); + System.out.println("收益数值规模 : " + v); + long start = System.currentTimeMillis(); + maxPorfit2(n, m, k, c, a, b); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + "毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/公开课/class110/Code02_EntryRoomGetMoney.java b/公开课/class110/Code02_EntryRoomGetMoney.java new file mode 100644 index 0000000..4e5dbd1 --- /dev/null +++ b/公开课/class110/Code02_EntryRoomGetMoney.java @@ -0,0 +1,199 @@ +package class110; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +// 来自美团 +// 某天小美进入了一个迷宫探险,根据地图所示,这个迷宫里有无数个房间 +// 序号分别为1、2、3、...入口房间的序号为1 +// 任意序号为正整数x的房间,都与序号 2*x 和 2*x + 1 的房间之间各有一条路径 +// 但是这些路径是单向的,即只能从序号为x的房间去到序号为 2*x 或 2*x+1 的房间 +// 而不能从 2*x 或 2*x+1 的房间去到序号为x的房间 +// 在任何时刻小美都可以选择结束探险并离开迷宫,但是离开之后将无法再次进入迷宫 +// 小美还提前了解了迷宫中宝藏的信息 +// 已知宝藏共有n个,其中第i个宝藏在序号为pi的房间,价值为wi +// 且一个房间中可能有多个宝藏 +// 小美为了得到更多的宝藏,需要精心规划路线,她找到你帮忙 +// 想请你帮她计算一下,能获得的宝藏价值和最大值为多少 +// 第一行一个正整数n,表示宝藏数量。 +// 第二行为n个正整数p1, p2,...... pn,其中pi表示第 i 个宝藏在序号为pi的房间。 +// 第三行为n个正整数w1, w2,...... wn,其中wi表示第i个宝藏的价值为wi。 +// 1 <= n <= 40000, 1 <= pi < 2^30, 1 <= wi <= 10^6。 +public class Code02_EntryRoomGetMoney { + + public static int pickMoney(int[][] values) { + // values : {5, 100}, {2, 3000}, {150, 80} {2^30, 18} + Arrays.sort(values, (a, b) -> a[0] - b[0]); + HashMap> graph = new HashMap<>(); + ArrayList starts = new ArrayList<>(); + for (int[] v : values) { + int room = v[0]; + int tmp = room; + while (tmp != 0) { + if (graph.containsKey(tmp)) { + graph.get(tmp).add(room); + break; + } + tmp /= 2; + } + graph.put(room, new ArrayList<>()); + if (tmp == 0) { + starts.add(room); + } + } + HashMap money = new HashMap<>(); + for (int[] v : values) { + money.put(v[0], v[1]); + } + int ans = 0; + for (int start : starts) { + ans = Math.max(ans, maxValue(start, graph, money)); + } + return ans; + } + + public static int maxValue(int room, HashMap> graph, HashMap money) { + if (graph.get(room).isEmpty()) { + return money.get(room); + } + int next = 0; + for (int child : graph.get(room)) { + next = Math.max(next, maxValue(child, graph, money)); + } + return next + money.get(room); + } + + // 为了测试 + // 普通动态规划 + public static int maxMoney1(int n, int[] p, int[] w) { + int[][] rooms = new int[n][2]; + for (int i = 0; i < n; i++) { + rooms[i][0] = p[i]; + rooms[i][1] = w[i]; + } + Arrays.sort(rooms, (a, b) -> a[0] - b[0]); + int ans = 0; + int[] dp = new int[n]; + Arrays.fill(dp, -1); + for (int i = 0; i < n; i++) { + ans = Math.max(ans, process1(i, rooms, n, dp)); + } + return ans; + } + + public static int process1(int index, int[][] rooms, int n, int[] dp) { + if (dp[index] != -1) { + return dp[index]; + } + int next = 0; + for (int i = index + 1; i < n; i++) { + if (reach(rooms[index][0], rooms[i][0])) { + next = Math.max(next, process1(i, rooms, n, dp)); + } + } + int ans = rooms[index][1] + next; + dp[index] = ans; + return dp[index]; + } + + public static boolean reach(int from, int to) { + while (to >= from) { + if (from == to) { + return true; + } else { + to /= 2; + } + } + return false; + } + + // 正式方法 + // 时间复杂度O(N)的动态规划 + // 利用图来优化枚举 + public static int maxMoney2(int n, int[] p, int[] w) { + int[][] rooms = new int[n][2]; + for (int i = 0; i < n; i++) { + rooms[i][0] = p[i]; + rooms[i][1] = w[i]; + } + Arrays.sort(rooms, (a, b) -> a[0] - b[0]); + HashMap first = new HashMap<>(); + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i < n; i++) { + int to = rooms[i][0]; + while (to > 0) { + if (first.containsKey(to)) { + graph.get(first.get(to)).add(i); + break; + } else { + to >>= 1; + } + } + graph.add(new ArrayList<>()); + if (!first.containsKey(rooms[i][0])) { + first.put(rooms[i][0], i); + } + } + int ans = 0; + int[] dp = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int post = 0; + for (int next : graph.get(i)) { + if (rooms[next][0] == rooms[i][0]) { + dp[i] += dp[next]; + } else { + post = Math.max(post, dp[next]); + } + } + dp[i] += post + rooms[i][1]; + ans = Math.max(ans, dp[i]); + } + return ans; + } + + // 为了测试 + 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 = 100; + int P = 5000; + int W = 5000; + int testTimes = 5000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] p = randomArray(n, P); + int[] w = randomArray(n, W); + int ans1 = maxMoney1(n, p, w); + int ans2 = maxMoney2(n, p, w); + if (ans1 != ans2) { + System.out.println("出错了"); + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + N = 50000; + P = 2000000000; + W = 1000000; + int[] p = randomArray(N, P); + int[] w = randomArray(N, W); + System.out.println("房间个数 : " + N); + System.out.println("位置范围 : " + P); + System.out.println("价值范围 : " + W); + long start = System.currentTimeMillis(); + maxMoney2(N, p, w); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/公开课/class111/Code01_ComplementaryPairsInStringArray.java b/公开课/class111/Code01_ComplementaryPairsInStringArray.java new file mode 100644 index 0000000..b2cc1c3 --- /dev/null +++ b/公开课/class111/Code01_ComplementaryPairsInStringArray.java @@ -0,0 +1,145 @@ +package class111; + +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 test(String[] strs) { + // key : 一种字符串的状态 + // value : 这种状态出现了多少次 + HashMap map = new HashMap<>(); + int ans = 0; + for (String str : strs) { + int status = 0; + for (int i = 0; i < str.length(); i++) { + status ^= 1 << (str.charAt(i) - 'a'); + } + // 之前的字符串,和当前字符串一样的状态,有多少个? + ans += map.get(status); + // 每一位都允许和当前字符串状态不一样,但是剩下的状态得一样 + // g f e d c b a + // status : 0 0 1 1 0 0 1 + // a上捣乱 : 0 0 1 1 0 0 0 + // b上捣乱 : 0 0 1 1 0 1 1 + // 0 ~ + // ~ 1 ~ + // ~ 2 ~ + for(int a = 0; a < 26; a++) { + ans += map.get(status ^ (1 << a)); + } + if (!map.containsKey(status)) { + map.put(status, 1); + } else { + map.put(status, map.get(status) + 1); + } + } + return ans; + } + + // 暴力方法 + // 为了测试 + 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 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'); + } + // 一点点都不捣乱,cur,map有几个状态也是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("测试结束"); + } + +} diff --git a/公开课/class111/Code02_CouplesHoldingHands.java b/公开课/class111/Code02_CouplesHoldingHands.java new file mode 100644 index 0000000..fcbab3d --- /dev/null +++ b/公开课/class111/Code02_CouplesHoldingHands.java @@ -0,0 +1,76 @@ +package class111; + +// 来自Facebook +// 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 + UnionFind uf = new UnionFind(n / 2); + for (int i = 0; i < n; i += 2) { + // row[i] / 2 -> 组 + // row[i+1] / 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; + } + + // 极快,单次代价认为 : O(1) + 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; + } + + } + +} diff --git a/公开课/class112/Code01_SortArrayByMovingItemsToEmptySpace.java b/公开课/class112/Code01_SortArrayByMovingItemsToEmptySpace.java new file mode 100644 index 0000000..bb35552 --- /dev/null +++ b/公开课/class112/Code01_SortArrayByMovingItemsToEmptySpace.java @@ -0,0 +1,106 @@ +package class112; + +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 Code01_SortArrayByMovingItemsToEmptySpace { + + public static int sortArray(int[] nums) { + // 1) 0 1 2 3 4 .... 这个样子,至少交换几次 ans1 + // 2) 1 2 3 4 .... 0 这个样子,至少交换几次 ans2 + int n = nums.length, ans1 = 0, ans2 = 0, m, next; + // 标记,i位置,在之前的环里的话!touched[i] == true + // 如果i位置不在之前的环里的话!touched[i] == false + boolean[] touched = new boolean[n]; + // 0 1 2 3 4 ....方案1 -> ans1 + for (int i = 0; i < n; i++) { + if (!touched[i]) { + // 4 6 0 + // 0(i) -> 4 6 + // y y y + touched[i] = true; + m = 1; + next = nums[i]; + while (next != i) { + m++; + touched[next] = true; + next = nums[next]; + } + // m个 -> 几次? + if (m > 1) { + ans1 += i == 0 ? (m - 1) : (m + 1); + } + } + } + Arrays.fill(touched, false); + // 1 2 3 4 ... 0 方案2 -> ans2 + for (int i = n - 1; i >= 0; i--) { + if (!touched[i]) { + touched[i] = true; + m = 1; + // n = 7 : 下标 0 ~ 6 + // nums[i] + // i + // next = nums[next] + 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); + } + + public static int sortYours(int[] arr) { + // ..... + return 100; + } + + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = i; + } + for (int i = len - 1; i >= 0; i--) { + int swap = (int) (Math.random() * (i + 1)); + // 0 1 2 3 4 5(i) + // 0 ~ 5 5 + int tmp = arr[i]; + arr[i] = arr[swap]; + arr[swap] = tmp; + } + return arr; + } + +// public static void main(String[] args) { +// int testTimes = 10000; +// int Len = 10; +// for(int i = 0; i < testTimes;i++) { +// int[] arr = randomArray(10); +// int ans1 = my(); +// int ans2 = yours(); +// if(ans1!=ans2) { +// 打印错误 +// } +// } +// } + +} diff --git a/公开课/class112/Code02_ZigZagConversion.java b/公开课/class112/Code02_ZigZagConversion.java new file mode 100644 index 0000000..3689e8c --- /dev/null +++ b/公开课/class112/Code02_ZigZagConversion.java @@ -0,0 +1,37 @@ +package class112; + +// 来自字节跳动 +// 将一个给定字符串 s 根据给定的行数 numRows +// 以从上往下、从左到右进行 Z 字形排列 +// 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下 +// P A H N +// A P L S I I G +// Y I R +// 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串 +// "PAHNAPLSIIGYIR" +// 请你实现这个将字符串进行指定行数变换的函数 +// string convert(string s, int numRows) +// 测试链接 : https://leetcode.cn/problems/zigzag-conversion/ +public class Code02_ZigZagConversion { + + public static String convert(String s, int row) { + int n = s.length(); + if (row == 1 || row >= n) { + return s; + } + int t = 2 * (row - 1); + char[] ans = new char[n]; + int fill = 0; + for (int i = 0; i < row; i++) { + // 来到i行,原始串开始下标j == i + for (int j = i, nextColTop = t; j < n; j += t, nextColTop += t) { + ans[fill++] = s.charAt(j); + if (i >= 1 && i <= row - 2 && nextColTop - i < n) { + ans[fill++] = s.charAt(nextColTop - i); + } + } + } + return String.valueOf(ans); + } + +} diff --git a/公开课/class113/Code01_TravelMinFuel.java b/公开课/class113/Code01_TravelMinFuel.java new file mode 100644 index 0000000..6d84436 --- /dev/null +++ b/公开课/class113/Code01_TravelMinFuel.java @@ -0,0 +1,79 @@ +package class113; + +// 来自微软 +// 给定两个数组A和B,比如 +// A = { 0, 1, 1 } +// B = { 1, 2, 3 } +// A[0] = 0, B[0] = 1,表示0到1有双向道路 +// A[1] = 1, B[1] = 2,表示1到2有双向道路 +// A[2] = 1, B[2] = 3,表示1到3有双向道路 +// 给定数字N,编号从0~N,所以一共N+1个节点 +// 题目输入一定保证所有节点都联通,并且一定没有环 +// 默认办公室是0节点,其他1~N节点上,每个节点上都有一个居民 +// 每天所有居民都去往0节点上班 +// 所有的居民都有一辆5座的车,也都乐意和别人一起坐车 +// 车不管负重是多少,只要走过一条路,就耗费1的汽油 +// 比如A、B、C的居民,开着自己的车来到D居民的位置,一共耗费3的汽油 +// D居民和E居民之间,假设有一条路 +// 那么D居民可以接上A、B、C,4个人可以用一辆车,去往E的话,就再耗费1的汽油 +// 求所有居民去办公室的路上,最少耗费多少汽油 +import java.util.ArrayList; + +public class Code01_TravelMinFuel { + + // a [ 2 .. + // b [ 3 .. + // n = 4,a、b数组的长度,a和b等长的! + // 0 : {} + // 1 : {} + // 2 : {} + // 3 : {} + // 4 : {} + public static int minFuel(int[] a, int[] b, int n) { + // 先建图 + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + graph.add(new ArrayList<>()); + } + for (int i = 0; i < a.length; i++) { + graph.get(a[i]).add(b[i]); + graph.get(b[i]).add(a[i]); + } + int[] size = new int[n+1]; + return cost(0, -1, graph, size); + } + + + + // cur : 点的编号! + // father : cur点的父节点! + // 返回 : cur整棵子树上的所有节点汇聚到cur,需要多少油! + public static int cost(int cur, int father, ArrayList> graph, int[] size) { + // cur的整棵子树上,包含cur自己的! + size[cur] = 1; + int cost = 0; + for (int next : graph.get(cur)) { + if (next != father) { // 不回到上级去! + // 下级节点的子树所有节点汇聚在下级节点的总消耗! + int nextDistance = cost(next, cur, graph, size); + cost += nextDistance; + cost += (size[next] + 4) / 5; // size[next] / 5向上取整! + size[cur] += size[next]; + } + } + return cost; + } + + public static void main(String[] args) { + int[] a1 = { 0, 1, 1 }; + int[] b1 = { 1, 2, 3 }; + int n1 = 3; + System.out.println(minFuel(a1, b1, n1)); + + int[] a2 = { 1, 1, 1, 9, 9, 9, 9, 7, 8 }; + int[] b2 = { 2, 0, 3, 1, 6, 5, 4, 0, 0 }; + int n2 = 9; + System.out.println(minFuel(a2, b2, n2)); + } + +} diff --git a/公开课/class113/Code02_MakingALargeIsland.java b/公开课/class113/Code02_MakingALargeIsland.java new file mode 100644 index 0000000..d123e38 --- /dev/null +++ b/公开课/class113/Code02_MakingALargeIsland.java @@ -0,0 +1,124 @@ +package class113; + +import java.util.ArrayList; + +// 来自亚马逊、谷歌、微软、Facebook、Bloomberg +// 给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。 +// 返回执行此操作后,grid 中最大的岛屿面积是多少? +// 岛屿 由一组上、下、左、右四个方向相连的 1 形成。 +// 测试链接 : https://leetcode.cn/problems/making-a-large-island/ +public class Code02_MakingALargeIsland { + + public static int largestIsland(int[][] grid) { + int n = grid.length; + int m = grid[0].length; + ArrayList sizes = new ArrayList<>(); + sizes.add(0); + sizes.add(0); + int id = 2; + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 1) { + int curSize = infect(grid, i, j, id, n, m); + ans = Math.max(ans, curSize); + sizes.add(id++, curSize); + } + } + } + boolean[] visited = new boolean[id]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 0) { + int up = i - 1 >= 0 ? grid[i - 1][j] : 0; + int down = i + 1 < n ? grid[i + 1][j] : 0; + int left = j - 1 >= 0 ? grid[i][j - 1] : 0; + int right = j + 1 < m ? grid[i][j + 1] : 0; + int merge = 1 + sizes.get(up); + visited[up] = true; + if (!visited[down]) { + merge += sizes.get(down); + visited[down] = true; + } + if (!visited[left]) { + merge += sizes.get(left); + visited[left] = true; + } + if (!visited[right]) { + merge += sizes.get(right); + visited[right] = true; + } + ans = Math.max(ans, merge); + visited[up] = false; + visited[down] = false; + visited[left] = false; + visited[right] = false; + } + } + } + return ans; + } + + public static int infect(int[][] grid, int i, int j, int v, int n, int m) { + if (i < 0 || i == n || j < 0 || j == m || grid[i][j] != 1) { + return 0; + } + int ans = 1; + grid[i][j] = v; + ans += infect(grid, i - 1, j, v, n, m); + ans += infect(grid, i + 1, j, v, n, m); + ans += infect(grid, i, j - 1, v, n, m); + ans += infect(grid, i, j + 1, v, n, m); + return ans; + } + + // O(N * M) + public static int[] infect(int[][] map) { + int cnt = 2; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + if (map[i][j] == 1) { // 一个新的岛! + zuo(map, i, j, cnt++); + } + } + } + int[] size = new int[cnt]; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + if (map[i][j] > 1) { + size[map[i][j]]++; + } + } + } + return size; + } + + public static void zuo(int[][] map, int i, int j, int th) { + if (i < 0 || i == map.length || j < 0 || j == map[0].length || map[i][j] != 1) { + return; + } + // i,j 不越界 map[i][j] == 1 + map[i][j] = th; + zuo(map, i - 1, j, th); + zuo(map, i + 1, j, th); + zuo(map, i, j - 1, th); + zuo(map, i, j + 1, th); + } + + public static void main(String[] args) { + int[][] map = { { 0, 1, 0, 0, 1, 1 }, { 1, 1, 1, 0, 0, 0 }, { 1, 1, 0, 1, 1, 0 }, { 1, 1, 0, 0, 1, 0 }, }; + int[] size = infect(map); + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + System.out.print(map[i][j] + " "); + } + System.out.println(); + } + + for (int i = 2; i < size.length; i++) { + System.out.println("编号" + i + ", 大小" + size[i]); + } + + } + +} diff --git a/公开课/class113/Code03_MinimumCostToHireKWorkers.java b/公开课/class113/Code03_MinimumCostToHireKWorkers.java new file mode 100644 index 0000000..53f95d7 --- /dev/null +++ b/公开课/class113/Code03_MinimumCostToHireKWorkers.java @@ -0,0 +1,54 @@ +package class113; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自亚马逊 +// 有 n 名工人。 给定两个数组 quality 和 wage , +// 其中quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。 +// 现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时, +// 我们必须按照下述规则向他们支付工资: +// 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。 +// 工资组中的每名工人至少应当得到他们的最低期望工资。 +// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/ +public class Code03_MinimumCostToHireKWorkers { + + public static class Employee { + public double rubbishDegree; + public int quality; + + public Employee(int w, int q) { + rubbishDegree = (double) w / (double) q; + quality = q; + } + } + + public double mincostToHireWorkers(int[] quality, int[] wage, int k) { + int n = quality.length; + Employee[] employees = new Employee[n]; + for (int i = 0; i < n; i++) { + employees[i] = new Employee(wage[i], quality[i]); + } + Arrays.sort(employees, (a, b) -> a.rubbishDegree <= b.rubbishDegree ? -1 : 1); + PriorityQueue minTops = new PriorityQueue((a, b) -> b - a); + double ans = Double.MAX_VALUE; + for (int i = 0, qualitySum = 0; i < n; i++) { + int curQuality = employees[i].quality; + if (minTops.size() < k) { + qualitySum += curQuality; + minTops.add(curQuality); + if (minTops.size() == k) { + ans = Math.min(ans, qualitySum * employees[i].rubbishDegree); + } + } else { + if (minTops.peek() > curQuality) { + qualitySum += curQuality - minTops.poll(); + minTops.add(curQuality); + } + ans = Math.min(ans, qualitySum * employees[i].rubbishDegree); + } + } + return ans; + } + +} diff --git a/公开课/class114/Code01_KthMissingPositiveNumber.java b/公开课/class114/Code01_KthMissingPositiveNumber.java new file mode 100644 index 0000000..21d82f0 --- /dev/null +++ b/公开课/class114/Code01_KthMissingPositiveNumber.java @@ -0,0 +1,26 @@ +package class114; + +// 给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。 +// 请你找到这个数组里第 k 个缺失的正整数。 +// 测试链接 : https://leetcode.cn/problems/kth-missing-positive-number/ +public class Code01_KthMissingPositiveNumber { + + public int findKthPositive(int[] arr, int k) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int find = arr.length; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] - (m + 1) >= k) { + find = m; + r = m - 1; + } else { + l = m + 1; + } + } + int preValue = find == 0 ? 0 : arr[find - 1]; + int under = preValue - find; + return preValue + (k - under); + } +} diff --git a/公开课/class114/Code02_ZigZagConversion.java b/公开课/class114/Code02_ZigZagConversion.java new file mode 100644 index 0000000..6bd44e4 --- /dev/null +++ b/公开课/class114/Code02_ZigZagConversion.java @@ -0,0 +1,40 @@ +package class114; + +// 将一个给定字符串 s 根据给定的行数 numRows +// 以从上往下、从左到右进行 Z 字形排列 +// 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下 +// P A H N +// A P L S I I G +// Y I R +// 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串 +// "PAHNAPLSIIGYIR" +// 请你实现这个将字符串进行指定行数变换的函数 +// string convert(string s, int numRows) +// 测试链接 : https://leetcode.cn/problems/zigzag-conversion/ +public class Code02_ZigZagConversion { + + public static String convert(String s, int row) { + int n = s.length(); + if (row == 1 || row >= n) { + return s; + } + // 周期! + int t = 2 * row - 2; + char[] ans = new char[n]; + int fill = 0; + for (int i = 0; i < row; i++) { // 行号 + for (int j = i, nextColTop = t; j < n; j += t, nextColTop += t) { + // nextColTop - i + // j ? j+t + ans[fill++] = s.charAt(j); + // 如果在中间行、并且多出来的下标不越界(真的有) + if (i >= 1 && i <= row - 2 && nextColTop - i < n) { + // 把多出来的那个,填进去 + ans[fill++] = s.charAt(nextColTop - i); + } + } + } + return String.valueOf(ans); + } + +} diff --git a/公开课/class114/Code03_MaximumWidthRamp.java b/公开课/class114/Code03_MaximumWidthRamp.java new file mode 100644 index 0000000..25999dd --- /dev/null +++ b/公开课/class114/Code03_MaximumWidthRamp.java @@ -0,0 +1,42 @@ +package class114; + +// 给定一个整数数组 A,坡是元组 (i, j),其中  i < j 且 A[i] <= A[j] +// 这样的坡的宽度为 j - i +// 找出 A 中的坡的最大宽度,如果不存在,返回 0 +// 示例 1: +// 输入:[6,0,8,2,1,5] +// 输出:4 +// 解释: +// 最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5 +// 示例 2: +// 输入:[9,8,1,0,1,9,4,0,4,1] +// 输出:7 +// 解释: +// 最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1 +// 测试链接 : https://leetcode.cn/problems/maximum-width-ramp/ +public class Code03_MaximumWidthRamp { + + public static int maxWidthRamp(int[] arr) { + int n = arr.length; + // 栈中只放下标 + int[] stack = new int[n]; + // 栈的大小 + int r = 0; + for (int i = 0; i < n; i++) { + if (r == 0 || arr[stack[r - 1]] > arr[i]) { + stack[r++] = i; + } + } + int ans = 0; + // 从右往左遍历 + // j = n - 1 + for (int j = n - 1; j >= 0; j--) { + while (r != 0 && arr[stack[r - 1]] <= arr[j]) { + int i = stack[--r]; + ans = Math.max(ans, j - i); + } + } + return ans; + } + +} diff --git a/公开课/class114/Code04_ThreeEqualParts.java b/公开课/class114/Code04_ThreeEqualParts.java new file mode 100644 index 0000000..7547ab8 --- /dev/null +++ b/公开课/class114/Code04_ThreeEqualParts.java @@ -0,0 +1,80 @@ +package class114; + +// 给定一个由 0 和 1 组成的数组 arr ,将数组分成 3 个非空的部分 +// 使得所有这些部分表示相同的二进制值。 +// 如果可以做到,请返回任何 [i, j],其中 i+1 < j,这样一来 +// arr[0], arr[1], ..., arr[i] 为第一部分 +// arr[i + 1], arr[i + 2], ..., arr[j - 1] 为第二部分 +// arr[j], arr[j + 1], ..., arr[arr.length - 1] 为第三部分 +// 这三个部分所表示的二进制值相等 +// 如果无法做到,就返回 [-1, -1] +// 注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体 +// 例如,[1,1,0] 表示十进制中的 6,而不会是 3。此外,前导零也是被允许的 +// 所以 [0,1,1] 和 [1,1] 表示相同的值。 +// 测试链接 : https://leetcode.cn/problems/three-equal-parts/ +public class Code04_ThreeEqualParts { + + public static int[] threeEqualParts(int[] arr) { + // 计算arr中1的数量 + int ones = 0; + for (int num : arr) { + ones += num == 1 ? 1 : 0; + } + // 如果1的数量不能被3整除,肯定不存在方案 + if (ones % 3 != 0) { + return new int[] { -1, -1 }; + } + int n = arr.length; + // 如果1的数量是0,怎么划分都可以了,因为全是0 + if (ones == 0) { + return new int[] { 0, n - 1 }; + } + // 接下来的过程 + // 因为1的数量能被3整除,比如一共有12个1 + // 那么第一段肯定含有第1个1~第4个1 + // 那么第二段肯定含有第5个1~第8个1 + // 那么第三段肯定含有第9个1~第12个1 + // 所以把第1个1,当做第一段的开头,start1 + // 所以把第5个1,当做第二段的开头,start2 + // 所以把第9个1,当做第三段的开头,start3 + int part = ones / 3; + int start1 = -1; + int start2 = -1; + int start3 = -1; + int cnt = 0; + // 1个数21个 + // part = 7 + // 1 8 + for (int i = 0; i < n; i++) { + if (arr[i] == 1) { + cnt++; + if (start1 == -1 && cnt == 1) { + start1 = i; + } + if (start2 == -1 && cnt == part + 1) { + start2 = i; + } + if (start3 == -1 && cnt == 2 * part + 1) { + start3 = i; + } + } + } + // 第一段的开头往下的部分 + // 第二段的开头往下的部分 + // 第三段的开头往下的部分 + // 要都一样,这三段的状态才是一样的 + // 所以接下来就验证这一点,是不是每一步都一样 + while (start3 < n) { + if (arr[start1] != arr[start2] || arr[start1] != arr[start3]) { + // 一旦不一样,肯定没方案了 + return new int[] { -1, -1 }; + } + start1++; + start2++; + start3++; + } + // 如果验证通过,返回断点即可 + return new int[] { start1 - 1, start2 }; + } + +} diff --git a/公开课/class114/Code05_CouplesHoldingHands.java b/公开课/class114/Code05_CouplesHoldingHands.java new file mode 100644 index 0000000..f7f865e --- /dev/null +++ b/公开课/class114/Code05_CouplesHoldingHands.java @@ -0,0 +1,74 @@ +package class114; + +// n对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手 +// 人和座位由一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的ID +// 情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1) +// 返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起 +// 每次交换可选择任意两人,让他们站起来交换座位 +// 测试链接 : https://leetcode.cn/problems/couples-holding-hands/ +public class Code05_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; + } + + } + +} diff --git a/公开课/class114/Code06_AbsToArrayFinalLength.java b/公开课/class114/Code06_AbsToArrayFinalLength.java new file mode 100644 index 0000000..6575e85 --- /dev/null +++ b/公开课/class114/Code06_AbsToArrayFinalLength.java @@ -0,0 +1,130 @@ +package class114; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +// 来自国外题目论坛 +// 给定一个非负数组arr +// 任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 +// 然后新的arr继续,任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 +// 一直到arr大小固定 +// 请问最终arr长度是多少 +// 1 <= arr的长度 <= 10^5 +// 0 <= arr的数值 <= 10^5 +public class Code06_AbsToArrayFinalLength { + + // 暴力方法 + // 为了验证 + public static int finalLen1(int[] arr) { + ArrayList list = new ArrayList<>(); + HashSet set = new HashSet<>(); + for (int num : arr) { + list.add(num); + set.add(num); + } + while (!finish(list, set)) + ; + return list.size(); + } + + public static boolean finish(ArrayList list, HashSet set) { + int len = list.size(); + for (int i = 0; i < len; i++) { + for (int j = i + 1; j < len; j++) { + int abs = Math.abs(list.get(i) - list.get(j)); + if (!set.contains(abs)) { + list.add(abs); + set.add(abs); + } + } + } + return len == list.size(); + } + + // 正式方法 + // 时间复杂O(N) + public static int finalLen2(int[] arr) { + // 收集最大的数 + int max = 0; + // 随便的一个非0的数,最后遇到的非0的数 + int gcd = 0; + for (int num : arr) { + max = Math.max(max, num); + if (num != 0) { + gcd = num; + } + } + // 最大值有了,max + // 最后遇到的非0的数, gcd + if (gcd == 0) { // 如果数组中所有数都是0,数组长度不会变化的! + return arr.length; + } + // 统计每一种数出现的次数 + // 求所有数的最大公约数 + HashMap counts = new HashMap<>(); + for (int num : arr) { + if (num != 0) { + gcd = gcd(gcd, num); + } + counts.put(num, counts.getOrDefault(num, 0) + 1); + } + // 假设不考虑0、也不考虑有没有重复值 + // 经典的max / gcd,有几个数 + int ans = max / gcd; + // 考虑0,考虑数组中原本有的0,而不是重复数值减出来的0 + // 长度变成多少 + ans += counts.getOrDefault(0, 0); + boolean add = false; + for (int key : counts.keySet()) { + // 遍历每一种数的词频 + // 2 7个 + 6 + // 5 4个 + 3 + // 0 ?个 + if (key != 0) { + ans += counts.get(key) - 1; + } + // 因为重复数字的出现,会不会减出来多一个0 + if (!add && counts.get(key) > 1 && !counts.containsKey(0)) { + ans++; + add = true; + } + } + return ans; + } + + public static int gcd(int m, int n) { + return n == 0 ? m : gcd(n, m % n); + } + + // 为了测试 + 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); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 15; + int V = 50; + int testTime = 8000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int ans1 = finalLen1(arr); + int ans2 = finalLen2(arr); + if (ans1 != ans2) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + } + +} diff --git a/公开课/class114/Code07_HeightAfterSubtreeRemoval.java b/公开课/class114/Code07_HeightAfterSubtreeRemoval.java new file mode 100644 index 0000000..df0976b --- /dev/null +++ b/公开课/class114/Code07_HeightAfterSubtreeRemoval.java @@ -0,0 +1,75 @@ +package class114; + +// 给你一棵 二叉树 的根节点 root ,树中有 n 个节点 +// 每个节点都可以被分配一个从 1 到 n 且互不相同的值 +// 另给你一个长度为 m 的数组 queries +// 你必须在树上执行 m 个 独立 的查询,其中第 i 个查询你需要执行以下操作: +// 从树中 移除 以 queries[i] 的值作为根节点的子树 +// 题目所用测试用例保证 queries[i] 不 等于根节点的值。 +// 返回一个长度为 m 的数组 answer ,其中 answer[i] 是执行第 i 个查询后树的高度。 +// 注意: +// 查询之间是独立的,所以在每个查询执行后,树会回到其 初始 状态。 +// 树的高度是从根到树中某个节点的 最长简单路径中的边数 。 +// 测试链接 : https://leetcode.cn/problems/height-of-binary-tree-after-subtree-removal-queries/ +public class Code07_HeightAfterSubtreeRemoval { + + // 提交时不用提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + // 提交如下方法 + public static final int MAXN = 100010; + public static int[] dfn = new int[MAXN]; + public static int[] deep = new int[MAXN]; + public static int[] size = new int[MAXN]; + public static int[] maxl = new int[MAXN]; + public static int[] maxr = new int[MAXN]; + public static int n; + + public static int[] treeQueries(TreeNode root, int[] queries) { + n = 0; + // 每个val,编号 + // 每个val,深度 + // 每个val的子树,大小 + dfs(root, 0); + for (int i = 1; i <= n; i++) { + maxl[i] = Math.max(maxl[i - 1], deep[i]); + } + maxr[n + 1] = 0; + for (int i = n; i >= 1; i--) { + maxr[i] = Math.max(maxr[i + 1], deep[i]); + } + int m = queries.length; + int[] ans = new int[m]; + for (int i = 0; i < m; i++) { + // queries[i] -> a + // a -> 编号x + // a -> 子树大小 + // x ... 子树大小这么多范围 删掉 + int leftMax = maxl[dfn[queries[i]] - 1]; + int rightMax = maxr[dfn[queries[i]] + size[dfn[queries[i]]]]; + ans[i] = Math.max(leftMax, rightMax); + } + return ans; + } + + // n = 0 1 2 3 4 5 6 7 + public static void dfs(TreeNode head, int h) { + int i = ++n; + dfn[head.val] = i; + deep[i] = h; + size[i] = 1; + if (head.left != null) { + dfs(head.left, h + 1); + size[i] += size[dfn[head.left.val]]; + } + if (head.right != null) { + dfs(head.right, h + 1); + size[i] += size[dfn[head.right.val]]; + } + } + +} diff --git a/公开课/class114/Code08_AsFarFromLandAsPossible.java b/公开课/class114/Code08_AsFarFromLandAsPossible.java new file mode 100644 index 0000000..b5aae73 --- /dev/null +++ b/公开课/class114/Code08_AsFarFromLandAsPossible.java @@ -0,0 +1,84 @@ +package class114; + +// 你现在手里有一份大小为 n x n 的 网格 grid +// 上面的每个 单元格 都用 0 和 1 标记好了其中 0 代表海洋,1 代表陆地。 +// 请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的 +// 并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。 +// 我们这里说的距离是「曼哈顿距离」( Manhattan Distance): +// (x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。 +// 测试链接 : https://leetcode.cn/problems/as-far-from-land-as-possible/ +public class Code08_AsFarFromLandAsPossible { + + // 队列接受一个东西,比如(i,j),就加到r位置 + // queue[r][0] = i + // queue[r++][1] = j + // 队列弹出一个东西,就把l位置的东西弹出 + public static int[][] queue = new int[10000][2]; + public static int l; + public static int r; + // 一个东西进入了队列,比如(i,j)进入了,visited[i][j] = true + // 如果(i,j)没进入过,visited[i][j] = false + public static boolean[][] visited = new boolean[100][100]; + // find表示发现了多少海洋 + public static int find; + + public static int maxDistance(int[][] grid) { + // 清空变量 + // 只要l = 0,r = 0,队列就算被清空了 + l = 0; + r = 0; + find = 0; + int n = grid.length; + int m = grid[0].length; + // 清空visited + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + visited[i][j] = false; + } + } + // 大体思路 : + // 1) 先把所有的陆地加入队列,并且统计一共有多少海洋 + int seas = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 1) { + visited[i][j] = true; + queue[r][0] = i; + queue[r++][1] = j; + } else { + seas++; + } + } + } + // 2) 从陆地开始广播出去(bfs),每一块陆地的上、下、左、右所能找到的海洋都是第一层海洋 + // 3) 第一层海洋继续bfs,每一块海洋的上、下、左、右所能找到的海洋都是第二层海洋 + // 4) 第二层海洋继续bfs,每一块海洋的上、下、左、右所能找到的海洋都是第三层海洋 + // ... + // 也就是说,以陆地做起点,每一层bfs都只找海洋! + // 看看最深能找到多少层海洋 + int distance = 0; // 这个变量就是最深的海洋层数 + while (l < r && find < seas) { // find < seas说明所有的海洋块没有找全,继续找! + int size = r - l; + for (int i = 0; i < size && find < seas; i++, l++) { + int row = queue[l][0]; + int col = queue[l][1]; + add(row - 1, col, n, m, grid); + add(row + 1, col, n, m, grid); + add(row, col - 1, n, m, grid); + add(row, col + 1, n, m, grid); + } + distance++; + } + return find == 0 ? -1 : distance; + } + + public static void add(int i, int j, int n, int m, int[][] grid) { + if (i >= 0 && i < n && j >= 0 && j < m && grid[i][j] == 0 && !visited[i][j]) { + find++; + visited[i][j] = true; + queue[r][0] = i; + queue[r++][1] = j; + } + } + +} diff --git a/公开课/class114/Code09_OrderlyQueue.java b/公开课/class114/Code09_OrderlyQueue.java new file mode 100644 index 0000000..48c1522 --- /dev/null +++ b/公开课/class114/Code09_OrderlyQueue.java @@ -0,0 +1,181 @@ +package class114; + +import java.util.Arrays; + +// 给定一个字符串 s 和一个整数 k 。你可以从 s 的前 k 个字母中选择一个 +// 并把它加到字符串的末尾 +// 返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串 +// 测试链接 : https://leetcode.cn/problems/orderly-queue/ +public class Code09_OrderlyQueue { + + public static String orderlyQueue(String s, int k) { + if (k > 1) { + // 时间复杂度O(N*logN) + // 证明 : + // 如果k == 2 + // 总可以做到 : 1小 2小 ... + // 总可以做到 : 3小 .... 1小 2小 ... + // 总可以做到 : 3小 1小 2小 ... + // 总可以做到 : 4小 .... 1小 2小 3小 ... + // 总可以做到 : 4小 1小 2小 3小 ..... + // 总可以做到 : 5小 ..... 1小 2小 3小 4小 ... + // ... + // 所以总可以做到有序 + // k > 2就更能做到了,所以k > 1直接排序返回 + char[] str = s.toCharArray(); + Arrays.sort(str); + return String.valueOf(str); + } else { + // 时间复杂度O(N) + // k == 1时 + // 把字符串看做一个环,就是看看从哪切开字典序最小 + // 通过s = s + s的方式,长度2n,可以得到所有环 + // 然后用DC3算法看看前n个位置,谁的字典序最小即可 + // 虽然从通过百分比来看并不优异 + // 但那是因为leetcode准备的数据量太小了,字符串才1000长度所以显不出优势 + // 如果字符串很长优势就明显了 + // 因为时间复杂度O(N)一定是最优解 + String s2 = s + s; + int n = s2.length(); + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = s2.charAt(i) - 'a' + 1; + } + DC3 dc3 = new DC3(arr, 26); + n >>= 1; + int minRankIndex = 0; + for (int i = 1; i < n; i++) { + if (dc3.rank[i] < dc3.rank[minRankIndex]) { + minRankIndex = i; + } + } + return s.substring(minRankIndex) + s.substring(0, minRankIndex); + } + } + + // 如果字符串长度N, + // DC3算法搞定字符串所有后缀串字典序排名的时间复杂度O(N) + // 体系学习班有讲,有兴趣的同学可以看看 + public static class DC3 { + + public int[] sa; + + public int[] rank; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + } + +} diff --git a/公开课/class115/Code01_StampingTheGrid.java b/公开课/class115/Code01_StampingTheGrid.java new file mode 100644 index 0000000..c1f8c9d --- /dev/null +++ b/公开课/class115/Code01_StampingTheGrid.java @@ -0,0 +1,69 @@ +package class115; + +// 给你一个 m x n 的二进制矩阵 grid +// 每个格子要么为 0 (空)要么为 1 (被占据) +// 给你邮票的尺寸为 stampHeight x stampWidth +// 我们想将邮票贴进二进制矩阵中,且满足以下 限制 和 要求 : +// 覆盖所有空格子,不覆盖任何被占据的格子 +// 可以放入任意数目的邮票,邮票可以相互有重叠部分 +// 邮票不允许旋转,邮票必须完全在矩阵内 +// 如果在满足上述要求的前提下,可以放入邮票,请返回 true ,否则返回 false +// 测试链接 : https://leetcode.cn/problems/stamping-the-grid/ +public class Code01_StampingTheGrid { + + public static boolean possibleToStamp(int[][] grid, int h, int w) { + int n = grid.length; + int m = grid[0].length; + // 左上角累加和数组 + // 查询原始矩阵中的某块儿累加和,快! + int[][] sum = new int[n + 1][m + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + sum[i + 1][j + 1] = grid[i][j]; + } + } + build(sum); + // 差分矩阵 + // 当贴邮票的时候,不再原始矩阵里贴 + // 在差分里贴 + int[][] diff = new int[n + 2][m + 2]; + for (int a = 1, c = a + h - 1; c <= n; a++, c++) { + for (int b = 1, d = b + w - 1; d <= m; b++, d++) { + // (a,b) (c,d) + if (empty(sum, a, b, c, d)) { + set(diff, a, b, c, d); + } + } + } + build(diff); + // 检查所有的格子! + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 0 && diff[i + 1][j + 1] == 0) { + return false; + } + } + } + return true; + } + + public static void build(int[][] m) { + for (int i = 1; i < m.length; i++) { + for (int j = 1; j < m[0].length; j++) { + m[i][j] += m[i - 1][j] + m[i][j - 1] - m[i - 1][j - 1]; + } + } + } + + public static boolean empty(int[][] sum, int a, int b, int c, int d) { + return sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1] == 0; + } + + public static void set(int[][] diff, int a, int b, int c, int d) { + diff[a][b] += 1; + diff[c + 1][d + 1] += 1; + diff[c + 1][b] -= 1; + diff[a][d + 1] -= 1; + } + +} diff --git a/公开课/class115/Code02_SumOfSubSequenceWidths.java b/公开课/class115/Code02_SumOfSubSequenceWidths.java new file mode 100644 index 0000000..a046e73 --- /dev/null +++ b/公开课/class115/Code02_SumOfSubSequenceWidths.java @@ -0,0 +1,32 @@ +package class115; + +import java.util.Arrays; + +// 一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。 +// 给你一个整数数组 nums ,返回 nums 的所有非空 子序列 的 宽度之和 +// 由于答案可能非常大,请返回对 109 + 7 取余 后的结果。 +// 子序列 定义为从一个数组里删除一些(或者不删除)元素, +// 但不改变剩下元素的顺序得到的数组 +// 例如,[3,6,2,7] 就是数组 [0,3,1,6,2,2,7] 的一个子序列。 +// 测试链接 : https://leetcode.cn/problems/sum-of-subsequence-widths/ +public class Code02_SumOfSubSequenceWidths { + + public static int sumSubseqWidths(int[] nums) { + Arrays.sort(nums); + int mod = 1000000007; + long ans = 0; + long A = 0; + long B = 0; + long C = 1; + long D = C; + for (int i = 1; i < nums.length; i++) { + A = (D * nums[i]) % mod; + B = (B * 2 + nums[i - 1]) % mod; + ans = (ans + A - B + mod) % mod; + C = (C * 2) % mod; + D = (D + C) % mod; + } + return (int) (ans); + } + +} diff --git a/公开课/class115/Code03_SumOfDistancesInTree.java b/公开课/class115/Code03_SumOfDistancesInTree.java new file mode 100644 index 0000000..3a60b1f --- /dev/null +++ b/公开课/class115/Code03_SumOfDistancesInTree.java @@ -0,0 +1,54 @@ +package class115; + +import java.util.ArrayList; + +// 给定一个无向、连通的树 +// 树中有 n 个标记为 0...n-1 的节点以及 n-1 条边 。 +// 给定整数 n 和数组 edges , +// edges[i] = [ai, bi]表示树中的节点 ai 和 bi 之间有一条边。 +// 返回长度为 n 的数组 answer ,其中 answer[i] : +// 树中第 i 个节点与所有其他节点之间的距离之和。 +// 测试链接 : https://leetcode.cn/problems/sum-of-distances-in-tree/ +public class Code03_SumOfDistancesInTree { + + public int N = 30001; + public int[] size = new int[N]; + public int[] distance = new int[N]; + + public int[] sumOfDistancesInTree(int n, int[][] edges) { + 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]); + } + collect(0, -1, graph); + int[] ans = new int[n]; + setAns(0, -1, 0, graph, ans); + return ans; + } + + public void collect(int cur, int father, ArrayList> graph) { + size[cur] = 1; + distance[cur] = 0; + for (int next : graph.get(cur)) { + if (next != father) { + collect(next, cur, graph); + distance[cur] += distance[next] + size[next]; + size[cur] += size[next]; + } + } + } + + public void setAns(int cur, int father, int upDistance, ArrayList> graph, int[] ans) { + ans[cur] = distance[cur] + upDistance; + for (int next : graph.get(cur)) { + if (next != father) { + setAns(next, cur, ans[cur] - distance[next] + size[0] - (size[next] << 1), graph, ans); + } + } + } + +} diff --git a/公开课/class116/Code01_MinimumNumberOfDaysToEatNOranges.java b/公开课/class116/Code01_MinimumNumberOfDaysToEatNOranges.java new file mode 100644 index 0000000..01e27a1 --- /dev/null +++ b/公开课/class116/Code01_MinimumNumberOfDaysToEatNOranges.java @@ -0,0 +1,81 @@ +package class116; + +import java.util.HashMap; + +// 来自腾讯面试 +// 厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子: +// 吃掉一个橘子。 +// 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子。 +// 如果剩余橘子数 n 能被 3 整除,那么你可以吃掉 2*(n/3) 个橘子。 +// 每天你只能从以上 3 种方案中选择一种方案。 +// 请你返回吃掉所有 n 个橘子的最少天数。 +// 测试链接 : https://leetcode.cn/problems/minimum-number-of-days-to-eat-n-oranges/ +public class Code01_MinimumNumberOfDaysToEatNOranges { + + // 斐波那契数列,求第n项 + // 1 1 2 3 5 8 13 .... + + public static HashMap fdp = new HashMap<>(); + + public static int f(int n) { + if (fdp.containsKey(n)) { + return fdp.get(n); + } + int ans = 0; + if (n == 1) { + ans = 1; + } else if (n == 2) { + ans = 2; + } else { + ans = f(n - 1) + f(n - 2); + } + fdp.put(n, ans); + return ans; + } + + // 所有的答案都填在这个表里 + // 这个表对所有的过程共用 + // 有橘子数量key,至少几天吃完这个结果 + // key -> value + public static HashMap dp = new HashMap<>(); + + public static int minDays(int n) { + // 0 0天 + // 1 1天 + if (n <= 1) { + return n; + } + // n >= 2 + if (dp.containsKey(n)) { + return dp.get(n); + } + // 1) 吃掉一个橘子 + // 2) 如果n能被2整除,吃掉一半的橘子,剩下一半 + // 3) 如果n能被3正数,吃掉三分之二的橘子,剩下三分之一 + // 因为方法2)和3),是按比例吃橘子,所以必然会非常快 + // 所以,决策如下: + // 可能性1:为了使用2)方法,先把橘子吃成2的整数倍,然后直接干掉一半,剩下的n/2调用递归 + // 即,n % 2 + 1 + minDays(n/2) + // 可能性2:为了使用3)方法,先把橘子吃成3的整数倍,然后直接干掉三分之二,剩下的n/3调用递归 + // 即,n % 3 + 1 + minDays(n/3) + // 至于方法1),完全是为了这两种可能性服务的,因为能按比例吃,肯定比一个一个吃快(显而易见的贪心) + // 1) 吃成,能被2整除,减一半 + // n = 16 + // 1天 吃掉8个橘子 + minDays(8) + // n = 17 + // 1天 吃掉1个橘子,一天 吃掉8个橘子 + minDays(8) + int p1 = n % 2 + 1 + minDays(n / 2); + // 2) 吃成,能被3整除,减2/3, 1/3 + // n = 9 + // 0天,3的整数倍,1天,6个(2/3) + minDays(n / 3) + // n = 10 + // 1天,3的整数倍,1天,6个(2/3) + minDays(n / 3) + // n = 11 + // 2天,3的整数倍,1天,6个(2/3) + minDays(n / 3) + int p2 = n % 3 + 1 + minDays(n / 3); + int ans = Math.min(p1, p2); + dp.put(n, ans); + return ans; + } + +} diff --git a/公开课/class116/Code02_HowManyObtuseAngles.java b/公开课/class116/Code02_HowManyObtuseAngles.java new file mode 100644 index 0000000..b8393b8 --- /dev/null +++ b/公开课/class116/Code02_HowManyObtuseAngles.java @@ -0,0 +1,44 @@ +package class116; + +import java.util.Arrays; + +// 来自hulu +// 有一个以原点为圆心,半径为1的圆 +// 在这个圆的圆周上,有一些点 +// 因为所有的点都在圆周上,所以每个点可以有很简练的表达 +// 比如:用0来表示一个圆周上的点,这个点就在(1,0)位置 +// 比如:用6000来表示一个点,这个点是(1,0)点沿着圆周逆时针转60.00度之后所在的位置 +// 比如:用18034来表示一个点,这个点是(1,0)点沿着圆周逆时针转180.34度之后所在的位置 +// 这样一来,所有的点都可以用[0, 36000)范围上的数字来表示 +// 那么任意三个点都可以组成一个三角形,返回能组成钝角三角形的数量 +public class Code02_HowManyObtuseAngles { + + public static long obtuseAngles(int[] arr) { + // 30.15 3015 + // 30.7 3070 + // n长度的排序,O(N * logN) + // O(N) + int n = arr.length; + int m = n << 1; + int[] enlarge = new int[m]; + Arrays.sort(arr); + for (int i = 0; i < n; i++) { + enlarge[i] = arr[i]; + enlarge[i + n] = arr[i] + 36000; + } + long ans = 0; + // 这里不用二分查找(太慢),能做一个不回退的优化 + for (int L = 0, R = 0; L < n; L++) { + while (R < m && enlarge[R] - enlarge[L] < 18000) { + R++; + } + ans += num(R - L - 1); + } + return ans; + } + + public static long num(long nodes) { + return nodes < 2 ? 0 : ((nodes - 1) * nodes) >> 1; + } + +} diff --git a/公开课/class116/Code03_CherryPickup.java b/公开课/class116/Code03_CherryPickup.java new file mode 100644 index 0000000..cb00d66 --- /dev/null +++ b/公开课/class116/Code03_CherryPickup.java @@ -0,0 +1,84 @@ +package class116; + +// 来自字节跳动 +// 牛客的测试链接: +// https://www.nowcoder.com/questionTerminal/8ecfe02124674e908b2aae65aad4efdf +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 把如下的全部代码拷贝进java编辑器 +// 把文件大类名字改成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; + +public class Code03_CherryPickup { + + 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; + in.nextToken(); + int M = (int) in.nval; + int[][] matrix = new int[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + in.nextToken(); + matrix[i][j] = (int) in.nval; + } + } + out.println(cherryPickup(matrix)); + out.flush(); + } + } + + // 如下方法,在leetcode上提交也能通过 + // 测试链接 : https://leetcode.cn/problems/cherry-pickup/ + public static int cherryPickup(int[][] grid) { + int N = grid.length; + int M = grid[0].length; + int[][][] dp = new int[N][M][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + for (int k = 0; k < N; k++) { + dp[i][j][k] = Integer.MIN_VALUE; + } + } + } + int ans = process(grid, 0, 0, 0, dp); + return ans < 0 ? 0 : ans; + } + + public static int process(int[][] grid, int x1, int y1, int x2, int[][][] dp) { + if (x1 == grid.length || y1 == grid[0].length || x2 == grid.length || x1 + y1 - x2 == grid[0].length) { + return Integer.MIN_VALUE; + } + if (dp[x1][y1][x2] != Integer.MIN_VALUE) { + return dp[x1][y1][x2]; + } + if (x1 == grid.length - 1 && y1 == grid[0].length - 1) { + dp[x1][y1][x2] = grid[x1][y1]; + return dp[x1][y1][x2]; + } + int next = Integer.MIN_VALUE; + next = Math.max(next, process(grid, x1 + 1, y1, x2 + 1, dp)); + next = Math.max(next, process(grid, x1 + 1, y1, x2, dp)); + next = Math.max(next, process(grid, x1, y1 + 1, x2, dp)); + next = Math.max(next, process(grid, x1, y1 + 1, x2 + 1, dp)); + if (grid[x1][y1] == -1 || grid[x2][x1 + y1 - x2] == -1 || next == -1) { + dp[x1][y1][x2] = -1; + return dp[x1][y1][x2]; + } + if (x1 == x2) { + dp[x1][y1][x2] = grid[x1][y1] + next; + return dp[x1][y1][x2]; + } + dp[x1][y1][x2] = grid[x1][y1] + grid[x2][x1 + y1 - x2] + next; + return dp[x1][y1][x2]; + } + +} \ No newline at end of file diff --git a/公开课/class117/Code01_BoatsToSavePeople.java b/公开课/class117/Code01_BoatsToSavePeople.java new file mode 100644 index 0000000..e125c68 --- /dev/null +++ b/公开课/class117/Code01_BoatsToSavePeople.java @@ -0,0 +1,34 @@ +package class117; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/boats-to-save-people/ +public class Code01_BoatsToSavePeople { + + // 数组里一定不会有大于limit的数值 + public static int numRescueBoats(int[] people, int limit) { + Arrays.sort(people); + // 最终的船数 + int ans = 0; + // 左指针 + int l = 0; + // 右指针 + int r = people.length - 1; + int sum = 0; + while (l <= r) { + // ? ? + // 17 39 + // l r + sum = l == r ? people[l] : people[l] + people[r]; + if (sum > limit) { + r--; + } else { + l++; + r--; + } + ans++; + } + return ans; + } + +} diff --git a/公开课/class117/Code02_MaximumProductSubarray.java b/公开课/class117/Code02_MaximumProductSubarray.java new file mode 100644 index 0000000..8429d67 --- /dev/null +++ b/公开课/class117/Code02_MaximumProductSubarray.java @@ -0,0 +1,28 @@ +package class117; + +// 测试链接:https://leetcode.cn/problems/maximum-product-subarray/ +public class Code02_MaximumProductSubarray { + + public static int maxProduct(int[] nums) { + // 0结尾时,最好答案; + // 1结尾时,最好答案;... max -> ans + int ans = nums[0]; + int min = nums[0]; + int max = nums[0]; + for (int i = 1; i < nums.length; i++) { + // i 结尾 + // 1) nums[i] + // 2) nums[i] * 之前的最大乘积 -> max + // 3) nums[i] * 之前的最小乘积 -> min + // i 结尾 最大乘积 : 1) 2) 3) 取最大! + // i 结尾 最小乘积 : 1) 2) 3) 取最小! + int curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); + int curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); + min = curmin; + max = curmax; + ans = Math.max(ans, max); + } + return ans; + } + +} diff --git a/公开课/class117/Code03_RotateImage.java b/公开课/class117/Code03_RotateImage.java new file mode 100644 index 0000000..f7e96e4 --- /dev/null +++ b/公开课/class117/Code03_RotateImage.java @@ -0,0 +1,30 @@ +package class117; + +// 测试链接 : https://leetcode.cn/problems/rotate-image/ +public class Code03_RotateImage { + + // 保证传入的参数是正方形矩阵 + public static void rotate(int[][] matrix) { + // 左上角点,(a,b) + int a = 0; + int b = 0; + // 右下角点,(c,d) + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + +} diff --git a/公开课/class117/Code04_SmallestRangeCoveringElementsfromKLists.java b/公开课/class117/Code04_SmallestRangeCoveringElementsfromKLists.java new file mode 100644 index 0000000..cecb3af --- /dev/null +++ b/公开课/class117/Code04_SmallestRangeCoveringElementsfromKLists.java @@ -0,0 +1,75 @@ +package class117; + +import java.util.Comparator; +import java.util.List; +import java.util.TreeSet; + +// 本题测试链接 : https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/ +public class Code04_SmallestRangeCoveringElementsfromKLists { + + public static void main(String[] args) { + TreeSet set = new TreeSet<>(); + set.add(700); + set.add(72); + set.add(70000); + set.add(7000); + set.add(7); + set.add(70); + set.add(17); + // log N + System.out.println(set.first()); + System.out.println(set.last()); + System.out.println(set.contains(170)); + System.out.println(set.floor(23)); + System.out.println(set.ceiling(23)); + } + + public static class Node { + public int value; + public int arrid; + public int index; + + public Node(int v, int ai, int i) { + value = v; + arrid = ai; + index = i; + } + } + + public static class NodeComparator implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.value != o2.value ? o1.value - o2.value : o1.arrid - o2.arrid; + } + + } + + public static int[] smallestRange(List> nums) { + int N = nums.size(); + TreeSet orderSet = new TreeSet<>(new NodeComparator()); + for (int i = 0; i < N; i++) { + orderSet.add(new Node(nums.get(i).get(0), i, 0)); + } + boolean set = false; + int a = 0; + int b = 0; + while (orderSet.size() == N) { + Node min = orderSet.first(); + Node max = orderSet.last(); + if (!set || (max.value - min.value < b - a)) { + set = true; + a = min.value; + b = max.value; + } + min = orderSet.pollFirst(); + int arrid = min.arrid; + int index = min.index + 1; + if (index != nums.get(arrid).size()) { + orderSet.add(new Node(nums.get(arrid).get(index), arrid, index)); + } + } + return new int[] { a, b }; + } + +} diff --git a/公开课/class117/Code05_CandyProblem.java b/公开课/class117/Code05_CandyProblem.java new file mode 100644 index 0000000..9993c8c --- /dev/null +++ b/公开课/class117/Code05_CandyProblem.java @@ -0,0 +1,74 @@ +package class117; + +// 测试链接 : https://leetcode.com/problems/candy/ +public class Code05_CandyProblem { + + public static int candy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] left = new int[N]; + for (int i = 1; i < N; i++) { + if (arr[i - 1] < arr[i]) { + left[i] = left[i - 1] + 1; + } + } + int[] right = new int[N]; + for (int i = N - 2; i >= 0; i--) { + if (arr[i] > arr[i + 1]) { + right[i] = right[i + 1] + 1; + } + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += Math.max(left[i], right[i]); + } + return ans + N; + } + + public static int circleCandy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return 1; + } + int n = arr.length; + int minIndex = 0; + for (int i = 0; i < n; i++) { + if (arr[i] <= arr[lastIndex(i, n)] && arr[i] <= arr[nextIndex(i, n)]) { + minIndex = i; + break; + } + } + int[] nums = new int[n + 1]; + for (int i = 0; i <= n; i++, minIndex = nextIndex(minIndex, n)) { + nums[i] = arr[minIndex]; + } + int[] left = new int[n + 1]; + left[0] = 1; + for (int i = 1; i <= n; i++) { + left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1; + } + int[] right = new int[n + 1]; + right[n] = 1; + for (int i = n - 1; i >= 0; i--) { + right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1; + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(left[i], right[i]); + } + return ans; + } + + public static int nextIndex(int i, int n) { + return i == n - 1 ? 0 : (i + 1); + } + + public static int lastIndex(int i, int n) { + return i == 0 ? (n - 1) : (i - 1); + } + +} diff --git a/公开课/class117/Code06_MinWindowLength.java b/公开课/class117/Code06_MinWindowLength.java new file mode 100644 index 0000000..c17bb3f --- /dev/null +++ b/公开课/class117/Code06_MinWindowLength.java @@ -0,0 +1,44 @@ +package class117; + +// 测试链接 : https://leetcode.com/problems/minimum-window-substring/ +public class Code06_MinWindowLength { + + public static String minWindow(String s, String t) { + if (s.length() < t.length()) { + return ""; + } + char[] str = s.toCharArray(); + char[] target = t.toCharArray(); + int[] map = new int[256]; + for (char cha : target) { + map[cha]++; + } + int all = target.length; + int L = 0; + int R = 0; + int minLen = Integer.MAX_VALUE; + int ansl = -1; + int ansr = -1; + while (R != str.length) { + map[str[R]]--; + if (map[str[R]] >= 0) { + all--; + } + if (all == 0) { + while (map[str[L]] < 0) { + map[str[L++]]++; + } + if (minLen > R - L + 1) { + minLen = R - L + 1; + ansl = L; + ansr = R; + } + all++; + map[str[L++]]++; + } + R++; + } + return minLen == Integer.MAX_VALUE ? "" : s.substring(ansl, ansr + 1); + } + +} diff --git a/公开课/class117/Code07_RangesHasDominateNumber.java b/公开课/class117/Code07_RangesHasDominateNumber.java new file mode 100644 index 0000000..5c4eebf --- /dev/null +++ b/公开课/class117/Code07_RangesHasDominateNumber.java @@ -0,0 +1,107 @@ +package class117; + +import java.util.HashMap; + +// 来自小红书 +// 小A认为如果在数组中有一个数出现了至少k次 +// 且这个数是该数组的众数,即出现次数最多的数之一 +// 那么这个数组被该数所支配 +// 显然当k比较大的时候,有些数组不被任何数所支配 +// 现在小A拥有一个长度为n的数组,她想知道内部有多少个区间是被某个数支配的 +// 2 <= k <= n <= 100000 +// 1 <= 数组的值 <= n +public class Code07_RangesHasDominateNumber { + + // 暴力方法 + // 为了验证 + // 时间复杂度O(N^3) + public static int dominates1(int[] arr, int k) { + int n = arr.length; + int ans = 0; + for (int l = 0; l < n; l++) { + for (int r = l; r < n; r++) { + if (ok(arr, l, r, k)) { + ans++; + } + } + } + return ans; + } + + public static boolean ok(int[] arr, int l, int r, int k) { + HashMap map = new HashMap(); + for (int i = l; i <= r; i++) { + map.put(arr[i], map.getOrDefault(arr[i], 0) + 1); + } + for (int times : map.values()) { + if (times >= k) { + return true; + } + } + return false; + } + + // 正式方法 + // 时间复杂度O(N) + public static int dominates2(int[] arr, int k) { + int n = arr.length; + // 总数量 + int all = n * (n + 1) / 2; + // 不被支配的区间数量 + int except = 0; + // 次数表 + // 0 : 0 + // 1 : 0 + // 2 : 0 + int[] cnt = new int[n + 1]; + // l ... r + // 窗口用这个形式[l,r) + // l...r-1 r(x) + // l == 0 r == 0 [l,r) 一个数也没有 + // l == 0 r == 1 [0..0] + for (int l = 0, r = 0; l < n; l++) { + // [r] 即将要进来的 + // cnt[arr[r]] + 1 < k + while (r < n && cnt[arr[r]] + 1 < k) { + // cnt[arr[r]]++; + // r++ + cnt[arr[r++]]++; + } + // l..l + // l..l+1 + // l..l+2 + // l..r-1 + except += r - l; + cnt[arr[l]]--; + } + return all - except; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * n) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n); + int k = (int) (Math.random() * n) + 1; + int ans1 = dominates1(arr, k); + int ans2 = dominates2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/公开课/class118/Code01_ExpressionCompute.java b/公开课/class118/Code01_ExpressionCompute.java new file mode 100644 index 0000000..6d15f00 --- /dev/null +++ b/公开课/class118/Code01_ExpressionCompute.java @@ -0,0 +1,71 @@ +package class118; + +import java.util.LinkedList; + +// 本题测试链接 : https://leetcode.com/problems/basic-calculator-iii/ +public class Code01_ExpressionCompute { + + public static int calculate(String str) { + return f(str.toCharArray(), 0)[0]; + } + + // 请从str[i...]往下算,遇到字符串终止位置或者右括号,就停止 + // 返回两个值,长度为2的数组 + // 0) 负责的这一段的结果是多少 + // 1) 负责的这一段计算到了哪个位置 + public static int[] f(char[] str, int i) { + LinkedList que = new LinkedList(); + int cur = 0; + int[] bra = null; + // 从i出发,开始撸串 + while (i < str.length && str[i] != ')') { + if (str[i] >= '0' && str[i] <= '9') { + cur = cur * 10 + str[i++] - '0'; + } else if (str[i] != '(') { // 遇到的是运算符号 + addNum(que, cur); + que.addLast(String.valueOf(str[i++])); + cur = 0; + } else { // 遇到左括号了 + bra = f(str, i + 1); + cur = bra[0]; + i = bra[1] + 1; + } + } + addNum(que, cur); + return new int[] { getNum(que), i }; + } + + public static void addNum(LinkedList que, int num) { + if (!que.isEmpty()) { + int cur = 0; + String top = que.pollLast(); + if (top.equals("+") || top.equals("-")) { + que.addLast(top); + } else { + cur = Integer.valueOf(que.pollLast()); + num = top.equals("*") ? (cur * num) : (cur / num); + } + } + que.addLast(String.valueOf(num)); + } + + public static int getNum(LinkedList que) { + int res = 0; + boolean add = true; + String cur = null; + int num = 0; + while (!que.isEmpty()) { + cur = que.pollFirst(); + if (cur.equals("+")) { + add = true; + } else if (cur.equals("-")) { + add = false; + } else { + num = Integer.valueOf(cur); + res += add ? num : (-num); + } + } + return res; + } + +} diff --git a/公开课/class118/Code02_ComputeExpressionValue.java b/公开课/class118/Code02_ComputeExpressionValue.java new file mode 100644 index 0000000..6a2a0e9 --- /dev/null +++ b/公开课/class118/Code02_ComputeExpressionValue.java @@ -0,0 +1,58 @@ +package class118; + +// 来自美团 +// () 分值为2 +// (()) 分值为3 +// ((())) 分值为4 +// 也就是说,每包裹一层,分数就是里面的分值+1 +// ()() 分值为2 * 2 +// (())() 分值为3 * 2 +// 也就是说,每连接一段,分数就是各部分相乘,以下是一个结合起来的例子 +// (()())()(()) -> (2 * 2 + 1) * 2 * 3 -> 30 +// 给定一个括号字符串str,已知str一定是正确的括号结合,不会有违规嵌套 +// 返回分数 +public class Code02_ComputeExpressionValue { + + public static int sores(String s) { + return compute(s.toCharArray(), 0)[0]; + } + + // s[i.....]一旦遇到 ) 或者 字符串终止位置,停! + // 返回值: + // 0) 负责这一段的结果(得分)是多少? + // 1) 计算到了什么位置也返回 + public static int[] compute(char[] s, int i) { + if (s[i] == ')') { + return new int[] { 1, i }; + } + int ans = 1; + while (i < s.length && s[i] != ')') { + int[] info = compute(s, i + 1); + ans *= info[0] + 1; + i = info[1] + 1; + } + return new int[] { ans, i }; + } + + public static void main(String[] args) { + + String str1 = "(()())()(())"; + System.out.println(sores(str1)); + + // (()()) + (((()))) + ((())()) + // (()()) -> 2 * 2 + 1 -> 5 + // (((()))) -> 5 + // ((())()) -> ((2 + 1) * 2) + 1 -> 7 + // 所以下面的结果应该是175 + String str2 = "(()())(((())))((())())"; + System.out.println(sores(str2)); + + // (()()()) + (()(())) + // (()()()) -> 2 * 2 * 2 + 1 -> 9 + // (()(())) -> 2 * 3 + 1 -> 7 + // 所以下面的结果应该是63 + String str3 = "(()()())(()(()))"; + System.out.println(sores(str3)); + } + +} diff --git a/公开课/class118/Code03_RegularExpressionMatch.java b/公开课/class118/Code03_RegularExpressionMatch.java new file mode 100644 index 0000000..864f59a --- /dev/null +++ b/公开课/class118/Code03_RegularExpressionMatch.java @@ -0,0 +1,61 @@ +package class118; + +// 测试链接 : https://leetcode.com/problems/regular-expression-matching/ +public class Code03_RegularExpressionMatch { + + // 暴力递归方法 + public static boolean isMatch1(String str, String exp) { + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + return f1(s, e, 0, 0); + } + + public static boolean f1(char[] s, char[] e, int si, int ei) { + if (ei == e.length) { + return si == s.length; + } + if (si == s.length) { + return ei + 1 < e.length && e[ei + 1] == '*' && f1(s, e, si, ei + 2); + } + if (ei + 1 >= e.length || e[ei + 1] != '*') { + return (s[si] == e[ei] || e[ei] == '.') && f1(s, e, si + 1, ei + 1); + } + // e[ei + 1] == '*' + boolean ans = f1(s, e, si, ei + 2); + if (s[si] == e[ei] || e[ei] == '.') { + ans |= f1(s, e, si + 1, ei); + } + return ans; + } + + // 暴力递归改的动态规划方法 + // 有套路的!课上讲这个套路的!非常有用! + public static boolean isMatch2(String str, String exp) { + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + int[][] dp = new int[s.length + 1][e.length + 1]; + return f2(s, e, 0, 0, dp); + } + + public static boolean f2(char[] s, char[] e, int si, int ei, int[][] dp) { + if (dp[si][ei] != 0) { + return dp[si][ei] == 1; + } + boolean ans = false; + if (ei == e.length) { + ans = si == s.length; + } else if (si == s.length) { + ans = ei + 1 < e.length && e[ei + 1] == '*' && f2(s, e, si, ei + 2, dp); + } else if (ei + 1 >= e.length || e[ei + 1] != '*') { + ans = (s[si] == e[ei] || e[ei] == '.') && f2(s, e, si + 1, ei + 1, dp); + } else { + ans = f2(s, e, si, ei + 2, dp); + if (s[si] == e[ei] || e[ei] == '.') { + ans |= f2(s, e, si + 1, ei, dp); + } + } + dp[si][ei] = ans ? 1 : -1; + return ans; + } + +}