diff --git a/pom.xml b/pom.xml index 919d74d..200cfc9 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,18 @@ org.me.study datastructure-algorithm 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + \ No newline at end of file diff --git a/src/main/java/com/study/array/ArrayOperation.java b/src/main/java/com/study/array/ArrayOperation.java index 9fa9c3d..fb7efe1 100644 --- a/src/main/java/com/study/array/ArrayOperation.java +++ b/src/main/java/com/study/array/ArrayOperation.java @@ -19,12 +19,12 @@ public static void main(String[] args) { } - private static int[] work (int[] a ) { + private static int[] work(int[] a) { int p = 0, n = a.length; int[] array = new int[n]; - for (int i=0; i0 && array[p-1]%2!=0) + for (int i = 0; i < n; i++) { + if (a[i] % 2 == 0) //如果是偶数 + while (p > 0 && array[p - 1] % 2 != 0) array[p--] = 0; //删除前面的奇数 array[p++] = a[i]; } diff --git a/src/main/java/com/study/array/MaximumSubarray.java b/src/main/java/com/study/array/MaximumSubarray.java new file mode 100644 index 0000000..fe96f1e --- /dev/null +++ b/src/main/java/com/study/array/MaximumSubarray.java @@ -0,0 +1,145 @@ +package com.study.array; + +/** + * 最大子序和 + *

+ * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + *

+ * 示例: + *

+ * 输入: [-2,1,-3,4,-1,2,1,-5,4], + * 输出: 6 + * 解释: 连续子数组 [4,-1,2,1] 的和最大,为6。 + * 进阶: + *

+ * 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 + *

+ * 链接:https://leetcode-cn.com/problems/maximum-subarray + */ +public class MaximumSubarray { + + public static void main(String[] args) { + //int[] nums = {-2, 1}; + int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; + //int result = maxSubArray(nums); + //int result = maxSubArray_greedy(nums); + int result = maxSubArray_greedy_2(nums); + //int result = maxSubArray2(nums); + System.out.println(result); + } + + /** + * 动态规划 + *

+ * 首先对数组进行遍历, 当前最大连续子序列和为currentSum,结果为max; + * 如果 currentSum>0, 说明currentSum + 下一个数 是有增益效果的. + * 如果 currentSum<0, 说明currentSum + 下一个数 没有增益效果. 需要舍弃, 使用下一个数进行替换 + * 比较 currentSum和max值, 取其中最大值作为max + * + * @param nums + * @return + */ + private static int maxSubArray(int[] nums) { + int max = nums[0]; + int currentSum = nums[0]; + for (int i = 1; i < nums.length; i++) { + // currentSum为正数时候, 有增益效果, 加下一个元素的和 比使用下一个元素更大 + if (currentSum > 0) { + currentSum += nums[i]; + } else { + // 为负数时候, 没有增益效果, +下一个元素 小于直接使用下一个元素,所以使用下一个元素替换 重新开始计算currentSum + currentSum = nums[i]; + } + // 取 当前和 最大值 中最大的一个 + max = Math.max(max, currentSum); + } + return max; + } + + private static int maxSubArray_2(int[] nums){ + int max = nums[0]; + int currentSum = nums[0]; + + for(int i =1; i < nums.length; i++){ + if(currentSum > 0) + currentSum += nums[i]; + else + currentSum = nums[i]; + + max = Math.max(max,currentSum); + } + return max; + } + + /** + * 使用贪心算法 + * + * 遍历每个元素,将当前所有数之和 与 当前元素做比较, 取大的 作为当前所有数之和 + * 同时保持更新最大值, 将当前所有数之和 跟 最大值做比较, 取最大的 更新为 最大值 + * + * 因为只需要一次循环遍历,时间复杂度O(n), 空间复杂度O(1) + * + * @param nums + * @return + */ + private static int maxSubArray_greedy(int[] nums) { + int max = nums[0]; + int currentSum = nums[0]; + for (int i = 1; i < nums.length; i++) { + // 贪心 当前所有数之和 把每个数都加上 + currentSum += nums[i]; + // 如果当前数 比当前所有数之和还大, 那就使用当前数 作为当前所有数之和 + // 如果当前数 比当前所有数之和还小, 那就继续使用当前所有数之和 + // 比如 [-2 1] -2 + 1 < 1, 那就直接从1开始计算 舍弃之前的-2 + currentSum = Math.max(nums[i], currentSum); + // 取 当前所有数之和 与 最大值 中最大的一个 + max = Math.max(max, currentSum); + } + return max; + } + + private static int maxSubArray_greedy_2(int[] nums) { + int max = nums[0]; + int currentSum = nums[0]; + for(int i =1; i 0) + curSum += nums[j]; + else// 负数没有增益效果, 需要舍去 + curSum = nums[j]; + + max = Math.max(curSum, max); + } + } + return max; + } +} diff --git a/src/main/java/com/study/array/MergeTwoASCSortedArray.java b/src/main/java/com/study/array/MergeTwoASCSortedArray.java new file mode 100644 index 0000000..c10a2fc --- /dev/null +++ b/src/main/java/com/study/array/MergeTwoASCSortedArray.java @@ -0,0 +1,133 @@ +package com.study.array; + +/** + * 合并两个升序数组 + *

+ * 可以使用如下方法: + * 1.新数组存两个数组参数的方法,两个数组不变 + */ +public class MergeTwoASCSortedArray { + public static void main(String[] args) { + int[] num1 = {1, 2, 5, 7, 9, 10}; + int[] num2 = {1, 4, 6, 8}; + +// int[] newNum = merge(num1, num2); +// int[] newNum = merge2(num1, num2); + int[] newNum = merge3(num1, num2); + + for (int i = 0; i < newNum.length; i++) { + System.out.print(newNum[i] + " "); + } + } + + /** + * 新建一个数组来存取两个数组的内容 + * 多次迭代完成 + * + * @param num1 + * @param num2 + * @return + */ + private static int[] merge(int[] num1, int[] num2) { + int a = 0, b = 0, c = 0; + + int[] newNum = new int[num1.length + num2.length]; + + // 在数组num1和num2都不超出索引的情况下遍历,一旦num1或者num2 其中一个数组遍历完就退出循环,否则将出现索引越界的情况。 + // 假设 num2遍历完了, b的长度等于num2,这个时候需要退出循环,否则里面执行num2【b】的时候会报索引越界 + while (a < num1.length && b < num2.length) { + // 将小的元素放到前面 + if (num1[a] < num2[b]) + newNum[c++] = num1[a++]; //先放元素 再各自索引+1 + else + newNum[c++] = num2[b++]; + } + + // 元素补齐 + // 执行到这里 num1或num2可能会有元素没有加到newNum中,需要再补充。 + // 如果num1中还有元素未添加的 继续添加 + while (a < num1.length) { + newNum[c++] = num1[a++]; + } + + // 如果num2中还有元素未添加的继续添加 + while (b < num2.length) { + newNum[c++] = num2[b++]; + } + return newNum; + } + + /** + * 新建一个数组来存取两个数组的内容 + * 多次迭代完成 + * + * @param num1 + * @param num2 + * @return + */ + private static int[] merge2(int[] num1, int[] num2) { + int i = 0, j = 0, k = 0; + + int[] newNum = new int[num1.length + num2.length]; + + // 一旦有一个数组的元素全部取出,就退出循环,避免数组越界的情况发生 + while (i < num1.length && j < num2.length) { + // 两个数组中的元素做比较, 将小的元素放前面 + if (num1[i] < num2[j]) + newNum[k++] = num1[i++]; + else + newNum[k++] = num2[j++]; + } + + // 将剩下的某个未取出元素的数组 再取出元素 + // 通常只剩下一个数组,因为上面的循环执行完 肯定是另一个数组已取完 + while (i < num1.length) { + newNum[k++] = num1[i++]; + } + + while (j < num2.length) { + newNum[k++] = num2[j++]; + } + + return newNum; + } + + /** + * 新建一个数组来存取两个数组的内容 + * 1次迭代完成 + * + * @param num1 + * @param num2 + * @return + */ + private static int[] merge3(int[] num1, int[] num2) { + int i = 0, j = 0, k = 0; + + int[] newNum = new int[num1.length + num2.length]; + + // 一次循环完成,需要处理数组越界的问题 + while (i < num1.length || j < num2.length) { + + // 如果数组num1越界,说明已取完,那就取另一个数组num2, 需要确保num2没越界 + if (i >= num1.length && j < num2.length) { + newNum[k++] = num2[j++]; + continue; + } + + // 如果数组num2越界,说明已取完,那就取另一个数组num1,需要确保num1没越界 + if (j >= num2.length && i < num1.length) { + newNum[k++] = num1[i++]; + continue; + } + + // 两个数组中的元素做比较, 将小的元素放前面 + if (num1[i] < num2[j]) + newNum[k++] = num1[i++]; + else + newNum[k++] = num2[j++]; + + } + + return newNum; + } +} diff --git a/src/main/java/com/study/array/MergeTwoASCSortedArray2.java b/src/main/java/com/study/array/MergeTwoASCSortedArray2.java new file mode 100644 index 0000000..10b5c41 --- /dev/null +++ b/src/main/java/com/study/array/MergeTwoASCSortedArray2.java @@ -0,0 +1,154 @@ +package com.study.array; + +import java.util.Arrays; + +/** + * 在不开辟新数组的情况下 合并两个升序数组, 使用归并排序 + *

+ * 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 + *

+ * 说明: + *

+ * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 + * 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 + * 示例: + *

+ * 输入: + * nums1 = [1,2,3,0,0,0], m = 3 + * nums2 = [2,5,6], n = 3 + *

+ * 输出: [1,2,2,3,5,6] + *

+ * 来源:力扣(LeetCode) + * 链接:https://leetcode-cn.com/problems/merge-sorted-array + */ +public class MergeTwoASCSortedArray2 { + + public static void main(String[] args) { + int[] num1 = {2, 3, 4, 0, 0, 0, 0}; + int[] num2 = {1, 2, 5, 6}; + + MergeTwoASCSortedArray2 m = new MergeTwoASCSortedArray2(); + //m.merge(num1, 3, num2, 4); + //m.merge2(num1, 3, num2, 4); + //m.merge3(num1, 3, num2, 4); + m.merge4(num1,3,num2,4); + } + + /** + * 最简单的办法就是先合并,再排序. 时间复杂度较差为 O((n+m)log(n+m)) + * 因为没有用到两个数组本身已经排好顺序这一点 + * + * @param nums1 目标数组 + * @param m 目标数组开始放置新元素的位置 + * @param nums2 源数组 + * @param n 目标数组需要放置新元素的长度 + */ + public void merge(int[] nums1, int m, int[] nums2, int n) { + // 源数组 源数组开始的位置 目标数组 目标数组开始放置新元素的位置 目标数组需要放置新元素的长度 + System.arraycopy(nums2, 0, nums1, m, n); + Arrays.sort(nums1); + + print(nums1); + } + + /** + * 使用一个新数组 来存放合并结果 + * 时间复杂度 : O(n + m)O(n+m) + * 空间复杂度 : O(m)O(m) + * + * @param nums1 目标数组 + * @param m 目标数组开始放置新元素的位置 + * @param nums2 源数组 + * @param n 目标数组需要放置新元素的长度 + */ + public void merge2(int[] nums1, int m, int[] nums2, int n) { + // Make a copy of nums1. + int[] nums1_copy = new int[m]; + System.arraycopy(nums1, 0, nums1_copy, 0, m); + + // Two get pointers for nums1_copy and nums2. + int p1 = 0; + int p2 = 0; + + // Set pointer for nums1 + int p = 0; + + // Compare elements from nums1_copy and nums2 + // and add the smallest one into nums1. + while ((p1 < m) && (p2 < n)) + nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++]; + + // if there are still elements to add + if (p1 < m) + System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2); + if (p2 < n) + System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2); + + print(nums1); + } + + /** + * 不开辟新的数组, 直接在nums1上面做调整, 从nums1的尾部开始插入元素 + * + * @param nums1 目标数组 + * @param m 目标数组开始放置新元素的位置 + * @param nums2 源数组 + * @param n 目标数组需要放置新元素的长度 + */ + public void merge3(int[] nums1, int m, int[] nums2, int n) { + int p1 = m - 1; // nums1数组有效元素的最大索引 + int p2 = n - 1; // nums2数组有效元素的最大索引 + + int p = m + n - 1; + // 将两个数组的有效元素从后往前做比较, 大的元素放在nums1的后面 + while (p1 >= 0 && p2 >= 0) { + if (nums1[p1] < nums2[p2]) { + nums1[p--] = nums2[p2--]; + } else { + nums1[p--] = nums1[p1--]; + } + } + + // 上面循环完, 可能因为mums2的元素更多,导致还有元素没有拷贝到nums1中 + // 将nums2剩余的最小的几个元素拷贝到mums1的最前面 + System.arraycopy(nums2, 0, nums1, 0, p2 + 1); + + print(nums1); + } + + + /** + * @param nums1 目标数组 + * @param m 目标数组从哪个位置开始放置源数组元素 + * @param nums2 源数组 + * @param n 需要将源数组元素拷贝到目标数组的个数 + */ + public void merge4(int[] nums1, int m, int[] nums2, int n) { + int p1 = m - 1; // p1为nums1有效元素的最大索引, 而m比nums1最大索引还要大1 + int p2 = n - 1;// p2为nums2的最大索引 + + int p = m + n - 1; // 新数组的最大索引 + + // 将nums1和nums2的有效元素从后往前做比较, 并将大的元素从后往前放到nums1中 + while (p1 >= 0 && p2 >= 0) { + if (nums1[p1] > nums2[p2]) { + nums1[p--] = nums1[p1--]; + } else { + nums1[p--] = nums2[p2--]; + } + } + + // 如果nums2的长度比nums1的有效长度还长, 那么循环结束后, nums2前面还有一些元素未拷贝到nums1中 + // 将nums2剩余的最小元素拷贝到nums1的前面 + System.arraycopy(nums2, 0, nums1, 0, p2 + 1); + + print(nums1); + } + + private static void print(int[] nums){ + for (int num : nums) { + System.out.println(num); + } + } +} diff --git a/src/main/java/com/study/array/NRepeatedElementInSizeTwoNArray.java b/src/main/java/com/study/array/NRepeatedElementInSizeTwoNArray.java index 4f306db..feb3993 100644 --- a/src/main/java/com/study/array/NRepeatedElementInSizeTwoNArray.java +++ b/src/main/java/com/study/array/NRepeatedElementInSizeTwoNArray.java @@ -38,8 +38,8 @@ public class NRepeatedElementInSizeTwoNArray { public static void main(String[] args) { //int[] a = {1, 2, 3, 3}; - //int[] a = {2, 1, 2, 5, 3, 2}; - int[] a = {5, 1, 5, 2, 5, 3, 5, 4}; + int[] a = {2, 1, 2, 5, 3, 2}; + //int[] a = {5, 1, 5, 2, 5, 3, 5, 4}; //int[] a = {9, 5, 3, 3}; //int[] a = {2, 1, 1, 1, 2, 5, 3, 2}; //int[] a = {2, 2, 4, 4, 2, 5, 3, 2}; @@ -58,16 +58,22 @@ private static int repeatedNTimes(int[] A) { HashMap map = new HashMap(A.length); for (int i = 0; i < A.length; i++) { + // 如果map中的key已经包含了这个元素 if (map.containsKey(A[i])) { + // 如果这个元素的个数已经达到数组的一半 直接返回 if (map.get(A[i]) == A.length / 2) { return A[i]; } else { + // 否则元素个数+1 map.put(A[i], map.get(A[i]) + 1); } } else { + // 该元素不存在map中, 将元素作为key 放入map中 map.put(A[i], 1); } } + + // 如果上面没有返回个数等于数组一半的元素, 说明这个元素是数组的最后一个 for (Map.Entry entry : map.entrySet()) { if (entry.getValue() == A.length / 2) { return entry.getKey(); diff --git a/src/main/java/com/study/array/SingleNumber.java b/src/main/java/com/study/array/SingleNumber.java new file mode 100644 index 0000000..5027d98 --- /dev/null +++ b/src/main/java/com/study/array/SingleNumber.java @@ -0,0 +1,153 @@ +package com.study.array; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 只出现一次的数字 + *

+ * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 + *

+ * 说明: + *

+ * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? + *

+ * 示例 1: + *

+ * 输入: [2,2,1] + * 输出: 1 + * 示例 2: + *

+ * 输入: [4,1,2,1,2] + * 输出: 4 + *

+ * 链接:https://leetcode-cn.com/problems/single-number + */ +public class SingleNumber { + + public static void main(String[] args) { + //int[] arr = {2, 2, 1}; + int[] arr = {4, 1, 2, 1, 2}; + //System.out.println(singleNumberHashSet(arr)); + //System.out.println(singleNumberHashMap(arr)); + //System.out.println(singleNumberBySum(arr)); + System.out.println(singleNumberByBit(arr)); + } + + /** + * 使用hashset来存每个元素, 当元素出现2次的时候,移除该元素,所以只会保留出现一次的元素. + * 最终hashset里面只会存1个元素, 即为结果 + *

+ * 时间复杂度 O(n), 空间复杂度O(n) + * + * @param nums + * @return + */ + private static int singleNumberHashSet(int[] nums) { + if (nums.length == 1) + return nums[0]; + + Set set = new HashSet(); + for (int i = 0; i < nums.length; i++) { + // 如果该元素已经存在, 就移除 + if (set.contains(nums[i])) + set.remove(nums[i]); + else // 不存在 就添加 + set.add(nums[i]); + } + + // 最终set里面只有一个元素即为结果 + for (Integer i : set) { + return i; + } + + return -1; + } + + /** + * 使用hashmap来存每个数字的个数, 最后遍历hashmap,找出个数为1的那个数字 + *

+ * 时间复杂度 O(n), 空间复杂度O(n) + * + * @param nums + * @return + */ + private static int singleNumberHashMap(int[] nums) { + if (nums.length == 1) + return nums[0]; + + Map map = new HashMap(); + for (int i = 0; i < nums.length; i++) { + // 如果该元素已经存在, 就移除 + if (map.containsKey(nums[i])) + map.put(nums[i], map.get(nums[i]) + 1); + else // 不存在 就添加 + map.put(nums[i], 1); + } + + // 最终set里面只有一个元素即为结果 + for (Map.Entry kv : map.entrySet()) { + if (kv.getValue() == 1) + return kv.getKey(); + } + + return -1; + } + + + /** + * 通过数学公式 2倍的不重复的元素之和 - 数组元素之和 = 单个元素 + * 2(a+b+c) - (2a + 2b + c) = c + *

+ * 遍历数组, 计算数组所有元素值 + * 使用set来存储不重复的元素, 存储的同时 计算不重复元素值 + * 使用不重复元素之和的2倍 - 数组元素值之和 = 单个元素 + *

+ * 时间复杂度 O(n), 空间复杂度O(n) + * + * @param nums + * @return + */ + private static int singleNumberBySum(int[] nums) { + Set set = new HashSet(); + + int twoSum = 0; + int arrSum = 0; + + for (int n : nums) { + arrSum += n; // 计算数组元素之和 + if (!set.contains(n)) { + set.add(n); + twoSum += n; // 计算不重复元素之和 + } + } + + return twoSum * 2 - arrSum; + } + + /** + * 使用位运算的 异或 + * 相同的两个数进行异或^结果为0, 0和任何数异或为任何数本身 + *

+ * 如果我们对 0 和二进制位做 XOR 运算,得到的仍然是这个二进制位 + * a ^ 0 = a + * 如果我们对相同的二进制位做 XOR 运算,返回的结果是 0 + * a ^ a = 0 + * XOR 满足交换律和结合律 + * a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b + *

+ * 链接:https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-zi-by-leetcode/ + * 来源:力扣(LeetCode) + * + * @return + */ + private static int singleNumberByBit(int[] nums) { + int result = 0; + for (int n : nums) { + result ^= n; // 相同数异或之后变成0, 0和某个数异或得到该数本身 + } + return result; + } +} diff --git a/src/main/java/com/study/array/SwapNumbersLcci.java b/src/main/java/com/study/array/SwapNumbersLcci.java new file mode 100644 index 0000000..1445af5 --- /dev/null +++ b/src/main/java/com/study/array/SwapNumbersLcci.java @@ -0,0 +1,63 @@ +package com.study.array; + +/** + * 交换数字 + *

+ * 编写一个函数,不用临时变量,直接交换numbers = [a, b]中a与b的值。 + *

+ * 示例: + *

+ * 输入: numbers = [1,2] + * 输出: [2,1] + * 提示: + *

+ * numbers.length == 2 + *

+ * https://leetcode-cn.com/problems/swap-numbers-lcci/ + */ +public class SwapNumbersLcci { + + public static void main(String[] args) { + //int[] arr = {1, 2}; + int[] arr = {5, 3}; + //int[] result = swapNumbers(arr); + int[] result = swapNumbers2(arr); + System.out.println(result[0] + " " + result[1]); + } + + + /** + * 求和相减法 + *

+ * 由于数组中只有两个元素, 可以使用两数之和 减去其中一个数 得到另一个数来处理 + * + * @param numbers + * @return + */ + private static int[] swapNumbers(int[] numbers) { + + if (numbers == null || numbers.length != 2) { + return new int[]{}; + } + + numbers[0] = numbers[0] + numbers[1]; // 3 = 1 + 2 + numbers[1] = numbers[0] - numbers[1]; // 1 = 3 - 2 这个时候2变成了1 + numbers[0] = numbers[0] - numbers[1]; // 2 = 3 - 1 这个时候1变成了2 + + return numbers; + } + + /** + * 使用位算法 + * + * @param numbers + * @return + */ + private static int[] swapNumbers2(int[] numbers) { + numbers[0] = numbers[0] ^ numbers[1]; // 3 = 1 ^ 2 + numbers[1] = numbers[0] ^ numbers[1]; // 1 = 3 ^ 2 + numbers[0] = numbers[0] ^ numbers[1]; // 2 = 3 ^ 1 + + return numbers; + } +} diff --git a/src/main/java/com/study/binarytree/PreorderTraversal.java b/src/main/java/com/study/binarytree/PreorderTraversal.java deleted file mode 100644 index 2696242..0000000 --- a/src/main/java/com/study/binarytree/PreorderTraversal.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.study.binarytree; - -import com.study.utils.TreeUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -/** - * 144. 二叉树的前序遍历 - *

- * 给定一个二叉树,返回它的 前序 遍历。 - *

- * 示例: - *

- * 输入: [1,null,2,3] - * 1 - * \ - * 2 - * / - * 3 - *

- * 输出: [1,2,3] - *

- * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? - *

- * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ - */ -public class PreorderTraversal { - public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; - List treeNodes = TreeUtils.buildTree(arr); - TreeNode root = treeNodes.get(0); - - //List list = preorderTraversal(root); - List list = preorderTraversal2(root); - for (Integer num : list) { - System.out.println(num); - } - } - - - /** - * 通过递归的方式遍历整个二叉树 - *

- * 前序遍历: 根 - 左 - 右 - * - * @param root - * @param list - */ - private static void traversal(TreeNode root, List list) { - if (root != null) { - list.add(root.val); - traversal(root.left, list); - traversal(root.right, list); - } - } - - - public static List preorderTraversal(TreeNode root) { - List list = new ArrayList(); - traversal(root, list); - return list; - } - - /** - * 使用迭代的方式 遍历二叉树 - * - * @param root - * @return - */ - public static List preorderTraversal2(TreeNode root) { - List list = new ArrayList(); - Stack stack = new Stack(); - - if (root == null) { - return list; - } - - stack.push(root); - - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - list.add(node.val); - - // 根据栈后进先出的原理, 把left节点放在right后面放入栈中 - if (node.right != null) { - stack.push(node.right); - } - - if (node.left != null) { - stack.push(node.left); - } - } - return list; - } -} diff --git a/src/main/java/com/study/binarytree/binarysearchtree/SearchTreeNode.java b/src/main/java/com/study/binarytree/binarysearchtree/SearchTreeNode.java deleted file mode 100644 index f325324..0000000 --- a/src/main/java/com/study/binarytree/binarysearchtree/SearchTreeNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.study.binarytree.binarysearchtree; - -/** - * 二叉搜索树节点 - */ -public class SearchTreeNode { - public SearchTreeNode left; - public SearchTreeNode right; - public SearchTreeNode parent; - public int val; - - public SearchTreeNode(int val) { - this.val = val; - } -} diff --git a/src/main/java/com/study/binarytree/binarysearchtree/ValidateBinarySearchTree.java b/src/main/java/com/study/binarytree/binarysearchtree/ValidateBinarySearchTree.java deleted file mode 100644 index 25a571b..0000000 --- a/src/main/java/com/study/binarytree/binarysearchtree/ValidateBinarySearchTree.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.study.binarytree.binarysearchtree; - -import com.study.utils.TreeUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * 验证二叉搜索树 又名 二叉排序树/二叉平衡(查找)树 - *

- * BinarySearchTree BST - * - *

- * 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 - *

- * 假设一个二叉搜索树具有如下特征: - *

- * 节点的左子树只包含小于当前节点的数。 - * 节点的右子树只包含大于当前节点的数。 - * 所有左子树和右子树自身必须也是二叉搜索树。 - * 示例 1: - *

- * 输入: - * 2 - * / \ - * 1 3 - * 输出: true - * 示例 2: - *

- * 输入: - * 5 - * / \ - * 1 4 - * / \ - * 3 6 - * 输出: false - * 解释: 输入为: [5,1,4,null,null,3,6]。 - * 根节点的值为 5 ,但是其右子节点值为 4 。 - */ -public class ValidateBinarySearchTree { - public static void main(String[] args) { - int[] arr = {5, 3, 7, 1, 4, 6, 8}; - - List treeNodes = TreeUtils.buildSearchTree(arr); - - List nodes = new ArrayList(); - - SearchTreeNode root = treeNodes.get(0); - inorder(root, nodes); // 验证树是否正确,从小到大输出 - - for (SearchTreeNode node : nodes) { - System.out.print(node.val + " "); - } - - System.out.println(isValidBST(root)); - - //System.out.println(isValidBST(root, 1, 8)); - System.out.println(isValidBST2(root, 1, 8)); - } - - - /** - * 判断该tree是否为一个有效的二叉搜索树 - *

- * 先使用中序遍历, 将每个节点放到一个arraylist集合中 - *

- * 然后遍历集合,对比前后两个元素, 看看是不是后面的元素都比前面元素大,如果是则说明集合是从小到大排列 - *

- * 同理说明二叉搜索树里的节点也是按左 根 右 从小到大排列的 - * - * @param root - * @return - */ - private static boolean isValidBST(SearchTreeNode root) { - List nodes = new ArrayList(); - inorder(root, nodes); // 验证树是否正确,从小到大输出 - - for (int i = 0; i < nodes.size() - 1; i++) { - //如果前一个元素大于后一个元素, 则说明不是有效二叉搜索树 - if (nodes.get(i).val >= nodes.get(i + 1).val) { - return false; - } - } - return true; - } - - /** - * 验证是否为二叉搜索树, 可以根据它的 左子节点 < 父节点 < 右子节点的特性来 判断每个节点是否都满足 - * - * @param root - * @return - */ - private static boolean isValidBST(SearchTreeNode root, Integer min, Integer max) { - if (root == null) //如果root为空 说明当前节点已经递归到最底层了 或者树本身就是空树 - return true; - if (min != null && root.val < min) //如果root小于最小值, 不是二叉搜索树 - return false; - if (max != null && root.val > max) //如果root大于最大值, 也不是二叉搜索树 - return false; - - //左子树的所有节点肯定都比根节点小 右子树的所有节点都比根大 - boolean left = isValidBST(root.left, min, root.val);// 左子树跟节点的值为最大值 - boolean right = isValidBST(root.right, root.val, max); //右子树跟节点为最小值 - - return left && right; //如果左边和右边都满足条件, 则说明该数为平衡二叉树 - //return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max); - } - - - private static boolean isValidBST2(SearchTreeNode root, Integer min, Integer max) { - if (root == null) - return true; - if (min != null && root.val < min) //跟节点不能小于最小值 - return false; - if (max != null && root.val > max) //跟节点也不能大于最大值 - return false; - - //不停的递归左子树 和 右子树, 判断是否有不满足条件(左<父<右)的节点存在 - return isValidBST2(root.left, min, root.val) && isValidBST2(root.right, max, root.val); - } - - - /** - * 使用中序遍历 获取二叉树每个节点 - *

- * 左 根 右 - * - * @param root - */ - private static void inorder(SearchTreeNode root, List nodes) { - if (root != null) { - inorder(root.left, nodes); - nodes.add(root); - inorder(root.right, nodes); - } - } -} diff --git a/src/main/java/com/study/cache/LRUCacheClient.java b/src/main/java/com/study/cache/LRUCacheClient.java new file mode 100644 index 0000000..0652f94 --- /dev/null +++ b/src/main/java/com/study/cache/LRUCacheClient.java @@ -0,0 +1,224 @@ +package com.study.cache; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * LRU least Recently Used 最近最少使用, 是一种淘汰策略,选择最久未使用的元素进行淘汰 + * + * 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 + *

+ * 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 + * 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 + *

+ * 进阶: + *

+ * 你是否可以在 O(1) 时间复杂度内完成这两种操作? + *

+ * 示例: + * LRUCache cache = new LRUCache(2); //2为缓存容量 + * cache.put(1, 1); + * cache.put(2, 2); + * cache.get(1); // 返回 1 + * cache.put(3, 3); // 该操作会使得密钥 2 作废 + * cache.get(2); // 返回 -1 (未找到) + * cache.put(4, 4); // 该操作会使得密钥 1 作废 + * cache.get(1); // 返回 -1 (未找到) + * cache.get(3); // 返回 3 + * cache.get(4); // 返回 4 + *

+ * 链接:https://leetcode-cn.com/problems/lru-cache + */ +public class LRUCacheClient { + public static void main(String[] args) { + LRUCache cache = new LRUCache(2); //2为缓存容量 + // 元素的热程度为最新放的最热, 下面是2最热 + cache.put(1, 1); + cache.put(2, 2); + // 获取key为1的值, 获取后 key为1的节点由于会到链表的头部,变成最热的节点(最不容易不被淘汰的节点) + System.out.println("cache.get(1): " + cache.get(1)); // 返回 1 + // 之后再将一个key为3的元素放入缓存中, 放完之后3的热度比1高 + // 由于缓存只能放2个元素,所以 + cache.put(3, 3); // 由于key为2的节点没有被访问过, 所以该操作会使得key为2的元素 作废 + System.out.println("cache.get(2): " + cache.get(2)); // 由于key为2的元素被淘汰了,所以返回-1 // 返回 -1 + // 再放入一个key为4的元素, 放完之后由于空间限制只能保留2个元素,所以将最不热的1给淘汰了 + cache.put(4, 4); // 该操作会使得密钥 1 作废 + cache.get(1); // 返回 -1 (未找到) + cache.get(3); // 返回 3 + cache.get(4); // 返回 4 + } +} + +/** + * 方法一:使用LinkedHashMap实现, LinkedHashMap是一个有序的hashmap, 可以对内部的节点进行排序 + * 在调用put和get方法会执行afterNodeInsertion()和afterNodeAccess()方法, 将当前操作的节点放到链表的头部,成为最热的节点(最不容易被删除的节点), + * 同时移除最老的元素(链表尾部的元素),一旦容量不够的情况下 + *

+ * 时间复杂度:对于 put 和 get 都是 O(1)O(1)。 + * 空间复杂度:O(capacity)O(capacity),因为哈希表和双向链表最多存储 capacity + 1 个元素。 + *

+ * 链接:https://leetcode-cn.com/problems/lru-cache/solution/lru-huan-cun-ji-zhi-by-leetcode/ + */ +class LRUCache extends LinkedHashMap { + private int capacity; + + public LRUCache(int capacity) { + super(capacity, 0.75f, true); + this.capacity = capacity; + } + + /** + * 获取key的值,如果没有返回-1, 访问后,该key的节点会被放到头部,变成最热的节点(最不容易被删除的节点) + * + * @param key + * @return + */ + public int get(int key) { + return super.getOrDefault(key, -1); + } + + /** + * 插入键值对 + * + * @param key + * @param value + */ + public void put(int key, int value) { + super.put(key, value); + } + + /** + * 移除最老元素的策略是容量不够 + * + * @param eldest + * @return + */ + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return super.size() > this.capacity; + } +} + + +class LRUCache2{ + class DLinkedNode { + int key; + int value; + DLinkedNode prev; + DLinkedNode next; + } + + /** + * 添加新节点, 将新节点放到链表头部 + * @param node + */ + private void addNode(DLinkedNode node) { + /** + * Always add the new node right after head. + */ + node.prev = head; + node.next = head.next; + + head.next.prev = node; + head.next = node; + } + + /** + * 删除一个节点 + * + * @param node + */ + private void removeNode(DLinkedNode node){ + /** + * Remove an existing node from the linked list. + */ + DLinkedNode prev = node.prev; + DLinkedNode next = node.next; + + prev.next = next; + next.prev = prev; + } + + /** + * 将当前节点放到链表头部 + * + * @param node + */ + private void moveToHead(DLinkedNode node){ + /** + * Move certain node in between to the head. + */ + removeNode(node); + addNode(node); + } + + /** + * 弹出尾部节点 + * @return + */ + private DLinkedNode popTail() { + /** + * Pop the current tail. + */ + DLinkedNode res = tail.prev; + removeNode(res); + return res; + } + + private HashMap cache = new HashMap(); + private int size; + private int capacity; + private DLinkedNode head, tail; + + public LRUCache2(int capacity) { + this.size = 0; + this.capacity = capacity; + + head = new DLinkedNode(); + // head.prev = null; + + tail = new DLinkedNode(); + // tail.next = null; + + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + DLinkedNode node = cache.get(key); + if (node == null) return -1; + + // 将当前节点放到链表头部 + moveToHead(node); + + return node.value; + } + + public void put(int key, int value) { + DLinkedNode node = cache.get(key); + + if(node == null) { + DLinkedNode newNode = new DLinkedNode(); + newNode.key = key; + newNode.value = value; + + cache.put(key, newNode); + addNode(newNode); + + ++size; + + if(size > capacity) { + // 从链表中删除尾部节点,同时从hashmap中删除 + DLinkedNode tail = popTail(); + cache.remove(tail.key); + --size; + } + } else { + // update the value. + node.value = value; + // 更新节点 也会将节点放到链表头部 + moveToHead(node); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/study/dynamicprogramming/ClimbingStairs.java b/src/main/java/com/study/dynamicprogramming/ClimbingStairs.java deleted file mode 100644 index 97e32dd..0000000 --- a/src/main/java/com/study/dynamicprogramming/ClimbingStairs.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.study.dynamicprogramming; - -/** - * 动态规划问题 - 爬楼梯 - *

- * 问题: 有n层台阶, 每次可以走1层或2层或3层, 求有多少种走法 - *

- * 可以通过回溯法或者动态规划来解 - *

- *

- * 台阶数 --> 走法数量 - * 1 --> 1 - * 2 --> 11 2 - * 3 --> 111 12 21 3 - *

- * https://time.geekbang.org/course/detail/130-69779 - */ -public class ClimbingStairs { - - public static void main(String[] args) { - System.out.println(climbStairs2(3)); - System.out.println(climbStairs2(4)); - System.out.println(climbStairs2(5)); - - System.out.println(climbStairs(3)); - System.out.println(climbStairs(4)); - System.out.println(climbStairs(5)); - } - - private static int climbStairs(int n) { - // 第0-3层的走法 已经知道 - if (n == 0) - return 0; - if (n == 1 || n == 2) - return 3; - if (n == 3) - return 4; - - int f1 = 1; - int f2 = 2; - int f3 = 4; - - int result = 0; - // 从第4层开始计算 - for (int i = 4; i <= n; i++) { - result = f1 + f2 + f3; - f1 = f2; - f2 = f3; - f3 = result; - } - - return result; - } - - /** - * 使用递归的解法 - * - * @param n - * @return - */ - private static int climbStairs2(int n) { - if (n == 0 || n == 1 || n == 2) { - return n; - } else if (n == 3) { - return 4; - } - - return climbStairs2(n - 1) + climbStairs2(n - 2) + climbStairs2(n - 3); - } -} diff --git a/src/main/java/com/study/dynamicprogramming/ClimbingStairs2Steps.java b/src/main/java/com/study/dynamicprogramming/ClimbingStairs2Steps.java new file mode 100644 index 0000000..efd22d8 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/ClimbingStairs2Steps.java @@ -0,0 +1,88 @@ +package com.study.dynamicprogramming; + +/** + * 动态规划问题 - 爬楼梯 + *

+ * 问题: 有n层台阶, 每次可以走1层或2层 求有多少种走法 + *

+ * + * 每层楼梯的走法为前两层走法之和 + * + * 和斐波拉契数列是相同的解法 + * f(n) = f(n-1) + f(n-2) + * f(0) = f(1) = 1 + */ +public class ClimbingStairs2Steps { + + public static void main(String[] args) { + + int n =5; + System.out.println(calcStepsByRecursion(n)); + System.out.println(calcStepsByLoop(n)); + System.out.println(calcStepsByArray(n)); + } + + /** + * 使用递归的方式进行计算 f(n) = f(n-1) + f(n-2) + * 缺点: 重复创建了大量元素 且每一层计算都需要递归2次 + *

+ * 时间复杂度为O(2^n) + * + * @param n + * @return + */ + private static int calcStepsByRecursion(int n) { + if (n == 0 || n == 1 || n == 2) + return n; + + return calcStepsByRecursion(n - 1) + calcStepsByRecursion(n - 2); + } + + /** + * 使用迭代的方式, 时间复杂都为O(n), 只用了3个变量 空间复杂度为O(1) + * @param n + * @return + */ + private static int calcStepsByLoop(int n) { + if (n == 0 || n == 1 || n == 2) + return n; + + int f1 = 1; + int f2 = 2; + int f3 = 0; + + // 从第3层开始计算, 每层结果为前两层之和 + for (int i = 2; i < n; i++) { + f3 = f2 + f1; + f1 = f2; + f2 = f3; + } + return f3; + } + + /** + * 使用动态规划的方式, 数组来存储每个元素,节省了大量临时变量的创建 + * + * 时间复杂度为O(n),空间复杂度为O(n) + * + * @param n + * @return + */ + private static int calcStepsByArray(int n) { + if (n == 0 || n == 1 || n == 2) + return n; + + // 创建n个元素的数组 + int[] arr = new int[n]; + + arr[0] = 1; + arr[1] = 2; + + for (int i = 2; i < n; i++) { + arr[i] = arr[i - 1] + arr[i - 2]; + } + + // 返回第n个元素, 数组是从0开始的 + return arr[n - 1]; + } +} diff --git a/src/main/java/com/study/dynamicprogramming/ClimbingStairs3Steps.java b/src/main/java/com/study/dynamicprogramming/ClimbingStairs3Steps.java new file mode 100644 index 0000000..21b45f5 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/ClimbingStairs3Steps.java @@ -0,0 +1,133 @@ +package com.study.dynamicprogramming; + +/** + * 动态规划问题 - 爬楼梯 3层 + *

+ * 问题: 有n层台阶, 每次可以走1层或2层或3层, 求有多少种走法 + *

+ * 可以通过回溯法或者动态规划来解 + *

+ *

+ * 台阶数 --> 走法数量 + * 1 --> 1 + * 2 --> 11 2 + * 3 --> 111 12 21 3 + *

+ * https://time.geekbang.org/course/detail/130-69779 + */ +public class ClimbingStairs3Steps { + + public static void main(String[] args) { + System.out.println(climbStairsByRecursion(3)); + System.out.println(climbStairsByRecursion(4)); + System.out.println(climbStairsByRecursion(5)); + + System.out.println(climbStairsByLoop(3)); + System.out.println(climbStairsByLoop(4)); + System.out.println(climbStairsByLoop(5)); + + // 递归走法 + System.out.println(climbStairsByRecursion2(5)); + + //动态规划走法 + System.out.println(climbStairsByArray(5)); + } + + /** + * 使用迭代的解法, 只用到了3个变量 + *

+ * 时间复杂度为O(n),空间复杂度为O(1) + * + * @param n + * @return + */ + private static int climbStairsByLoop(int n) { + // 第0-3层的走法 已经知道 + if (n == 0 || n == 1 || n == 2) + return n; + if (n == 3) + return 4; + + int f1 = 1; + int f2 = 2; + int f3 = 4; + + int result = 0; + // 从第4层开始计算 + for (int i = 4; i <= n; i++) { + result = f1 + f2 + f3; + f1 = f2; + f2 = f3; + f3 = result; + } + + return result; + } + + /** + * 动态规划的解法, 使用数组来存储每n层可以走的方式 + * + * @param n + * @return + */ + private static int climbStairsByArray(int n) { + // 临界条件 + // 第1层和第2层各有1和2种走法 + if (n == 0 || n == 1 || n == 2) + return n; + // 第3层有4种走法 + if (n == 3) + return 4; + + // 定义状态 + int[] arr = new int[n]; + + // 初始状态 + arr[0] = 1; + arr[1] = 2; + arr[2] = 4; + + // 状态推导 + // 从第4个层开始计算 + for (int i = 3; i < n; i++) { + // 将前3个元素相加得到第4个元素 + // 此外下一轮循环由于i+1了,所以这轮的i自动变成了下一轮的i-1,这轮的i-1变成了下一轮的i-2... + arr[i] = arr[i - 1] + arr[i - 2] + arr[i - 3]; + } + + // 得出结果 + // 返回第n个元素, 因为数组是从第0个元素开始的 + return arr[n - 1]; + } + + /** + * 使用递归的解法 + * + * 时间复杂度 O(3^n) + * + * @param n + * @return + */ + private static int climbStairsByRecursion(int n) { + // 如果是0,1,2层楼梯, 走法都刚好为层数 + if (n == 0 || n == 1 || n == 2) { + return n; + // 如果是3层楼梯,走法为4种 + } else if (n == 3) { + return 4; + } + + return climbStairsByRecursion(n - 1) + climbStairsByRecursion(n - 2) + climbStairsByRecursion(n - 3); + } + + + private static int climbStairsByRecursion2(int n) { + if (n == 0 || n == 1 || n == 2) { + return n; + } else if (n == 3) { + return 4; + } + + return climbStairsByRecursion2(n - 1) + climbStairsByRecursion2(n - 2) + climbStairsByRecursion2(n - 3); + } +} diff --git a/src/main/java/com/study/dynamicprogramming/CountPathMatrix.java b/src/main/java/com/study/dynamicprogramming/CountPathMatrix.java new file mode 100644 index 0000000..c2d6e88 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/CountPathMatrix.java @@ -0,0 +1,148 @@ +package com.study.dynamicprogramming; + +import java.util.Arrays; + +/** + * 求二维矩阵的走法(不同路径) + *

+ * 一个人从二维矩阵的起点位置[-1,-1]走到目标位置[-4,-5],只能往左或者往下,不能往回走,求一共有多少种走法. + * 思路: 如下经过从推导发现, 一共有35种走法. 其中可以发现一个规律就是每个格子的走法数量 = 右和下两个邻格走法数量之和. + * 算法推导: dp[-3,-4] = dp[-4,-4] + dp[-3,-5] + * dp[-2,-4] = dp[-3,-4] + dp[-2,-5] + * ... + * dp[-1,-1] = dp[-2,-1] + dp[-1,-2] + * dp[m,n] = dp[m-1,n] + dp[m,n-1] + *

+ * https://leetcode-cn.com/problems/unique-paths/ + */ +public class CountPathMatrix { + + public static void main(String[] args) { + + //System.out.println(uniquePaths(4, 5)); + //System.out.println(uniquePaths2(4, 5)); + // System.out.println(uniquePaths2_2(4, 5)); + System.out.println(uniquePaths3(4, 5)); +// System.out.println(uniquePaths(8, 9)); +// System.out.println(uniquePaths2(8, 9)); + } + + /** + * 有一个宽为m,长为n的矩阵 + * 从起点[0,0] 走到终点[m-1,n-1] 一共有多少种走法 + *

+ *

+ * 解法 动态规划, 通过二维数组进行递推 + *

+ * 时间复杂度为O(m*n), 空间复杂度为O(m*n) + * + * @return + */ + private static int uniquePaths(int m, int n) { + // 状态定义 + // 创建一个宽为m,长为n + int[][] matrix = new int[m][n]; + + // 初始状态 + // 先将横坐标为0 和 纵坐标为0的所有表格都填上, 走法都是一种. + // 横坐标为0的只能从左往右横着走. 纵坐标为0的只能从上往下竖着走 + for (int i = 0; i < m; i++) { + matrix[i][0] = 1; + } + for (int j = 0; j < n; j++) { + matrix[0][j] = 1; + } + + // 状态推导 + // 从横纵坐标不为0的格子开始推倒 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + matrix[i][j] = matrix[i - 1][j] + matrix[i][j - 1]; + } + } + + // 得出结果 + //返回走后一个格子的走法数量, 由于数组是从0开始,所以实际最大索引为长度-1 + return matrix[m - 1][n - 1]; + } + + /** + * 由于计算一个坐标元素的值只需要用到它的下方和右边元素的值即可. 所以我们可以使用两个一维数组来存当前这一列和相邻的右边这一列即可 + *

+ * 针对uniquePaths()方法的优化, 使用2个一维数组来节省空间, 空间复杂度变为O(2n), 时间复杂度为O(m*n) + *

+ * https://leetcode-cn.com/problems/unique-paths/solution/dong-tai-gui-hua-by-powcai-2/ + * + * @param m + * @param n + * @return + */ + private static int uniquePaths2(int m, int n) { + int[] pre = new int[n]; + int[] cur = new int[n]; + Arrays.fill(pre, 1); //将1存在数组的每个元素位置上 + Arrays.fill(cur, 1); + // 从右往左遍历每列的元素 + for (int i = 1; i < m; i++) { + // 从下往上遍历每行的元素 + for (int j = 1; j < n; j++) { + // 当前列的元素等于 相邻下方元素 + 右边元素 + cur[j] = cur[j - 1] + pre[j]; + System.out.println(String.format("j=%d", j)); + System.out.println(String.format("cur[j]=%d, cur[j-1]=%d, pre[j]=%d", cur[j], cur[j - 1], pre[j])); + } + // 一轮循环后,将当前这一列数组赋给右边这一列,便于下一轮循环使用 + pre = cur; + } + return pre[n - 1]; + } + + private static int uniquePaths2_2(int m, int n) { + // 定义状态 + int[] pre = new int[n]; + int[] cur = new int[n]; + + // 初始状态 + //将第1列和第2列用1进行填充 + Arrays.fill(pre, 1); + Arrays.fill(cur, 1); + + // 状态推导 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + // 某一个格子的走法等于下方和右方走法之和 + cur[j] = cur[j - 1] + pre[j]; + } + pre = cur; + } + + // 得出结果 + // 返回最后一列最后一行的元素 走法数 + return pre[n - 1]; + } + + /** + * 针对两列数组进行优化,将两列数组合并为一列数组,不断的使用 新值 = 上一列新值 + 旧值(右侧) 可以进一步优化为 新值 += 旧值(右侧) + * + * 由于只用了一个数组, 空间复杂度变成了O(n), 时间复杂度没变,依旧为O(m*n) + * + * @param m + * @param n + * @return + */ + private static int uniquePaths3(int m, int n) { + int[] cur = new int[n]; + Arrays.fill(cur,1); + for (int i = 1; i < m;i++){ + for (int j = 1; j < n; j++){ + // 充分利用每一列开始的时候下方格子都为1 + 上一列右边的值 = 当前值 = 1 + cur[j-1] = a + // 然后下一列 = 当前值 + 上一列右边的值 a + cur[j] = b + // 接着再下一列 = 当前值 + 上一列右边的值 b + cur[j+1] = c + //cur[j] = cur[j] + cur[j-1]; + cur[j] += cur[j-1]; + System.out.println("cur[j] = " + cur[j]); + } + } + return cur[n-1]; + } +} diff --git a/src/main/java/com/study/dynamicprogramming/CountPathMatrix2.java b/src/main/java/com/study/dynamicprogramming/CountPathMatrix2.java new file mode 100644 index 0000000..3cf740a --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/CountPathMatrix2.java @@ -0,0 +1,122 @@ +package com.study.dynamicprogramming; + +/** + * 求二维矩阵的走法2(石头篇) + *

+ * 1个机器人位于1个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 + *

+ * 机器人每次只能向下或者向右移动1步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 + *

+ * 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? + * 网格中的障碍物和空位置分别用 1 和 0 来表示。 + *

+ * 说明:m 和 n 的值均不超过 100。 + *

+ * 示例 1: + *

+ * 输入: + * [ + *   [0,0,0], + *   [0,1,0], + *   [0,0,0] + * ] + * 输出: 2 + * 解释: + * 3x3 网格的正中间有1个障碍物。 + * 从左上角到右下角1共有 2 条不同的路径: + * 1. 向右 -> 向右 -> 向下 -> 向下 + * 2. 向下 -> 向下 -> 向右 -> 向右 + *

+ * 算法推导: + * if(dp[m][n] == 1) + * dp[m][n] = 0 + * else + * dp[m][n] = dp[m-1][n] + dp[m][n-1] + *

+ * https://leetcode-cn.com/problems/unique-paths-ii/ + */ +public class CountPathMatrix2 { + + public static void main(String[] args) { + + // 3行3列矩阵 1为石头 + int[][] obstacleGrid = new int[3][3]; + obstacleGrid[0] = new int[]{0, 0, 0}; + obstacleGrid[1] = new int[]{0, 1, 0}; + obstacleGrid[2] = new int[]{0, 0, 0}; + + System.out.println(uniquePathsWithObstacles(obstacleGrid)); + + + // 5行4列矩阵 1为石头 + obstacleGrid = new int[5][4]; + obstacleGrid[0] = new int[]{0, 0, 0, 0}; + obstacleGrid[1] = new int[]{0, 1, 0, 0}; + obstacleGrid[2] = new int[]{0, 0, 0, 0}; + obstacleGrid[3] = new int[]{0, 0, 0, 0}; + obstacleGrid[4] = new int[]{0, 0, 1, 0}; + + System.out.println(uniquePathsWithObstacles(obstacleGrid)); + } + + /** + * @return + */ + public static int uniquePathsWithObstacles(int[][] obstacleGrid) { + + int r = obstacleGrid.length; + int c = obstacleGrid[0].length; + + // 终点堵住了 走法为0 + if (obstacleGrid[0][0] == 1) + return 0; + + // 对网格进行填充, 第1行和第1列都填为1 + // 对第1行进行填充 + for (int i = 0; i < r; i++) { + // 如果第1行上有石头, 需要将该行后面的元素全部改为0,因为石头阻挡了道路,走法都为0 + if (obstacleGrid[i][0] == 1) { + for (; i < r; i++) { + obstacleGrid[i][0] = 0; + } + } else { + // 没有石头影响的地方 走法为1 + obstacleGrid[i][0] = 1; + } + } + + // 对第1列进行填充, 从第1列的第2行开始, 因为第1列第1行和第1行重复了 + for (int j = 1; j < c; j++) { + // 如果第1列上有石头, 需要对该列以上的元素全部该为0,因为石头阻挡了道路,走法为0 + if (obstacleGrid[0][j] == 1) { + for (; j < c; j++) { + obstacleGrid[0][j] = 0; + } + } else { + // 没有石头影响的地方 走法为1 + obstacleGrid[0][j] = 1; + } + } + + // 计算走法, 从第2行第2列开始 + for (int i = 1; i < r; i++) { + for (int j = 1; j < c; j++) { + // 石头位置上的走法为0 + if (obstacleGrid[i][j] == 1) + obstacleGrid[i][j] = 0; + else// 没石头的位置走法 = 相邻下面 + 相邻右边 + obstacleGrid[i][j] = obstacleGrid[i][j - 1] + obstacleGrid[i - 1][j]; + } + } + + return obstacleGrid[r - 1][c - 1]; + } + + /** + * @param obstacleGrid + * @return + */ +// private static int countPath2(int[][] obstacleGrid) { +// +// } +} diff --git a/src/main/java/com/study/dynamicprogramming/FibonacciSequence.java b/src/main/java/com/study/dynamicprogramming/FibonacciSequence.java new file mode 100644 index 0000000..7f63385 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/FibonacciSequence.java @@ -0,0 +1,213 @@ +package com.study.dynamicprogramming; + +/** + * 斐波拉契 + * 斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”, + * 指的是这样一个数列:1、1、2、3、5、8、13、21、34、…… + *

+ * 临界值: 第1和2个数 + * + *

+ * https://www.jianshu.com/p/25ca6afa2fa4 + * https://www.cnblogs.com/swfpt/p/6850396.html + */ +public class FibonacciSequence { + + public static void main(String[] args) { +// int n = 1; +// int[] cache = new int[n]; +// //System.out.println(String.format("第%d个数为: %d", n, fibByRecursion(n)); +// System.out.println(String.format("第%d个数为: %d", n, fibByRecursion2(n, cache))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByLoop(n))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByDP(n))); +// +// n = 5; +// cache = new int[n]; +// //System.out.println(String.format("第%d个数为: %d", n, fibByRecursion(n)); +// System.out.println(String.format("第%d个数为: %d", n, fibByRecursion2(n, cache))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByLoop(n))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByDP(n))); +// +// n = 6; +// cache = new int[n]; +// //System.out.println(String.format("第%d个数为: %d", n, fibByRecursion(n)); +// System.out.println(String.format("第%d个数为: %d", n, fibByRecursion2(n, cache))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByLoop(n))); +// //System.out.println(String.format("第%d个数为: %d", n, fibByDP(n))); + + + // 计算双递归的时间复杂度问题 时间复杂度为O(2^n) + int n = 2; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 5; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 10; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 15; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 20; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 30; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + n = 40; + fibByRecursion_count(n); + System.out.println(String.format("n: %d, 计算次数: %d",n, count)); + count = 0; + + // 由于递归的执行次数是指数级上涨, 当n为50的时候, 机器就算不出来了 +// n = 50; +// fibByRecursion_count(n); +// System.out.println(String.format("n: %d, 计算次数: %d",n, count)); +// count = 0; + } + + /** + * 计算递归的执行次数 + */ + private static int count = 0; + + /** + * 求第n个数的值, 使用分治的方法 使用两个递归分别求出n-1和n-2两数的值,然后进行相加得到第n个数的值 + *

+ * 缺点: 当n比较大时递归非常慢,因为递归过程中存在很多重复计算。 + *

+ * 时间复杂度为O(2^n) + * + * @param n + * @return + */ + private static int fibByRecursion(int n) { + if (n == 0) + return 0; + + count++; + + // 斐波拉契的第1和第2个数为1 + // 临界条件 n = 1 和 n=2 + if (n == 1 || n == 2) { + System.out.println(String.format("往内递归结束:第%d个数", n)); + return 1; + } + System.out.println(String.format("往内递归,第%d个数", n)); + int result = fibByRecursion(n - 1) + fibByRecursion(n - 2); //第n个数为前两个数之后 比如 5 = 3 +2 + System.out.println(String.format("往外递归计算开始: 第%d个数为%d", n, result)); + return result; + } + + private static int fibByRecursion_count(int n) { + count++; + + if (n == 0) + return 0; + + // 斐波拉契的第1和第2个数为1 + // 临界条件 n = 1 和 n=2 + if (n == 1 || n == 2) { + return 1; + } + return fibByRecursion_count(n - 1) + fibByRecursion_count(n - 2); //第n个数为前两个数之后 比如 5 = 3 +2; + } + + + /** + * 由于递归过程中计算了大量元素, 我们可以使用一个数组cache来对其进行优化 + * + * @param n + * @return + */ + private static int fibByRecursion2(int n, int[] cache) { + if (n == 0) + return 0; + + // 斐波拉契的第1和第2个数为1 + // 临界条件 n = 1 和 n=2 + if (n == 1 || n == 2) { + System.out.println(String.format("往内递归结束:第%d个数", n)); + return 1; + } + + // 如果缓存中不存在,则将计算结果加入到缓存,如果存在,则直接使用缓存 + if (cache[n - 1] == 0) { + System.out.println(String.format("往内递归,第%d个数", n)); + cache[n - 1] = fibByRecursion(n - 1) + fibByRecursion(n - 2); //第n个数为前两个数之后 比如 5 = 3 +2 + } + + System.out.println(String.format("往外递归计算开始: 第%d个数为%d", n, cache[n-1])); + return cache[n - 1]; + } + + /** + * 使用迭代的方式, 保存之前计算的结果, 用空间换时间 + *

+ * 时间复杂度为 O(n), 相比递归要快很多 + * + * @return + */ + private static int fibByLoop(int n) { + + if (n == 0) + return 0; + + if (n == 1 || n == 2) + return 1; + + int f1 = 1; + int f2 = 1; + // 1 1 2 3 5 8 ... + // 从第3个元素开始计算,因为前两个元素值已知 + for (int i = 3; i <= n; i++) { + int f3 = f1 + f2; // 记录当前两个元素之和,为下轮的第2个元素 + // 每一次循环都将当前两个元素往后移动一位 + f1 = f2;// 下轮的第1个元素为本轮的第2个元素 + f2 = f3; // 下轮的第2个元素为本轮的第3个元素 + System.out.println(String.format("fibByLoop:第%d个数为%d", i, f2)); + } + return f2; + } + + /** + * 动态规划(递归+记忆化(使用数组来存变量)) + * 使用斐波拉契来计算动态规划, 使用一个数组来存储整个斐波拉契数列, 这样的做法可以节省大量中间变量的创建, 而且时间复杂度和循环一样 + *

+ * 时间复杂度为O(n), 空间复杂度为O(n) + * + * @return + */ + private static int fibByDP(int n) { + if (n == 0) + return 0; + + if (n == 1 || n == 2) + return 1; + + int[] arr = new int[n]; + + arr[0] = 1; + arr[1] = 1; + + for (int i = 2; i < n; i++) { + arr[i] = arr[i - 1] + arr[i - 2]; + System.out.println(String.format("fibByDP:第%d个数为%d", i + 1, arr[i])); + } + + return arr[n - 1]; + } +} diff --git a/src/main/java/com/study/dynamicprogramming/LongestCommonSubsequence.java b/src/main/java/com/study/dynamicprogramming/LongestCommonSubsequence.java new file mode 100644 index 0000000..58ddb60 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/LongestCommonSubsequence.java @@ -0,0 +1,139 @@ +package com.study.dynamicprogramming; + +/** + * 最长公共子序列 + *

+ * 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列。 + *

+ * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 + * 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 + *

+ * 若这两个字符串没有公共子序列,则返回 0。 + *

+ * 示例 1: + *

+ * 输入:text1 = "abcde", text2 = "ace" + * 输出:3 + * 解释:最长公共子序列是 "ace",它的长度为 3。 + * 示例 2: + *

+ * 输入:text1 = "abc", text2 = "abc" + * 输出:3 + * 解释:最长公共子序列是 "abc",它的长度为 3。 + * 示例 3: + *

+ * 输入:text1 = "abc", text2 = "def" + * 输出:0 + * 解释:两个字符串没有公共子序列,返回 0。 + *

+ * 链接:https://leetcode-cn.com/problems/longest-common-subsequence + */ +public class LongestCommonSubsequence { + + public static void main(String[] args) { + String text1 = "abc"; + String text2 = "def"; + +// String text1 = "abcde"; +// String text2 = "ace"; + +// String text1 = "oxcpqrsvwf"; +// String text2 = "shmtulqrypy"; + int result = longestCommonSubsequence(text1, text2); + System.out.println(result); + } + + /** + * 使用动态规划 dp方程来解 + * 具体操作: 将两个字符串x和y转到一个二维数组操做. + * 第一步: 定义basecase: 由于dp方程需要从0开始,让索引为0的长和宽的列表示空字符串,所以数组的长i和宽j为字符串长度+1. + * dp[0][i]和dp[j][0]都应该初始化为0. + * 比如 dp[0][3] = 0 的含义是, 对于字符串""和"abc", 它们的最长公共子序列lcs为0 + * 第二步: 找状态转移dp方程: + * i=0或者j=0 LCS(i,j)=0 //如果两个字符中有有一个为空, 那LCS=0 + * i>0且j>0 且Xi=Yj LCS(i,j)=LCS(i-1,j-1) + 1 // 如果两个字符相等, 那么LCS为其相邻左斜方值+1 + * i>0且j>0 且Xi!=Yj LCS(i,j)=MAX{(LCS(i-1,j),LCS(i,j-1)} //如果连个字符不相等, 那么LCS为其相邻的左和上方值中最大的一个 + * 就这样不断的将二维数组上各个位置上的值填满. + * 最后取数组右下角最后一个值为lcs + * + * @param text1 + * @param text2 + * @return + */ + public static int longestCommonSubsequence(String text1, String text2) { + // 创建一个dp二维数组, 长宽为两个字符串的长度+1 + int x = text1.length() + 1; + int y = text2.length() + 1; + int[][] dp = new int[x][y]; + // 从数组的索引为1位置开始, 是为了使用根据索引为0的位置(空字符串)的值进行推倒 + // 数组中长和宽为0的位置, 所有值默认为0 + for (int i = 1; i < x; i++) { + for (int j = 1; j < y; j++) { + // 如果字符text1[i] == text2[j], 那么对应的dp[i][j] = dp[i-1][j-1] + 1 相邻左斜方值+1 + if (text1.charAt(i - 1) == text2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + // 如果字符text1[i] != text2[j], 那么对应的dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 取相邻左和上值中最大的一个 + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + return dp[x - 1][y - 1]; + } + + + public static int longestCommonSubsequence_2(String text1, String text2) { + int x = text1.length(); + int y = text2.length(); + + int[][] dp = new int[x + 1][y + 1]; + for (int i = 1; i <= x; i++) { + for (int j = 1; j <= y; j++) { + if (text1.charAt(i-1) == text2.charAt(j-1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + return dp[x][y]; + } + + +// public static int longestCommonSubsequence(String text1, String text2) { +// int maxLength = 0; +// // 记录短的字符数组开始计算的位置 +// int shortPosition = 0; +// +// char[] chars1 = text1.toCharArray(); +// char[] chars2 = text2.toCharArray(); +// +// char[] longChars = new char[]{}; +// char[] shortChars = new char[]{}; +// +// if (chars1.length >= chars2.length) { +// longChars = chars1; +// shortChars = chars2; +// } else { +// longChars = chars2; +// shortChars = chars1; +// } +// +// int currentLength = 0; +// for (int i = 0; i < longChars.length; i++) { +// System.out.println("longChars: " + i); +// int j = shortPosition; +// while (j < shortChars.length) { +// if (longChars[i] == shortChars[j]) { +// currentLength++; +// shortPosition = j + 1; //修改下一次开始检查的位置 +// +// maxLength = Math.max(maxLength, currentLength); +// break; +// } +// j++; +// } +// } +// return maxLength; +// } +} diff --git a/src/main/java/com/study/dynamicprogramming/MaximumProductSubarray.java b/src/main/java/com/study/dynamicprogramming/MaximumProductSubarray.java new file mode 100644 index 0000000..e9f6bc4 --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/MaximumProductSubarray.java @@ -0,0 +1,96 @@ +package com.study.dynamicprogramming; + +/** + * 乘积最大子序列 + *

+ * 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 + *

+ * 示例 1: + *

+ * 输入: [2,3,-2,4] + * 输出: 6 + * 解释: 子数组 [2,3] 有最大乘积 6。 + * 示例 2: + *

+ * 输入: [-2,0,-1] + * 输出: 0 + * 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 + *

+ * 链接:https://leetcode-cn.com/problems/maximum-product-subarray + */ +public class MaximumProductSubarray { + + public static void main(String[] args) { + //int[] nums = {2, 3, -2, 4}; + //int[] nums = {-2, 0, -1}; + //int[] nums = {0, 2}; + int[] nums = {2, -5, -2, -3}; + System.out.println(maxProduct(nums)); + System.out.println(maxProduct2(nums)); + } + + /** + * 使用穷举法(暴力法), 获取所有元素的值 以及 所有连续元素的乘积, 求其中的最大值 + *

+ * 时间复杂度O(m*n) 空间复杂度O(1) + * + * @param nums + * @return + */ + private static int maxProduct(int[] nums) { + if (nums.length == 0) + return 0; + + int max = nums[0]; + for (int i = 0; i < nums.length; i++) { + // 最大乘积可以是单个元素 + int result = nums[i]; +// if (result > max) +// max = result; + max = Math.max(result, max); + + // 最大乘积可以是多个元素乘积 + // 每个元素为了确保不重复,只能乘以自己后面的元素 + // 乘以自己前面的元素结果跟前面的元素乘以自己是一样的,所以重复了 + int j = i + 1; + while (j < nums.length) { + result *= nums[j]; +// if (result > max) +// max = result; + max = Math.max(result, max); + j++; + } + } + return max; + } + + /** + * 标签:动态规划 + * 遍历数组时计算当前最大值,不断更新 + * 令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i]) + * 由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i]) + * 当负数出现时则imax与imin进行交换再进行下一步计算 + * 时间复杂度:O(n) + *

+ * 作者:guanpengchn + * 链接:https://leetcode-cn.com/problems/maximum-product-subarray/solution/hua-jie-suan-fa-152-cheng-ji-zui-da-zi-xu-lie-by-g/ + * + * @param nums + * @return + */ + private static int maxProduct2(int[] nums) { + int max = Integer.MIN_VALUE, imax = 1, imin = 1; + for (int i = 0; i < nums.length; i++) { + if (nums[i] < 0) { + int tmp = imax; + imax = imin; + imin = tmp; + } + imax = Math.max(imax * nums[i], nums[i]); + imin = Math.min(imin * nums[i], nums[i]); + + max = Math.max(max, imax); + } + return max; + } +} diff --git a/src/main/java/com/study/dynamicprogramming/Triangle.java b/src/main/java/com/study/dynamicprogramming/Triangle.java new file mode 100644 index 0000000..d01ea0b --- /dev/null +++ b/src/main/java/com/study/dynamicprogramming/Triangle.java @@ -0,0 +1,172 @@ +package com.study.dynamicprogramming; + +import java.util.*; + +/** + * 三角形最小路径和 + *

+ * 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 + *

+ * 例如,给定三角形: + *

+ * [ + * [2], + * [3,4], + * [6,5,7], + * [4,1,8,3] + * ] + * 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 + *

+ * 说明: + *

+ * 如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 + *

+ * 链接:https://leetcode-cn.com/problems/triangle + */ +public class Triangle { + + /** + * 这个题解法跟贪心不一样的是, 它要求全局最优值, 而不是当前最优值 + *

+ * 每个点[i][j] 只能访问下一行相邻的两个点 也就是[i+1][j] 和 [i+1][j+1] + * + * @param args + */ + public static void main(String[] args) { + List> triangle = new ArrayList<>(); + triangle.add(Arrays.asList(2)); + triangle.add(Arrays.asList(3, 4)); + triangle.add(Arrays.asList(6, 5, 7)); + triangle.add(Arrays.asList(4, 1, 8, 3)); + + //System.out.println(minimumTotal(triangle)); + //System.out.println(minimumTotal2(triangle)); + + System.out.println("最小路径和为: " + minimumTotal_dfs(triangle)); + } + + /** + * 动态规划法 + *

+ * 从后往前推, 修改每个点的值为其最小路径和, 一层层往上,最终顶点的最短路径 = 顶点值 + 第二层最短路径的那个点的值 + *

+ * 2 + * 3 4 + * 6 5 7 + * 4 1 8 3 + * 最终三角形被改成了 + * 11 + * 9 10 + * 7 6 10 + * 4 1 8 3 + * + *

+ * 时间复杂度为O(m*n) + *

+ * https://leetcode-cn.com/problems/triangle/solution/120-san-jiao-xing-zui-xiao-lu-jing-he-by-alexer-66/ + * + * @param triangle + * @return + */ + public static int minimumTotal(List> triangle) { + + print(triangle); + + if (triangle.size() == 0) + return 0; + + // 从后面往前推, i为倒数第二行, 下面的i+1则为倒数第一行 + // 每一层推完后得出当前层每个节点的最小路径和, 最终将顶点值+第二层最小路径和的那个点 = 最短路径和 + for (int i = triangle.size() - 2; i >= 0; i--) { + for (int j = 0; j < triangle.get(i).size(); j++) { + // 当前节点的最短路径path = 当前节点下一层两个节点中最短那个节点的路径值 + 当前节点的路径值 + int path = Math.min(triangle.get(i + 1).get(j), triangle.get(i + 1).get(j + 1)) + triangle.get(i).get(j); + triangle.get(i).set(j, path);//修改当前节点路径值为最短路径值 + } + } + + // 最终结果 + print(triangle); + return triangle.get(0).get(0); + } + + private static int minSum = Integer.MAX_VALUE; + + public static int minimumTotal_dfs(List> triangle) { + if (triangle.size() == 0 || triangle.get(0).size() == 0) { + return 0; + } + + dfs(triangle, 0, 0, 0, ""); + return minSum; + } + + /** + * 使用深度优先进行暴力求解, 得出所有不同的路径和, 然后取其中最小的 + * 类似二叉树的先序遍历 + * + * 时间复杂度为O(2^N) + * + * @param triangle + * @param i + * @param j + * @param sum + * @return + */ + private static void dfs(List> triangle, int i, int j, int sum, String path) { + // terminator 终止条件 + if (i == triangle.size() - 1) { + // 递归到达最后一行结束, 返回路径和 + sum += triangle.get(i).get(j); + System.out.println(sum); // 输出所有路径之和 + minSum = Math.min(sum, minSum); + + path += triangle.get(i).get(j); + System.out.println(path); // 输出所有路径 + + return; + } + + // 将每层路径之和相加 + sum += triangle.get(i).get(j); + path += triangle.get(i).get(j) + "->"; + + // 每个元素往下有两种方式可走, 一个是i+1,j 一个是i+1,j+1 + // 往每层正下方遍历 + dfs(triangle, i + 1, j, sum, path); + //System.out.println(); + // 往每层正下方+1遍历 + dfs(triangle, i + 1, j + 1, sum, path); + //System.out.println(); + } + + + public static int minimumTotal2(List> triangle) { + + print(triangle); + + if (triangle.size() == 0) { + return 0; + } + + // 遍历从倒数第2行开始 直到 最顶层元素 + for (int i = triangle.size() - 2; i >= 0; i--) { + for (int j = 0; j < triangle.get(i).size(); j++) { + int minPath = Math.min(triangle.get(i + 1).get(j), triangle.get(i + 1).get(j + 1)); + int pathSum = triangle.get(i).get(j) + minPath; + triangle.get(i).set(j, pathSum); + } + } + print(triangle); + return triangle.get(0).get(0); + } + + private static void print(List> triangle) { + for (int i = 0; i < triangle.size(); i++) { + for (int j = 0; j < triangle.get(i).size(); j++) { + System.out.print(triangle.get(i).get(j) + " "); + } + System.out.println(); + } + } +} diff --git a/src/main/java/com/study/hash/MyHashMapClient.java b/src/main/java/com/study/hash/MyHashMapClient.java new file mode 100644 index 0000000..2b22a68 --- /dev/null +++ b/src/main/java/com/study/hash/MyHashMapClient.java @@ -0,0 +1,75 @@ +package com.study.hash; + +/** + * 一个简单的hashmap, 主要用于key value存储, 查找,插入,删除的时间复杂度为O1 + */ +public class MyHashMapClient { + public static void main(String[] args) { + MyHashMap hashMap = new MyHashMap(10); + hashMap.put("aaa","jack"); + hashMap.put("bbb","mike"); + + System.out.println(hashMap.get("aaa"));// O1的时间复杂度 + } +} + +/** + * 一个简易的hashmap + * + * @param + * @param + */ +class MyHashMap { + + private int size; + private Node[] table; + + public MyHashMap(int size) { + this.size = size; + table = (Node[]) new Node[size]; + } + + public void put(K key, V value) { + int hash = hash(key); + table[hash] = new Node<>(hash, key, value, null); + } + + public V get(K key) { + return table[hash(key)].getValue(); + } + + private int hash(K key) { + int h = key.hashCode(); +// int v = h >>>16; +// System.out.println(h ^ v); + return h % this.size; + } + + static class Node { + final int hash; + final K key; + V value; + Node next; + + Node(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + public final K getKey() { + return key; + } + + public final V getValue() { + return value; + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + } +} diff --git a/src/main/java/com/study/linkedlist/DeleteRepeatedNode.java b/src/main/java/com/study/linkedlist/DeleteRepeatedNode.java new file mode 100644 index 0000000..3abae99 --- /dev/null +++ b/src/main/java/com/study/linkedlist/DeleteRepeatedNode.java @@ -0,0 +1,83 @@ +package com.study.linkedlist; + +import com.study.utils.LinkedListUtils; + +/** + * 删除排序链表中的重复元素 + * + * 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 + * + * 示例 1: + * + * 输入: 1->1->2 + * 输出: 1->2 + * 示例 2: + * + * 输入: 1->1->2->3->3 + * 输出: 1->2->3 + * + * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list + */ +public class DeleteRepeatedNode { + + public static void main(String[] args) { + LinkedNode node1 = new LinkedNode(1); + LinkedNode node2 = new LinkedNode(2); + LinkedNode node3 = new LinkedNode(2); + LinkedNode node4 = new LinkedNode(3); + LinkedNode node5 = new LinkedNode(3); + + node1.next = node2; + node2.next = node3; + node3.next = node4; + node4.next = node5; + + LinkedListUtils.printLinkedList(node1); + + //LinkedNode result = deleteDuplicates(node1); + LinkedNode result = deleteDuplicates2(node1); + LinkedListUtils.printLinkedList(result); + } + + /** + * 遍历链表, 如果当前节点和下一个节点值相同, 就把下一个节点的指针指向下下一个, + * 去重后再下轮检查一下当前节点是否和下一个节点相同,如果相同就继续指向下下一个去重, 当前节点相同的节点直到去重结束. + * 然后指向当前节点指向下一个节点继续遍历. + * + * 时间复杂度O(n), 空间复杂度O(1) + * + * @param head + * @return + */ + public static LinkedNode deleteDuplicates(LinkedNode head) { + LinkedNode cur = head; + // 遍历条件是当前节点和下一个节点不为空 + while(cur!=null && cur.next !=null){ + // 如果当前节点和下一个节点值相同, 就把下一个节点的指针指向下下一个 + // 2->2->3 + if (cur.val == cur.next.val){ + // 不断将指针指向下下个 进行去重, 直到去重结束 + // 2->2->3 变成了 2->3 //然后下一轮循环 判断 当前2不等于next3, 于是就执行cur = cur.next + cur.next = cur.next.next; // 将下一个指针指向下下一个, 这样下一轮遍历 当前值和下一个值可能就不再一样了,但也可能一样,那就再尝试下一个,直到不一样 + }else{ + cur = cur.next; // 如果当前和下一个节点不相同, 那就直接切换到下一个节点继续遍历 + } + } + + return head; + } + + public static LinkedNode deleteDuplicates2(LinkedNode head) { + LinkedNode cur = head; + while(cur!=null && cur.next!=null){ + if(cur.val == cur.next.val){ + cur.next = cur.next.next; // 不断移除与当前节点相同的重复节点 + }else{ + cur = cur.next;// 往当前节点的下一个节点遍历 + } + } + return head; + } +} + + diff --git a/src/main/java/com/study/linkedlist/DoubleLinkedList.java b/src/main/java/com/study/linkedlist/DoubleLinkedList.java new file mode 100644 index 0000000..fa4538b --- /dev/null +++ b/src/main/java/com/study/linkedlist/DoubleLinkedList.java @@ -0,0 +1,177 @@ +package com.study.linkedlist; + +import com.study.utils.LinkedListUtils; + +/** + * 双向升序链表 + *

+ * 每个节点都有直接前驱后直接后继, 除了第一个节点没有直接前驱和最后一个节点没有直接后继 + * 结构如下 + *

+ * 1 -> 2 -> 4 -> 5 -> 6 -> null + * null <- 1 <- 2 <- 4 <- 5 <- 6 + */ +public class DoubleLinkedList { + + public static void main(String[] args) { + //往 1 -> 2 -> 4 -> 5 -> 6 中插入 一个0或3 + // <- <- <- <- + DoubleLinkedNode node1 = new DoubleLinkedNode(1); + DoubleLinkedNode node2 = new DoubleLinkedNode(2); + DoubleLinkedNode node3 = new DoubleLinkedNode(3); + DoubleLinkedNode node4 = new DoubleLinkedNode(4); + DoubleLinkedNode node5 = new DoubleLinkedNode(5); + DoubleLinkedNode node6 = new DoubleLinkedNode(6); + + node1.next = node2; + node2.prev = node1; + node2.next = node3; + node3.prev = node2; + node3.next = node4; + node4.prev = node3; + node4.next = node5; + node5.prev = node4; + node5.next = node6; + node6.prev = node5; + +// printLinkedList(node1); +// +// Node head1 = insertNode(node1, 0); +// +// printLinkedList(head1); +// System.out.println(); +// +// Node head2 = insertNode(head1, 8); +// +// printLinkedList(head2); +// System.out.println(); +// Node head3 = insertNode(head2, 4); +// +// printLinkedList(head3); + + System.out.println("---------------------"); + DoubleLinkedNode head4 = deleteNode(node1, 3); + LinkedListUtils.printLinkedList(head4); + +// System.out.println(); +// head4 = deleteNode(head4, 0); +// printLinkedList(head4); +// +// System.out.println(); +// head4 = deleteNode(head4, 5); +// printLinkedList(head4); +// +// System.out.println(); +// head4 = deleteNode(head4, 8); +// printLinkedList(head4); + } + + + /** + * 插入一个新value并返回插入后的头节点 + *

+ * 新插入的节点可能是第一个,也可能是中间或者最后一个 + * + * @param head + * @param value + * @return + */ + private static DoubleLinkedNode insertNode(DoubleLinkedNode head, int value) { + DoubleLinkedNode newNode = new DoubleLinkedNode(value); + DoubleLinkedNode originalHead = head; + while (head != null) { + // 如果插入的节点比链表里的每个节点都小 + if (newNode.val < head.val) { + // 头节点 + // 如果head是头节点, 将新节点插在头节点的前面 + if (head.prev == null) { + newNode.next = head; + head.prev = newNode; + return newNode; + } else { + // 中间节点 + //如果当前head不是头节点,插入新节点的时候需要修改上一个节点的next,当前节点的pre和next, 以及下一个节点的pre, 4个指针 + //修改当前节点的上一个节点, 将上一个节点存一个临时变量 + DoubleLinkedNode prevNode = head.prev; + //将上一个节点的临时变量的后继指针指向新节点, 将新节点的前驱指针指向上一个节点的临时变量 + prevNode.next = newNode; + newNode.prev = prevNode; + // 将当前节点的前驱指向新节点, 新节点的后继指向当前节点 + head.prev = newNode; + newNode.next = head; + return originalHead; + } + } + // 末尾节点 + //如果新插入的节点比链表最后一个还要大 + if (head.next == null) { + //修改当前节点的后继和新节点的前驱 + head.next = newNode; + newNode.prev = head; + return originalHead; + } + head = head.next; + } + return originalHead; + } + + + /** + * 删除一个值对应的节点,并返回新的头节点 + *

+ * 该值对应的节点可能不存在, 也可能是头,中,尾部位的节点 + * + * @param head + * @param value + */ + private static DoubleLinkedNode deleteNode(DoubleLinkedNode head, int value) { + System.out.println("要删除的节点是" + value); + DoubleLinkedNode deleteNode = new DoubleLinkedNode(value); + DoubleLinkedNode originalHead = head; + while (head != null) { + // 如果要删除的节点存在 + if (head.val == deleteNode.val) { + // 如果删除的节点是头节点, 将头节点的next和下一个节点prev改为null + if (head.prev == null) { + DoubleLinkedNode secondNode = head.next; + secondNode.prev = null; + head.next = null; //这行代码也可以不写 因为我们返回的链表secondNode里面已经没有了head,所以head.next也可以不处理 + System.out.println("删除的节点为头节点"); + return secondNode; //返回新的头结点,第二个节点 + } else { + //删除的节点是中间节点和尾节点 + //如果删除的节点是尾节点 + if (head.next == null) { + // 将尾节点的prev和上一个节点的next改为null + DoubleLinkedNode prev = head.prev; + prev.next = null; + head.prev = null; //这行代码也可以不写 + System.out.println("删除的节点为尾节点"); + + } else { //如果删除的节点为中间的一个节点 + //将要删除的节点的上一个节点next改为要删除节点的下一个节点, 将要删除节点的下一个节点的prev改为它的上一个节点 + + //将当前节点的上一个和下一个节点拿出来存临时变量 + DoubleLinkedNode prev = head.prev; + DoubleLinkedNode next = head.next; + //将当前节点的前驱和后继改为null + //head.prev = head.next = null; //这行代码也可以不写 + //将上一个节点的next指向下一个节点 + prev.next = next; + //将下一个节点的prev指向上一个节点 + next.prev = prev; + + System.out.println("删除的节点为中间节点"); + } + return originalHead; //返回原来的头结点 + } + } + head = head.next; + } + System.out.println("要删除的节点不存在"); + // 如果删除的节点不存在 + return originalHead; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/study/linkedlist/DoubleLinkedNode.java b/src/main/java/com/study/linkedlist/DoubleLinkedNode.java index ce5c31f..6f78d54 100644 --- a/src/main/java/com/study/linkedlist/DoubleLinkedNode.java +++ b/src/main/java/com/study/linkedlist/DoubleLinkedNode.java @@ -1,207 +1,11 @@ package com.study.linkedlist; -/** - * 双向升序链表 - *

- * 每个节点都有直接前驱后直接后继, 除了第一个节点没有直接前驱和最后一个节点没有直接后继 - * 结构如下 - *

- * 1 -> 2 -> 4 -> 5 -> 6 -> null - * null <- 1 <- 2 <- 4 <- 5 <- 6 - */ public class DoubleLinkedNode { + public int val; + public DoubleLinkedNode prev; + public DoubleLinkedNode next; - public static void main(String[] args) { - //往 1 -> 2 -> 4 -> 5 -> 6 中插入 一个0或3 - // <- <- <- <- - Node node1 = new Node(1); - Node node2 = new Node(2); - Node node3 = new Node(3); - Node node4 = new Node(5); - Node node5 = new Node(6); - Node node6 = new Node(7); - - node1.next = node2; - node2.prev = node1; - node2.next = node3; - node3.prev = node2; - node3.next = node4; - node4.prev = node3; - node4.next = node5; - node5.prev = node4; - node5.next = node6; - node6.prev = node5; - - printLinkedList(node1); - - Node head1 = insertNode(node1, 0); - - printLinkedList(head1); - System.out.println(); - - Node head2 = insertNode(head1, 8); - - printLinkedList(head2); - System.out.println(); - Node head3 = insertNode(head2, 4); - - printLinkedList(head3); - - System.out.println("---------------------"); - Node head4 = deleteNode(head3, 9); - printLinkedList(head4); - - System.out.println(); - head4 = deleteNode(head4, 0); - printLinkedList(head4); - - System.out.println(); - head4 = deleteNode(head4, 5); - printLinkedList(head4); - - System.out.println(); - head4 = deleteNode(head4, 8); - printLinkedList(head4); - } - - - /** - * 插入一个新value并返回插入后的头节点 - *

- * 新插入的节点可能是第一个,也可能是中间或者最后一个 - * - * @param head - * @param value - * @return - */ - private static Node insertNode(Node head, int value) { - Node newNode = new Node(value); - Node originalHead = head; - while (head != null) { - // 如果插入的节点比链表里的每个节点都小 - if (newNode.value < head.value) { - // 如果head是头节点, 将新节点插在头节点的前面 - if (head.prev == null) { - newNode.next = head; - head.prev = newNode; - return newNode; - } else { - //如果当前head不是头节点,插入新节点的时候需要修改上一个节点的next,当前节点的pre和下一个节点的pre,当前节点的next 4个指针 - //修改当前节点的上一个节点, 将上一个节点存一个临时变量 - Node prevNode = head.prev; - //将上一个节点的临时变量的后继指针指向新节点, 将新节点的前驱指针指向上一个节点的临时变量 - prevNode.next = newNode; - newNode.prev = prevNode; - // 将当前节点的前驱指向新节点, 新节点的后继指向当前节点 - head.prev = newNode; - newNode.next = head; - return originalHead; - } - } - //如果新插入的节点比链表最后一个还要大 - if (head.next == null) { - //修改当前节点的后继和新节点的前驱 - head.next = newNode; - newNode.prev = head; - return originalHead; - } - head = head.next; - } - return originalHead; - } - - - /** - * 删除一个值对应的节点,并返回新的头节点 - *

- * 该值对应的节点可能不存在, 也可能是头,中,尾部位的节点 - * - * @param head - * @param value - */ - private static Node deleteNode(Node head, int value) { - System.out.println("要删除的节点是" + value); - Node deleteNode = new Node(value); - Node originalHead = head; - while (head != null) { - // 如果要删除的节点存在 - if (head.value == deleteNode.value) { - // 如果删除的节点是头节点, 将头节点的next和下一个节点prev改为null - if (head.prev == null) { - Node secondNode = head.next; - secondNode.prev = null; - head.next = null; //这行代码也可以不写 因为我们返回的链表secondNode里面已经没有了head,所以head.next也可以不处理 - System.out.println("删除的节点为头节点"); - return secondNode; //返回新的头结点,第二个节点 - } else { - //删除的节点是中间节点和尾节点 - //如果删除的节点是尾节点 - if (head.next == null) { - // 将尾节点的prev和上一个节点的next改为null - Node prev = head.prev; - prev.next = null; - head.prev = null; //这行代码也可以不写 - System.out.println("删除的节点为尾节点"); - - } else { //如果删除的节点为中间的一个节点 - //将要删除的节点的上一个节点next改为要删除节点的下一个节点, 将要删除节点的下一个节点的prev改为它的上一个节点 - - //将当前节点的上一个和下一个节点拿出来存临时变量 - Node prev = head.prev; - Node next = head.next; - //将当前节点的前驱和后继改为null - head.prev = head.next = null; //这行代码也可以不写 - //将上一个节点的next指向下一个节点 - prev.next = next; - //将下一个节点的prev指向上一个节点 - next.prev = prev; - - System.out.println("删除的节点为中间节点"); - } - return originalHead; //返回原来的头结点 - } - } - head = head.next; - } - System.out.println("要删除的节点不存在"); - // 如果删除的节点不存在 - return originalHead; - } - - - private static void printLinkedList(Node head) { - - Node originalHead = head; - while (head != null) { - if (head.next == null) { - System.out.printf("%d->NULL", head.value); - } else { - System.out.printf("%d->", head.value); - } - head = head.next; - } - - System.out.println(); - - while (originalHead != null) { - if (originalHead.prev == null) { - System.out.printf("NULL<-%d", originalHead.value); - } else { - System.out.printf("<-%d", originalHead.value); - } - originalHead = originalHead.next; - } - - System.out.println(); + public DoubleLinkedNode(int val) { + this.val = val; } } - - -class Node { - public int value; - public Node prev, next; - - public Node(int value) { - this.value = value; - } -} \ No newline at end of file diff --git a/src/main/java/com/study/linkedlist/FindMiddleOne.java b/src/main/java/com/study/linkedlist/FindMiddleOne.java new file mode 100644 index 0000000..ed19985 --- /dev/null +++ b/src/main/java/com/study/linkedlist/FindMiddleOne.java @@ -0,0 +1,159 @@ +package com.study.linkedlist; + +import com.study.utils.LinkedListUtils; + +/** + * 找到链表中间的元素, + *

+ * 对于单链表可以使用使用快慢双指针,快指针走完,慢指针所在的位置即是中间. + * 对于双链表,可以使用快慢双指针, 如果知道头尾两个节点, 可以使用前后两个指针以相同的速度往中间走. + * + *

+ * https://blog.csdn.net/lihui930310560/article/details/53319367/ + */ +public class FindMiddleOne { + + public static void main(String[] args) { + + //int length = 8; + int length = 9; + + // 创建一个长度为20的 单向链表 + LinkedNode head = new LinkedNode(1); + LinkedNode cur = head; + + int i = 2; + while (i <= length) { + cur.next = new LinkedNode(i); + cur = cur.next; + i++; + } + + LinkedListUtils.printLinkedList(head); + + // 找出单向链表中间的元素 + System.out.println(findMiddle(head)); + + //创建一个双向链表 + DoubleLinkedNode head2 = new DoubleLinkedNode(1); + + DoubleLinkedNode cur2 = head2; + i = 2; + while (i <= length) { + DoubleLinkedNode nextNode = new DoubleLinkedNode(i); + cur2.next = nextNode; + nextNode.prev = cur2; + + cur2 = cur2.next; + i++; + } + + LinkedListUtils.printLinkedList(head2); + // 找出双向链表中间的元素 + System.out.println(findMiddle(head2)); + + // 通过头尾节点找出中间元素 + System.out.println(findMiddle(head2, cur2)); + } + + /** + * 找到单向链表的中间节点 + * 可以使用一个快慢指针, 一个走一步,一个走两步 + * + * @param head + */ + private static int findMiddle(LinkedNode head) { + if (head == null) + return -1; + + // 默认都已经走了1步 + LinkedNode slow = head; + LinkedNode fast = head; + + while (fast != null && slow != null && fast.next != null) { + + slow = slow.next; + fast = fast.next.next; + + // 如果快指针刚好走完或者差半步走完,结束旅程 + if (fast.next == null || fast.next.next == null) + break; + } + + // 由于大家都是从第一个节点开始 1 + 2 * n + // 如果快指针的下一个节点为空,说明链表长度为奇数,则当前慢指针的值为中间值 + if (fast.next == null) + return slow.val; + + // 如果快指针差一步走完, 链表长度为偶数, 则当前慢指针的值也是中间值 + if (fast.next.next == null) + return slow.val; + + return -1; + } + + /** + * 找到双向链表中间的元素, 使用快慢双指针 + * + * @param head + * @return + */ + private static int findMiddle(DoubleLinkedNode head) { + if (head == null) + return -1; + + // 默认都已经走了1步 + DoubleLinkedNode slow = head; + DoubleLinkedNode fast = head; + + while (fast != null && slow != null && fast.next != null) { + + slow = slow.next; + fast = fast.next.next; + + // 如果快指针刚好走完或者差半步走完,结束旅程 + if (fast.next == null || fast.next.next == null) + break; + } + + // 由于大家都是从第一个节点开始 1 + 2 * n + // 如果快指针的下一个节点为空,说明链表长度为奇数,则当前慢指针的值为中间值 + if (fast.next == null) + return slow.val; + + // 如果快指针差一步走完, 链表长度为偶数, 则当前慢指针的值也是中间值 + if (fast.next.next == null) + return slow.val; + + return -1; + } + + /** + * 使用前后双指针, 一个从头开始走, 一个从尾开始走, 走到相碰的地方就是 + * + * @param head + * @return + */ + private static int findMiddle(DoubleLinkedNode head, DoubleLinkedNode tail) { + + DoubleLinkedNode newHead = head; + DoubleLinkedNode newTail = tail; + + int middle = -1; + + while (newHead != null && newTail != null) { + + // 链表长度为奇数的情况下,两个指针会走在同一个节点上,则该节点就是中间节点 + // 链表长度为偶数的情况下,头指针所在的节点为中间点 + if (newHead == newTail || newHead.next == newTail) { + middle = newHead.val; + break; + } + + newHead = newHead.next;// 头节点往后走 + newTail = newTail.prev;// 尾节点往前走 + } + + return middle; + } +} diff --git a/src/main/java/com/study/linkedlist/LinkedListCycle.java b/src/main/java/com/study/linkedlist/LinkedListCycle.java index c493209..bb1b007 100644 --- a/src/main/java/com/study/linkedlist/LinkedListCycle.java +++ b/src/main/java/com/study/linkedlist/LinkedListCycle.java @@ -1,7 +1,5 @@ package com.study.linkedlist; -import com.study.utils.Printer; - import java.util.HashSet; import java.util.Set; @@ -22,11 +20,11 @@ */ public class LinkedListCycle { public static void main(String[] args) { - ListNode node1 = new ListNode(1); - ListNode node2 = new ListNode(2); - ListNode node3 = new ListNode(3); - ListNode node4 = new ListNode(4); - ListNode node5 = new ListNode(5); + LinkedNode node1 = new LinkedNode(1); + LinkedNode node2 = new LinkedNode(2); + LinkedNode node3 = new LinkedNode(3); + LinkedNode node4 = new LinkedNode(4); + LinkedNode node5 = new LinkedNode(5); node1.next = node2; node2.next = node3; @@ -48,10 +46,10 @@ public static void main(String[] args) { * @param head * @return */ - private static boolean hasCycle(ListNode head) { - ListNode fast = head; - ListNode slow = head; - while (fast != null && slow != null && fast.next != null) { + private static boolean hasCycle(LinkedNode head) { + LinkedNode fast = head; + LinkedNode slow = head; + while (fast != null && slow != null && fast.next != null) {// 因为快指针需要走两步,如果fast.next为null,那走第二步就报空指针了 // 注意因为起始位置都一样,都是head,所以需要让两个指针都先跑起来,之后再相遇 slow = slow.next; fast = fast.next.next; @@ -73,8 +71,8 @@ private static boolean hasCycle(ListNode head) { * @param head * @return */ - private static boolean hasCycle2(ListNode head) { - Set set = new HashSet(); + private static boolean hasCycle2(LinkedNode head) { + Set set = new HashSet(); while (head != null) { if (set.contains(head)) { @@ -92,7 +90,7 @@ private static boolean hasCycle2(ListNode head) { * @param head * @return */ - private static boolean hasCycle3(ListNode head) { + private static boolean hasCycle3(LinkedNode head) { long start = System.currentTimeMillis(); while (head != null) { head = head.next; diff --git a/src/main/java/com/study/linkedlist/LinkedListCycleTwo.java b/src/main/java/com/study/linkedlist/LinkedListCycleTwo.java index 22b7d09..3cb2289 100644 --- a/src/main/java/com/study/linkedlist/LinkedListCycleTwo.java +++ b/src/main/java/com/study/linkedlist/LinkedListCycleTwo.java @@ -41,11 +41,11 @@ */ public class LinkedListCycleTwo { public static void main(String[] args) { - ListNode node1 = new ListNode(1); - ListNode node2 = new ListNode(2); - ListNode node3 = new ListNode(3); - ListNode node4 = new ListNode(4); - ListNode node5 = new ListNode(5); + LinkedNode node1 = new LinkedNode(1); + LinkedNode node2 = new LinkedNode(2); + LinkedNode node3 = new LinkedNode(3); + LinkedNode node4 = new LinkedNode(4); + LinkedNode node5 = new LinkedNode(5); node1.next = node2; node2.next = node3; @@ -53,7 +53,7 @@ public static void main(String[] args) { node4.next = node5; // 节点5之后又回到了节点3 node5.next = node3; - ListNode cycyleBegins = detectCycle(node1); + LinkedNode cycyleBegins = detectCycle(node1); //ListNode cycyleBegins = detectCycle2(node1); //ListNode cycyleBegins = detectCycle3(node1); System.out.println(cycyleBegins.val); @@ -83,12 +83,12 @@ public static void main(String[] args) { * @param head * @return */ - private static ListNode detectCycle(ListNode head) { + private static LinkedNode detectCycle(LinkedNode head) { if (head == null || head.next == null) return null; - ListNode slow = head; - ListNode fast = head; - ListNode start = head; + LinkedNode slow = head; + LinkedNode fast = head; + LinkedNode start = head; boolean isCycle = false; // 使用快慢节点,找出是否为有还链表 @@ -122,11 +122,11 @@ private static ListNode detectCycle(ListNode head) { * @param head * @return */ - private static ListNode detectCycle2(ListNode head) { - ListNode fast = head; - ListNode slow = head; + private static LinkedNode detectCycle2(LinkedNode head) { + LinkedNode fast = head; + LinkedNode slow = head; - ListNode start = head; + LinkedNode start = head; while (fast != null && slow != null && fast.next != null) { fast = fast.next.next; slow = slow.next; @@ -150,8 +150,8 @@ private static ListNode detectCycle2(ListNode head) { *

* 存储前判断是否已存在该节点,存在则返回该节点. */ - private static ListNode detectCycle3(ListNode head) { - Set set = new HashSet(); + private static LinkedNode detectCycle3(LinkedNode head) { + Set set = new HashSet(); while (head != null) { if (set.contains(head)) { diff --git a/src/main/java/com/study/linkedlist/LinkedNode.java b/src/main/java/com/study/linkedlist/LinkedNode.java new file mode 100644 index 0000000..0d6860f --- /dev/null +++ b/src/main/java/com/study/linkedlist/LinkedNode.java @@ -0,0 +1,10 @@ +package com.study.linkedlist; + +public class LinkedNode { + public int val; + public LinkedNode next; + + public LinkedNode(int x) { + val = x; + } +} diff --git a/src/main/java/com/study/linkedlist/ListNode.java b/src/main/java/com/study/linkedlist/ListNode.java deleted file mode 100644 index 15de10a..0000000 --- a/src/main/java/com/study/linkedlist/ListNode.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.study.linkedlist; - -public class ListNode { - public int val; - public ListNode next; - - public ListNode(int x) { - val = x; - } -} diff --git a/src/main/java/com/study/linkedlist/MergeTwoLists.java b/src/main/java/com/study/linkedlist/MergeTwoLists.java new file mode 100644 index 0000000..4e34126 --- /dev/null +++ b/src/main/java/com/study/linkedlist/MergeTwoLists.java @@ -0,0 +1,159 @@ +package com.study.linkedlist; + +import com.study.utils.LinkedListUtils; + +/** + * 合并两个有序链表 + * 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  + *

+ * 示例: + *

+ * 输入:1->2->4, 1->3->4 + * 输出:1->1->2->3->4->4 + *

+ * https://leetcode-cn.com/problems/merge-two-sorted-lists/ + *

+ * https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/hua-jie-suan-fa-21-he-bing-liang-ge-you-xu-lian-bi/ + */ +public class MergeTwoLists { + + public static void main(String[] args) { + LinkedNode l1 = new LinkedNode(1); + l1.next = new LinkedNode(2); + l1.next.next = new LinkedNode(4); + + LinkedNode l2 = new LinkedNode(1); + l2.next = new LinkedNode(3); + l2.next.next = new LinkedNode(4); + l2.next.next.next = new LinkedNode(6); + + System.out.println("l1链表"); + LinkedListUtils.printLinkedList(l1); + System.out.println("l2链表"); + LinkedListUtils.printLinkedList(l2); + + // ListNode result = mergeTwoLists(l1, l2); + // ListNode result = mergeTwoListsByRecursion(l1,l2); + LinkedNode result = mergeTwoListsByRecursion2(l1, l2); + LinkedListUtils.printLinkedList(result); + } + + /** + * 创建一个新链表 + * 迭代的方式将两个链表中的小值加入到一个新链表中, 迭代完后需要把没有处理完的列表加到新链表尾部. 时间复杂度O(m+n) + *

+ * 时间复杂度 O(m+n) m和n各位两个链表的长度 + * + * @param l1 + * @param l2 + * @return + */ + public static LinkedNode mergeTwoLists(LinkedNode l1, LinkedNode l2) { + LinkedNode prev = new LinkedNode(-1); + LinkedNode head = prev; + + // 先将l1和l2中小的元素依次放入新链表中 + while (l1 != null && l2 != null) { + // 将小的加入到链表前面 + if (l1.val > l2.val) { + head.next = new LinkedNode(l2.val); + l2 = l2.next; + } else { + head.next = new LinkedNode(l1.val); + l1 = l1.next; + } + LinkedListUtils.printLinkedList(prev.next); + head = head.next; + } + + // 由于两个链表长度和节点值原因, 可能存在某个链表没有遍历完成 + + // 如果l1没有遍历完成 +// while (l1 != null) { +// head.next = new ListNode(l1.val); +// head = head.next; +// l1 = l1.next; +// } +// +// // 如果l2没有遍历完成 +// while (l2 != null) { +// head.next = new ListNode(l2.val); +// head = head.next; +// l2 = l2.next; +// } + + // 针对上面注释的代码进行优化 + // 因为最终只可能有一条链表没有遍历完成, 而且链表本身也是有序的,所以直接加到新链表的next即可 + head.next = l1 == null ? l2 : l1; + LinkedListUtils.printLinkedList(prev.next); + return prev.next; + } + + /** + * 使用递归的方式, 将链表从大到小(node.next)进行组装, 时间复杂度为O(m+n) + * + * 递归往内直到终止条件前,都是按值小的先递归,这样一旦递归条件终止, 之后开始递归计算, 就把返回的剩下的大节点附加到小节点的.next后面, + * + * 依次往外计算, 小的.next = 大的, 最后得到结果 从小到大的完整链表 + * + * @param l1 + * @param l2 + * @return + */ + public static LinkedNode mergeTwoListsByRecursion(LinkedNode l1, LinkedNode l2) { + // 如果l1递归结束,说明l2剩下的节点比l1大, 于是返回l2, 让前面的l1.next接上 + if (l1 == null) { + System.out.print("l1递归往内终止完成, l2 = "); + LinkedListUtils.printLinkedList(l2); + return l2; + } + // 如果l2递归结束, 说明l1剩下的节点比l2大, 于是返回l1, 让前面的l2.next接上 + else if (l2 == null) { + System.out.print("l2递归往内终止完成, l1 = "); + LinkedListUtils.printLinkedList(l1); + return l1; + // 递归的顺序是从小的节点开始往内递归, 从小往大,所以到了最内层结束都的节点都是最大的. 之后从内层外外计算,是从大往小计算. + } else if (l1.val < l2.val) { + // 如果l1的当前节点比l2小, 那么l1.next与排序好的表头相接, 为什么是接表头? 因为递归是从里往外开始计算的, + // 里是最后一个节点(最大的节点), 而l1是前面的小节点, 顺序是从小到大 + System.out.println("l1递归往内找终止条件"); + LinkedListUtils.printLinkedList(l1); + // 对l1进行递归遍历 + // 真正计算是从递归最里层往外 + l1.next = mergeTwoListsByRecursion(l1.next, l2);// l1.next 后面接的是递归里层的比自己大的节点 + System.out.println("l1递归往外,开始计算:"); + LinkedListUtils.printLinkedList(l1); + return l1;// 返回l1, 供外层递归的 l1或者l2中的 + } else { + // 如果l2的节点小于l1, 那么使用l2的next与排序好的表头相接 + System.out.println("l2递归往内找终止条件"); + LinkedListUtils.printLinkedList(l2); + // 对l2开始递归, 真正计算是从递归最里层往外, 从小往大, l2.next 接的节点都是比它大的 + l2.next = mergeTwoListsByRecursion(l1, l2.next); + System.out.println("l2递归往外,开始计算:"); + LinkedListUtils.printLinkedList(l2); + return l2; + } + } + + + public static LinkedNode mergeTwoListsByRecursion2(LinkedNode l1, LinkedNode l2) { + // 哪个链表先递归到终止条件为null, 说明另一个链表的剩下节点比该链表大, 于是返回对方剩余链表, 加在自己的.next后面 + if (l1 == null) { + return l2; + } else if (l2 == null) { + return l1; + } + + // 先递归小的, 然后将后面大的放在小的.next上面 + if (l1.val < l2.val) { + // l1的当前节点小就先递归l1 + l1.next = mergeTwoListsByRecursion2(l1.next, l2); + return l1; + } else { + // l2的当前节点小那就递归l2 + l2.next = mergeTwoListsByRecursion2(l1, l2.next); + return l2; + } + } +} diff --git a/src/main/java/com/study/linkedlist/Reverse.java b/src/main/java/com/study/linkedlist/Reverse.java index 59c8ffb..0ab5908 100644 --- a/src/main/java/com/study/linkedlist/Reverse.java +++ b/src/main/java/com/study/linkedlist/Reverse.java @@ -1,6 +1,6 @@ package com.study.linkedlist; -import com.study.utils.Printer; +import com.study.utils.LinkedListUtils; import java.util.Stack; @@ -19,22 +19,25 @@ public class Reverse { public static void main(String[] args) { - ListNode node1 = new ListNode(1); - ListNode node2 = new ListNode(2); - ListNode node3 = new ListNode(3); - ListNode node4 = new ListNode(4); - ListNode node5 = new ListNode(5); + LinkedNode node1 = new LinkedNode(1); + LinkedNode node2 = new LinkedNode(2); + LinkedNode node3 = new LinkedNode(3); + LinkedNode node4 = new LinkedNode(4); + LinkedNode node5 = new LinkedNode(5); node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node5; - Printer.printLinkedList(node1); + LinkedListUtils.printLinkedList(node1); System.out.println(); - Printer.printLinkedList(reverseList(node1)); - //Printer.printLinkedList(reverseList2(node1)); + //Printer.printLinkedList(reverseList(node1)); + //Printer.printLinkedList(reverseListByRecursion(node1)); + LinkedListUtils.printLinkedList(reverseListByRecursion2(node1)); //Printer.printLinkedList(reverseList3(node1)); + //Printer.printLinkedList(reverseList4(node1)); + //Printer.printLinkedList(reverstListByStack(node1)); } /** @@ -45,14 +48,14 @@ public static void main(String[] args) { * @param head * @return */ - private static ListNode reverseList(ListNode head) { + private static LinkedNode reverseList(LinkedNode head) { // cur的上一个节点 - ListNode prev = null; + LinkedNode prev = null; // 将当前节点指针指向head节点 - ListNode cur = head; + LinkedNode cur = head; while (null != cur) { // 将当前节点指针的下一个节点暂存起来 - ListNode next = cur.next; + LinkedNode next = cur.next; // 反转指针,将指针从后一个节点转为指向前一个节点 // 比如2->3 变成 2->1 cur.next = prev; @@ -64,6 +67,25 @@ private static ListNode reverseList(ListNode head) { return prev; } + + private static LinkedNode reverseList4(LinkedNode head) { + LinkedNode prev = null; + + LinkedNode cur = head; + + while (cur != null) { + LinkedNode next = cur.next; + + cur.next = prev; + + prev = cur; + + cur = next; + } + + return prev; + } + /** * 使用递归的方法反转单链表 *

@@ -83,31 +105,49 @@ private static ListNode reverseList(ListNode head) { * @param head * @return */ - private static ListNode reverseList2(ListNode head) { - if (head == null || head.next == null) + private static LinkedNode reverseListByRecursion(LinkedNode head) { + if (head == null || head.next == null) { + System.out.print("递归往内终止, head = "); + LinkedListUtils.printLinkedList(head); return head; + } + + System.out.print("递归往内"); + LinkedListUtils.printLinkedList(head); // 首先进入递归,由于head不为空,且head.next不为空,所以直到最后一个head为5的时候,才满足条件,结束递归,返回prev为5->null,head.next为5->null,head为4->5->null - ListNode prev = reverseList2(head.next); + LinkedNode prev = reverseListByRecursion(head.next); + System.out.print("递归往外"); + LinkedListUtils.printLinkedList(head); // 使得head.next.head.next成为一个闭环 // 赋值前4->5->null // 赋值head后 4->5->4->5->4->.....成为一个闭环 // 由于4->5->4成为一个闭环, 这样prev也由5->null变成了5->4->5一个闭环 - head.next.next = head; + head.next.next = head; //修改head的同时, prev也跟着变化 // 将4->5->4闭环剪断 变成4->null // 这样prev也变成了 5->4->null - head.next = null; + head.next = null; //修改head的同时, prev也跟着变化 return prev; } + private static LinkedNode reverseListByRecursion2(LinkedNode head) { + if (head == null || head.next == null) { + return head; + } + LinkedNode prev = reverseListByRecursion2(head.next); + head.next.next = head; + head.next = null; + return prev; + } /** * 借用stack后进先出的原理,对链表进行反转 + * 进的顺序为1 2 3 4 5, 出的顺序为 5 4 3 2 1 * * @param head * @return */ - private static ListNode reverseList3(ListNode head) { - Stack stack = new Stack(); + private static LinkedNode reverseList3(LinkedNode head) { + Stack stack = new Stack(); while (head != null) { stack.push(head); @@ -115,18 +155,44 @@ private static ListNode reverseList3(ListNode head) { } // 创建一个新节点prev,也是一个只有单个节点的链表 - ListNode prev = new ListNode(-1); + LinkedNode prev = new LinkedNode(-1); // 将head指向prev head = prev; while (!stack.isEmpty()) { - ListNode current = stack.pop(); + LinkedNode current = stack.pop(); // 因为stack中取出的节点可能带有原始链表的后继节点,所以重新创建一个相同val的新节点,移除原有的next属性 // 给prev链表的添加下一个节点 - head.next = new ListNode(current.val); + head.next = new LinkedNode(current.val); //将指针指向head下一个节点 head = head.next; } return prev.next; } + + + /** + * 利用栈的先进后出原理, 实现链表的反转 + * + * @param head + * @return + */ + private static LinkedNode reverstListByStack(LinkedNode head) { + // 先将链表每个节点压入栈中, 放入顺序为 1 2 3 4 5 + Stack stack = new Stack(); + while (head != null) { + stack.push(new LinkedNode(head.val));// 将节点压栈的时候需要放入没有后继的节点 + head = head.next; + } + + // 把栈顶部的元素一个个弹出, 弹出顺序为5 4 3 2 1 + LinkedNode newNode = new LinkedNode(0); + LinkedNode newHead = newNode; + while (stack.size() > 0) { + newHead.next = stack.pop(); + newHead = newHead.next; + } + + return newNode.next; + } } diff --git a/src/main/java/com/study/linkedlist/SwapNodesInPairs.java b/src/main/java/com/study/linkedlist/SwapNodesInPairs.java index 99cf917..9d43d09 100644 --- a/src/main/java/com/study/linkedlist/SwapNodesInPairs.java +++ b/src/main/java/com/study/linkedlist/SwapNodesInPairs.java @@ -1,6 +1,6 @@ package com.study.linkedlist; -import com.study.utils.Printer; +import com.study.utils.LinkedListUtils; /** * 两两交换链表中的节点 @@ -20,12 +20,12 @@ public class SwapNodesInPairs { public static void main(String[] args) { - ListNode node1 = new ListNode(1); - ListNode node2 = new ListNode(2); - ListNode node3 = new ListNode(3); - ListNode node4 = new ListNode(4); - ListNode node5 = new ListNode(5); - ListNode node6 = new ListNode(6); + LinkedNode node1 = new LinkedNode(1); + LinkedNode node2 = new LinkedNode(2); + LinkedNode node3 = new LinkedNode(3); + LinkedNode node4 = new LinkedNode(4); + LinkedNode node5 = new LinkedNode(5); + LinkedNode node6 = new LinkedNode(6); node1.next = node2; node2.next = node3; @@ -33,33 +33,36 @@ public static void main(String[] args) { node4.next = node5; node5.next = node6; - Printer.printLinkedList(node1); + LinkedListUtils.printLinkedList(node1); //Printer.printLinkedList(swapPairs(node1)); - Printer.printLinkedList(swapPairs2(node1)); + //Printer.printLinkedList(swapPairs2(node1)); + LinkedListUtils.printLinkedList(swapPairs3(node1)); } /** * 通过循环遍历链表, 每次取未交换的前2个节点进行交换,交换完后将指针指向后2个节点,一遍下一轮循环处理 * - * 这里需要3个指针,其中两个是pair对中相邻的两个节点指针a和b,另一个是pair对之前的一个节点指针 + * 这里需要3个指针,其中两个是pair对中相邻的两个节点指针a和b,另一个是pair对之前的一个节点指针prev * * @param head * @return */ - private static ListNode swapPairs(ListNode head) { - ListNode result = new ListNode(-1); + private static LinkedNode swapPairs(LinkedNode head) { + LinkedNode result = new LinkedNode(-1); result.next = head; - ListNode prev = result; + LinkedNode prev = result; while (prev.next != null && prev.next.next != null) { - ListNode a = prev.next; //1 - ListNode b = prev.next.next; //2 + LinkedNode a = prev.next; //1 + LinkedNode b = prev.next.next; //2 - prev.next = b; //2 - a.next = b.next; //3 - b.next = a; //1 + prev.next = b; //2 prev -> 2 + a.next = b.next; //3 1 -> 3 + b.next = a; //1 2 -> 1 + + // 经过上面的交换后 变成了 prev -> 2 ->1 - >3 // prev.next = a.next = 3, prev.next.next = 4 // prev = a; // 也可以 (a = prev.next.next) prev = prev.next.next; @@ -68,23 +71,52 @@ private static ListNode swapPairs(ListNode head) { return result.next; } + + /** + * 需要3个指针, 替换节点对的前继指针和 节点对的两个指针 + * @param head + * @return + */ + private static LinkedNode swapPairs3(LinkedNode head){ + LinkedNode headPrev = new LinkedNode(0); + headPrev.next = head; //在头节点前面在放一个前置节点, 用于后面的迭代使用 + LinkedNode prev = headPrev; + + // 每次循环都判断接下来的 一对节点不为空, 然后对他们进行对调 + while(prev.next !=null && prev.next.next !=null){ + LinkedNode oldFirst = prev.next; + LinkedNode oldSecond = prev.next.next; + + //将当前循环的第一和第二节点进行对换 + LinkedNode third = oldSecond.next; + prev.next = oldSecond; + oldSecond.next = oldFirst; + oldFirst.next = third; + + // 将下一次循环的两节点前继指针指向新的第2节点 + //prev = first; + prev = prev.next.next; + } + return headPrev.next; + } + /** * 使用递归 * * @param head * @return */ - private static ListNode swapPairs2(ListNode head) { + private static LinkedNode swapPairs2(LinkedNode head) { if (head == null || head.next == null){ return head; } // prev 为最后一个节点 // prev = null, head = 5, head = 3 - ListNode prev = swapPairs2(head.next.next); + LinkedNode prev = swapPairs2(head.next.next); // tmp 为倒数第二个节点 // 下面3行代码将每一对的两个节点进行互换 // 将tmp指向当前pair对的第2个节点 - ListNode tmp = head.next; //tmp2 = 6,tmp2 = 4->5->null, tmp2 = 2->3->6->5->null + LinkedNode tmp = head.next; //tmp2 = 6,tmp2 = 4->5->null, tmp2 = 2->3->6->5->null // 将当前pair对第2个节点指向已经反转好的节点 head.next = prev; // 5.next = null, 3.next = 6->5->null, 1.next = 4->3->6->5->null // 将pair对的第3个节点的指向第1个节点, 这样第2个节点后面为第1个节点,形成了反转 diff --git a/src/main/java/com/study/number/BestTimeToBuyAndSellStockII.java b/src/main/java/com/study/number/BestTimeToBuyAndSellStockII.java new file mode 100644 index 0000000..54e0f01 --- /dev/null +++ b/src/main/java/com/study/number/BestTimeToBuyAndSellStockII.java @@ -0,0 +1,114 @@ +package com.study.number; + +/** + * 买卖股票的最佳时机 II + *

+ * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + *

+ * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + *

+ * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + *

+ * 示例 1: + *

+ * 输入: [7,1,5,3,6,4] + * 输出: 7 + * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + *   随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + * 示例 2: + *

+ * 输入: [1,2,3,4,5] + * 输出: 4 + * 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + *   注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + *   因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + * 示例 3: + *

+ * 输入: [7,6,4,3,1] + * 输出: 0 + * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + *

+ * 链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii + */ +public class BestTimeToBuyAndSellStockII { + + /** + * 同一支股票卖完还能再买, 也能买了再卖. + * 但是不能连续买两次(不卖) + * + * 可以通过贪心法,波峰法以及暴力法来解答 + * + * @param args + */ + public static void main(String[] args) { + int[] prices = {7, 1, 5, 3, 6, 4}; + //System.out.println(maxProfit(prices)); + System.out.println(maxProfit2(prices)); + + int[] prices2 = {1, 2, 3, 4, 5}; + //System.out.println(maxProfit(prices2)); + System.out.println(maxProfit2(prices2)); + + int[] prices3 = {7, 6, 4, 3, 1}; + //System.out.println(maxProfit(prices3)); + System.out.println(maxProfit2(prices3)); + } + + + /** + * 使用贪心算法, 每支股票的利润都不放过 + * + * 循环遍历,支要下一个股票比当前股票贵,我就买当前的,然后卖下一个,获取下一个和当前之间的利润 + * + * 时间复杂度O(n) 空间复杂度O(1) + * + * @param prices + * @return + */ + public static int maxProfit(int[] prices) { + int profit = 0; + for (int i = 0; i < prices.length; i++) { + // 只要下一个股票比当前股票贵,我就买当前的,然后卖下一个,获取下一个和当前之间的利润 + if (prices.length > i + 1 && prices[i] < prices[i + 1]) { + profit += prices[i + 1] - prices[i]; + } + } + return profit; + } + + /** + * 峰谷法 + * + * 循环数组, 先找波谷(用于先买),再找波峰(用于后卖), 利润=波峰-波谷 + * + * 波谷为当前价格小于下一支股票, 波峰为当前价格大于下一支股票 + * + * 时间复杂度:O(n)。遍历一次。 + * + * 空间复杂度:O(1)。需要常量的空间。 + * + * @param prices + * @return + */ + public static int maxProfit2(int[] prices) { + int i = 0; + int valley; + int peak; + int maxprofit = 0; + // 遍历每个股票 + while (i < prices.length - 1) { + // 找波谷, 波谷为当前股票价格低于下一支股票 + while (i < prices.length - 1 && prices[i] >= prices[i + 1]) + i++; + valley = prices[i];// 记录波谷 + // 找波峰, 波峰为当前股票价格高于下一支股票 + while (i < prices.length - 1 && prices[i] <= prices[i + 1]) + i++; + peak = prices[i]; // 记录波峰 + + // 利润 = 波峰 - 波谷 + maxprofit += peak - valley; + } + return maxprofit; + } +} diff --git a/src/main/java/com/study/number/CountPrime.java b/src/main/java/com/study/number/CountPrime.java new file mode 100644 index 0000000..2d9f0ab --- /dev/null +++ b/src/main/java/com/study/number/CountPrime.java @@ -0,0 +1,63 @@ +package com.study.number; + +import java.util.ArrayList; +import java.util.List; + +/** + * 204. 计数质数 + *

+ * 统计所有小于非负整数 n 的质数的数量。 + *

+ * 示例: + *

+ * 输入: 10 + * 输出: 4 + * 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 + *

+ * https://leetcode-cn.com/problems/count-primes/ + */ +public class CountPrime { + public static void main(String[] args) { + CountPrime c = new CountPrime(); + List primeList = c.countPrimes(10); + for (Integer p : primeList) { + System.out.println(p); + } + } + + /** + * 计算n以内所有质数的个数 + * + * @param n + * @return + */ + public List countPrimes(int n) { + List primeList = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + if (isPrime(i)) + primeList.add(i); + } + return primeList; + } + + public boolean isPrime(int num) { + // 0和1不是质数 + if (num <= 1) + return false; + + // 求开方根 会导致结果不准 +// int numSq = (int) Math.sqrt(num); +// for (int i = 2; i <= numSq; i++) { +// if (numSq % i == 0) { +// return false; +// } +// } + // 如果一个数如果只能被 1 和它本身整除, 那就是质数 + for (int i = 2; i < num; i++) { + if (num % i == 0) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/study/number/FactorialN.java b/src/main/java/com/study/number/FactorialN.java new file mode 100644 index 0000000..5c282e6 --- /dev/null +++ b/src/main/java/com/study/number/FactorialN.java @@ -0,0 +1,52 @@ +package com.study.number; + +/** + * 计算N的阶层 + *

+ * https://time.geekbang.org/course/detail/130-42710 + */ +public class FactorialN { + + public static void main(String[] args) { + int n = 5; + int result = factorialN(n); // 5 * 4 * 3 * 2 * 1 + System.out.println(String.format("%d的阶层为: %d", n, result)); + result = factorialNByLoop(n); + System.out.println(String.format("%d的阶层为: %d", n, result)); + } + + /** + * 计算n的阶层 n! = 1 * 2 * 3... * n 或者 n * (n-1) * (n-2) ... * 2 * 1 + * 使用递归的方式 + * + * @param n + * @return + */ + private static int factorialN(int n) { + // 递归结束条件是n==1 + if (n == 1) { + System.out.println("递归往内结束: n == 1"); + return 1; + } + // 由于factorialN调用自己, 所以每次进来n都比上一次少1 + System.out.println("递归往内开始, n == " + n); + int result = n * factorialN(n - 1); // 通过 n * (n -1) * (n - 2) * .... * 1 + System.out.println(String.format("递归往外开始,n = %d, 计算结果: result = %d", n, result)); + return result; + } + + /** + * 使用迭代的方式来实现递归的效果 + * + * @param n + * @return + */ + private static int factorialNByLoop(int n) { + int result = 1; + while (n >= 1) {// 当n减少到1的时候退出循环 + result = result * n; + n--;// 每次减一 + } + return result; + } +} diff --git a/src/main/java/com/study/number/KthLargest.java b/src/main/java/com/study/number/KthLargest.java index 42406bb..2748351 100644 --- a/src/main/java/com/study/number/KthLargest.java +++ b/src/main/java/com/study/number/KthLargest.java @@ -14,6 +14,7 @@ * int k = 3; * int[] arr = [4,5,8,2]; * KthLargest kthLargest = new KthLargest(3, arr); + * 加入的元素必须必数组中最小的元素大,否则无法加入 * kthLargest.add(3); // returns 4 * kthLargest.add(5); // returns 5 * kthLargest.add(10); // returns 5 @@ -21,8 +22,8 @@ * kthLargest.add(4); // returns 8 * 说明: * 你可以假设 nums 的长度≥ k-1 且k ≥ 1。 - * - * + *

+ *

* https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/comments/ */ public class KthLargest { @@ -42,41 +43,58 @@ public KthLargest(int k, int[] nums) { } /** - * 往队列中添加新的元素 + * 往队列中添加新的元素, 只有新元素比堆中最小元素还小才能加入并移除最小元素 * * @param element - * @return + * @return 最小元素 */ public int add(int element) { //如果队列中的元素没有满, 则往里添加 if (queue.size() < k) { - queue.offer(element); + queue.offer(element);// 将元素加到末尾 } else { // 如果队列中的元素满了, 则判断队列最小的元素(顶端的元素)是否比新元素小 - // 如果最小的元素比该元素小, 那将顶端最小的元素移除, 将新元素插入 + // 如果要放入的元素大于顶部元素,则移除顶部元素,放入新元素 if (queue.peek() < element) { queue.poll(); //移除顶端最小元素 queue.offer(element);//插入新元素 } } - return queue.peek(); //返回最小的元素 + return queue.peek(); //返回顶部最小的元素 + } + + + public int add2(int element){ + if(queue.size() < 3){ + queue.offer(element); + } + else{ + // 只有当添加元素大于堆中最小元素才可以加入 + if(queue.peek() < element){ + queue.poll(); //移除最小元素 + queue.offer(element); //加入新元素 + } + } + return queue.peek();// 返回 } /** * PriorityQueue 优先队列会将里面的元素进行从小到大排序, 每次插入一个新元素后,都会将最小的元素放到最上面 *

- * 优先队列的作用是能保证每次取出的元素都是队列中权值最小的 + * 优先队列的作用是能保证每次取出的元素都是队列中最小的 + * + * 只有大于队列顶部最小的元素才能放入, 同时移除最小的元素. 这样确保新来元素后,队列顶部的元素始终是第k小的 */ public static void main(String[] args) { int k = 3; - int[] arr = {8, 5, 4, 2}; - KthLargest kthLargest = new KthLargest(k, arr); + int[] arr = {4, 5, 8, 2}; + KthLargest kthLargest = new KthLargest(k, arr); //会自动移除第三大的2 // 每次加入新元素都返回第k大的元素 - System.out.println(kthLargest.add(8)); // 第k大的元素为5 - System.out.println(kthLargest.add(6)); // 第k大的元素为6 - System.out.println(kthLargest.add(4)); // 第k大的元素为6 - System.out.println(kthLargest.add(10)); // 第k大的元素为8 - System.out.println(kthLargest.add(9)); // 第k大的元素为8 + System.out.println(kthLargest.add(3)); // 3放入失败 第3大的元素为4 堆中元素为 4 5 8 + System.out.println(kthLargest.add(5)); // 5放入尾部 4被移除 第3大的元素为5 堆中元素为 5 8 5 + System.out.println(kthLargest.add(10)); // 10放入尾部 5移除 第3大的元素为5 堆中元素为 5 8 10 + System.out.println(kthLargest.add(9)); // 9放入尾部 5移除 第3大的元素为8 堆中元素为 8 10 9 + System.out.println(kthLargest.add(4)); // 4放入失败 第3大的元素为8 堆中元素为 8 10 9 } } diff --git a/src/main/java/com/study/number/MajorityElement.java b/src/main/java/com/study/number/MajorityElement.java new file mode 100644 index 0000000..21c373f --- /dev/null +++ b/src/main/java/com/study/number/MajorityElement.java @@ -0,0 +1,258 @@ +package com.study.number; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 多数元素, 也叫众数 + *

+ * 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 + *

+ * 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 + *

+ * 示例 1: + *

+ * 输入: [3,2,3] + * 输出: 3 + * 示例 2: + *

+ * 输入: [2,2,1,1,1,2,2] + * 输出: 2 + *

+ * 链接:https://leetcode-cn.com/problems/majority-element + */ +public class MajorityElement { + + /** + 解法: 1. 暴力解法, 循环两次, 第一次循环每个元素, 第二次循环统计当前元素在整个数组中出现的个数,并判断是否大于数组长度的1/2,如果大于就返回该元素. 时间复杂度为 O(n2) + 2. 使用hashmap存储每个元素的数量, 由于hashmap读写的时间复杂度为O(1), 主要时间花费在一层循环上. 时间复杂度为O(n) + 3. 对数组进行排序, 排完序后, 如果题目说一定存在某个元素个数大于长度的一半,那么这个元素肯定会在数组的中间位置. 时间复杂度为O(nlogn) + 4. 利用递归 + 分治的方式,将数组分成左右两个数组,分别从两个数组中获取众数, 如果两边众数相等,则直接返回该众数. 如果众数不相等, 则计算两个众数的个数, 取多的那个. 时间复杂度 O(nlogn) + 5. Boyer-Moore 投票算法 + 原理: 利用众数比非众数多的优势. 众数肯定比非众数多, 所以众数和非众数抵消后,剩下的必然是众数 + 只需要对原数组进行两趟扫描,并且简单易实现。第一趟扫描我们得到一个候选节点candidate,第二趟扫描我们判断candidate出现的次数是否大于⌊ n/2 ⌋。 + 第一趟扫描中,我们需要记录2个值: candidate,初值可以为任何数. count,初值为0 + 之后,对于数组中每一个元素,首先判断count是否为0,若为0,则把candidate设置为当前元素。之后判断candidate是否与当前元素相等,若相等则count+=1,否则count-=1。 + 时间复杂度为O(n),空间复杂度为O(1). + * @param args + */ + public static void main(String[] args) { + //int[] nums = {3, 2, 3}; + //int[] nums = {1}; + //int[] nums = {8, 8, 7, 7, 7}; + int[] nums = {8, 1, 2, 7, 7}; + //System.out.println(majorityElementByDoubleLoop(nums)); + //System.out.println(majorityElementByHashMap(nums)); + //System.out.println(majorityElementBySort(nums)); + //System.out.println(majorityElementByRecursion(nums)); + System.out.println(majorityElementByBoyerMoore(nums)); //投票算法 + } + + /** + * 暴力解法, 循环两次, 第一次循环每个元素, 第二次循环统计当前元素在整个数组中出现的个数,并判断是否大于数组长度的1/2,如果大于就返回该元素. + *

+ * 时间复杂度为 O(n2) + * + * @param nums + * @return + */ + private static int majorityElementByDoubleLoop(int[] nums) { + int count = 1; + + // 边界处理 + if (nums.length == 1) { + return nums[0]; + } + + // 循环每一个元素 + for (int i = 0; i < nums.length; i++) { + // 将当前元素 与其他元素做对比, 如果相同次数就+1, 一旦达到数组长度1/2就返回该元素 + for (int j = 0; j < nums.length; j++) { + if (i != j && nums[i] == nums[j]) { + count++; + if (count > nums.length / 2) + return nums[i]; + } + } + // 循环到下一个元素后,计数器需要还原 + count = 1; + } + return -1; + } + + /** + * 使用hashmap存储每个元素的数量, 由于hashmap读写的时间复杂度为O(1), 主要时间花费在一层循环上, + *

+ * 时间复杂度为O(n) + * + * @param nums + * @return + */ + private static int majorityElementByHashMap(int[] nums) { + Map map = new HashMap<>(); + + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(nums[i])) + map.put(nums[i], map.get(nums[i]) + 1); + else + map.put(nums[i], 1); + + // 放完元素后 计算一下该元素个数是否大于数组长度的一半 + Integer count = map.get(nums[i]); + if (count != null && count > nums.length / 2) { + return nums[i]; + } + } + + return -1; + } + + /** + * 对数组进行排序, 排完序后, 如果题目说一定存在某个元素个数大于长度的一半,那么这个元素肯定会在数组的中间位置. + * + * 时间复杂度为O(nlogn) + * + * @param nums + * @return + */ + private static int majorityElementBySort(int[] nums) { + + // 对数组进行排序, 时间复杂度为O(nlogn) + Arrays.sort(nums); + + // 如果题目说肯定有元素超过数组长度的一半 + return nums[nums.length / 2]; //那排序后 这个元素肯定会处在1/2的位置 + } + + + /** + * 对数组进行排序, 排完序后, 记录每个元素的个数. + * 对于排序后的元素, 可以直接进行一次遍历,然后对每个元素进行count[num]++,以及判断元素个数 + *

+ * 由于排序,所以最快的时间复杂度也要O(nlogn) + * + * @param nums + * @return + */ + private static int majorityElementBySort2(int[] nums) { + + // 边界处理 + if (nums.length == 1) + return nums[0]; + + // 对数组进行排序, 时间复杂度为O(nlogn) + Arrays.sort(nums); + + int count = 1; + int lastNum = nums[0]; + + // 循环部分时间复杂度为O(n) + // 从第2个元素开始循环 + for (int i = 1; i < nums.length; i++) { + // 如果当前元素和上一个元素相等, 那么出现次数+1 + if (nums[i] == lastNum) + count++; + else //否则次数恢复为1 + count = 1; + + // 修改上一个元素 + lastNum = nums[i]; + + // 如果该元素出现次数大于数组的一半,直接返回 + if (count > nums.length / 2) + return nums[i]; + } + + return -1; + } + + + private static int countInRange(int[] nums, int num, int low, int high) { + int count = 0; + for (int i = low; i <= high; i++) { + if (nums[i] == num) { + count++; + } + } + return count; + } + + /** + * 利用递归 + 分治的方式,将数组分成左右两个数组,分别从两个数组中获取众数, 如果两边众数相等,则直接返回该众数. + * + * 如果众数不相等, 则计算两个众数的个数, 取多的那个. + * + * 时间复杂度 O(nlogn) + * + * @param nums + * @param low + * @param high + * @return + */ + private static int majorityElementRecursion(int[] nums, int low, int high) { + // 只有一个元素的情况 + if (low == high) { + // 递归终止条件 + System.out.println(String.format("从外往内递归终止: high = %d, low = %d, result = %d", high, low, nums[low])); + return nums[low]; + } + + // 将数组分成左右两边两部分, 分别进行递归 + int mid = (high - low) / 2 + low; + System.out.println(String.format("从外往内递归: high = %d, low = %d, mid = %d", high, low, mid)); + int left = majorityElementRecursion(nums, low, mid); + int right = majorityElementRecursion(nums, mid + 1, high); + + System.out.println(String.format("从内往外递归: left = %d, right = %d",left, right)); + + // 如果左右两边的众数一样,那该众数即为数组的众数 + if (left == right) { + return left; + } + + // 如果左右众数不一样, 那么分别计算两边众数的个数 ,多的为整个数组的众数 + int leftCount = countInRange(nums, left, low, high); + int rightCount = countInRange(nums, right, low, high); + + return leftCount > rightCount ? left : right; + } + + public static int majorityElementByRecursion(int[] nums) { + return majorityElementRecursion(nums, 0, nums.length - 1); + } + + /** + * Boyer-Moore 投票算法 + * + * 原理: 众数肯定比非众数多, 所以众数和非众数抵消后,剩下的必然是众数 + * + * 该算法时间复杂度为O(n),空间复杂度为O(1),只需要对原数组进行两趟扫描,并且简单易实现。第一趟扫描我们得到一个候选节点candidate,第二趟扫描我们判断candidate出现的次数是否大于⌊ n/2 ⌋。 + * + * 第一趟扫描中,我们需要记录2个值: + * + * candidate,初值可以为任何数 + * count,初值为0 + * 之后,对于数组中每一个元素,首先判断count是否为0,若为0,则把candidate设置为当前元素。之后判断candidate是否与当前元素相等,若相等则count+=1,否则count-=1。 + * 原文链接:https://blog.csdn.net/kimixuchen/article/details/52787307 + * + * @param nums + * @return + */ + public static int majorityElementByBoyerMoore(int[] nums) { + int count = 0; + // 众数 + Integer candidate = null; + + for (int num : nums) { + // 如果count变回了0, 相当于众数和非众数互相抵消, 则后面的候选数(众数)需要重新选取 + if (count == 0) { + candidate = num; //只有count为0的时候 才重新选取候选数(众数) + } + // 如果新的数字为众数, 则count+1, 否则count-1 + count += (num == candidate) ? -1 : 1; + } + + return candidate; + } +} diff --git a/src/main/java/com/study/number/MaxOneAndTwo.java b/src/main/java/com/study/number/MaxOneAndTwo.java index d0d809c..e780e95 100644 --- a/src/main/java/com/study/number/MaxOneAndTwo.java +++ b/src/main/java/com/study/number/MaxOneAndTwo.java @@ -7,13 +7,14 @@ public class MaxOneAndTwo { public static void main(String[] args) { int[] a = {12, 23, 9, 24, 15, 3, 18}; - int[] result = findMaxOneAndTwo(a); + //int[] result = findMaxOneAndTwo(a); + int[] result = findMax1AndMax2(a); System.out.printf("max1:%d, max2:%d", result[0], result[1]); } /** - * 找出数组中最大和第二大的数 + * 找出数组中最大和第二大的数, O1的时间复杂度 * * @param arr * @return @@ -21,7 +22,7 @@ public static void main(String[] args) { private static int[] findMaxOneAndTwo(int[] arr) { // 定义最大数和第二大数 int max1 = arr[0]; - int max2 = max1; + int max2 = arr[0]; // 遍历数组, 将每个元素与最大数进行比较,如果比最大数大,先将最大数赋给第二大,然后再其赋给最大数. for (int i = 0; i < arr.length - 1; i++) { if (arr[i + 1] > max1) { @@ -29,7 +30,23 @@ private static int[] findMaxOneAndTwo(int[] arr) { max1 = arr[i + 1]; } } - + // 循环完成后 得到最大值和第二大值 return new int[]{max1, max2}; } + + + private static int[] findMax1AndMax2(int[] arr){ + // 假设第一个元素为最大和第二大 + int max1 = arr[0]; + int max2 = max1; + for(int i =0; i < arr.length -1; i++){ + // 从第二个元素开始判断 + if(arr[i+1] > max1){ + // 如果后一个元素比最大的还大,那就将最大的改成第二大,后一个元素改为最大 + max2 = max1; + max1 = arr[i+1]; + } + } + return new int[]{max1,max2}; + } } diff --git a/src/main/java/com/study/number/PowXN.java b/src/main/java/com/study/number/PowXN.java new file mode 100644 index 0000000..d5eea48 --- /dev/null +++ b/src/main/java/com/study/number/PowXN.java @@ -0,0 +1,291 @@ +package com.study.number; + +/** + * 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 + *

+ * 示例 1: + *

+ * 输入: 2.00000, 10 + * 输出: 1024.00000 + * 示例 2: + *

+ * 输入: 2.10000, 3 + * 输出: 9.26100 + * 示例 3: + *

+ * 输入: 2.00000, -2 + * 输出: 0.25000 + * 解释: 2-2 = 1/22 = 1/4 = 0.25 + * 说明: + *

+ * -100.0 < x < 100.0 + * n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。 + *

+ * 来源:力扣(LeetCode) + * 链接:https://leetcode-cn.com/problems/powx-n + */ +public class PowXN { + + /** + * 解法: 一种是暴力解法, 利用循环或者递归的方式 将x乘以自身n次. 时间复杂度O(n) + * 另一种是用递归+分治的解法, 将递归的次数减少到logN次. + * 注意: 需要考虑n为负数和奇偶数的情况. + * + * @param args + */ + public static void main(String[] args) { + double result = myPow(2.0, 10); + System.out.println(result); +// +// double result = myPow2(2.1000, 3); +// System.out.println(result); + + //double result = myPow3(2.1000, -3); +// double result = myPow3(2.1000, 10); +// System.out.println(result); + +// result = myPowRecursion(2.1000, 10); +// System.out.println(result); + + //result = myPowRecursion(2, 10); + result = myPowRecursion(2.0, 10); + System.out.println(result); + + result = fastPow3(2.0, 10); + System.out.println(result); + +// result = myPowRecursion2(2, 10); +// System.out.println(result); + } + + /** + * 暴力法, 循环将x乘以自己n次, 时间复杂度O(n) + * 空间复杂度O(1) 只需要一个变量来保存最终x的连乘结果 + * + * @param x + * @param n + * @return + */ + public static double myPow(double x, int n) { + double result = 1; + + // 正数次幂 + if (n > 0) { + while (n > 0) { + result *= x; + n--; + } + // 负数次幂 + } else { + n = -n; + while (n > 0) { + result *= x; + n--; + } + result = 1 / result; + } + return result; + } + + /** + * 暴力法, 优化n为负数的情况. + * + * @param x + * @param n + * @return + */ + public static double myPow2(double x, int n) { + long N = n; + if (N < 0) { + // 当n为负数的情况下, 实际上就是 1/x 的n次方 + x = 1 / x; + N = -N; + } + double ans = 1; + for (long i = 0; i < N; i++) + ans = ans * x;// 将x乘以自身n次 + return ans; + } + + /** + * 暴力法(优化), 乘以自己的次数改为n/2次, x的2n次方 = x的n次方 * x的n次方. + * 如 temp = x的n/2次方 然后结果为temp * temp即可. 比如 5的10次方 改为 a = 5的5次方, a * a = 5的10次方 + * 这里需要考虑n为偶数还是奇数, 如果为偶数, 那结果为temp * temp; 如果为奇数需要 n/2实际上为(n-1)/2, 所以结果为 temp * temp * x; + * + * @param x + * @param n + * @return + */ + public static double myPow3(double x, int n) { + long N = n; + if (N < 0) { + // 当n为负数的情况下, 实际上就是 1/x 的n次方 + x = 1 / x; + N = -N; + } + double half = 1; + // 循环 n / 2次, 这里如果n为偶数, 则结果为n的一半, 如果是奇数, 结果为n-1的一半 + for (long i = 0; i < N / 2; i++) + half = half * x;// 将x乘以自身 n/2 次 + + // 如果n是偶数 + if (n % 2 == 0) { + half = half * half; //直接temp的平方即可 + } else { + // 如果n为奇数 + half = half * half * x; // temp的平方还需要乘以x + } + return half; + } + + + /** + * 使用递归 + 分治法, 时间复杂度O(logn), 每一次我们使用公式 x的n/2次, n都为原来的一半, 所以之多O(log n)次即可求出结果 + * + * @param x + * @param n + * @return + */ + public static double myPowRecursion(double x, int n) { + // 处理n为负数的情况 + long N = n; + if (n < 0) { + x = 1 / x; + N = -N; + } + return fastPow(x, N); + //return fastPow2(x, N); + //return fastPowByLoop(x, N); + } + + public static double fastPow(double x, long n) { + // 当n/2 不断递归除尽后, n==0, 返回1.0 + if (n == 0) { + System.out.println("往内递归结束 n= " + n); + return 1.0; + } + System.out.println("往内递归 n= " + n); + // 使用递归, 将循环乘以x的次数减少到logn次 (类似于二分查找) + double half = fastPow(x, n / 2);//每次递归的n都是上一次的1/2 + double beforeHalf = half; + // 每一次执行会将上一递归的half结果拿来相乘,这样每次的half都是上一次的half的平方或者平方乘以x + // 需要考虑n为偶数和奇数的情况 + //if (n % 2 == 0) { + if ((n & 1) == 0) { + half = half * half; + System.out.println(String.format("往外递归 n = %d, half = %s * %s = %s", n, beforeHalf, beforeHalf, half)); + } else { + half = half * half * x; + System.out.println(String.format("往外递归 n = %d, half = %s * %s * %s = %s", n, beforeHalf, beforeHalf, x, half)); + } + return half; + } + +// /** +// * 迭代实现分治的算法有问题, 需要修改 +// * @param x +// * @param n +// * @return +// */ +//// public static double fastPowByLoop(double x, long n) { +// double result = x; +// while (n >= 2) { +// if (n % 2 == 0) { +// result = result * result; +// } else { +// result = result * result * x; +// } +// // n = 1的时候 n /2 没有任何意义 +// n = n / 2; +// } +// return result; +// } + + + public static double fastPow2(double x, long n) { + if (n == 0) { + return 1.0; + } + + long N = n; + if (n < 0) { + x = 1 / x; + N = -N; + } + + double half = fastPow2(x, N / 2); + + //if (N % 2 == 0) { + if ((N & 1) == 0) { + half = half * half; + } else { + half = half * half * x; + } + return half; + } + + + /** + * 使用迭代的方法 每次进行n/2次求解, 时间复杂度为O(logN) + * + * @param x + * @param n + * @return + */ + public static double fastPow3(double x, long n) { + if (n == 0) { + return 1.0; + } + + // 处理n为负数的情况 + long N = n; + if (n < 0) { + x = 1 / x; // x为倒数 + N = -N; + } + + double result = 1; + + // x^10 = x^2 * x^2 * x^2 * x^2 * x^2; + while (N > 0) { + if ((N & 1) == 1) { + result *= x; + } + result *= x * x; + N >>= 1; // 相当于N = N / 2; + } + + return result; + } + + + /** + * 暴利法(递归) 利用递归的方式将 x乘以自己n次, 效果和迭代一样. 时间复杂度O(n) + * + * @param x + * @param n + * @return + */ + public static double myPowRecursion2(double x, int n) { + // 处理n为负数的情况 + long N = n; + if (n < 0) { + x = 1 / x; + N = -N; + } + return slowPow(x, N); + } + + public static double slowPow(double x, long n) { + if (n == 0) { + return 1.0; + } + + // 从外往内 找终止条件 + double result = slowPow(x, n - 1); + // 从内往外递归 真正开始计算 + result *= x; + + return result; + } +} diff --git a/src/main/java/com/study/number/Prime.java b/src/main/java/com/study/number/Prime.java index e351ce1..0b8d1fe 100644 --- a/src/main/java/com/study/number/Prime.java +++ b/src/main/java/com/study/number/Prime.java @@ -9,12 +9,15 @@ public class Prime { public static void main(String[] args) { int a = 2; //int a = 11; + //int a = 4; //System.out.println(a + (isPrime(a) ? "是" : "不是") + "质数"); //System.out.println(a + (isPrime2(a) ? "是": "不是") + "质数"); - System.out.println(a + (isPrime3(a) ? "是" : "不是") + "质数"); + System.out.println(a + (isPrime2_1(a) ? "是": "不是") + "质数"); + + //System.out.println(a + (isPrime3(a) ? "是" : "不是") + "质数"); } public static boolean isPrime(int n) { @@ -44,26 +47,46 @@ public static boolean isPrime2(int n) { return true; } - /** - * 优化后的算法, 只需要判断从2到该数的根号之前的整数即可 + * 判断n是否为质数, 只要n对任何2-n的数取余都不为0即可 * * @param n * @return */ - public static boolean isPrime3(int n) { - // 1不是质数 - if (n <= 1) { - return false; - } + private static boolean isPrime2_1(int n) { + if (n <= 1) + return false; - // 将n开根号 - double s = Math.sqrt(n); - // 如果 2到n的根号数之内的数 都不能被n整除, 那么n为质数 - for (int i = 2; i <= s; i++) { + // 遍历2到n的所有元素, 如果有n能取余为0的元素, 那么n都不是质数 + for (int i = 2; i < n; i++) { if (n % i == 0) return false; } return true; } + + + /** + * 优化后的算法, 只需要判断从2到该数的根号之前的整数即可 + * 求开方根 会导致结果不准 + * + * @param n + * @return + */ +// public static boolean isPrime3(int n) { +// // 1不是质数 +// if (n <= 1) { +// return false; +// } +// +// // 求开方根 会导致结果不准 +// // 将n开根号 +// double s = Math.sqrt(n); +// // 如果 2到n的根号数之内的数 都不能被n整除, 那么n为质数 +// for (int i = 2; i <= s; i++) { +// if (n % i == 0) +// return false; +// } +// return true; +// } } diff --git a/src/main/java/com/study/number/ReverseInteger.java b/src/main/java/com/study/number/ReverseInteger.java index 7d9f73c..28bdad9 100644 --- a/src/main/java/com/study/number/ReverseInteger.java +++ b/src/main/java/com/study/number/ReverseInteger.java @@ -25,9 +25,9 @@ public class ReverseInteger { public static void main(String[] args) { - //int x = 1234567890; + int x = 1234567890; //int x = -1234567890; - int x = 0; + //int x = 0; System.out.println(reverse(x)); diff --git a/src/main/java/com/study/array/SameNumberPossition.java b/src/main/java/com/study/number/SameNumberPossition.java similarity index 67% rename from src/main/java/com/study/array/SameNumberPossition.java rename to src/main/java/com/study/number/SameNumberPossition.java index 04b52c0..2dc7576 100644 --- a/src/main/java/com/study/array/SameNumberPossition.java +++ b/src/main/java/com/study/number/SameNumberPossition.java @@ -1,4 +1,4 @@ -package com.study.array; +package com.study.number; /** * 求数组中连续数第一次出现的位置 @@ -10,6 +10,11 @@ public static void main(String[] args) { System.out.println(position(arr)); } + /** + * 需要判断上一个索引位置和当前位置的元素是否相等,如果相等说明该元素连续出现了,返回该索引位置 + * @param arr + * @return + */ private static int position(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { if (arr[i] == arr[i + 1]) { diff --git a/src/main/java/com/study/number/Sqrt.java b/src/main/java/com/study/number/Sqrt.java new file mode 100644 index 0000000..33aec7e --- /dev/null +++ b/src/main/java/com/study/number/Sqrt.java @@ -0,0 +1,192 @@ +package com.study.number; + +/** + * x 的平方根 + *

+ * 实现 int sqrt(int x) 函数。 + *

+ * 计算并返回 x 的平方根,其中 x 是非负整数。 + *

+ * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 + *

+ * 示例 1: + *

+ * 输入: 4 + * 输出: 2 + * 示例 2: + *

+ * 输入: 8 + * 输出: 2 + * 说明: 8 的平方根是 2.82842..., + *   由于返回类型是整数,小数部分将被舍去。 + *

+ * 链接:https://leetcode-cn.com/problems/sqrtx + */ +public class Sqrt { + + public static void main(String[] args) { + //int a = (0 + 2 + 1) / 2; + //int a = (0 + 2 + 1) >>> 1; + //System.out.println(a); + + + int x = 1; + //int x = 3; + //int x = 4; + //int x = 8; + //int x = 2; + //int x = 2147395599; + //System.out.println(String.format("%d的平方根为: %d", x, (int)Math.sqrt(x))); + //System.out.println(String.format("%d的平方根为: %d", x, mySqrt(x))); + //System.out.println(String.format("%d的平方根为: %d", x, mySqrt2(x))); + //System.out.println(String.format("%d的平方根为: %d", x, mySqrtForcely(x))); + + System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); + x = 2; + System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); +// x = 3; +// System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); +// x = 4; +// System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); +// x = 5; +// System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); +// x = 9; +// System.out.println(String.format("%d的平方根为: %d", x, mySqrt4(x))); + } + + /** + * 使用暴力法一个个尝试, 范围为从 x/2 +1 到 0 + * 时间复杂度为O(N) + * 不推荐, 容易超时 + * + * @param x + * @return + */ + private static int mySqrtForcely(int x) { + // 考虑到result * result 会超过int 最大值范围, 这里需要使用long类型来存储 + // 平方根一定小于平方值的一半+1 + long result = x / 2 + 1; // +1是为了处理x为1的特殊情况 + + while (result > 0) { + long square = result * result; + + // 如果平方和大于x, 就每次减1, 一个个尝试直到成功 + if (square > x) { + result--; + } else { + break; + } + } + return (int) result; + } + + /** + * 通过二分查找提高快速定位值 + *

+ * 时间复杂度为O(logN) + * + * @param x + * @return + */ + private static int mySqrt(int x) { + // 为了照顾到0,将左边界值设为0 + long left = 0; + // 为了照顾到1,将有右界值设为1 + long right = x / 2 + 1; + + while (left < right) { + // 这里中位数 左边范围值+右边范围值+1, 往右取中位数, 往左取中位数可能会因为少取 导致获取不到结果 + long mid = (left + right + 1) / 2; + long square = mid * mid; + // 如果中位数的平方大于该数, 就将最大范围缩小,改为中间值-1 + if (square > x) + right = mid - 1; + else // 由于x的平方根很难刚好为整数,所以有时候取不到相等的值, 所以只能是无限接近左边 + // 如果中位数的平方小于该数, 就将最小范围调大,改为中位数 + left = mid; //给结果赋值中位数是因为结果可能是小数, 它大于中位数,但是小于中位数+1. 而且该中位数折半之前也是+1的,比实际中位数大一点 + } + // 返回一个相等或者最接近的值 + return (int) left; + } + + + private static int mySqrt_2(int x) { + long left = 0;// 处理左边界x=0的情况 + long right = x / 2 + 1; //处理右边界x=1的情况 + + while (left < right) { + long mid = (left + right + 1) / 2; //往右取中位数 + long square = mid * mid; + if (square > x) { + right = mid - 1; + } else + left = mid; + } + return (int) left; + } + + + private static int mySqrt3(int x) { + // 处理1的特殊情况 + if (x == 1) + return x; + + long left = 0; + long right = x / 2; //这里无法处理x=1的情况 + + while (left < right) { + // 这里中位数 左边范围值+右边范围值+1, 往右取中位数, 往左取中位数可能会因为少取 导致获取不到结果(出现死循环) + long mid = (left + right + 1) / 2; + long square = mid * mid; + // 如果中位数的平方大于该数, 就将最大范围缩小,改为中间值-1 + if (square > x) + right = mid - 1; + else // 由于x的平方根很难刚好为整数,所以有时候取不到相等的值, 所以只能是无限接近左边 + // 如果中位数的平方小于该数, 就将最小范围调大,改为中位数 + left = mid; //给结果赋值中位数是因为结果可能是小数, 它大于中位数,但是小于中位数+1. 而且该中位数折半之前也是+1的,比实际中位数大一点 + } + // 返回一个相等或者最接近的值 + return (int) left; + } + + private static int mySqrt4(int x) { + if (x == 0 || x == 1) + return x; + + long left = 1; + long right = x; + long result = 0; + + while (left <= right) { + long mid = (left + right) / 2; + long square = mid * mid; + if (square == x) { + return (int) mid; + } + // 大于x, 缩小右界, 从左半部分查找 + if (square > x) { + right = mid - 1; + } else { + // 否则增大左界, 从右半部分查找 + left = mid + 1; + result = mid; + } + } + return (int) result; + } + + /** + * 牛顿迭代法 + * 时间复杂度O(logN) + * + * @param x + * @return + */ + private static int mySqrt5(int x) { + long r = x; + while (r * r > x) { + r = (r + x / r) / 2; + } + return (int)r; + } +} diff --git a/src/main/java/com/study/number/SumofArithmeticSequences.java b/src/main/java/com/study/number/SumofArithmeticSequences.java new file mode 100644 index 0000000..2bba7e6 --- /dev/null +++ b/src/main/java/com/study/number/SumofArithmeticSequences.java @@ -0,0 +1,47 @@ +package com.study.number; + +/** + * n的等差数列求和 + */ +public class SumofArithmeticSequences { + + public static void main(String[] args) { + int n = 10; + System.out.println(sum(n)); + System.out.println(sum2(n)); + } + + /** + * 使用递归实现 n的等差数列求和 + * + * @param n + * @return + */ + private static int sum(int n) { + // 当n=1的时候 结束迭代 + if (n == 1){ + System.out.println("递归往内终止, n = 1"); + return 1; + } + System.out.println("递归往内直到终止条件, n = " + n); + int result = n + sum(n - 1); // n + (n-1) + (n-2) + ... + 1; + System.out.println(String.format("开始真正计算结果, n= %d, result = %d", n, result)); + return result; + } + + /** + * 使用迭代实现 n的等差数列求和 + * + * @param n + * @return + */ + private static int sum2(int n) { + int result = 0; + while (n >= 1) { + result += n; + n--; + } + return result; + } + +} diff --git a/src/main/java/com/study/number/ThreeSum.java b/src/main/java/com/study/number/ThreeSum.java new file mode 100644 index 0000000..de7113a --- /dev/null +++ b/src/main/java/com/study/number/ThreeSum.java @@ -0,0 +1,157 @@ +package com.study.number; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 三数之和 + *

+ * 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 + *

+ * 注意:答案中不可以包含重复的三元组。 + *

+ * 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], + *

+ * 满足要求的三元组集合为: + * [ + * [-1, 0, 1], + * [-1, -1, 2] + * ] + *

+ * https://leetcode-cn.com/problems/3sum/ + */ +public class ThreeSum { + public static void main(String[] args) { + //int[] nums = {-1, 0, 1, 2, -4}; + //int[] nums = {-1, 0, 1}; + //int[] nums = {-1, 0, 1, 2, -2, -4}; + // int[] nums = {-1, 0, 1, 2, -1, -4}; + int[] nums = {0, -4, -1, -4, -2, -3, 2}; + //int[] nums = {82597, -9243, 62390, 83030, -97960, -26521, -61011, 83390, -38677, 12333, 75987, 46091, 83794, 19355, -71037, -6242, -28801, 324, 1202, -90885, -2989, -95597, -34333, 35528, 5680, 89093, -90606, 50360, -29393, -27012, 53313, 65213, 99818, -82405, -41661, -3333, -51952, 72135, -1523, 26377, 74685, 96992, 92263, 15929, 5467, -99555, -43348, -41689, -60383, -3990, 32165, 65265, -72973, -58372, 12741, -48568, -46596, 72419, -1859, 34153, 62937, 81310, -61823, -96770, -54944, 8845, -91184, 24208, -29078, 31495, 65258, 14198, 85395, 70506, -40908, 56740, -12228, -40072, 32429, 93001, 68445, -73927, 25731, -91859, -24150, 10093, -60271, -81683, -18126, 51055, 48189, -6468, 25057, 81194, -58628, 74042, 66158, -14452, -49851, -43667, 11092, 39189, -17025, -79173, 13606, 83172, 92647, -59741, 19343, -26644, -57607, 82908, -20655, 1637, 80060, 98994, 39331, -31274, -61523, 91225, -72953, 13211, -75116, -98421, -41571, -69074, 99587, 39345, 42151, -2460, 98236, 15690, -52507, -95803, -48935, -46492, -45606, -79254, -99851, 52533, 73486, 39948, -7240, 71815, -585, -96252, 90990, -93815, 93340, -71848, 58733, -14859, -83082, -75794, -82082, -24871, -15206, 91207, -56469, -93618, 67131, -8682, 75719, 87429, -98757, -7535, -24890, -94160, 85003, 33928, 75538, 97456, -66424, -60074, -8527, -28697, -22308, 2246, -70134, -82319, -10184, 87081, -34949, -28645, -47352, -83966, -60418, -15293, -53067, -25921, 55172, 75064, 95859, 48049, 34311, -86931, -38586, 33686, -36714, 96922, 76713, -22165, -80585, -34503, -44516, 39217, -28457, 47227, -94036, 43457, 24626, -87359, 26898, -70819, 30528, -32397, -69486, 84912, -1187, -98986, -32958, 4280, -79129, -65604, 9344, 58964, 50584, 71128, -55480, 24986, 15086, -62360, -42977, -49482, -77256, -36895, -74818, 20, 3063, -49426, 28152, -97329, 6086, 86035, -88743, 35241, 44249, 19927, -10660, 89404, 24179, -26621, -6511, 57745, -28750, 96340, -97160, -97822, -49979, 52307, 79462, 94273, -24808, 77104, 9255, -83057, 77655, 21361, 55956, -9096, 48599, -40490, -55107, 2689, 29608, 20497, 66834, -34678, 23553, -81400, -66630, -96321, -34499, -12957, -20564, 25610, -4322, -58462, 20801, 53700, 71527, 24669, -54534, 57879, -3221, 33636, 3900, 97832, -27688, -98715, 5992, 24520, -55401, -57613, -69926, 57377, -77610, 20123, 52174, 860, 60429, -91994, -62403, -6218, -90610, -37263, -15052, 62069, -96465, 44254, 89892, -3406, 19121, -41842, -87783, -64125, -56120, 73904, -22797, -58118, -4866, 5356, 75318, 46119, 21276, -19246, -9241, -97425, 57333, -15802, 93149, 25689, -5532, 95716, 39209, -87672, -29470, -16324, -15331, 27632, -39454, 56530, -16000, 29853, 46475, 78242, -46602, 83192, -73440, -15816, 50964, -36601, 89758, 38375, -40007, -36675, -94030, 67576, 46811, -64919, 45595, 76530, 40398, 35845, 41791, 67697, -30439, -82944, 63115, 33447, -36046, -50122, -34789, 43003, -78947, -38763, -89210, 32756, -20389, -31358, -90526, -81607, 88741, 86643, 98422, 47389, -75189, 13091, 95993, -15501, 94260, -25584, -1483, -67261, -70753, 25160, 89614, -90620, -48542, 83889, -12388, -9642, -37043, -67663, 28794, -8801, 13621, 12241, 55379, 84290, 21692, -95906, -85617, -17341, -63767, 80183, -4942, -51478, 30997, -13658, 8838, 17452, -82869, -39897, 68449, 31964, 98158, -49489, 62283, -62209, -92792, -59342, 55146, -38533, 20496, 62667, 62593, 36095, -12470, 5453, -50451, 74716, -17902, 3302, -16760, -71642, -34819, 96459, -72860, 21638, 47342, -69897, -40180, 44466, 76496, 84659, 13848, -91600, -90887, -63742, -2156, -84981, -99280, 94326, -33854, 92029, -50811, 98711, -36459, -75555, 79110, -88164, -97397, -84217, 97457, 64387, 30513, -53190, -83215, 252, 2344, -27177, -92945, -89010, 82662, -11670, 86069, 53417, 42702, 97082, 3695, -14530, -46334, 17910, 77999, 28009, -12374, 15498, -46941, 97088, -35030, 95040, 92095, -59469, -24761, 46491, 67357, -66658, 37446, -65130, -50416, 99197, 30925, 27308, 54122, -44719, 12582, -99525, -38446, -69050, -22352, 94757, -56062, 33684, -40199, -46399, 96842, -50881, -22380, -65021, 40582, 53623, -76034, 77018, -97074, -84838, -22953, -74205, 79715, -33920, -35794, -91369, 73421, -82492, 63680, -14915, -33295, 37145, 76852, -69442, 60125, -74166, 74308, -1900, -30195, -16267, -60781, -27760, 5852, 38917, 25742, -3765, 49097, -63541, 98612, -92865, -30248, 9612, -8798, 53262, 95781, -42278, -36529, 7252, -27394, -5021, 59178, 80934, -48480, -75131, -54439, -19145, -48140, 98457, -6601, -51616, -89730, 78028, 32083, -48904, 16822, -81153, -8832, 48720, -80728, -45133, -86647, -4259, -40453, 2590, 28613, 50523, -4105, -27790, -74579, -17223, 63721, 33489, -47921, 97628, -97691, -14782, -65644, 18008, -93651, -71266, 80990, -76732, -47104, 35368, 28632, 59818, -86269, -89753, 34557, -92230, -5933, -3487, -73557, -13174, -43981, -43630, -55171, 30254, -83710, -99583, -13500, 71787, 5017, -25117, -78586, 86941, -3251, -23867, -36315, 75973, 86272, -45575, 77462, -98836, -10859, 70168, -32971, -38739, -12761, 93410, 14014, -30706, -77356, -85965, -62316, 63918, -59914, -64088, 1591, -10957, 38004, 15129, -83602, -51791, 34381, -89382, -26056, 8942, 5465, 71458, -73805, -87445, -19921, -80784, 69150, -34168, 28301, -68955, 18041, 6059, 82342, 9947, 39795, 44047, -57313, 48569, 81936, -2863, -80932, 32976, -86454, -84207, 33033, 32867, 9104, -16580, -25727, 80157, -70169, 53741, 86522, 84651, 68480, 84018, 61932, 7332, -61322, -69663, 76370, 41206, 12326, -34689, 17016, 82975, -23386, 39417, 72793, 44774, -96259, 3213, 79952, 29265, -61492, -49337, 14162, 65886, 3342, -41622, -62659, -90402, -24751, 88511, 54739, -21383, -40161, -96610, -24944, -602, -76842, -21856, 69964, 43994, -15121, -85530, 12718, 13170, -13547, 69222, 62417, -75305, -81446, -38786, -52075, -23110, 97681, -82800, -53178, 11474, 35857, 94197, -58148, -23689, 32506, 92154, -64536, -73930, -77138, 97446, -83459, 70963, 22452, 68472, -3728, -25059, -49405, 95129, -6167, 12808, 99918, 30113, -12641, -26665, 86362, -33505, 50661, 26714, 33701, 89012, -91540, 40517, -12716, -57185, -87230, 29914, -59560, 13200, -72723, 58272, 23913, -45586, -96593, -26265, -2141, 31087, 81399, 92511, -34049, 20577, 2803, 26003, 8940, 42117, 40887, -82715, 38269, 40969, -50022, 72088, 21291, -67280, -16523, 90535, 18669, 94342, -39568, -88080, -99486, -20716, 23108, -28037, 63342, 36863, -29420, -44016, 75135, 73415, 16059, -4899, 86893, 43136, -7041, 33483, -67612, 25327, 40830, 6184, 61805, 4247, 81119, -22854, -26104, -63466, 63093, -63685, 60369, 51023, 51644, -16350, 74438, -83514, 99083, 10079, -58451, -79621, 48471, 67131, -86940, 99093, 11855, -22272, -67683, -44371, 9541, 18123, 37766, -70922, 80385, -57513, -76021, -47890, 36154, 72935, 84387, -92681, -88303, -7810, 59902, -90, -64704, -28396, -66403, 8860, 13343, 33882, 85680, 7228, 28160, -14003, 54369, -58893, 92606, -63492, -10101, 64714, 58486, 29948, -44679, -22763, 10151, -56695, 4031, -18242, -36232, 86168, -14263, 9883, 47124, 47271, 92761, -24958, -73263, -79661, -69147, -18874, 29546, -92588, -85771, 26451, -86650, -43306, -59094, -47492, -34821, -91763, -47670, 33537, 22843, 67417, -759, 92159, 63075, 94065, -26988, 55276, 65903, 30414, -67129, -99508, -83092, -91493, -50426, 14349, -83216, -76090, 32742, -5306, -93310, -60750, -60620, -45484, -21108, -58341, -28048, -52803, 69735, 78906, 81649, 32565, -86804, -83202, -65688, -1760, 89707, 93322, -72750, 84134, 71900, -37720, 19450, -78018, 22001, -23604, 26276, -21498, 65892, -72117, -89834, -23867, 55817, -77963, 42518, 93123, -83916, 63260, -2243, -97108, 85442, -36775, 17984, -58810, 99664, -19082, 93075, -69329, 87061, 79713, 16296, 70996, 13483, -74582, 49900, -27669, -40562, 1209, -20572, 34660, 83193, 75579, 7344, 64925, 88361, 60969, 3114, 44611, -27445, 53049, -16085, -92851, -53306, 13859, -33532, 86622, -75666, -18159, -98256, 51875, -42251, -27977, -18080, 23772, 38160, 41779, 9147, 94175, 99905, -85755, 62535, -88412, -52038, -68171, 93255, -44684, -11242, -104, 31796, 62346, -54931, -55790, -70032, 46221, 56541, -91947, 90592, 93503, 4071, 20646, 4856, -63598, 15396, -50708, 32138, -85164, 38528, -89959, 53852, 57915, -42421, -88916, -75072, 67030, -29066, 49542, -71591, 61708, -53985, -43051, 28483, 46991, -83216, 80991, -46254, -48716, 39356, -8270, -47763, -34410, 874, -1186, -7049, 28846, 11276, 21960, -13304, -11433, -4913, 55754, 79616, 70423, -27523, 64803, 49277, 14906, -97401, -92390, 91075, 70736, 21971, -3303, 55333, -93996, 76538, 54603, -75899, 98801, 46887, 35041, 48302, -52318, 55439, 24574, 14079, -24889, 83440, 14961, 34312, -89260, -22293, -81271, -2586, -71059, -10640, -93095, -5453, -70041, 66543, 74012, -11662, -52477, -37597, -70919, 92971, -17452, -67306, -80418, 7225, -89296, 24296, 86547, 37154, -10696, 74436, -63959, 58860, 33590, -88925, -97814, -83664, 85484, -8385, -50879, 57729, -74728, -87852, -15524, -91120, 22062, 28134, 80917, 32026, 49707, -54252, -44319, -35139, 13777, 44660, 85274, 25043, 58781, -89035, -76274, 6364, -63625, 72855, 43242, -35033, 12820, -27460, 77372, -47578, -61162, -70758, -1343, -4159, 64935, 56024, -2151, 43770, 19758, -30186, -86040, 24666, -62332, -67542, 73180, -25821, -27826, -45504, -36858, -12041, 20017, -24066, -56625, -52097, -47239, -90694, 8959, 7712, -14258, -5860, 55349, 61808, -4423, -93703, 64681, -98641, -25222, 46999, -83831, -54714, 19997, -68477, 66073, 51801, -66491, 52061, -52866, 79907, -39736, -68331, 68937, 91464, 98892, 910, 93501, 31295, -85873, 27036, -57340, 50412, 21, -2445, 29471, 71317, 82093, -94823, -54458, -97410, 39560, -7628, 66452, 39701, 54029, 37906, 46773, 58296, 60370, -61090, 85501, -86874, 71443, -72702, -72047, 14848, 34102, 77975, -66294, -36576, 31349, 52493, -70833, -80287, 94435, 39745, -98291, 84524, -18942, 10236, 93448, 50846, 94023, -6939, 47999, 14740, 30165, 81048, 84935, -19177, -13594, 32289, 62628, -90612, -542, -66627, 64255, 71199, -83841, -82943, -73885, 8623, -67214, -9474, -35249, 62254, -14087, -90969, 21515, -83303, 94377, -91619, 19956, -98810, 96727, -91939, 29119, -85473, -82153, -69008, 44850, 74299, -76459, -86464, 8315, -49912, -28665, 59052, -69708, 76024, -92738, 50098, 18683, -91438, 18096, -19335, 35659, 91826, 15779, -73070, 67873, -12458, -71440, -46721, 54856, 97212, -81875, 35805, 36952, 68498, 81627, -34231, 81712, 27100, -9741, -82612, 18766, -36392, 2759, 41728, 69743, 26825, 48355, -17790, 17165, 56558, 3295, -24375, 55669, -16109, 24079, 73414, 48990, -11931, -78214, 90745, 19878, 35673, -15317, -89086, 94675, -92513, 88410, -93248, -19475, -74041, -19165, 32329, -26266, -46828, -18747, 45328, 8990, -78219, -25874, -74801, -44956, -54577, -29756, -99822, -35731, -18348, -68915, -83518, -53451, 95471, -2954, -13706, -8763, -21642, -37210, 16814, -60070, -42743, 27697, -36333, -42362, 11576, 85742, -82536, 68767, -56103, -63012, 71396, -78464, -68101, -15917, -11113, -3596, 77626, -60191, -30585, -73584, 6214, -84303, 18403, 23618, -15619, -89755, -59515, -59103, -74308, -63725, -29364, -52376, -96130, 70894, -12609, 50845, -2314, 42264, -70825, 64481, 55752, 4460, -68603, -88701, 4713, -50441, -51333, -77907, 97412, -66616, -49430, 60489, -85262, -97621, -18980, 44727, -69321, -57730, 66287, -92566, -64427, -14270, 11515, -92612, -87645, 61557, 24197, -81923, -39831, -10301, -23640, -76219, -68025, 92761, -76493, 68554, -77734, -95620, -11753, -51700, 98234, -68544, -61838, 29467, 46603, -18221, -35441, 74537, 40327, -58293, 75755, -57301, -7532, -94163, 18179, -14388, -22258, -46417, -48285, 18242, -77551, 82620, 250, -20060, -79568, -77259, 82052, -98897, -75464, 48773, -79040, -11293, 45941, -67876, -69204, -46477, -46107, 792, 60546, -34573, -12879, -94562, 20356, -48004, -62429, 96242, 40594, 2099, 99494, 25724, -39394, -2388, -18563, -56510, -83570, -29214, 3015, 74454, 74197, 76678, -46597, 60630, -76093, 37578, -82045, -24077, 62082, -87787, -74936, 58687, 12200, -98952, 70155, -77370, 21710, -84625, -60556, -84128, 925, 65474, -15741, -94619, 88377, 89334, 44749, 22002, -45750, -93081, -14600, -83447, 46691, 85040, -66447, -80085, 56308, 44310, 24979, -29694, 57991, 4675, -71273, -44508, 13615, -54710, 23552, -78253, -34637, 50497, 68706, 81543, -88408, -21405, 6001, -33834, -21570, -46692, -25344, 20310, 71258, -97680, 11721, 59977, 59247, -48949, 98955, -50276, -80844, -27935, -76102, 55858, -33492, 40680, 66691, -33188, 8284, 64893, -7528, 6019, -85523, 8434, -64366, -56663, 26862, 30008, -7611, -12179, -70076, 21426, -11261, -36864, -61937, -59677, 929, -21052, 3848, -20888, -16065, 98995, -32293, -86121, -54564, 77831, 68602, 74977, 31658, 40699, 29755, 98424, 80358, -69337, 26339, 13213, -46016, -18331, 64713, -46883, -58451, -70024, -92393, -4088, 70628, -51185, 71164, -75791, -1636, -29102, -16929, -87650, -84589, -24229, -42137, -15653, 94825, 13042, 88499, -47100, -90358, -7180, 29754, -65727, -42659, -85560, -9037, -52459, 20997, -47425, 17318, 21122, 20472, -23037, 65216, -63625, -7877, -91907, 24100, -72516, 22903, -85247, -8938, 73878, 54953, 87480, -31466, -99524, 35369, -78376, 89984, -15982, 94045, -7269, 23319, -80456, -37653, -76756, 2909, 81936, 54958, -12393, 60560, -84664, -82413, 66941, -26573, -97532, 64460, 18593, -85789, -38820, -92575, -43663, -89435, 83272, -50585, 13616, -71541, -53156, 727, -27644, 16538, 34049, 57745, 34348, 35009, 16634, -18791, 23271, -63844, 95817, 21781, 16590, 59669, 15966, -6864, 48050, -36143, 97427, -59390, 96931, 78939, -1958, 50777, 43338, -51149, 39235, -27054, -43492, 67457, -83616, 37179, 10390, 85818, 2391, 73635, 87579, -49127, -81264, -79023, -81590, 53554, -74972, -83940, -13726, -39095, 29174, 78072, 76104, 47778, 25797, -29515, -6493, -92793, 22481, -36197, -65560, 42342, 15750, 97556, 99634, -56048, -35688, 13501, 63969, -74291, 50911, 39225, 93702, -3490, -59461, -30105, -46761, -80113, 92906, -68487, 50742, 36152, -90240, -83631, 24597, -50566, -15477, 18470, 77038, 40223, -80364, -98676, 70957, -63647, 99537, 13041, 31679, 86631, 37633, -16866, 13686, -71565, 21652, -46053, -80578, -61382, 68487, -6417, 4656, 20811, 67013, -30868, -11219, 46, 74944, 14627, 56965, 42275, -52480, 52162, -84883, -52579, -90331, 92792, 42184, -73422, -58440, 65308, -25069, 5475, -57996, 59557, -17561, 2826, -56939, 14996, -94855, -53707, 99159, 43645, -67719, -1331, 21412, 41704, 31612, 32622, 1919, -69333, -69828, 22422, -78842, 57896, -17363, 27979, -76897, 35008, 46482, -75289, 65799, 20057, 7170, 41326, -76069, 90840, -81253, -50749, 3649, -42315, 45238, -33924, 62101, 96906, 58884, -7617, -28689, -66578, 62458, 50876, -57553, 6739, 41014, -64040, -34916, 37940, 13048, -97478, -11318, -89440, -31933, -40357, -59737, -76718, -14104, -31774, 28001, 4103, 41702, -25120, -31654, 63085, -3642, 84870, -83896, -76422, -61520, 12900, 88678, 85547, 33132, -88627, 52820, 63915, -27472, 78867, -51439, 33005, -23447, -3271, -39308, 39726, -74260, -31874, -36893, 93656, 910, -98362, 60450, -88048, 99308, 13947, 83996, -90415, -35117, 70858, -55332, -31721, 97528, 82982, -86218, 6822, 25227, 36946, 97077, -4257, -41526, 56795, 89870, 75860, -70802, 21779, 14184, -16511, -89156, -31422, 71470, 69600, -78498, 74079, -19410, 40311, 28501, 26397, -67574, -32518, 68510, 38615, 19355, -6088, -97159, -29255, -92523, 3023, -42536, -88681, 64255, 41206, 44119, 52208, 39522, -52108, 91276, -70514, 83436, 63289, -79741, 9623, 99559, 12642, 85950, 83735, -21156, -67208, 98088, -7341, -27763, -30048, -44099, -14866, -45504, -91704, 19369, 13700, 10481, -49344, -85686, 33994, 19672, 36028, 60842, 66564, -24919, 33950, -93616, -47430, -35391, -28279, 56806, 74690, 39284, -96683, -7642, -75232, 37657, -14531, -86870, -9274, -26173, 98640, 88652, 64257, 46457, 37814, -19370, 9337, -22556, -41525, 39105, -28719, 51611, -93252, 98044, -90996, 21710, -47605, -64259, -32727, 53611, -31918, -3555, 33316, -66472, 21274, -37731, -2919, 15016, 48779, -88868, 1897, 41728, 46344, -89667, 37848, 68092, -44011, 85354, -43776, 38739, -31423, -66330, 65167, -22016, 59405, 34328, -60042, 87660, -67698, -59174, -1408, -46809, -43485, -88807, -60489, 13974, 22319, 55836, -62995, -37375, -4185, 32687, -36551, -75237, 58280, 26942, -73756, 71756, 78775, -40573, 14367, -71622, -77338, 24112, 23414, -7679, -51721, 87492, 85066, -21612, 57045, 10673, -96836, 52461, -62218, -9310, 65862, -22748, 89906, -96987, -98698, 26956, -43428, 46141, 47456, 28095, 55952, 67323, -36455, -60202, -43302, -82932, 42020, 77036, 10142, 60406, 70331, 63836, 58850, -66752, 52109, 21395, -10238, -98647, -41962, 27778, 69060, 98535, -28680, -52263, -56679, 66103, -42426, 27203, 80021, 10153, 58678, 36398, 63112, 34911, 20515, 62082, -15659, -40785, 27054, 43767, -20289, 65838, -6954, -60228, -72226, 52236, -35464, 25209, -15462, -79617, -41668, -84083, 62404, -69062, 18913, 46545, 20757, 13805, 24717, -18461, -47009, -25779, 68834, 64824, 34473, 39576, 31570, 14861, -15114, -41233, 95509, 68232, 67846, 84902, -83060, 17642, -18422, 73688, 77671, -26930, 64484, -99637, 73875, 6428, 21034, -73471, 19664, -68031, 15922, -27028, 48137, 54955, -82793, -41144, -10218, -24921, -28299, -2288, 68518, -54452, 15686, -41814, 66165, -72207, -61986, 80020, 50544, -99500, 16244, 78998, 40989, 14525, -56061, -24692, -94790, 21111, 37296, -90794, 72100, 70550, -31757, 17708, -74290, 61910, 78039, -78629, -25033, 73172, -91953, 10052, 64502, 99585, -1741, 90324, -73723, 68942, 28149, 30218, 24422, 16659, 10710, -62594, 94249, 96588, 46192, 34251, 73500, -65995, -81168, 41412, -98724, -63710, -54696, -52407, 19746, 45869, 27821, -94866, -76705, -13417, -61995, -71560, 43450, 67384, -8838, -80293, -28937, 23330, -89694, -40586, 46918, 80429, -5475, 78013, 25309, -34162, 37236, -77577, 86744, 26281, -29033, -91813, 35347, 13033, -13631, -24459, 3325, -71078, -75359, 81311, 19700, 47678, -74680, -84113, 45192, 35502, 37675, 19553, 76522, -51098, -18211, 89717, 4508, -82946, 27749, 85995, 89912, -53678, -64727, -14778, 32075, -63412, -40524, 86440, -2707, -36821, 63850, -30883, 67294, -99468, -23708, 34932, 34386, 98899, 29239, -23385, 5897, 54882, 98660, 49098, 70275, 17718, 88533, 52161, 63340, 50061, -89457, 19491, -99156, 24873, -17008, 64610, -55543, 50495, 17056, -10400, -56678, -29073, -42960, -76418, 98562, -88104, -96255, 10159, -90724, 54011, 12052, 45871, -90933, -69420, 67039, 37202, 78051, -52197, -40278, -58425, 65414, -23394, -1415, 6912, -53447, 7352, 17307, -78147, 63727, 98905, 55412, -57658, -32884, -44878, 22755, 39730, 3638, 35111, 39777, 74193, 38736, -11829, -61188, -92757, 55946, -71232, -63032, -83947, 39147, -96684, -99233, 25131, -32197, 24406, -55428, -61941, 25874, -69453, 64483, -19644, -68441, 12783, 87338, -48676, 66451, -447, -61590, 50932, -11270, 29035, 65698, -63544, 10029, 80499, -9461, 86368, 91365, -81810, -71914, -52056, -13782, 44240, -30093, -2437, 24007, 67581, -17365, -69164, -8420, -69289, -29370, 48010, 90439, 13141, 69243, 50668, 39328, 61731, 78266, -81313, 17921, -38196, 55261, 9948, -24970, 75712, -72106, 28696, 7461, 31621, 61047, 51476, 56512, 11839, -96916, -82739, 28924, -99927, 58449, 37280, 69357, 11219, -32119, -62050, -48745, -83486, -52376, 42668, 82659, 68882, 38773, 46269, -96005, 97630, 25009, -2951, -67811, 99801, 81587, -79793, -18547, -83086, 69512, 33127, -92145, -88497, 47703, 59527, 1909, 88785, -88882, 69188, -46131, -5589, -15086, 36255, -53238, -33009, 82664, 53901, 35939, -42946, -25571, 33298, 69291, 53199, 74746, -40127, -39050, 91033, 51717, -98048, 87240, 36172, 65453, -94425, -63694, -30027, 59004, 88660, 3649, -20267, -52565, -67321, 34037, 4320, 91515, -56753, 60115, 27134, 68617, -61395, -26503, -98929, -8849, -63318, 10709, -16151, 61905, -95785, 5262, 23670, -25277, 90206, -19391, 45735, 37208, -31992, -92450, 18516, -90452, -58870, -58602, 93383, 14333, 17994, 82411, -54126, -32576, 35440, -60526, -78764, -25069, -9022, -394, 92186, -38057, 55328, -61569, 67780, 77169, 19546, -92664, -94948, 44484, -13439, 83529, 27518, -48333, 72998, 38342, -90553, -98578, -76906, 81515, -16464, 78439, 92529, 35225, -39968, -10130, -7845, -32245, -74955, -74996, 67731, -13897, -82493, 33407, 93619, 59560, -24404, -57553, 19486, -45341, 34098, -24978, -33612, 79058, 71847, 76713, -95422, 6421, -96075, -59130, -28976, -16922, -62203, 69970, 68331, 21874, 40551, 89650, 51908, 58181, 66480, -68177, 34323, -3046, -49656, -59758, 43564, -10960, -30796, 15473, -20216, 46085, -85355, 41515, -30669, -87498, 57711, 56067, 63199, -83805, 62042, 91213, -14606, 4394, -562, 74913, 10406, 96810, -61595, 32564, 31640, -9732, 42058, 98052, -7908, -72330, 1558, -80301, 34878, 32900, 3939, -8824, 88316, 20937, 21566, -3218, -66080, -31620, 86859, 54289, 90476, -42889, -15016, -18838, 75456, 30159, -67101, 42328, -92703, 85850, -5475, 23470, -80806, 68206, 17764, 88235, 46421, -41578, 74005, -81142, 80545, 20868, -1560, 64017, 83784, 68863, -97516, -13016, -72223, 79630, -55692, 82255, 88467, 28007, -34686, -69049, -41677, 88535, -8217, 68060, -51280, 28971, 49088, 49235, 26905, -81117, -44888, 40623, 74337, -24662, 97476, 79542, -72082, -35093, 98175, -61761, -68169, 59697, -62542, -72965, 59883, -64026, -37656, -92392, -12113, -73495, 98258, 68379, -21545, 64607, -70957, -92254, -97460, -63436, -8853, -19357, -51965, -76582, 12687, -49712, 45413, -60043, 33496, 31539, -57347, 41837, 67280, -68813, 52088, -13155, -86430, -15239, -45030, 96041, 18749, -23992, 46048, 35243, -79450, 85425, -58524, 88781, -39454, 53073, -48864, -82289, 39086, 82540, -11555, 25014, -5431, -39585, -89526, 2705, 31953, -81611, 36985, -56022, 68684, -27101, 11422, 64655, -26965, -63081, -13840, -91003, -78147, -8966, 41488, 1988, 99021, -61575, -47060, 65260, -23844, -21781, -91865, -19607, 44808, 2890, 63692, -88663, -58272, 15970, -65195, -45416, -48444, -78226, -65332, -24568, 42833, -1806, -71595, 80002, -52250, 30952, 48452, -90106, 31015, -22073, 62339, 63318, 78391, 28699, 77900, -4026, -76870, -45943, 33665, 9174, -84360, -22684, -16832, -67949, -38077, -38987, -32847, 51443, -53580, -13505, 9344, -92337, 26585, 70458, -52764, -67471, -68411, -1119, -2072, -93476, 67981, 40887, -89304, -12235, 41488, 1454, 5355, -34855, -72080, 24514, -58305, 3340, 34331, 8731, 77451, -64983, -57876, 82874, 62481, -32754, -39902, 22451, -79095, -23904, 78409, -7418, 77916}; + //List> resultList = threeSum(nums); + List> resultList = threeSum_2(nums); + for (List list : resultList) { + for (Integer num : list) { + System.out.print(num + " "); + } + System.out.println(); + } + } + + private static List> threeSum(int[] nums) { + List> result = new ArrayList<>(); + + if (nums == null || nums.length == 0) { + return result; + } + + // 对数组进行排序, 确保可以使用第2和第3个元素前后双指针 + Arrays.sort(nums); + + // 遍历第一个元素 + for (int i = 0; i < nums.length - 2; i++) { + // 第2个元素从第一个的下一个开始 + int left = i + 1; + // 第3个元素从后面开始 + int right = nums.length - 1; + while (left < right) { + // 符合条件的 + if (nums[left] + nums[right] == -nums[i]) { + result.add(Arrays.asList(nums[i], nums[left], nums[right])); + left++; //之后将left++, 再找满足条件的right + } else { + right--;//如果right过大不满足,那就往前挪动变小一些 + } + } + } + return result; + } + + private static List> threeSum_2(int[] nums) { + List> result = new ArrayList<>(); + if (nums == null || nums.length == 0) { + return result; + } + // 数组排序后方可使用双指针 + Arrays.sort(nums); + + // 枚举第一个元素, 并对另外的两个元素设置左右两个指针 + for (int i = 0; i < nums.length - 2; i++) {//由于left 和 right元素在第一个元素右边,所以第一个元素只需要遍历数组长度-3个即可 + int left = i + 1; //左边指针位置 + int right = nums.length - 1; //右边指针位置 + + while (left < right) { + // 满足条件的 + if (nums[left] + nums[right] == -nums[i]) { + System.out.println(nums[i] + " " + nums[left] + " " + nums[right]); + List elements = Arrays.asList(nums[i], nums[left], nums[right]); + // 过滤重复的元素 + if (!result.contains(elements)) { + result.add(elements); + } + left++; // 再讲left往右移, 找下一个满足条件的 + } else { + // 如果right不满足条件, 比结果target 那就往前挪动一位,尝试下一个 + right--; + } + } + } + return result; + } + + + /** + * 穷举法 (不推荐) + *

+ * 时间复杂度O^3 + * + * @param nums + * @return + */ + private static List> threeSum2(int[] nums) { + List> resultList = new ArrayList>(); + for (int i = 0; i < nums.length; i++) { + for (int j = i + 1; j < nums.length; j++) { + for (int z = j + 1; z < nums.length; z++) { + if (nums[i] + nums[j] + nums[z] == 0) { + if (i != j && i != z && j != z) { + //去掉可能重复的值 这里需要注意相同值的处理 比如 0 0 0 + if (!contains(resultList, nums[i], nums[j], nums[z])) + resultList.add(Arrays.asList(nums[i], nums[j], nums[z])); + } + } + } + } + } + return resultList; + } + + /** + * 判断resultList是否有一条集合包含a,b,c3个元素 + * + * @param resultList + * @param a + * @param b + * @param c + * @return + */ + private static boolean contains(List> resultList, int a, int b, int c) { + for (List result : resultList) { + // 首先判断resultList某一个子集合中是否有元素与a,b,c相等 比如子集合{0,1,-1} 中有元素0和 {0,0,0}相等 + boolean contains = result.contains(a) && result.contains(b) && result.contains(c); + // 再判断这个子集合中是否有元素与a,b,c均不相等 比如 子集合{0,1,-1}中有1和-1 与 {0,0,0}中的元素均不相等 + + if (!contains) continue; + + for (Integer r : result) { + if (r != a && r != b && r != c) { + contains = false; //说明子集合有元素与{0,0,0}中的元素不相等, 那么子集合元素和a,b,c并不完全相同 + } + } + + if (contains) return true; + } + return false; + } +} diff --git a/src/main/java/com/study/number/ThreeSumSmaller.java b/src/main/java/com/study/number/ThreeSumSmaller.java new file mode 100644 index 0000000..2121fd0 --- /dev/null +++ b/src/main/java/com/study/number/ThreeSumSmaller.java @@ -0,0 +1,58 @@ +package com.study.number; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 三数之和小于target + *

+ * 题目描述:给定一个n个整数的数组和一个目标整数target,找到下标为i、j、k的数组元素0 <= i < j < k < n, + * 满足条件nums[i] + nums[j] + nums[k] < target。输出满足上述条件的组合的总数。 + */ +public class ThreeSumSmaller { + + public static void main(String[] args) { + int[] nums = {5, 1, 7, 2, 8, 9, 3, 4}; + + //List arr = threeSumSmaller(nums, 9); + List arr = threeSumSmaller(nums, 12); + + for (int i = 0; i < arr.size(); i += 3) { + System.out.println(String.format("%d %d %d", arr.get(i), arr.get(i + 1), arr.get(i + 2))); + } + } + + private static List threeSumSmaller(int[] nums, int target) { + List result = new ArrayList<>(); + if (nums == null || nums.length == 0) { + return result; + } + + // 数组排序后方可使用双指针 + Arrays.sort(nums); + + // 枚举第一个元素, 并对另外的两个元素设置左右两个指针 + for (int i = 0; i < nums.length - 2; i++) {//由于left 和 right元素在第一个元素右边,所以第一个元素只需要遍历数组长度-3个即可 + int left = i + 1; //左边指针位置 + int right = nums.length - 1; //右边指针位置 + + while (left < right) { + // 左边保持位置不变, 如果right + left 满足条件, 那么从right指针从此处一直移动到left+1的元素都满足条件, 因为数组元素是从小到大排列的,最大的right满足,那更小的right都应该满足 + if (nums[left] + nums[right] < target - nums[i]) { + // 固定left和i, 调整right, 获取所有满足条件的right + for (int j = left + 1; j <= right; j++) { + result.add(nums[i]); + result.add(nums[left]); + result.add(nums[j]); + } + left++; // 获取完 固定i和left后的right元素后, 将left往后移 + } else { + right--; + // 如果right不满足条件,比结果target 那就往前挪动一位 + } + } + } + return result; + } +} diff --git a/src/main/java/com/study/number/TwoSum.java b/src/main/java/com/study/number/TwoSum.java index 4904026..64bd610 100644 --- a/src/main/java/com/study/number/TwoSum.java +++ b/src/main/java/com/study/number/TwoSum.java @@ -76,10 +76,12 @@ private static int[] twoSum3(int[] nums, int target) { int complement = target - nums[i]; //将目标元素减去当前元素求得差值元素 // 查看hashmap里面是否存在这个差值元素, //if (map.containsKey(complement) && map.get(complement) != i) { //并且这个差值元素不能是nums[i]本身, 实际来说map是不会存在nums[i]的, 因为往map里面添加nums[i]是在最后面 + // map是否包含7这个值, map.get(complement)就是7的索引 if (map.containsKey(complement)){ //所以只判断是否包含该元素和上面代码是一样的 return new int[]{i, map.get(complement)}; } - map.put(nums[i], i); + // 每次都将差值和索引存入, 以后如果数组存在差值这个元素, 那就直接取出value索引就找到了 + map.put(nums[i], i);// map 可以为值, value为索引 } return new int[0]; } diff --git a/src/main/java/com/study/search/BinarySearch.java b/src/main/java/com/study/search/BinarySearch.java index 66a5f17..a779a7e 100644 --- a/src/main/java/com/study/search/BinarySearch.java +++ b/src/main/java/com/study/search/BinarySearch.java @@ -25,18 +25,19 @@ public static void main(String[] args) { * @return */ private static int search(int[] arr, int key) { - int low, high, mid; //第1个元素索引 - low = 0; + int low = 0; + //最后一个元素索引 - high = arr.length - 1; + int high = arr.length - 1; while (low <= high) { // 先从中间开始查找 - mid = (low + high) / 2; + //int mid = (low + high) / 2; // low + high 可能会大于 Integer.MAX_VALUE + int mid = ((high - low) >> 1) + low; // 如果小于中间的,就把范围改成最左边到中间-1 if (key < arr[mid]) { high = mid - 1; - // 如果大于中间的,就把范围改成中间+1到最右边 + // 如果大于中间的,就把范围改成中间+1到最右边 } else if (key > arr[mid]) { low = mid + 1; } else { diff --git a/src/main/java/com/study/search/BinarySearch2.java b/src/main/java/com/study/search/BinarySearch2.java new file mode 100644 index 0000000..68a990f --- /dev/null +++ b/src/main/java/com/study/search/BinarySearch2.java @@ -0,0 +1,228 @@ +package com.study.search; + +/** + * 二分查找4种变形题: + *

+ * 找到第一个相同的元素(存在重复的元素) + * 找到最后一个相同的元素(存在重复的元素) + * 找到第一个大于等于的元素(存在重复的元素) + * 找到最后一个小于等于的元素(存在重复的元素) + */ +public class BinarySearch2 { + + /** + * ((right - left) >> 1) + left 等价于 (left + right) / 2 + * + * @param args + */ + public static void main(String[] args) { + //int[] arr = {-1, 0, 1, 2, 4, 5}; + //int[] arr = {0}; + int[] arr = {}; + System.out.println(find(arr, 0)); + System.out.println(find(arr, -1)); + System.out.println(find(arr, 1)); + System.out.println(find(arr, 2)); + System.out.println(find(arr, 4)); + System.out.println(find(arr, 5)); + System.out.println("---------------查找第一个相同元素"); + //arr = new int[]{-2, -1, 0, 2, 2, 3, 3, 5}; + //arr = new int[]{}; + arr = new int[]{-2}; + System.out.println(findFirst(arr, -2)); + System.out.println(findFirst(arr, 0)); + System.out.println(findFirst(arr, 2)); + System.out.println(findFirst(arr, 3)); + System.out.println(findFirst(arr, 5)); + System.out.println("---------------查找最后一个相同元素"); + System.out.println(findLast(arr, -2)); + System.out.println(findLast(arr, 0)); + System.out.println(findLast(arr, 2)); + System.out.println(findLast(arr, 3)); + System.out.println(findLast(arr, 5)); + System.out.println("---------------查找第一个大于等于元素"); + arr = new int[]{-2, 0, 2, 2, 3, 3, 5}; + System.out.println(findFirstLargerEquals(arr, -2)); //0 + System.out.println(findFirstLargerEquals(arr, -1)); //1 + System.out.println(findFirstLargerEquals(arr, 1)); //2 + System.out.println(findFirstLargerEquals(arr, 5)); //6 + System.out.println(findFirstLargerEquals(arr, 2)); //2 + System.out.println(findFirstLargerEquals(arr, 7)); //-1 没找到 + System.out.println(findFirstLargerEquals(arr, 0)); //1 + + arr = new int[]{-2}; + System.out.println(findFirstLargerEquals(arr, -3)); //0 + + arr = new int[]{}; + System.out.println(findFirstLargerEquals(arr, -3)); //-1 + + System.out.println("---------------查找最后一个小于等于元素"); + arr = new int[]{-2, 0, 2, 2, 3, 3, 5}; + System.out.println(findLastSmallerEquals(arr, -2)); //0 + System.out.println(findLastSmallerEquals(arr, -1)); //0 + System.out.println(findLastSmallerEquals(arr, 1)); //1 + System.out.println(findLastSmallerEquals(arr, 5)); //6 + System.out.println(findLastSmallerEquals(arr, 2)); //3 + System.out.println(findLastSmallerEquals(arr, 7)); //6 + System.out.println(findLastSmallerEquals(arr, 0)); //1 +// System.out.println(findLastLargerEquals(arr, 3)); +// System.out.println(findLastLargerEquals(arr, 4)); + } + + + /** + * 在一个有序数组中(没有重复的元素),找到目标元素所在的位置 + * + * @param arr + * @param target + * @return + */ + private static int find(int[] arr, int target) { + int left = 0, right = arr.length - 1; + + while (left <= right) { + //int mid = (right - left) >> 1 + left;// 由于+left的优先级高, 会先于前面的 >> 1执行, 这样查找某些元素的时候会出现死循环 + int mid = ((right - left) >> 1) + left; // 左边需要加括号 + //int mid2 = (left + right) / 2; + //System.out.println(String.format("mid: %d, mid2: %d", mid, mid2)); + if (arr[mid] == target) { + return mid;// 找到目标元素直接返回 + // 当前元素大于目标元素 + } else if (arr[mid] > target) { + right = mid - 1; // 往前找 + } else { + left = mid + 1; // 否则往后找 + } + } + // 找不到或者空数组返回-1 + return -1; + } + + /** + * 找到第一个相同的元素(存在重复的元素) + *

+ * 如果找到, 再往前找, 找到的条件是前一个小于目标元素, 那么当前这个就是目标元素. (往前找需要考虑边界值为0,已经找到第一个了还等于目标,那直接返回) + * + * @param array + * @param target + * @return + */ + public static int findFirst(int[] array, int target) { + int left = 0, right = array.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + if (array[mid] > target) { + right = mid - 1; //往前查找 + } else if (array[mid] < target) { + left = mid + 1; // 往后查找 + } else { + // 如果找到相同的元素, 并且前面一个不是该元素, 说明找到了结果 + // 两种情况, 该元素已经是数组第一个元素了(前面没有了元素) 或者 前面的元素不是目标元素 + if (mid == 0 || array[mid - 1] != target) { + return mid; + } else { + // 否则 继续往左查找 + right = mid - 1; + } + } + } + + return -1; + } + + /** + * 找到最后一个相同的元素, + *

+ * 找到的基础上往后找, 如果后一个大于目标元素,则当前为最后一个相同元素. 边界值为当前元素为最后一个 + * + * @return + */ + private static int findLast(int[] arr, int target) { + int left = 0, right = arr.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + if (arr[mid] > target) { + right = mid - 1; //往前找 + } else if (arr[mid] < target) { + left = mid + 1; //往后找 + } else { + // 如果下一个元素不是目标元素, 说明当前这个为目标最后一个元素 + // 两种情况, 该元素已经是数组最后一个(不用再找) 或者 下一个不是目标元素(该元素不是最后一个) + if (mid == arr.length - 1 || arr[mid + 1] != target) + return mid; + else + left = mid + 1; //继续往右找 + } + } + return -1; + } + + /** + * 找到 第一个大于等于目标元素的位置 + *

+ * 思路, 先找大于等于的元素, 找到以后再里面判断是否是第一个大于等于的(因为大于等于的可能很多), 如果不是,再往前找第一个大于等于的 + *

+ * 第一个大于等于的标志为 上一个元素小于目标 (或者 当前位置为第0个, 后面的元素都大于等于目标 边界值) + * + * @param arr + * @param target + * @return + */ + public static int findFirstLargerEquals(int[] arr, int target) { + int left = 0; + int right = arr.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + // 如果小于,完全不满足条件,往后找 + if (arr[mid] < target) { + left = mid + 1; + } else { + // 大于或者等于的情况, 因为大于等于的目标元素非常多,所以需要判断是否为第一个 + // 如果当前找到的位置是第0个元素, 说明后面的元素都比目标元素大, 那第0个就是结果 (或者数组就一个元素) + // 或者前一个元素小于目标, 那当前元素肯定是第一个大于等于的元素 + if (mid == 0 || arr[mid - 1] < target) { + return mid; + } else { + // 如果没找到目标, 就往前找, 最终一定能找到第一个大于等于的 + right = mid - 1; + } + } + } + return -1; + } + + /** + * 找最后一个小于等于目标元素的索引(目标元素之前的最后一个) + *

+ * 思路: 先排除大于的, 如果大于目标元素就往前找. 如果小于等于目标元素, + * 就往后找, 找到的条件是 下一个大于目标元素. (这里需要考虑边界问题,就是最后一个元素还是小于目标元素(整个数组都比目标元素小),那就直接返回最后一个) + * + * @param array + * @param target + * @return + */ + private static int findLastSmallerEquals(int[] array, int target) { + int left = 0; + int right = array.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + // 当前元素大于目标元素 往前找 + if (array[mid] > target) { + right = mid - 1; + } else { + // 当前元素小于等于目标元素, 往后找 + if (mid == array.length - 1 || array[mid + 1] > target) + return mid; + else + left = mid + 1; + } + } + // 没找到 + return -1; + } +} + diff --git a/src/main/java/com/study/search/FindLastK.java b/src/main/java/com/study/search/FindLastK.java new file mode 100644 index 0000000..8b8e556 --- /dev/null +++ b/src/main/java/com/study/search/FindLastK.java @@ -0,0 +1,128 @@ +package com.study.search; + +/** + * 找到数组中最后一个目标元素 + */ +public class FindLastK { + + public static void main(String[] args) { + //int[] arr = {1, 2, 2, 2, 2, 2, 3, 3, 4, 5}; + int[] arr = {1, 2, 3, 3, 4, 5}; + //int[] arr = {2}; + System.out.println(find(arr, 2)); + //System.out.println(find2(arr, 2)); + } + + /** +// private static int findLastSameElement(int[] arr, int target) { +// +// if (arr.length == 1) { +// if (arr[0] == target) { +// return 0; +// } else { +// return -1; +// } +// } +// +// int left = 0; +// int right = arr.length - 1; +// +// +// while (left <= right) { +// int middle = (left + right) / 2; +// // 当前位置元素大于目标元素 +// if (arr[middle] > target) { +// // 如果上一个元素为目标元素 +// if (arr[middle - 1] > 0 && arr[middle - 1] == target) { +// return middle - 1; // 返回上一个元素索引 +// } else { +// // 从左边找 +// right = middle - 1; +// } +// // 当前位置元素小于目标元素 +// } else if (arr[middle] < target) { +// // 从右边找 +// left = middle + 1; +// } else { +// // 目标元素 +// // 后面存在目标元素, 继续往后找 +// if (arr[middle + 1] < arr.length && arr[middle + 1] == target) { +// left = middle; +// // 如果后面元素大于目标元素, 那当前元素就是结果 +// } else if (arr[middle + 1] < arr.length && arr[middle + 1] > target) { +// return middle; +// } +// } +// } +// return -1; +// } + */ + + private static int find(int[] arr, int target) { + + int left = 0; + int right = arr.length - 1; + int mid = 0; + while (left <= right) { + mid = (left + right) >> 1; + // 目标元素小于当前元素 + if (arr[mid] > target) { + right = mid - 1; //把范围放到左边找 + // 目标元素大于当前元素 + } else if (arr[mid] < target) { + left = mid + 1; //把范围放到右边找 + } else { + // 找到值相等的元素, 需要判断下一个是否为非值相同的元素 + // 如果下一个值与目标元素不同,则说明当前元素为目标元素 + // 这里需要特殊考虑一下数组只有一个元素的情况 + if (mid == arr.length - 1 || (mid + 1 <= arr.length -1 && arr[mid + 1] != target)) { // 索引+1或者-1需要考虑数组越界的情况 + return mid; + } else {//否则继续往右找,直到找到下一个元素值与目标元素不同 + left = mid + 1; + } + } + } + return -1; + } + + public static int find2(int[] array, int target) { + int left = 0, mid = 0, right = array.length - 1; + + while (left <= right) { + mid = (right - left) >> 1 + left; + + if (array[mid] < target) { + left = mid + 1; + } else if (array[mid] > target) { + right = mid - 1; + } else { //相等的前提下 + if (mid == array.length - 1 || (array[mid + 1] != target)) { + return mid; + } else { + left = mid + 1; //往后找 + } + } + } + return -1; + } + + + private static int findLastK2(int[] arr, int target) { + int left = 0; + int right = arr.length - 1; + + while (left < right) { + int temp = (left + right + 1) >> 1; //保证取到中间靠后的位置 + //int temp = (left + right + 1) / 2; //保证取到中间靠后的位置 + // 当前位置元素大于目标元素 + if (arr[temp] > target) { + // 往左边找, 位置-1 + right = temp - 1; + } else { + // 如果当前位置元素小于或等于目标元素, 往右边找 + left = temp; + } + } + return right; + } +} diff --git a/src/main/java/com/study/search/FindMinAbs.java b/src/main/java/com/study/search/FindMinAbs.java new file mode 100644 index 0000000..5b71061 --- /dev/null +++ b/src/main/java/com/study/search/FindMinAbs.java @@ -0,0 +1,64 @@ +package com.study.search; + +/** + * 找出有序数组中绝对值最小的元素 + */ +public class FindMinAbs { + + public static void main(String[] args) { + // 一个升序整形数组, 找到绝对值最小的输出 + + //int[] arr = {-3,-2,-1,5}; + //int[] arr = {-3, -2, -1, 0, 3, 5}; + //int[] arr = {-1, 1, 3, 5}; + + //int[] arr = {1, 3, 5}; + //int[] arr = {-9, -6, - 2, 0}; + //int[] arr = {-6}; + int[] arr = {}; + System.out.println(min(arr)); + } + + /** + * 利用二分查找进行,使得时间复杂度为O(logN), 低于遍历的O(N) + * + * @param arr + * @return + */ + private static int min(int[] arr) { + int left = 0; + int right = arr.length - 1; + + while (left <= right) { + //int middle = (left + right) / 2; // 这段代码可能越界 + int middle = left + (right - left) >>1; //防止越界 + // 大于0往左找最近的负数 + if (arr[middle] > 0) { + // 判断下一个数是否为负数 + if (middle - 1 >= 0 && arr[middle - 1] < 0) { + // 返回正负交届的绝对值最小的那个数 + if (Math.abs(arr[middle]) > Math.abs(arr[middle - 1])) + return arr[middle - 1]; + else + return arr[middle]; + } else + right = middle - 1; //从左边找, 右边界需要减一,因为有边界这个元素不满足 + // 小于0往右找最近的正数 + } else if (arr[middle] < 0) { + // 判断下一个数是否为正数 (注意边界) + if (middle + 1 < arr.length && arr[middle + 1] > 0) + // 返回正负交届的绝对值最小的那个数 + if (Math.abs(arr[middle]) > Math.abs(arr[middle + 1])) + return arr[middle + 1]; + else + return arr[middle]; + else + left = middle + 1; // 从左边找 左边界需要+1,因为边界这个元素不满足 + } else { + // 如果为0,直接返回 (0是所有数中绝对值最小的) + return arr[middle]; + } + } + return -1; + } +} diff --git a/src/main/java/com/study/search/FindSmall.java b/src/main/java/com/study/search/FindSmall.java new file mode 100644 index 0000000..e53afee --- /dev/null +++ b/src/main/java/com/study/search/FindSmall.java @@ -0,0 +1,295 @@ +package com.study.search; + +/** + * 旋转数组的二分查找问题 + *

+ * 截取一个有序数组的后面一段放到前面, 求整个数组中的最小元素 + *

+ * 比如有序数组为 [2, 3, 6, 7, 8, 10, 12, 15], 将后面的10,12,15截取后放到数组的最前面,变成[10, 12, 15, 2, 3, 6, 7, 8] + *

+ * 问题一: 请写一个算法, 找出最小值 + * https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/ + *

+ * 问题二: 给定一个数,查找其在序列中是否存在(返回其位置),请问如何实现? + * https://leetcode-cn.com/problems/search-in-rotated-sorted-array/ + *

+ * 问题三: 给定任意一个序列,找出其中的一个谷/峰,谷的定义为两边的数均大于某个数。 + *

+ * + * https://segmentfault.com/q/1010000000181714 + *

+ * https://blog.csdn.net/weixin_34018169/article/details/89698260?utm_source=distribute.pc_relevant.none-task + */ +public class FindSmall { + + public static void main(String[] args) { + int[] arr = {10, 12, 15, 2, 3, 6, 7, 8}; + System.out.println("------找最小值"); + System.out.println(findMin(arr)); // 2 + System.out.println(findMin2(arr)); // 2 + System.out.println("------找目标值"); + System.out.println(findTarget(arr, 2)); //3 + System.out.println(findTarget(arr, 10));//0 + System.out.println(findTarget(arr, 12));//1 + System.out.println(findTarget(arr, 15));//2 + System.out.println(findTarget(arr, 8));//7 + System.out.println(findTarget2(arr, 2)); //3 + System.out.println(findTarget2(arr, 10));//0 + System.out.println(findTarget2(arr, 12));//1 + System.out.println(findTarget2(arr, 15));//2 + System.out.println(findTarget2(arr, 8));//7 + + arr = new int[]{10, 12, 15, 2}; + System.out.println("------找最小值"); + System.out.println(findMin(arr)); // 2 + System.out.println(findMin2(arr)); // 2 + System.out.println("------找目标值"); + System.out.println(findTarget(arr, 2));//3 + System.out.println(findTarget(arr, 10));//0 + System.out.println(findTarget(arr, 12));//1 + System.out.println(findTarget(arr, 15));//2 + arr = new int[]{10, 2}; + System.out.println("------找最小值"); + System.out.println(findMin(arr)); // 2 + System.out.println(findMin2(arr)); // 2 + System.out.println("------找目标值"); + System.out.println(findTarget(arr, 2));//1 + System.out.println(findTarget(arr, 10));//0 + System.out.println(findTarget(arr, 12));//-1 + arr = new int[]{2}; + System.out.println("------找最小值"); + System.out.println(findMin(arr)); // 2 只有一个元素的时候,返回本身 + System.out.println(findMin2(arr)); // 2 + System.out.println("------找目标值"); + System.out.println(findTarget(arr, 2));//0 + System.out.println(findTarget(arr, 10));//-1 + + arr = new int[]{3, 4, 5, 1, 1, 2}; + //arr = new int[]{1, 0, 1, 1, 1}; + System.out.println("------找最小值"); + System.out.println(findMin(arr)); // 2 只有一个元素的时候,返回本身 + System.out.println(findMin2(arr)); // 2 + System.out.println("------找目标值"); + System.out.println(findTarget(arr, 2));//5 + System.out.println(findTarget(arr, 3));//0 + System.out.println(findTarget(arr, 1));//4 + } + + /** + * 问题一: 请写一个算法, 找出最小值 + *

+ * 思路: 跟第0个元素作比较, 如果大于或等于第0个元素, 就往后找. 如果小于第0个元素, 就往前找. + *

+ * 找到的条件是 当前元素小于前一个元素, 那么当前元素就是最小元素. + *

+ * 注意: 需要考虑数组只有一个元素的情况 + *

+ * 时间复杂度O(logN) + * + * @param arr + * @return + */ + private static int findMin(int[] arr) { + int left = 0, right = arr.length - 1; + + if (arr.length == 1) + return arr[left]; + + // 改进, leetcode上面还需要处理数组没有旋转的情况 + if(arr[left] <= arr[right]) + return arr[left]; + + // 找到目标为前面一个大于后面 + while (left <= right) { + // 如果数组长度为1或者2的时候, mid长度为0 + int mid = ((right - left) >> 1) + left; + // 当前元素大于等于第一个, 往后找 + if (arr[mid] >= arr[0]) { + left = mid + 1; + } else + // 当前元素小于第一个,往前找 + // 找到条件为上一个大于当前这个 + if (mid == 0 || arr[mid] < arr[mid - 1]) //通常mid == 0 || mid < mid -1 是常用的手法. 这样能避免mid-1越界, 也能处理数组长度为1的情况 + return arr[mid]; + else// 否则继续往前走 + right = mid - 1; + } + return -1; + } + + /** + * 问题一: 请写一个算法, 找出最小值 + * 解法二 + * https://blog.csdn.net/weixin_34018169/article/details/89698260?utm_source=distribute.pc_relevant.none-task + * + * @param nums + * @return + */ + public static int findMin2(int nums[]) { + if (nums == null || nums.length == 0) { + throw new RuntimeException("input error!"); + } + // 如果只有一个元素,直接返回 + if (nums.length == 1) + return nums[0]; + + int result = nums[0]; + int low = 0, high = nums.length - 1; + + // 改进, leetcode上面还需要处理数组没有旋转的情况 + if(nums[low] <= nums[high]) + return nums[low]; + + int mid; + // 确保 low 下标对应的值在左边的递增子数组,high 对应的值在右边递增子数组 + while (nums[low] >= nums[high]) { + // 确保循环结束条件 + if (high - low == 1) { + return nums[high]; + } + // 取中间位置 + mid = (low + high) / 2; + // 代表中间元素在左边递增子数组 + if (nums[mid] >= nums[low]) { + low = mid; + } else { + high = mid; + } + } + return result; + } + + /** + * 给定一个数,查找其在序列中是否存在(返回其位置),请问如何实现? + * + * 实现方案一: 按 目标元素所处的位置(前半段或后半段), 当前元素和目标元素大小比较, 当前元素的位置(前半段或后半段) 的顺序来处理 + *

+ * 思路: 需要考虑目标元素和当前元素所处的位置, 与第0个元素做比较. + * 如果目标元素在前半段, + * 当前元素小于目标元素 + * 当前元素在前半段 怎么做 + * 当前元素在后半段 怎么做 + * + * 当前元素大于目标元素 + * 当前元素[肯定]在前半段 怎么做 + * + * 目标元素在后半段 + 当前元素小于目标元素 + * 当前元素[肯定]在后半段 怎么做 + * + * 当前元素大于目标元素 + * 当前元素在前半段 怎么做 + * 当前元素在后半段 怎么做 + * + * @param arr + * @param target + * @return + */ + private static int findTarget(int[] arr, int target) { + int left = 0, right = arr.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + // 目标元素在前半段 + if (target >= arr[0]) { + // 如果当前元素小于目标元素 + if (arr[mid] < target) { + // 需要判断一下当前元素所处的区域是前半段还是后半段 + // 如果当前元素在前半段 + if (arr[mid] >= arr[0]) { + //往后找 + left = mid + 1; + } else { + //如果当前元素在后半段 + //往前找 + right = mid - 1; + } + //如果当前元素大于目标元素 + //当前元素也在前半段 + } else if (arr[mid] > target) { + //直接往前找即可 + right = mid - 1; + } else { + // 在前半段找到目标 + return mid; + } + //如果目标元素在后半段 + } else { + // 当前元素小于目标元素 + if (arr[mid] < target) { + //往后找即可 + left = mid + 1; + // 当前元素大于目标元素 + } else if (arr[mid] > target) { + // 需要判断一下当前元素处于前后哪个半段 + // 如果在前半段 + if (arr[mid] >= arr[0]) { + // 往后找 + left = mid + 1; + } else { + //在后半段,往前找即可 + right = mid - 1; + } + } else { + // 在后半段找到目标 + return mid; + } + } + } + return -1; + } + + /** + * 实现方案二: 按 目标元素所处的位置(前半段或后半段), 当前元素的位置(前半段或后半段), 当前元素和目标元素大小比较 的顺序来处理 + * + * @param arr + * @param target + * @return + */ + private static int findTarget2(int[] arr, int target) { + int left = 0, right = arr.length - 1; + + while (left <= right) { + int mid = ((right - left) >> 1) + left; + // 目标元素在前半段 + if (target >= arr[0]) { + // 如果当前元素也处于前半段 + if (arr[mid] >= arr[0]) { + // 如果当前元素大于目标元素, 往前找 + if (arr[mid] > target) { + right = mid - 1; + // 小于则往后找 + } else if (arr[mid] < target) { + left = mid + 1; + } else { + // 或者等于 + return mid; + } + // 如果当前元素处于后半段 + } else if (arr[mid] < arr[0]) { + // 往前找即可 + right = mid - 1; + } + } else { + // 目标元素在后半段 + // 如果当前元素也处于后半段 + if (arr[mid] < arr[0]) { + // 如果当前元素大于目标元素 往前找 + if (arr[mid] > target) { + right = mid - 1; + // 小于目标元素 往后找 + } else if (arr[mid] < target) { + left = mid + 1; + } else { + // 否则等于 返回坐标 + return mid; + } + //如果当前元素在前半段 直接往后找 + } else if (arr[mid] >= arr[0]) { + left = mid + 1; + } + } + } + return -1; + } +} diff --git a/src/main/java/com/study/search/SearchA2dMatrix.java b/src/main/java/com/study/search/SearchA2dMatrix.java new file mode 100644 index 0000000..cac4dc2 --- /dev/null +++ b/src/main/java/com/study/search/SearchA2dMatrix.java @@ -0,0 +1,38 @@ +package com.study.search; + +/** + * 搜索二维矩阵 + * + * 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: + * + * 每行中的整数从左到右按升序排列。 + * 每行的第一个整数大于前一行的最后一个整数。 + * 示例 1: + * + * 输入: + * matrix = [ + * [1, 3, 5, 7], + * [10, 11, 16, 20], + * [23, 30, 34, 50] + * ] + * target = 3 + * 输出: true + * 示例 2: + * + * 输入: + * matrix = [ + * [1, 3, 5, 7], + * [10, 11, 16, 20], + * [23, 30, 34, 50] + * ] + * target = 13 + * 输出: false + * + * 链接:https://leetcode-cn.com/problems/search-a-2d-matrix + */ +public class SearchA2dMatrix { + + public static void main(String[] args) { + + } +} diff --git a/src/main/java/com/study/sort/HeapSort.java b/src/main/java/com/study/sort/HeapSort.java new file mode 100644 index 0000000..2dc7fe9 --- /dev/null +++ b/src/main/java/com/study/sort/HeapSort.java @@ -0,0 +1,15 @@ +package com.study.sort; + +/** + * 堆排序是利用堆这种数据结构而设计的一种排序算法, 堆排序是一种选择排序. + * 它的最好,最坏,平均时间复杂度均为O(NlogN), 它也是不稳定排序. + * + * 堆是具有以下性质的完全二叉树: 每个结点的值都大于或等于其左右孩子节点的值,称为大顶堆; + * 每个结点的值都小于或等于其左右孩子节点的值,称为小顶堆. + */ +public class HeapSort { + + public static void main(String[] args) { + + } +} diff --git a/src/main/java/com/study/sort/MergeSort.java b/src/main/java/com/study/sort/MergeSort.java new file mode 100644 index 0000000..54ffd38 --- /dev/null +++ b/src/main/java/com/study/sort/MergeSort.java @@ -0,0 +1,176 @@ +package com.study.sort; + + +/** + * 归并排序, 节省内存空间 直接在原有的数组内部直接排序, 需要额外借助一个数组存放临时排序的元素 + * + * 时间复杂度 O(NlogN) + * + * 搜先通过不停的左右二分,将数组分成了类似二叉树的结构, + * 其递归的方式类似 二叉树的 后续遍历 左 右 根, 合并处理在根阶段 + * 使用数组5, 4, 7, 9, 3, 8, 2, 1进行举例 + * 根节点为完整数组, 一级子节点为 5479 和 3821 二级节点为 54,79 ,38,21 三级节点也就是叶子节点为 5,4,7,9, 3,8,2,1 + * 然后通过最底层左边5,4开始进行merge,得到4,5, 再右边7,9得到7,9, 再往上回溯 左边54和右边79合并得到5479, + * 然后是根的右边最底层3和8合并得到38,21合并得到12, 往上回溯38和12合并得到1238, + * 最后是 左5479节点和右1238合并得到根节点12345789 + *

+ * https://www.jianshu.com/p/33cffa1ce613 + */ +public class MergeSort { + + public static void main(String[] args) { + int[] arr = {5, 4, 7, 9, 3, 8, 2, 1}; + +// mergeSort(arr); +// print(arr); + + mergeSort_2(arr); + print(arr); + } + + private static void mergeSort(int[] arr) { + int[] tmp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 + sort(arr, 0, arr.length - 1, tmp); + } + + /** + * 搜先通过不停的左右二分,将数组分成了类似二叉树的结构, + * 其递归的方式类似 二叉树的 后续遍历 左 右 根, 合并处理在根阶段 + * 根节点为完整数组, 一级子节点为 5479 和 3821 二级节点为 54,79 ,38,21 三级节点也就是叶子节点为 5,4,7,9, 3,8,2,1 + * 然后通过最底层左边5,4开始进行merge,得到4,5, 再右边7,9得到7,9, 再往上回溯 左边54和右边79合并得到5479, + * 然后是根的右边最底层3和8合并得到38,21合并得到12, 往上回溯38和12合并得到1238, + * 最后是 左5479节点和右1238合并得到根节点12345789 + * + * @param arr + * @param left + * @param right + * @param tmp + */ + private static void sort(int[] arr, int left, int right, int[] tmp) { + if (left < right) { + int mid = left + ((right - left) >> 1); + + // 将数组不断的分成左右两部分, 直到分成无数个单个元素数组后,进行merge排序 + + // 左边分到不能再分 就进行回溯, 回溯的过程中再进行右边分 + sort(arr, left, mid, tmp); // 左边归并排序,使得左子序列有序 + sort(arr, mid + 1, right, tmp); // 右边归并排序, 使得右子序列有序 + + String s1 = ""; + for (int i = left; i <= mid; i++) + s1 += arr[i] + " "; + String s2 = ""; + for (int j = mid + 1; j <= right; j++) + s2 += arr[j] + " "; + + System.out.println(String.format("分成了数组%s和数组%s", s1, s2)); + // 每次左右两部分排序完后 再进行合并 + merge(arr, left, mid, right, tmp); //将两个有序子数组合并操作 + String s = ""; + for (int a = left; a <= right; a++) + s += arr[a]; + System.out.println("合并后得到: " + s); + } + } + + /** + * 将arr中的 left到mid, mid+1到right 两部分进行合并 + * + * @param arr + * @param left + * @param mid + * @param right + * @param tmp + */ + private static void merge(int[] arr, int left, int mid, int right, int[] tmp) { + int i = left; // 左边数组开始指针 + int j = mid + 1;// 右边数组开始指针 + int t = 0; // 临时数组开始指针 + + // 将左边和右边数组里的元素 从小到大排放入临时数组 + while (i <= mid && j <= right) { + if (arr[i] <= arr[j]) { + tmp[t++] = arr[i++]; + } else { + tmp[t++] = arr[j++]; + } + } + + // 上面循环执行完后, 左右数组里可能有一个数组还有元素没放入临时数组, 需要再次处理一下 + // 处理左边 + while (i <= mid) { + tmp[t++] = arr[i++]; + } + + // 处理右边 + while (j <= right) { + tmp[t++] = arr[j++]; + } + + // 最后将临时数组中合并好的元素重新放回原来的数组 + t = 0;//每次合并排序的元素放入tmp中都是从0开始的,所以取出也从0开始 + while (left <= right) { + arr[left++] = tmp[t++];//将tmp中的元素依次放回数组的left到right位置 + } + } + + private static void print(int[] arr) { + for (int e : arr) { + System.out.print(e + " "); + } + System.out.println(); + } + + private static void mergeSort_2(int[] arr) { + int[] tmp = new int[arr.length]; + sort_2(arr, 0, arr.length - 1, tmp); + } + + private static void sort_2(int[] arr, int left, int right, int[] tmp) { + // 左右两个数组中的元素不能相同 + if (left >= right) + return; + + int mid = ((right - left) >> 1) + left; + // 分治递归, 将数组不断分成单个元素的左右数组 + sort_2(arr, left, mid, tmp); + sort_2(arr, mid + 1, right, tmp); + + // 将左右数组进行排序合并, 合并后回溯到上层大数组再进行排序合并,最终完成整个数组排序 + merge_2(arr, left, mid, right, tmp); + } + + // 将左右两块数组进行合并 + // 将arr中的left 到 mid, mid+1 到 right 两部分数组进行合并排序人放入tmp中 + // 最后再从tmp中取出放回arr中 + private static void merge_2(int[] arr, int left, int mid, int right, int[] tmp) { + int i = left; + int j = mid + 1; + int t = 0; + + // 左边数组范围 left 到mid, 右边数组范围 mid+1到right + // 将元素从小到大放入tmp中 + while (i <= mid && j <= right) { + if (arr[i] >= arr[j]) { + tmp[t++] = arr[j++]; + } else { + tmp[t++] = arr[i++]; + } + } + + // 将左右数组中没有取出的元素放入tmp中 + while (i <= mid) { + tmp[t++] = arr[i++]; + } + + while (j <= right) { + tmp[t++] = arr[j++]; + } + + // 将tmp中的元素放回arr中 + t = 0; + while (left <= right) { + arr[left++] = tmp[t++]; + } + } +} diff --git a/src/main/java/com/study/sort/QuickSort.java b/src/main/java/com/study/sort/QuickSort.java index 4ff818a..54b87dc 100644 --- a/src/main/java/com/study/sort/QuickSort.java +++ b/src/main/java/com/study/sort/QuickSort.java @@ -2,28 +2,38 @@ /** * 快速排序算法 - * + *

* 最简单的理解: 确保数组里面每一个元素当做基准值后,前后的元素都比它小或者大. 即完成排序. - * - * + *

+ *

* 通过一轮的排序将序列分割成独立的两部分,其中一部分序列的关键字(这里主要用值来表示)均比另一部分关键字小。 * 继续对长度较短的序列进行同样的分割,最后到达整体有序。在排序过程中,由于已经分开的两部分的元素不需要进行比较,故减少了比较次数,降低了排序时间。 - * + *

* 快速排序在进行交换时,只是根据比较基数值判断是否交换,且不是相邻元素来交换,在交换过程中可能改变相同元素的顺序,因此是一种不稳定的排序算法。 - * + *

* 时间复杂度介于O(nlogn) 和O(n^2) 之间 - * + * https://www.cnblogs.com/yueansixing/articles/9125634.html + *

* https://www.cnblogs.com/foreverking/articles/2234225.html */ public class QuickSort { + /** + * 快速排序算法有很多: 挖坑法, 左右指针法, 前后指针法 + * + * @param args + */ public static void main(String[] args) { - //int[] a = {12, 20, 5, 16, 15, 1, 30, 5, 23, 9}; - int[] a = {12, 20, 9, 5, 16}; + //int[] a = {12, 20, 5, 16, 15, 1, 30, 45}; + //int[] a = {21, 32, 43, 98, 54, 45, 23, 4, 66, 86}; + int[] a = {72, 6, 57, 88, 60, 42, 83, 73, 48, 85}; + // int[] a = {12, 20, 9, 5, 16}; //int[] a = {12, 1}; + print(a); int start = 0; int end = a.length - 1; - sort(a, start, end); + //sort(a, start, end); + sort2(a, start, end); for (int i = 0; i < a.length; i++) { System.out.printf("%s ", a[i]); } @@ -41,7 +51,7 @@ public static void main(String[] args) { *

* 反复循环,直到start和end重叠, 退出循环. *

- * 此时,左边的值都比基准值小,右边的值都比基准值大,但是两边的顺序还有可能是不一样的. + * 此时, start左边的值都小于或等于基准值,右边的都大于或等于基准值,但是两边的顺序还有可能是不一样的. *

* 所以还需要进行递归调整一下, 递归分别从基准值左右两边进行调整 */ @@ -50,40 +60,97 @@ private static void sort(int[] a, int low, int high) { int end = high; // 选取第一个元素作为基准值 int key = a[low]; - + System.out.println("基准值为: " + key); + // 循环里 start往后挪, end往前挪 while (end > start) { // 从后往前比较 - // 如果没有比基准值小的,比较下一个,直到有比基准值小的交换位置,交换的为start和end索引上的元素;然后又从前往后比较, - while (end > start && a[end] >= key){ + // 如果没有比基准值小的,比较下一个,直到有比基准值小的交换位置,将此时start和end上的元素进行交换;然后又从前往后比较, + while (end > start && a[end] >= key) { end--; } - + // 把小于基准值的放到基准值左边 if (a[end] < key) { int temp = a[end]; a[end] = a[start]; a[start] = temp; + System.out.println(String.format("从后往前: 将%d和%d进行对调", a[end], a[start])); + print(a); } // 从前往后比较 // 如果没有比基准值大的,交换的为start和end索引上的元素,比较下一个,直到有比基准值大的交换位置,之后继续循环,又从后往前比较 - while (end > start && a[start] <= key){ + while (end > start && a[start] <= key) { start++; } + // 把大于基准值的放到基准值右边 if (a[start] > key) { int temp = a[start]; a[start] = a[end]; a[end] = temp; + System.out.println(String.format("从前往后: 将%d和%d进行对调", a[start], a[end])); + print(a); + } + } + // 当循环结束后,基准值的位置已经确定了。start左边的值都小于或等于基准值,end右边的值都大于或等于基准值,但是两边的顺序还有可能是不一样的. + + // 于是将start和end左右两边划分成两个区间, 不停的进行二分分区, 最后等左右两个区间都完成排序后,那么整个数组就变成有序了 + // 不管是左边还是右边区间, 都取第0个作为基准值 + // 将左边序列进行排序 + if (start > low) { + sort(a, low, start - 1); // 取左边数组第0个做基准值, 最小索引不变, 然后最大范围不断缩小 + System.out.println("start: " + start); + } + + // 将右边序列进行排序 + if (end < high) { + sort(a, end + 1, high); // 取右边数组第0个作基准值, 最大索引不变, 最小范围不断加大 + System.out.println("end: " + end); + } + } + + private static void sort2(int[] arr, int low, int high) { + int start = low; + int end = high; + + int key = arr[low]; + + while (start < end) { + // end从后往前和基准值做比较, 小于基准值就和start进行交换 + while (end > start && arr[end] >= key) { + end--; + } + + //如果后面存在元素小于基准值, 将将start和end位置上的元素互换 + if (arr[end] < key) { + int tmp = arr[end]; + arr[end] = arr[start]; + arr[start] = tmp; + } + + while (end > start && arr[start] <= key) { + start++; + } + + //如果前面有元素大于基准值, 就将start和end位置上的元素互换 + if (arr[start] > key) { + int tmp = arr[start]; + arr[start] = arr[end]; + arr[end] = tmp; } } - // 当循环结束后,基准值的位置已经确定了。左边的值都比基准值小,右边的值都比基准值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用 - //左边序列. 第一个索引位置到基准值索引-1 if (start > low) { - sort(a, low, start - 1); + sort2(arr, low, start - 1); } - //右边序列. 从基准值索引+1到最后一个 if (end < high) { - sort(a, end + 1, high); + sort2(arr, end + 1, high); } } + + private static void print(int[] num) { + for (int i = 0; i < num.length; i++) { + System.out.print(num[i] + " "); + } + System.out.println(); + } } diff --git a/src/main/java/com/study/sort/SelectSort.java b/src/main/java/com/study/sort/SelectSort.java index 8b791b7..3d94ce5 100644 --- a/src/main/java/com/study/sort/SelectSort.java +++ b/src/main/java/com/study/sort/SelectSort.java @@ -18,18 +18,23 @@ public static void main(String[] args) { int[] a = {12, 23, 9, 24, 15, 3, 18}; - selectSort(a); + //selectSort(a); + selectSort2(a); print(a); System.out.println(); - selectSortDesc(a); + //selectSortDesc(a); + selectSortDesc2(a); print(a); } /** * 选择排序, 从每次遍历未排序的元素中找出值最小的元素索引 - * + *

+ * 每一次循环能找出一个当前最小的索引,然后将最小索引上的元素放在当前遍历的索引上 + *

+ * 跟冒泡排序类似,只是它不会做元素的两两替换,而是找最大或最小索引,找到后再替换 */ private static void selectSort(int[] a) { //由于最后一个元素无需再遍历了,只要前面的所有元素都是按顺序从小到大排列,那么最后一个就是最大值, 所以只要判断i < a.length-1即可 @@ -42,7 +47,7 @@ private static void selectSort(int[] a) { min = j; } } - //将最小的索引为m的元素放到前面k位置上 + //将前面未排序的元素与后面最小元素做替换 if (i != min) { int temp = a[i]; a[i] = a[min]; @@ -51,16 +56,34 @@ private static void selectSort(int[] a) { } } + private static void selectSort2(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 每一轮循环找出一个最小值索引 + int min = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[j] < arr[min]) { + min = j; + } + } + + // 最小索引不再是当前元素的索引, 那就需要将最小元素和当前元素位置替换一下 + if (min != i) { + int tmp = arr[min]; + arr[min] = arr[i]; + arr[i] = tmp; + } + } + } + /** * 从大到小排序 - * */ private static void selectSortDesc(int[] a) { for (int i = 0; i < a.length - 1; i++) { int max = i; //将后面的元素与前面元素做比较,如果后面的大,则把max索引改为最大的元素索引 for (int j = i + 1; j < a.length; j++) { - if (a[max] < a[j]) { + if (a[j] > a[max]) { max = j; } } @@ -73,6 +96,24 @@ private static void selectSortDesc(int[] a) { } } + + private static void selectSortDesc2(int[] arr) { + for (int i = 0; i < arr.length - 1; i++) {// 这里用arr.length 或者arr.length -1都行, 因为排完前面的, 最后一个肯定是最小的 + int maxIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[j] > arr[maxIndex]) { + maxIndex = j; + } + } + + if (i != maxIndex) { + int tmp = arr[maxIndex]; + arr[maxIndex] = arr[i]; + arr[i] = tmp; + } + } + } + private static void print(int[] arr) { for (int i = 0; i < arr.length; i++) { if (i == arr.length - 1) { diff --git a/src/main/java/com/study/stack/ImplementQueueUsingStacks.java b/src/main/java/com/study/stack/ImplementQueueUsingStacks.java index ac1618b..903e1ab 100644 --- a/src/main/java/com/study/stack/ImplementQueueUsingStacks.java +++ b/src/main/java/com/study/stack/ImplementQueueUsingStacks.java @@ -1,42 +1,48 @@ package com.study.stack; -import java.util.Stack; +import java.util.*; +import java.util.function.Consumer; /** * 232. 用栈实现队列 - * + *

* 使用栈实现队列的下列操作: - * + *

* push(x) -- 将一个元素放入队列的尾部。 * pop() -- 从队列首部移除元素。 * peek() -- 返回队列首部的元素。 * empty() -- 返回队列是否为空。 * 示例: - * + *

* MyQueue queue = new MyQueue(); - * + *

* queue.push(1); * queue.push(2); * queue.peek(); // 返回 1 * queue.pop(); // 返回 1 * queue.empty(); // 返回 false * 说明: - * + *

* 你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 * 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 * 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。 - * + *

* https://leetcode-cn.com/problems/implement-queue-using-stacks */ public class ImplementQueueUsingStacks { public static void main(String[] args) { - MyQueue obj = new MyQueue(); + //MyQueue obj = new MyQueue(); + //MyQueueByStack2 obj = new MyQueueByStack2(); + MyQueueByList obj = new MyQueueByList(); obj.push(1); obj.push(2); + obj.push(3); //根据队列先入先出的原理, 下面pop和peak都应该是第一个元素1 System.out.println(obj.pop()); - System.out.println(obj.peek()); - System.out.println(obj.empty()); + System.out.println(obj.pop()); + System.out.println(obj.pop()); +// System.out.println(obj.peek()); +// System.out.println(obj.empty()); } } @@ -45,10 +51,10 @@ public static void main(String[] args) { * 使用两个栈来实现一个队列 * 比如将1-2-3放入第一个栈里变成3-2-1 先进的到了最底下 * 再将第一个栈的元素3-2-1放到第2个栈里变成了1-2-3 先进的到了最底下 - * + *

* 最后使用栈的pop方法将最顶上的元素1移除,之后是2,3 */ -class MyQueue { +class MyQueueByStack { private Stack input; private Stack output; @@ -56,23 +62,25 @@ class MyQueue { /** * Initialize your data structure here. */ - public MyQueue() { + public MyQueueByStack() { input = new Stack(); output = new Stack(); } /** * Push element x to the back of queue. + *

+ * 需要借助两个stack进行元素的颠倒, 1 2 3 入input栈后 变成 3 2 1, 再取出入output栈后变成 1 2 3, 就成了先进先出的效果 */ public void push(int x) { //在往input栈中方元素之前, 需要把output栈里的元素全部放回input栈 - while(!output.isEmpty()){ + while (!output.isEmpty()) { input.push(output.pop()); } //然后再将元素x放入input input.push(x); //最后再将所有元素放入到output栈, 这样从output出去第一个的元素为x - while(!input.isEmpty()){ + while (!input.isEmpty()) { output.push(input.pop()); } } @@ -81,7 +89,7 @@ public void push(int x) { * Removes the element from in front of queue and returns that element. */ public int pop() { - if(output.isEmpty()){ + if (output.isEmpty()) { return -1; } return output.pop(); @@ -91,7 +99,7 @@ public int pop() { * Get the front element. */ public int peek() { - if(output.isEmpty()){ + if (output.isEmpty()) { return -1; } return output.peek(); @@ -104,3 +112,91 @@ public boolean empty() { return output.isEmpty(); } } + +/** + * 使用两个栈LIFO实现FIFO队列 + */ +class MyQueueByStack2 { + + private Stack inputStack = new Stack(); + private Stack outputStack = new Stack(); + + public void push(int x) { + // 先将元素放到input栈中, 然后再转入output栈中 + + // 将outputStack中的元素1 2 3全部取出放到inputStack中变成 3 2 1 + while (!outputStack.isEmpty()) { + inputStack.push(outputStack.pop()); + } + + // 将新入的元素放到inputStack顶部 顺序变成了 x 3 2 1 + inputStack.push(x); + + // 再将inputStack中的元素x 3 2 1取出放到outputStack中变成 1 2 3 x + while (!inputStack.isEmpty()) { + outputStack.push(inputStack.pop()); + } + } + + /** + * 取出并移除栈顶元素 + * + * @return + */ + public int pop() { + if (outputStack.isEmpty()) + return -1; + return outputStack.pop(); + } + + /** + * 取出不移除栈顶元素 + * + * @return + */ + public int peek() { + if (outputStack.isEmpty()) + return -1; + return outputStack.peek(); + } + + /** + * 获取堆中元素总个数 + * + * @return + */ + public int size() { + return outputStack.size(); + } +} + +class MyQueueByList { + + List result = new ArrayList(); + + public void push(int x) { + result.add(x); + } + + /** + * 将第一个元素弹出, 并移除该元素 + * @return + */ + public int pop() { + if (result.isEmpty()) + return -1; + int element = result.get(0); + result.remove(0); + return element; + } + + /** + * 将第一个元素弹出, 不移除该元素 + * @return + */ + public int peek() { + if (result.isEmpty()) + return -1; + return result.get(0); + } +} diff --git a/src/main/java/com/study/string/FirstUniqueCharacter.java b/src/main/java/com/study/string/FirstUniqueCharacter.java new file mode 100644 index 0000000..2ecbda7 --- /dev/null +++ b/src/main/java/com/study/string/FirstUniqueCharacter.java @@ -0,0 +1,35 @@ +package com.study.string; + + +/** + * 字符串中的第一个唯一字符 + * + * 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 + * + * 案例: + * + * s = "leetcode" + * 返回 0. + * + * s = "loveleetcode", + * 返回 2. + *   + * + * 注意事项:您可以假定该字符串只包含小写字母。 + * + * 链接:https://leetcode-cn.com/problems/first-unique-character-in-a-string + * + */ +public class FirstUniqueCharacter { + + /** + * 方法一: 遍历字符串使用hashmap来存每个字符的个数, 遍历hashmap,找到第一个不重复的字符 + * 方法二: 使用char[256]数组代替hashmap + * + * @param args + */ + public static void main(String[] args) { + String s = "leetcode"; + + } +} diff --git a/src/main/java/com/study/string/GenerateParentheses.java b/src/main/java/com/study/string/GenerateParentheses.java new file mode 100644 index 0000000..b05db23 --- /dev/null +++ b/src/main/java/com/study/string/GenerateParentheses.java @@ -0,0 +1,183 @@ +package com.study.string; + +import java.util.ArrayList; +import java.util.List; + +/** + * 括号生成 + *

+ * 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。 + *

+ * 例如,给出 n = 3,生成结果为: + *

+ * [ + * "((()))", + * "(()())", + * "(())()", + * "()(())", + * "()()()" + * ] + *

+ * 链接:https://leetcode-cn.com/problems/generate-parentheses + */ +public class GenerateParentheses { + + /** + * 有几个需要注意的条件: + * 1. 左括号和右括号数量必须一致 + * 2. 必须先有左括号 + * + * @param args + */ + public static void main(String[] args) { + //List resultList = generateParenthesis(3); + //List resultList = generateParenthesis2(3); + List resultList = generateParenthesis(3); + for (String result : resultList) { + System.out.println(result); + } + } + + private static List generateParenthesis(int n) { + List resultList = new ArrayList<>(); + //gen(0, 0, n, "", resultList); + //gen2(0, 0, n, "", resultList); + //gen3(0, 0, n, "", resultList); + gen2_2(0, 0, n, "", resultList); + return resultList; + } + + /** + * 闭合数 + * 思路 + *

+ * 为了枚举某些内容,我们通常希望将其表示为更容易计算的不相交子集的总和。 + *

+ * 考虑有效括号序列 S 的 闭包数:至少存在 index >= 0,使得 S[0], S[1], ..., S[2*index+1]是有效的。 显然,每个括号序列都有一个唯一的闭包号。 我们可以尝试单独列举它们。 + *

+ * 算法 + *

+ * 对于每个闭合数 c,我们知道起始和结束括号必定位于索引 0 和 2*c + 1。然后两者间的 2*c 个元素一定是有效序列,其余元素一定是有效序列。 + *

+ * https://leetcode-cn.com/problems/generate-parentheses/solution/gua-hao-sheng-cheng-by-leetcode/ + * + * @param n + * @return + */ + private static List generateParenthesis2(int n) { + List ans = new ArrayList(); + if (n == 0) { + ans.add(""); + } else { + for (int c = 0; c < n; ++c) + for (String left : generateParenthesis2(c)) + for (String right : generateParenthesis2(n - 1 - c)) + ans.add("(" + left + ")" + right); + } + return ans; + } + + + /** + * 使用递归枚举所有左右组合的情况, 增加一些限定条件(必须先有左括号再有右括号, 而且过程中, 右括号数量不能大于左括号) + *

+ * 时间复杂度O + * + * @param left + * @param right + * @param n + * @param result + * @param resultList + */ + private static void gen(int left, int right, int n, String result, List resultList) { + // 当左右括号都满足n个的时候, 就达到了结果要求 + if (left == n && right == n) { + resultList.add(result); + System.out.println("递归从外往内终止: result = " + result); + return; + } + + // 左括号增加条件: 左括号数量不到n个 + if (left < n) { + System.out.println("左括号递归从外往内开始: result = " + result); + gen(left + 1, right, n, result + "(", resultList); + } + + System.out.println("左括号递归从内往外开始: result = " + result); + + // 右括号增加条件: 数量少于左括号, 并且不满足n个(这里因为left小于n, 所以right必然小于n) + if (left > right) { + System.out.println("右括号递归从外往内开始: result = " + result); + gen(left, right + 1, n, result + ")", resultList); + } + + System.out.println("右括号递归从内往外开始: result = " + result); + } + + + private static void gen2(int left, int right, int n, String result, List resultList) { + // 结束递归的条件 + if (left == n && right == n) { + resultList.add(result); + return; + } + + // 先有左括号 再有右括号, 而且右括号一定不能大于左括号数量 + if (left < n) { + gen2(left + 1, right, n, result + "(", resultList); + } + + if (right < left) { + gen2(left, right + 1, n, result + ")", resultList); + } + } + + private static void gen2_2(int left, int right, int n, String result, List resultList) { + // 当左右空格数量都达到3个的时候, 一次括号组合完成 + if (left == n && right == n) { + resultList.add(result); + return; + } + + // 一定要先放左括号 + if (left < n) { + gen2_2(left + 1, right, n, result + "(", resultList); + } + + // 右边括号数量不超过嘴边的情况下可以放入 + if (right < left) { + gen2_2(left, right + 1, n, result + ")", resultList); + } + } + + /** + * 递归中使用剪枝 或者也叫回溯 + * + * @param left + * @param right + * @param n + * @param result + * @param resultList + */ + private static void gen3(int left, int right, int n, String result, List resultList) { + // 结束递归的条件 + if (left == n && right == n) { + resultList.add(result); + return; + } + + // 使用剪枝的方式 去除右括号大于左括号的所有情况 + // 剪枝 也是回溯法的一种解题思路, 因为当前做法不满足条件,所以直接返回避免无效操作 + if (left < right) + return; + + // 先有左括号 再有右括号, 而且右括号一定不能大于左括号数量 + if (left < n) { + gen2(left + 1, right, n, result + "(", resultList); + } + + if (right < n) { + gen2(left, right + 1, n, result + ")", resultList); + } + } +} diff --git a/src/main/java/com/study/string/JewelsAndStones.java b/src/main/java/com/study/string/JewelsAndStones.java index efed918..595a61b 100644 --- a/src/main/java/com/study/string/JewelsAndStones.java +++ b/src/main/java/com/study/string/JewelsAndStones.java @@ -30,20 +30,20 @@ public class JewelsAndStones { public static void main(String[] args) { - //String j = "aA", s = "aAAbbbb"; + String j = "aA", s = "aAAbbbb"; //String j = "z", s = "ZZ"; //System.out.println(numJewelsInStones(j,s)); - String j = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", s = ""; - for (int i = 0; i < SIZE; i++) { - if (i == SIZE - 2) { - s += 'Y'; - } else if (i == SIZE - 1) { - s += 'Z'; - } else { - s += i; - } - } +// String j = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", s = ""; +// for (int i = 0; i < SIZE; i++) { +// if (i == SIZE - 2) { +// s += 'Y'; +// } else if (i == SIZE - 1) { +// s += 'Z'; +// } else { +// s += i; +// } +// } long start = System.currentTimeMillis(); diff --git a/src/main/java/com/study/string/LastWordCount.java b/src/main/java/com/study/string/LastWordCount.java new file mode 100644 index 0000000..966eb26 --- /dev/null +++ b/src/main/java/com/study/string/LastWordCount.java @@ -0,0 +1,84 @@ +package com.study.string; + +import java.util.Scanner; + +/** + * 获取控制台输入的一行字符串, 取最后一个单词的长度 + * + * 比如hello jack, 输出4 + */ +public class LastWordCount { + + public static void main(String[] args) { + String input = "hello jack"; + //System.out.println(getLastWordCount(input)); + //System.out.println(getLastWordCount2(input)); + System.out.println(getLastWordCount3(input)); + + Scanner scan = new Scanner(System.in);// 获取控制台的输入 + // 输出敲回车之前的所有内容 + while (scan.hasNext()) { + //System.out.println(scan.next()); + System.out.println(getLastWordCount(scan.nextLine())); // 输入整行内容 + //System.out.println(getLastWordCount(scan.next()));// 将输入的内容按空格分割 + } + } + + /** + * 获取最后一个单词的长度, 将字符串反向遍历 + * @param input + * @return + */ + private static int getLastWordCount(String input) { + if (input == null || input.length() == 0) + return 0; + int count = 0; + int length = input.length(); + // 将字符串从后往前计算count, 当遇到空格的时候, 中断统计 + char[] chars = input.toCharArray(); + for (int i = length - 1; i >= 0; i--) { + if (chars[i] == ' ') + break; + count++; + } + return count; + } + + /** + * 从后往前遍历 + * @param input + * @return + */ + private static int getLastWordCount2(String input) { + if (input == null || input.length() == 0) + return 0; + int count = 0; + int length = input.length(); + // 将字符串从后往前计算count, 当遇到空格的时候, 中断统计 + for (int i = length - 1; i >= 0; i--) { + if (input.charAt(i) == ' ') + break; + count++; + } + return count; + } + + /** + * 按空格号分割字符串, 将字符串正序遍历 + * + * @param input + * @return + */ + private static int getLastWordCount3(String input) { + if (input == null || input.length() == 0) + return 0; + int count = 0; + for (int i = 0; i < input.length(); i++) { + count++; + if (input.charAt(i) == ' ') { + count = 0; // 每次遇到空格 都将count清零, 只有最后一个单词没有空格不会被清零 + } + } + return count; + } +} diff --git a/src/main/java/com/study/string/LongestNumberInString.java b/src/main/java/com/study/string/LongestNumberInString.java index d4c31d7..943dded 100644 --- a/src/main/java/com/study/string/LongestNumberInString.java +++ b/src/main/java/com/study/string/LongestNumberInString.java @@ -8,7 +8,8 @@ public static void main(String[] args) { String input = "abcd123cc45678977ed125ss6789"; //String longestNum = getLongestNumber(input); - String longestNum = getLongestNumber2(input); + //String longestNum = getLongestNumber2(input); + String longestNum = getLongestNumber3(input); System.out.printf("最长连续数字为%s,长度为%d", longestNum, longestNum.length()); } @@ -65,4 +66,24 @@ private static String getLongestNumber2(String input) { } return longestNum; } + + + private static String getLongestNumber3(String input) { + String longestNum = ""; + String currentNum = ""; + + for(int i =0; i < input.length(); i++){ + // 位于0-9之间 + if(input.charAt(i) >= '0' && input.charAt(i) <= '9'){ + currentNum += input.charAt(i); + }else{ + if(currentNum.length() > longestNum.length()){ + longestNum = currentNum; + } + currentNum = ""; + } + } + + return longestNum; + } } diff --git a/src/main/java/com/study/string/LongestPalindrome.java b/src/main/java/com/study/string/LongestPalindrome.java new file mode 100644 index 0000000..17bae22 --- /dev/null +++ b/src/main/java/com/study/string/LongestPalindrome.java @@ -0,0 +1,208 @@ +package com.study.string; + + +import java.util.HashMap; +import java.util.Map; + +/** + * 最长回文串 (本题可以将字符串拆解重新构造出字符串) + *

+ * 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。 + *

+ * 在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。 + *

+ * 注意: + * 假设字符串的长度不会超过 1010。 + *

+ * 示例 1: + *

+ * 输入: + * "abccccdd" + *

+ * 输出: + * 7 + *

+ * 解释: + * 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。 + *

+ * 来源:力扣(LeetCode) + * 链接:https://leetcode-cn.com/problems/longest-palindrome + */ +public class LongestPalindrome { + + /** + * 本题允许将字符串拆解后构造字符串, 所以只需要计算每个字符出现的次数即可 + * + * @param args + */ + public static void main(String[] args) { + //String s = "abcbad"; + //String s = "a"; + //String s = "abccccdd"; + String s = "Aabcfecd"; +// System.out.println(1 / 2 * 2); +// System.out.println(3 / 2 * 2); +// System.out.println(5 / 2 * 2); +// System.out.println(0 / 2 * 2); + System.out.println(1 % 2); // 1 % 2 = 1; + System.out.println("--------------------"); + + //System.out.println(longestPalindrome(s)); + //System.out.println(longestPalindrome_2(s)); + //System.out.println(longestPalindrome2(s)); + //System.out.println(longestPalindrome3(s)); + System.out.println(longestPalindrome_3(s)); + + } + + /** + * 使用贪心法计算每个字符的个数, 取它的偶数个数(实现偶数对称回文数), 在偶数个存在的情况下可以再加上1个字符放在中间(实现奇数对称回文数) + * 这里用到了char的ASCII值(hash值)来定位每个char在数组中的位置 比如 'A'hash值为65, 'a' hash值为97, 而'z'的hash值为122 + * 由于ASCII一共有128个字符, 所以创建了一个长度为128的数组来存. + *

+ * 当然也可以优化一下, 由于所有的大小写字符所处的位置是65 到 122, 所以只需要使用长度为58的数组来存即可, 存的时候 每个字符需要减去第65个字符'A' 得出范围0-58的索引 + *

+ * 使用一个变量 length来计算满足条件的字符个数, 遍历字符数组, 得出每个字符的偶数个(0 2 4 ...), 加到总个数length中, 如果当前length总个数为偶数个, 那么还能再加一个字符作为中间对称字符 + *

+ *

+ * 时间复杂度O(n), 最多用了一层循环 + * 空间复杂度O(n), 用到了数组 + *

+ * ASCII码表 http://ascii.911cha.com/ + * + * @param s + * @return + */ + public static int longestPalindrome(String s) { + // ASCII总共128个字符, 所以使用长度为128的数组来存 +// int[] count = new int[128]; +// for (char c : s.toCharArray()) { +// count[c]++; //记录每个char的数量 +// } + + // 也可以对数组进行优化, 使用一个长度为58的数组来存, 因为大小写字母位于ASCII码的第65 到 122位, 刚好是58个, 其中A为第65位 + int[] count = new int[58]; + for (char c : s.toCharArray()) { + // 每个字符都比A大 0-58 + count[c - 'A']++; //记录每个char的数量 + } + + // 统计满足条件的字符个数 + int length = 0; + for (int v : count) { + // 找出每个字符的偶数个, 比如 aaaaa出现了5次,取偶数次为 aaaa 4次 5 / 2 *2 = 2 * 2 = 4 + // 当然如果该字符次数不足2, 则结果为0 比如 1 / 2 * 2 = 0*2 = 0 这里巧妙的用到了整数除法无法整除得到0的技巧 + length += v / 2 * 2; + // 如果当前length总个数为偶数个, 那么还能再加一个字符作为中间对称字符 + if (v % 2 == 1 && length % 2 == 0) // 判断奇数个的前提是当前字符个数为奇数个, 可以是1 3 5 ...,且满足条件的字符总个数为偶数 + length++; + } + return length; + } + + public static int longestPalindrome_2(String s) { + int[] chars = new int[58]; + + for (char c : s.toCharArray()) { + chars[c - 'A']++; + } + + int length = 0; + for (int c : chars) { + length += c / 2 * 2; //获取偶数个数字符 + + // 在当前满足条件的字符总个数为偶数的情况下, 可以再增加一个奇数作为中间对称字符 + if (length % 2 == 0 && c % 2 == 1) + length++; + } + + return length; + } + + public static int longestPalindrome_3(String s) { + char[] chars = new char[128]; + + for (int i = 0; i < s.length(); i++) { + chars[s.charAt(i)]++; + } + + int length = 0; + // 记录可以做成回文数的字符个数 + for (int c : chars) { + length += c / 2 * 2; // 如果是偶数得到本身, 如果是奇数则-1, 如果是0得到0 + + if (length % 2 == 0 && c % 2 == 1) { //如果当前总数为偶数,而且c本身的个数为奇数, 可以使用这个c + length++; + } + } + + return length; + } + + + /** + * 贪心算法, 使用hashmap来存储每个字符 + * + * @param s + * @return + */ + public static int longestPalindrome2(String s) { + // 记录每个char出现的次数 + Map kv = new HashMap<>(); + for (char c : s.toCharArray()) { + if (!kv.containsKey(c)) + kv.put(c, 1); + else + kv.put(c, kv.get(c) + 1); + } + + // 计算回文数总个数 + int count = 0; + for (Map.Entry entry : kv.entrySet()) { + // 统计元素出现的总个数, 必须是出现个数>=2以上的才满足, 而且取其最大偶数次数, 比如2次的count=2, 3次的count=2,5次的count=4 + count += entry.getValue() / 2 * 2; + // 只有之前出现的总个数为偶数(0 2 4), 才允许将出现一次的元素加到总数 + if (count % 2 == 0 && entry.getValue() % 2 == 1) { + count++; + } + } + return count; + } + + /** + * 获取最长会问字符串, 没有经过排序处理, 只是得到有效的字符 + * + * @param s + * @return + */ + public static String longestPalindrome3(String s) { + StringBuilder result = new StringBuilder(); + // 记录每个char出现的次数 + Map kv = new HashMap<>(); + for (char c : s.toCharArray()) { + if (!kv.containsKey(c)) + kv.put(c, 1); + else + kv.put(c, kv.get(c) + 1); + } + + // 计算回文数总个数 + int count = 0; + for (Map.Entry entry : kv.entrySet()) { + // 统计元素出现的总个数, 必须是出现个数>=2以上的才满足, 而且取其最大偶数次数, 比如2次的count=2, 3次的count=2,5次的count=4 + int curCount = entry.getValue() / 2 * 2; + count += curCount; + if (curCount >= 2) { + for (int i = 0; i < curCount; i++) { + result.append(entry.getKey()); + } + } + // 只有之前出现的总个数为偶数, 才允许将出现一次的元素加到总数 + if (count % 2 == 0 && entry.getValue() % 2 == 1) { + result.insert(count / 2, entry.getKey()); + count++; + } + } + return result.toString(); + } +} diff --git a/src/main/java/com/study/string/ReverseString.java b/src/main/java/com/study/string/ReverseString.java new file mode 100644 index 0000000..5fc53cb --- /dev/null +++ b/src/main/java/com/study/string/ReverseString.java @@ -0,0 +1,43 @@ +package com.study.string; + +/** + * 反转字符串 + *

+ * 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 + *

+ * 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 + *

+ * 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 + *

+ * https://leetcode-cn.com/problems/reverse-string/ + */ +public class ReverseString { + + public static void main(String[] args) { + char[] chars = {'h', 'e', 'l', 'l', 'o'}; + reverse(chars); + + for (char c : chars) { + System.out.print(c + " "); + } + } + + /** + * 使用前后指针 从首位开始将前后两个元素进行互换 + * + * @param chars + * @return + */ + private static void reverse(char[] chars) { + int left = 0; + int right = chars.length - 1; + // 将前后两个字符进行对调 + while (left < right) { // 如果是奇数, 中间元素不用换位置 + char tmp = chars[left]; + chars[left] = chars[right]; + chars[right] = tmp; + left++; + right--; + } + } +} diff --git a/src/main/java/com/study/string/ReverseWordsInAString.java b/src/main/java/com/study/string/ReverseWordsInAString.java new file mode 100644 index 0000000..5e0ad6b --- /dev/null +++ b/src/main/java/com/study/string/ReverseWordsInAString.java @@ -0,0 +1,107 @@ +package com.study.string; + + +/** + * 翻转字符串里的单词 + *

+ * 给定一个字符串,逐个翻转字符串中的每个单词。 + *

+ *   + *

+ * 示例 1: + *

+ * 输入: "the sky is blue" + * 输出: "blue is sky the" + * 示例 2: + *

+ * 输入: "  hello world!  " + * 输出: "world! hello" + * 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + * 示例 3: + *

+ * 输入: "a good   example" + * 输出: "example good a" + * 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *   + *

+ * 说明: + *

+ * 无空格字符构成一个单词。 + * 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + * 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *

+ * https://leetcode-cn.com/problems/reverse-words-in-a-string/ + */ +public class ReverseWordsInAString { + + public static void main(String[] args) { + //String s = "the sky is blue"; + String s = "hello world!"; + //s = reverse(s); + s = reverse2(s); + System.out.println(s); + } + + /** + * 方法一: 先用trim()去除字符串首位的空格, 并分割成单词数组 + * 将单词数组反向遍历 放入新字符串中, 从第一个单词之后放入空格 + * + * @param s + * @return + */ + private static String reverse(String s) { + // 将字符串首位去除空格 + // 并分割成单词数组 + String[] words = s.trim().split(" "); + + String result = ""; + // 将单词数组反向遍历 放入新字符串中 + for (int i = words.length - 1; i >= 0; i--) { + if (i != words.length - 1) { + result += " ";// 从第一个单词之后放入空格 + } + result += words[i]; + } + return result; + } + + + /** + * 方法二: + *

+ * 先用trim()去除字符串首位的空格, 将整个字符串进行翻转, 翻转后再将每个单词进行翻转 + * + * @param s + * @return + */ + private static String reverse2(String s) { + s = s.trim(); + + // 将字符串进行翻转, 可以使用前后指针进行对调 + char[] chars = s.toCharArray(); + int left = 0; + int right = chars.length - 1; + + // 将字符串里的字符进行翻转 + while (left < right) { + char tmp = chars[left]; + chars[left] = chars[right]; + chars[right] = tmp; + left++; + right--; + } + + // 将每个单词进行翻转 + String result = ""; + String newString = new String(chars); + String[] words = newString.split(" "); + for (int i = 0; i < words.length; i++) { + for (int j = words[i].length() -1; j >= 0; j--) { + result += words[i].charAt(j); + } + if (i != words.length - 1) + result += " "; //最后一个单词末尾没有空格 + } + return result; + } +} diff --git a/src/main/java/com/study/string/ToUpperCase.java b/src/main/java/com/study/string/ToUpperCase.java index aa72c44..6459f53 100644 --- a/src/main/java/com/study/string/ToUpperCase.java +++ b/src/main/java/com/study/string/ToUpperCase.java @@ -17,6 +17,8 @@ public static void main(String[] args) { * * 利用ascii大小写字母对应的差值进行转换 * + * 小于字母是 97 - 122 大写字母是 65 - 90 比大些字母多了32 + * * @param str * @return */ diff --git a/src/main/java/com/study/string/UniqueEmailAddresses.java b/src/main/java/com/study/string/UniqueEmailAddresses.java index 8525bbe..9464925 100644 --- a/src/main/java/com/study/string/UniqueEmailAddresses.java +++ b/src/main/java/com/study/string/UniqueEmailAddresses.java @@ -39,7 +39,7 @@ public static void main(String[] args) { * * 这里使用了hashset,它的add()的时间复杂度为O(1), 并且不会存在相同的元素 * - * 时间复杂度O(1) + * 时间复杂度O(n) * @param emails * @return */ diff --git a/src/main/java/com/study/string/ValidAnagram.java b/src/main/java/com/study/string/ValidAnagram.java new file mode 100644 index 0000000..e653b11 --- /dev/null +++ b/src/main/java/com/study/string/ValidAnagram.java @@ -0,0 +1,161 @@ +package com.study.string; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 242. 有效的字母异位词 + *

+ * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * 示例 1: + *

+ * 输入: s = "anagram", t = "nagaram" + * 输出: true + * 示例 2: + *

+ * 输入: s = "rat", t = "car" + * 输出: false + * 说明: + * 你可以假设字符串只包含小写字母。 + *

+ * 来源:力扣(LeetCode) + * 链接:https://leetcode-cn.com/problems/valid-anagram + */ +public class ValidAnagram { + + /** + * 两种解法, + * 一种是 对字符串中所有字符转成字符数组进行排序, 然后遍历数组对比每个字符是否, 如果都一样则是 + * 一种是 使用hashmap来存储两个字符串里的每个字符以及个数, 判断两个hashmap是否相同. + * + * @param args + */ + public static void main(String[] args) { + String s1 = "cat"; + String s2 = "tac"; + ValidAnagram va = new ValidAnagram(); + //System.out.printf("%s==%s: %s", s1, s2, va.isAnagram(s1, s2)); + //System.out.printf("%s==%s: %s", s1, s2, va.isAnagram2(s1, s2)); + System.out.printf("%s==%s: %s", s1, s2, va.isAnagram3(s1, s2)); + System.out.println(); + + s1 = "aaab"; + s2 = "bbaa"; + //System.out.printf("%s==%s: %s", s1, s2, va.isAnagram(s1, s2)); + //System.out.printf("%s==%s: %s", s1, s2, va.isAnagram2(s1, s2)); + System.out.printf("%s==%s: %s", s1, s2, va.isAnagram3(s1, s2)); + System.out.println(); + + s1 = "anagram"; + s2 = "nagaram"; + //System.out.printf("%s==%s: %s", s1, s2, va.isAnagram2(s1, s2)); + System.out.printf("%s==%s: %s", s1, s2, va.isAnagram3(s1, s2)); + System.out.println(); + } + + + /** + * 使用排序的方式进行比较 时间复杂度为O(nlogn) + *

+ * 对字符串中所有字符进行排序, 然后将两个字符串的每个字符进行比较, 如果每个字符都一样,且字符串长度相同 那么是字母异位词 + * + * @param s + * @param t + * @return + */ + public boolean isAnagram(String s, String t) { + // 判断字符串长度是否一样, 不一样直接返回false + if (s.length() != t.length()) + return false; + + // 将两个字符串转成char数组 + char[] chars1 = s.toCharArray(); + char[] chars2 = t.toCharArray(); + + // 使用Arrays.sort对char数组进行排序 (内部是快速排序,时间复杂度为O(nlogn)) + Arrays.sort(chars1); + Arrays.sort(chars2); + + // 将排序好的数组进行比较, 比较每一位的字符是否一样 + for (int i = 0; i < chars1.length; i++) { + if (chars1[i] != chars2[i]) + return false; + } + return true; + } + + /** + * 借助hashmap进行比较 时间复杂度为O(n) + *

+ * 使用hashmap来存储两个字符串里的每个字符以及个数, 判断两个hashmap里面的key和value是否相同. + * + * @param s + * @param t + * @return + */ + public boolean isAnagram2(String s, String t) { + // 长度不一样,直接返回false + if (s.length() != t.length()) + return false; + + HashMap mapS = new HashMap<>(); + HashMap mapT = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (mapS.containsKey(c)) { + mapS.put(c, mapS.get(c) + 1); + } else { + mapS.put(c, 1); + } + } + + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + if (mapT.containsKey(c)) { + mapT.put(c, mapT.get(c) + 1); + } else { + mapT.put(c, 1); + } + } + + // 因为前面判断了是否长度一样 + // 所以这里只需要判断每个key对应的value是否一样 + for (Map.Entry kv : mapS.entrySet()) { + if (!kv.getValue().equals(mapT.get(kv.getKey()))) + return false; + } + + return true; + } + + /** + * 将字符串的每个字符转成hash值作为key放到一个数组中进行计数, 一个字符串进行计数,另一个字符串进行减数. + * 最终如果这个数组里面所有key的值都为0, 就说明两个字符串里的字符相同 时间复杂度为O(n) + * + * @param s + * @param t + * @return + */ + public boolean isAnagram3(String s, String t) { + if (s.length() != t.length()) { + return false; + } + // 由于字符串中所有字符都为26个小写字母, 且他们对应的hashcode在97-123之间, 比如a为97,b为98... + // 将字符串s中的每个字母 减去'a' 最终得到的数字在0-26之间 + int[] counter = new int[26]; + for (int i = 0; i < s.length(); i++) { + // charAt()方法返回每个字符的hash值 + counter[s.charAt(i) - 'a']++; // 将字符串s中的每个char 减去'a', 得到的数字作为key,然后value+1 + counter[t.charAt(i) - 'a']--; // 将字符串t中的每个char 减去'a', 得到的数字作为key,然后value-1; + } + + // 如果字符串s和t的每个char都相同, 那么上面的循环结束后,counter里面每个元素的value都为0 + for (int count : counter) { + if (count != 0) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/study/traceback/Queen4.java b/src/main/java/com/study/traceback/Queen4.java new file mode 100644 index 0000000..9d3839e --- /dev/null +++ b/src/main/java/com/study/traceback/Queen4.java @@ -0,0 +1,127 @@ +package com.study.traceback; + +/** + * 4皇后问题 + *

+ * 皇后问题研究的是如何将 4个皇后放置在 4×4 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * 每个皇后的攻击路线为皇后所在的同一行和列以及对角线(意味着这些位置不能放置其他皇后) + *

+ * https://leetcode-cn.com/problems/n-queens/ n皇后问题 + * https://mp.weixin.qq.com/s/hTcRURIWs_9YOfwlOjsA3w 8皇后问题 + *

+ * 解决皇后问题,可以分为两个层面: + * 1.找出第一种正确摆放方式,也就是深度优先遍历。 + * 2.找出全部的正确摆放方式,也就是广度优先遍历。 + */ +public class Queen4 { + + // 棋盘长宽和皇后数量(这里棋盘长宽和皇后数量相等) + private static int maxNum = 4; + + // 由于这里使用的是int数组,int的初始值是0,代表没有落子。当有皇后放置的时候,对应的元素值改为1。 + private static int chessBoard[][] = new int[maxNum][maxNum]; + + public static void main(String[] args) { + // 设置棋盘的长宽 + maxNum = 4; + // 从第一行开始,一行一行递归放置皇后 + setQueen(0); + // 打印整个棋盘 输出皇后的位置 + printChessBoard(); + } + + /** + * 定义一个check方法,传入新皇后的落点,通过检查横纵向以及斜向是否存在皇后来判断该位置是否能放. + * + * @param x 新皇后的横坐标 + * @param y 新皇后的纵坐标 + * @return 该位置是否可放新皇后 false不可 true可以 + */ + private static boolean check(int x, int y) { + for (int i = 0; i < y; i++) { + // 检查同一列是否已经有皇后存在(横坐标x相同) + if (chessBoard[x][i] == 1) { + return false; // 同一行存在皇后 + } + // 检查左斜上方是否有皇后, 左斜方坐标为 x-i, y-i + // 由于是从上往下放皇后,所以只需要检测左斜上方是否有皇后即可, 所以x坐标范围是 0 到 x-1 + if (x - 1 - i >= 0 && chessBoard[x - 1 - i][y - 1 - i] == 1) { + return false; //存在皇后 + } + + // 检查右斜上方是否有皇后, 右斜上方的坐标为 x +i, y-i + if (x + 1 + i < maxNum && chessBoard[x + 1 + i][y - 1 - i] == 1) { + return false;//存在皇后 + } + } + // 不存在皇后, 可以放新皇后 + return true; + } + + /** + * 通过递归+回溯进行放置新皇后 + * 在每一行放置新皇后, 一旦放到第5行就说明前面4行皇后都已放置成功,则结束放置 + * + * @param y + * @return + */ + private static boolean setQueen(int y) { + // 一旦放置到第5行的时候 说明放置所有皇后成功,结束递归 + if (y == maxNum) { + System.out.println("所有皇后放置完成"); + return true;// //递归从外往内终止条件, 之后进行从内往外回溯, 回到上一行进行放皇后试验, y-- + } + + // 遍历y所在的当前行,逐一个列进行检查和放置皇后 + for (int x = 0; x < maxNum; x++) { + // 清理当前行的皇后, 重新放置 + // 如果在当前行的某一列放置皇后失败,则需要将当前行之前放置的皇后清除,重新在下一列放置皇后 + for (int i = 0; i < maxNum; i++) { + chessBoard[i][y] = 0; + } + // 检查该位置能否放置皇后 + System.out.printf("----检查第%d行,第%d列能否放置皇后------\r\n", y, x); + + if (check(x, y)) { + // 如果可以, 放置皇后 + chessBoard[x][y] = 1; + System.out.printf("----放置第%d行,第%d列成功------\r\n", y, x); +// printChessBoard(); +// System.out.println("-------------"); + // 同时递归到下一行进行放置皇后,如果返回true,说明所有皇后已放好 + if (setQueen(y + 1)) { + // 递归达到终止条件,开始从内往外回溯, 这里会执行棋盘的行次数 +// System.out.printf("----全部皇后放置完成------\r\n", x, y); +// printChessBoard(); +// System.out.println("-------------"); + return true; //递归从外往内终止条件, 之后进行从内往外回溯, 回到上一行进行放皇后试验, y-- + } else { + // 如果放置所有行失败, 则回溯到上一行,并且上一行之前放置的某列也失败,并从下一列继续尝试检查放置 + //----放置第2行失败------ + //----放置第1行,第2列失败------ + System.out.printf("----放置第%d行,第%d列失败------\r\n", y, x); +// printChessBoard(); +// System.out.println("-------------"); + } + } else { + System.out.printf("----检查发现第%d行,第%d列无法放入皇后------\r\n", y, x); + } + } + // 如果当前行所有列都无法放置新皇后, 否则当前行放置失败 + System.out.printf("----放置第%d行失败------\r\n", y); + // 这里的false会返回给递归中的setQueen()方法 + return false; //递归从外往内终止条件, 之后进行从内往外回溯, 回到上一行进行放皇后试验, y-- + } + + /** + * 打印整个棋盘,输出皇后的位置 + */ + private static void printChessBoard() { + for (int x = 0; x < maxNum; x++) { + for (int y = 0; y < maxNum; y++) { + System.out.print(chessBoard[x][y] + " "); + } + System.out.println(); + } + } +} diff --git a/src/main/java/com/study/traceback/Queen4_2.java b/src/main/java/com/study/traceback/Queen4_2.java new file mode 100644 index 0000000..18140fb --- /dev/null +++ b/src/main/java/com/study/traceback/Queen4_2.java @@ -0,0 +1,85 @@ +package com.study.traceback; + +public class Queen4_2 { + + // 棋盘的行和列, 以及皇后数量 + private static int maxNum = 4; + + private static int[][] chessBoard = new int[maxNum][maxNum]; + + public static void main(String[] args) { + maxNum = 4; + + // 从第0行开始放置皇后 + setQueen(0); + + printChessBoard(); + } + + /** + * 按行进行放置皇后 + * + * @param y 行号 + * @return + */ + private static boolean setQueen(int y) { + if (y == maxNum) + return true; // 结束递归条件 + + // 遍历第y行的每一列, 检查并放置皇后, 如果下一行放置失败同时进行回溯,从下一列重新放置 + for (int x = 0; x < maxNum; x++) { + // 清理当前行的数据, 重新放置 + for (int i = 0; i < maxNum; i++) { + chessBoard[i][y] = 0; + } + + // 检查当前位置能否放皇后 + if (check(x, y)) { + // 放入皇后 + chessBoard[x][y] = 1; + + // 递归进入下一行继续操作, 这里不能返回false + if (setQueen(y + 1)) { + return true; //所有皇后放置成功,递归达到终止条件 y-- + } + } + } + // 当前行都无法放置皇后, 递归达到终止条件 y-- + return false; + } + + + /** + * 检查落子的纵上方和左右斜上方是否有皇后 + * + * @param x 落子的横坐标 + * @param y 落子的纵坐标 + * @return + */ + private static boolean check(int x, int y) { + // 从当前行往上遍历 + for (int i = 0; i < y; i++) { + // 检查纵上方 + if (chessBoard[x][y - 1 - i] == 1) + return false; + // 检查左斜上方 + if (x - 1 - i >= 0 && chessBoard[x - 1 - i][y - 1 - i] == 1) { + return false; + } + // 检查右斜上方 + if (x + 1 + i < maxNum && chessBoard[x + 1 + i][y - 1 - i] == 1) { + return false; + } + } + return true; + } + + private static void printChessBoard() { + for (int x = 0; x < maxNum; x++) { + for (int y = 0; y < maxNum; y++) { + System.out.print(chessBoard[x][y] + " "); + } + System.out.println(); + } + } +} diff --git a/src/main/java/com/study/traceback/QueenN.java b/src/main/java/com/study/traceback/QueenN.java new file mode 100644 index 0000000..701c614 --- /dev/null +++ b/src/main/java/com/study/traceback/QueenN.java @@ -0,0 +1,75 @@ +package com.study.traceback; + +/** + * N皇后问题, 找出一种可以放置n个皇后的方法,使得每个皇后之间无法互相攻击 + * + * 使用深度优先搜索dfs找出一种可以放置皇后的方法 + * + * 具体做法为递归+回溯 + */ +public class QueenN { + + private static int N = 0; + private static int[][] chessBoard; + + public static void main(String[] args) { + //createChessBoard(4); + //createChessBoard(5); + createChessBoard(8); + setQueen(0); + printChessBoard(); + } + + private static void createChessBoard(int n){ + N = n; + chessBoard = new int[N][N]; + } + + private static boolean setQueen(int y) { + if (y == N) + return true; + + for (int x = 0; x < N; x++) { + // 清理当前行的数据 + for (int i = 0; i < N; i++) { + chessBoard[i][y] = 0; + } + + if (check(x, y)) { + chessBoard[x][y] = 1; + + if (setQueen(y + 1)) { + return true; + } + } + } + return false; + } + + private static boolean check(int x, int y) { + // 从当前位置的上一行开始往上遍历 + for (int i = 0; i < y; i++) { + // 检查纵向 + if (chessBoard[x][y - 1 - i] == 1) + return false; + + // 检查左斜 + if (x - 1 - i >= 0 && chessBoard[x - 1 - i][y - 1 - i] == 1) + return false; + + // 检查右斜 + if (x + 1 + i < N && chessBoard[x + 1 + i][y - 1 - i] == 1) + return false; + } + return true; + } + + private static void printChessBoard(){ + for(int x = 0; x < N; x ++){ + for(int y =0; y < N; y++){ + System.out.print(chessBoard[x][y] + " "); + } + System.out.println(); + } + } +} diff --git a/src/main/java/com/study/tree/binarytree/ArrayToTree.java b/src/main/java/com/study/tree/binarytree/ArrayToTree.java new file mode 100644 index 0000000..1a84ca9 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/ArrayToTree.java @@ -0,0 +1,125 @@ +package com.study.tree.binarytree; + + +import java.util.*; + +/** + * 有一个数组, 存着二叉树所有不为空的叶子节点, 通过该数组构建整个二叉树 + *

+ * array = [-9,8,3,-4,1] + *

+ * int v1 = hash(-9,8); + * v2 = hash(3,-4) + * v3 = hash(v1,v2) + * v4 = hash(1,0) + * v5 = hash(v4,0) + *

+ * root + * v3 v5 + * v1 v2 v4 0 + * -9 8 3 -4 1 0 0 0 + */ +public class ArrayToTree { + + public static void main(String[] args) { + int[] arr = {-9, 8, 3, -4, 1}; + + buildTree(arr, 4); + } + + /** + * 构建二叉树 + */ + private static void buildTree(int[] arr, int level) { + // 记算叶子节点总结点数 + int fullNodes = 1; + int i = 1; + while (i < level) { + fullNodes *= 2; + i++; + } + + List> allNodes = new ArrayList<>(); + + List knifeNodes = new ArrayList<>(); + // 将叶子节点放入集合中 + for (int a = 0; a < arr.length; a++) { + knifeNodes.add(new TreeNode(arr[a])); + } + + i = arr.length; + // 将叶子节点补全 + while (i < fullNodes) { + knifeNodes.add(new TreeNode(0)); + i++; + } + + allNodes.add(knifeNodes); + + // 构建叶子节点以上所有的层节点, 并把他们加入到集合中 + int currentLevel = 0; + while (currentLevel < level - 1) { + List parentNodes = new ArrayList<>(); + // 构造父节点 + for (i = 0; i < allNodes.get(currentLevel).size(); i += 2) { + parentNodes.add(getParentNode(allNodes.get(currentLevel).get(i), allNodes.get(currentLevel).get(i + 1))); + } + allNodes.add(parentNodes); + + currentLevel++; + } + + TreeNode root = allNodes.get(currentLevel).get(0); + System.out.println(root.val); + + // 通过根节点遍历树 + List> lists = levelOrder(root); + for (List nodesInALevel : lists) { + System.out.println(); + for (Integer node : nodesInALevel) { + System.out.print(node + " "); + } + } + } + + private static List> levelOrder(TreeNode root) { + // 创建一个2维的节点集合, 第1维度为层集合,第2维度为每层的节点集合 + List> levels = new ArrayList>(); + //如果根节点为空,直接返回空列表 + if (root == null) return levels; + + // 创建一个当前层的队列 + Queue queue = new LinkedList(); + queue.add(root); + + while (!queue.isEmpty()) { + // 往节点结合中添加当前层级的空节点集合 + List currentLevelNodes = new ArrayList<>(); + // 获取当前层次的节点个数 + int level_length = queue.size(); + // 遍历当前层的节点个数 + for (int i = 0; i < level_length; i++) { + TreeNode node = queue.remove();//取出并移除队列的第一个节点 根据先进先出原则, 取出的顺序为 根 左 右(根据添加时候的顺序一致) + + // 往节点集合中添加当前层级的节点 + currentLevelNodes.add(node.val); + + // 往当前层队列中添加当前层的子节点,用于下一次迭代处理 + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); + } + levels.add(currentLevelNodes); + } + return levels; + } + + private static TreeNode getParentNode(TreeNode left, TreeNode right) { + int parentValue = left.val + right.val; + TreeNode parentNode = new TreeNode(parentValue); + parentNode.left = left; + parentNode.right = right; + return parentNode; + } +} diff --git a/src/main/java/com/study/tree/binarytree/BFS.java b/src/main/java/com/study/tree/binarytree/BFS.java new file mode 100644 index 0000000..1975f27 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/BFS.java @@ -0,0 +1,108 @@ +package com.study.tree.binarytree; + +import com.study.tree.binarytree.TreeNode; +import com.study.utils.TreeUtils; + +import java.util.*; + +/** + * BFS 广度优先搜索 + * 按层级一层一层进行搜索 + * + * 常见的解法有: 循环 + 队列 + * + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ + */ +public class BFS { + + + public static void main(String[] args) { + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); + + List> visitedNodes = searchByLoop(root); + // List> visitedNodes = searchByRecursion(root); + for (List currentLevelNodes : visitedNodes) { + for(TreeNode node : currentLevelNodes){ + System.out.print(node.val + " "); + } + System.out.println(); + } + } + + /** + * 使用遍历的方式进行广度优先搜索, 需要借助一个Queue的结构,先进先出 + * + * 时间复杂度为O(n). 空间复杂度为O(n^2),用到了两层集合 + * + * @param root + * @return + */ + private static List> searchByLoop(TreeNode root) { + // 用于存储访问过的节点 + List> visited = new ArrayList<>(); + + if (root == null) + return visited; + + // 记录节点层级 + int level = 0; + + // 利用队列先进先出的特性,实现层级的从左往右遍历 + Queue nodes = new LinkedList<>(); //链表也是有序的,并且实现了队列的功能,先进先出 + nodes.offer(root); + + while (!nodes.isEmpty()) { + // 添加层 + visited.add(new ArrayList<>()); + // 获取当前层级的节点数 + int size = nodes.size(); + + // 遍历当前层级所有节点, 将他们的子节点从左往右加到队列中去 + for (int i = 0; i < size; i++) { + TreeNode node = nodes.poll(); //取出最先加入的节点, 并将其子节点从左往右加入到队列中, 用于下一轮遍历 + visited.get(level).add(node); //给当前层添加节点 + if (node.left != null) { + nodes.offer(node.left); + } + if (node.right != null) { + nodes.offer(node.right); + } + } + level++; + } + return visited; + } + + /** + * 使用递归的方式实现广度优先, 这里在递归的过程中用到了层级控制,如果集合当前层没有节点,就新建; 如果有节点, 就加到当前层里面 + * + * @param root + * @return + */ + private static List> searchByRecursion(TreeNode root) { + List> nodeList = new ArrayList<>(); + if (root == null) + return nodeList; + // 递归获取所有子节点 + search(root, 0, nodeList); + return nodeList; + } + + private static void search(TreeNode root, int level, List> nodeList) { + if (root == null) { + return; + } + // 集合的当前层没有节点, 就新建 + if (level >= nodeList.size()) { + List subList = new ArrayList<>(); + subList.add(root); + nodeList.add(subList); + } else { + // 集合的当前层有节点, 就直接添加 + nodeList.get(level).add(root); + } + search(root.left, level + 1, nodeList); + search(root.right, level + 1, nodeList); + } +} diff --git a/src/main/java/com/study/tree/binarytree/BalancedBinaryTree.java b/src/main/java/com/study/tree/binarytree/BalancedBinaryTree.java new file mode 100644 index 0000000..80a31f7 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/BalancedBinaryTree.java @@ -0,0 +1,144 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +/** + * 验证平衡二叉树 + * + * 给定一个二叉树,判断它是否是高度平衡的二叉树。 + *

+ * 本题中,一棵高度平衡二叉树定义为: + *

+ * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 + *

+ * 示例 1: + *

+ * 给定二叉树 [3,9,20,null,null,15,7] + *

+ * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回 true 。 + *

+ * 示例 2: + *

+ * 给定二叉树 [1,2,2,3,3,null,null,4,4] + *

+ * 1 + * / \ + * 2 2 + * / \ + * 3 3 + * / \ + * 4 4 + * 返回 false 。 + *

+ * https://leetcode-cn.com/problems/balanced-binary-tree/ + */ +public class BalancedBinaryTree { + + public static void main(String[] args) { + //Integer[] nodes = {3, 9, 20, null, null, 15, 7}; + Integer[] nodes = {1, 2, 2, 3, 3, null, null, 4, 4}; + TreeNode root = TreeUtils.buildTree(nodes); + TreeUtils.show(root); + + //System.out.println(isBalanced(root)); + System.out.println(isBalanced2(root)); + } + + /** + * 使用前序遍历(根左右),先处理根节点获取高度, 再遍历左右子树是否为平衡二叉树. 从顶至底(暴力法) + * 通过比较左右子树最大高度差是否大于1 来判断以此节点为根节点下是否是二叉平衡树 + *

+ * 从顶至底DFS,以每个节点为根节点,递归判断是否是平衡二叉树: + * 若所有根节点都满足平衡二叉树性质,则返回 True ; + * 若其中任何一个节点作为根节点时,不满足平衡二叉树性质,则返回False。 + *

+ * 最差时间复杂度为O(N^2) + *

+ * 作者:jyd + * 链接:https://leetcode-cn.com/problems/balanced-binary-tree/solution/balanced-binary-tree-di-gui-fang-fa-by-jin40789108/ + * + * @param root + * @return + */ + public static boolean isBalanced(TreeNode root) { + // 根节点为空, 说明是空树,肯定是平衡的 + if (root == null) + return true; + + // 先处理根节点, 获取高度 + // 获取每个节点的左右子树的高度 + int left = getHeight(root.left); + int right = getHeight(root.right); + + // 判断当前root节点为根的树是否为平衡二叉树 + if (Math.abs(left - right) > 1) + return false; + + // 再进行左右子树遍历 + // 如果当前树为平衡树, 那么继续判断递归当前树的左右子树是否为平衡二叉树 + return isBalanced(root.left) && isBalanced(root.right); + } + + /** + * 获取一颗树的最大高度 + * + * @param root + * @return + */ + private static int getHeight(TreeNode root) { + if (root == null) + return 0; + + int left = getHeight(root.left); + + int right = getHeight(root.right); + + return Math.max(left, right) + 1; + } + + /** + * 后续遍历(左右根), 先获取左右子树的高度, 再作判断是否为平衡二叉树 + * 从底部到顶部, 获取每个节点为root根时候的子树最大高度差是否大于1(不平衡), 如果是返回-1; 否则返回当前子树的最大高度 + * + * 时间复杂度为O(n) + * + * https://leetcode-cn.com/problems/balanced-binary-tree/solution/balanced-binary-tree-di-gui-fang-fa-by-jin40789108/ + * + * @return + */ + private static boolean isBalanced2(TreeNode root) { + return depth(root) != -1; + } + + /** + * 后续遍历 + * @param root + * @return + */ + private static int depth(TreeNode root) { + if (root == null) + return 0; + + // 左子树出现不满足条件的 + int left = depth(root.left); + if (left == -1) // 得到-1 说明不满足条件 + return -1; + + // 右子树出现不满足条件的 + int right = depth(root.right); + if (right == -1) // 得到-1 说明不满足条件 + return -1; + + // 检查是否满足条件, 左右子树高度差绝对值不超过1 + if (Math.abs(left - right) > 1) + return -1; //不满足条件就返回-1 + + // 返回当前左右子树的最大高都 + return Math.max(left, right) + 1; + } +} diff --git a/src/main/java/com/study/tree/binarytree/DFS.java b/src/main/java/com/study/tree/binarytree/DFS.java new file mode 100644 index 0000000..8844dff --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/DFS.java @@ -0,0 +1,75 @@ +package com.study.tree.binarytree; + +import com.study.tree.binarytree.TreeNode; +import com.study.utils.TreeUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 深度优先搜索 + *

+ * 其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次. + *

+ * 常见的解法有: 递归(递归是天然的深度优先遍历法则) + */ +public class DFS { + + /** + * 深度优先遍历 + * + * @param args + */ + public static void main(String[] args) { + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); + List nodes = search(root); + for (TreeNode node : nodes) { + System.out.print(node.val + " "); + } + } + + /** + * 通过递归的方式实现深度优先遍历 + * + * @return + */ + private static List search(TreeNode root) { + List nodeList = new ArrayList<>(); + if (root == null) + return nodeList; + dfs(root, nodeList); + return nodeList; + } + + private static void dfs(TreeNode node, List nodeList) { + // 递归结束条件为节点为空 + if (node == null) { + return; + } + + // 将节点加到集合中, 因为有些节点会反复访问,所以这里需要去重 + if (!nodeList.contains(node)) { + nodeList.add(node); + //System.out.println("经过节点:" + node.val); + } else { + //System.out.println("经过重复节点:" + node.val); + } + + + // 先从左边从外往内递归 直到最深处, 当node为null的时候结束递归开始从内往外 + if (node.left != null) { + // 1 3 7 5 + System.out.println("递归左边: " + node.left.val); + dfs(node.left, nodeList); + } + + // 再从右边一直往内部 + // 左边从外往内直到node==null后终止往内, 开始由内往外, 再往外的过程中不断再由外往内递归右子树 + if (node.right != null) { + // 4 2 6 + System.out.println("递归右边: " + node.right.val); + dfs(node.right, nodeList); + } + } +} diff --git a/src/main/java/com/study/binarytree/InorderTraversal.java b/src/main/java/com/study/tree/binarytree/InorderTraversal.java similarity index 55% rename from src/main/java/com/study/binarytree/InorderTraversal.java rename to src/main/java/com/study/tree/binarytree/InorderTraversal.java index 9a47ae4..bc01fac 100644 --- a/src/main/java/com/study/binarytree/InorderTraversal.java +++ b/src/main/java/com/study/tree/binarytree/InorderTraversal.java @@ -1,9 +1,8 @@ -package com.study.binarytree; +package com.study.tree.binarytree; import com.study.utils.TreeUtils; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.Stack; @@ -29,16 +28,17 @@ public class InorderTraversal { public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; - List treeNodes = TreeUtils.buildTree(arr); - TreeNode root = treeNodes.get(0); + TreeNode root = TreeUtils.buildTree(arr); + TreeUtils.show(root); //List list = inorderTraversal(root); //List list = inorderTraversal2(root); - List list = inorderTraversal3(root); + //List list = inorderTraversal3(root); + List list = inorderTraversal4(root); for (Integer num : list) { - System.out.println(num); + System.out.print(num + " "); } } @@ -56,10 +56,10 @@ private static List inorderTraversal(TreeNode root) { private static void traversal(TreeNode root, List list) { if (root != null) { - //递归左(右)子树 + //递归左子树 traversal(root.left, list); list.add(root.val); - //递归右(左)子树 + //递归右子树 traversal(root.right, list); } } @@ -96,20 +96,61 @@ private static List inorderTraversal2(TreeNode root) { */ private static List inorderTraversal3(TreeNode root) { List list = new ArrayList(); + + if (root == null) + return list; + Stack stack = new Stack(); + // 遍历条件为当前节点不为空 或者 栈不为空 while (root != null || !stack.isEmpty()) { - // 左 + // 将当前节点的所有左节点入栈 + // 如果当前节点不为空,递归其左节点 + while (root != null) { + stack.push(root);//将左节点入栈 + System.out.println(String.format("节点%d被放入栈中", root.val)); + root = root.left; + } + + // 从栈中弹出节点 + if (!stack.isEmpty()) { + // 将栈中的节点的第一个弹出 + // 存在左节点的,优先将最深层左节点弹出 + root = stack.pop(); + System.out.println(String.format("节点%d从栈中弹出", root.val)); + list.add(root.val); + System.out.println(String.format("将节点%d放入集合中", root.val)); + // 切到右子树,给下一轮找左子树使用 + root = root.right; + } + } + return list; + } + + private static List inorderTraversal4(TreeNode root) { + List list = new ArrayList<>(); + + if (root == null) + return list; + + Stack stack = new Stack<>(); + + // 中序遍历条件有两个 当前节点不为空 或者 栈不为空 + while (root != null || !stack.isEmpty()) { + + // 将当前节点的所有左边子节点入栈(包括根节点) while (root != null) { stack.push(root); root = root.left; } + // 处理栈中的节点 if (!stack.isEmpty()) { - // 根 (当左子节点为空的时候,该节点变为根) + // 将栈顶节点弹出并放入list root = stack.pop(); list.add(root.val); - // 右 + + // 将节点切到弹出节点的右子节点, 便于下次迭代处理 root = root.right; } } diff --git a/src/main/java/com/study/tree/binarytree/InvertTree.java b/src/main/java/com/study/tree/binarytree/InvertTree.java new file mode 100644 index 0000000..db1851f --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/InvertTree.java @@ -0,0 +1,104 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 二叉树翻转 + *

+ * 翻转一棵二叉树。 + *

+ * 示例: + *

+ * 输入: + *

+ * 4 + * / \ + * 2 7 + * / \ / \ + * 1 3 6 9 + * 输出: + *

+ * 4 + * / \ + * 7 2 + * / \ / \ + * 9 6 3 1 + *

+ * 链接:https://leetcode-cn.com/problems/invert-binary-tree + */ +public class InvertTree { + + public static void main(String[] args) { + + Integer[] nodes = {4, 2, 7, 1, 3, 6, 9}; + TreeNode root = TreeUtils.buildTree(nodes); + TreeUtils.show(root); + TreeNode newRoot = invertTree(root); + //TreeNode newRoot = invertTree2(root); + TreeUtils.show(newRoot); + } + + + /** + * 后序遍历, 深度优先dfs 自下而上左右节点互换 + *

+ * 时间复杂度O(n) + * + * @param root + * @return + */ + public static TreeNode invertTree(TreeNode root) { + if (root == null) + return null; + + TreeNode left = invertTree(root.left); + TreeNode right = invertTree(root.right); + + // 从最底层叶子节点开始左右互换 + root.left = right; + root.right = left; + + // 返回新的root节点,其左右子节点已经互换 + return root; + } + + /** + * 使用层级遍历的方式 + * + * 时间复杂度O(n) + * @param root + * @return + */ + public static TreeNode invertTree2(TreeNode root) { + if (root == null) + return null; + + Queue queue = new LinkedList(); + + queue.offer(root); + + // 一层一层遍历, 并将左右子节点互换 + while (!queue.isEmpty()) { + int size = queue.size(); + // 当前层级的所有节点取出, 并将其左右子节点进行互换 + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node != null) { + TreeNode tmp = node.left; + node.left = node.right; + node.right = tmp; + + // 左右节点互换后, 再将左右节点依次放入队列中 + if (node.left != null) + queue.offer(node.left); + if (node.right != null) + queue.offer(node.right); + } + } + } + return root; + } +} diff --git a/src/main/java/com/study/tree/binarytree/LevelTraversal.java b/src/main/java/com/study/tree/binarytree/LevelTraversal.java new file mode 100644 index 0000000..b0b7fc9 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/LevelTraversal.java @@ -0,0 +1,184 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +import java.util.*; + +/** + * 层级遍历二叉树,广度优先遍历bfs + * 1 + * 2 3 + * 4 5 6 7 + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/comments/ + *

+ * 解题思路, + * 1.借用一个queue先进先出的结构来遍历二叉树 remove和add方法 + * 2.借用一个stack后进先出的接口来遍历二叉树 pop和push方法 + *

+ * 用宽度优先搜索遍历来划分层次:[[1], [2, 3], [4, 5 , 6 ,7]]。 + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/er-cha-shu-de-ceng-ci-bian-li-by-leetcode/ + */ +public class LevelTraversal { + public static void main(String[] args) { + Integer[] arr = {1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); + + //List> allNodes = levelOrder(root); + List> allNodes = levelOrder2(root); + + for (List nodesInALevel : allNodes) { + System.out.println(); + for (Integer node : nodesInALevel) { + System.out.print(node + " "); + } + } + } + + /** + * 返回二叉树的所有节点的值 + *

+ * 使用迭代的方式 + *

+ * 思路: 使用一个队列来装载二叉树的每一层节点, 通过先进先出的原理使得取出来的节点可以根据装入的节点顺序一样(根 左 右) + * + * @param root + * @return + */ + private static List> levelOrder(TreeNode root) { + // 创建一个2维的节点集合, 第1维度为层集合,第2维度为每层的节点集合 + List> levels = new ArrayList>(); + //如果根节点为空,直接返回空列表 + if (root == null) return levels; + + // 创建一个当前层的队列 + Queue queue = new LinkedList(); + queue.add(root); + + while (!queue.isEmpty()) { + // 往节点结合中添加当前层级的空节点集合 + List currentLevelNodes = new ArrayList<>(); + // 获取当前层次的节点个数 + int level_length = queue.size(); + // 遍历当前层的节点个数 + for (int i = 0; i < level_length; i++) { + TreeNode node = queue.remove();//取出并移除队列的第一个节点 根据先进先出原则, 取出的顺序为 根 左 右(根据添加时候的顺序一致) + + // 往节点集合中添加当前层级的节点 + currentLevelNodes.add(node.val); + + // 往当前层队列中添加当前层的子节点,用于下一次迭代处理 + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); + } + levels.add(currentLevelNodes); + } + return levels; + } + + /** + * 使用 queue + * + * @param root + * @return + */ + private static List> levelOrder2(TreeNode root) { + List> nodes = new ArrayList>(); + + if (root == null) + return nodes; + + Queue queue = new LinkedList(); + queue.add(root); + + // 遍历队列中的节点,当前层里的所有节点 + while (queue.size() > 0) { + List currentLevelNodes = new ArrayList<>(); + + // 当前层节点数 + int nodeCount = queue.size(); + + // 遍历当前层所有的节点, 往节点结合中添加当前层所有节点值, 往队列中添加当前所有节点的子节点 + for (int i = 0; i < nodeCount; i++) { + // 添加当前层节点值 + TreeNode node = queue.remove(); + currentLevelNodes.add(node.val); + + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); + } + nodes.add(currentLevelNodes); + } + return nodes; + } + + /** + * 使用递归的方式实现广度优先, 这里在递归的过程中用到了层级控制,如果集合当前层没有节点,就新建; 如果有节点, 就加到当前层里面 + * + * @param root + * @return + */ + private static List> searchByRecursion(TreeNode root) { + List> nodeList = new ArrayList<>(); + if (root == null) + return nodeList; + // 递归获取所有子节点 + search(root, 0, nodeList); + return nodeList; + } + + private static void search(TreeNode root, int level, List> nodeList) { + if (root == null) { + return; + } + // 集合的当前层没有节点, 就新建 + if (level >= nodeList.size()) { + List subList = new ArrayList<>(); + subList.add(root); + nodeList.add(subList); + } else { + // 集合的当前层有节点, 就直接添加 + nodeList.get(level).add(root); + } + search(root.left, level + 1, nodeList); + search(root.right, level + 1, nodeList); + } + + private static List> levelOrder3(TreeNode root) { + List> nodes = new ArrayList>(); + + if (root == null) + return nodes; + + int level = 0; + Queue queue = new LinkedList(); + queue.add(root); + + // 遍历队列中的节点,当前层里的所有节点 + while (queue.size() > 0) { + nodes.add(new ArrayList()); + + // 当前层节点数 + int nodeCount = queue.size(); + + // 遍历当前层所有的节点, 往节点结合中添加当前层所有节点值, 往队列中添加当前所有节点的子节点 + for (int i = 0; i < nodeCount; i++) { + // 添加当前层节点值 + TreeNode node = queue.remove(); + nodes.get(level).add(node.val); + + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); + } + + // 接着循环下一层 + level++; + } + return nodes; + } +} diff --git a/src/main/java/com/study/tree/binarytree/LevelTraversal2.java b/src/main/java/com/study/tree/binarytree/LevelTraversal2.java new file mode 100644 index 0000000..96802b3 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/LevelTraversal2.java @@ -0,0 +1,81 @@ +package com.study.tree.binarytree; + + +import com.study.utils.TreeUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + + +/** + * 二叉树遍历2 + * + * 将一个二叉树各节点输出, 奇数行按顺序输出,偶数行倒叙输出 + * input: + * A + * / \ + * B C + * / \ \ + * D E F + * / \ + * G H + * output: + * A + * CB + * DEF + * HG + */ +public class LevelTraversal2 { + + public static void main(String[] args) { + String[] nodes = {"A", "B", "C", "D", "E", null, "F", null,null, "G", null, null, null, null, "H"}; + TreeNode2 root = TreeUtils.buildTree(nodes); + TreeUtils.show(root); + List> list = getTreeList(root); + + int level = 1; + for (List levelList : list) { + // 奇数行 正序输出 + if (level % 2 != 0) { + for (int i = 0; i < levelList.size(); i++) { + System.out.print(levelList.get(i)); + } + } else { + //偶数行 倒叙输出 + for (int i = levelList.size() - 1; i >= 0; i--) { + System.out.print(levelList.get(i)); + } + } + System.out.println(); + level++; + } + } + + private static List> getTreeList(TreeNode2 root) { + List> list = new ArrayList<>(); + + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + List levelList = new ArrayList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode2 node = queue.poll(); + levelList.add(node.val); + + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + list.add(levelList); + } + + return list; + } +} diff --git a/src/main/java/com/study/binarytree/LowestCommonAncestor.java b/src/main/java/com/study/tree/binarytree/LowestCommonAncestor.java similarity index 70% rename from src/main/java/com/study/binarytree/LowestCommonAncestor.java rename to src/main/java/com/study/tree/binarytree/LowestCommonAncestor.java index 09b22c7..a1e2005 100644 --- a/src/main/java/com/study/binarytree/LowestCommonAncestor.java +++ b/src/main/java/com/study/tree/binarytree/LowestCommonAncestor.java @@ -1,4 +1,4 @@ -package com.study.binarytree; +package com.study.tree.binarytree; import com.study.utils.TreeUtils; @@ -37,9 +37,8 @@ public class LowestCommonAncestor { public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - List treeNodes = TreeUtils.buildTree(arr); - + int[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + List treeNodes = TreeUtils.buildTreeAndList(arr); //TreeNode p = treeNodes.get(4); //TreeNode q = treeNodes.get(5); @@ -63,15 +62,15 @@ public static void main(String[] args) { /** * 递归的方式查找最近公共祖先 - * - * 先从左子树进行查找,如果找到第一个目标节点p, 则不在往下查找,而是直接从目标节点的父节点r的右子树进行查找 - * + *

+ * 先从左子树进行查找,如果找到第一个目标节点p, 那么不再往下查找,而是直接从目标节点的父节点r的右子树进行查找 + *

* 如果右子树也找到目标节点q,说明r就是p和q的最近公共祖先 - * + *

* 如果右子树没有找到目标节点q, 那么目标节点q肯定在目标节点p的子节点中. 那么p就是p和q的最近公共祖先 - * + *

* 如果左子树没有找到目标节点, 那么再从右子树找, 如果找到第一个目标节点q, 那么q就是p和q的最近公共祖先,因为p肯定是q的子节点 - * + *

* 若果左右子树都没有找到目标节点, 那么直接返回null * * @param root @@ -80,28 +79,45 @@ public static void main(String[] args) { * @return */ private static TreeNode getlca(TreeNode root, TreeNode p, TreeNode q) { - if (root == null) + if (root == null) { + System.out.println("递归往内中止返回null值"); return null; + } - if (root == p || root == q) + if (root == p || root == q) { + if (root == p) + System.out.println("递归往内中止, root == p ==" + root.val); + if (root == q) + System.out.println("递归往内中止, root == q ==" + root.val); return root; + } // 遍历左子树看有没有目标节点, 有返回目标节点, 没有返回null TreeNode left = getlca(root.left, p, q); // 如果找到目标节点, 无需下遍历, 直接从当前节点的右子树进行遍历 - + System.out.println("递归往外开始, 左边目标节点left = " + (left == null ? "null" : left.val)); // 遍历右子树有看没有目标节点, 有返回目标节点, 没有返回null TreeNode right = getlca(root.right, p, q); + System.out.println("递归往外开始, 右边目标节点right = " + (right == null ? "null" : right.val)); // 如果左右子树都有目标节点, 说明公共祖先就是它本身 - if (left != null && right != null) + if (left != null && right != null) { + System.out.println("左右子树都有目标节点,公共祖先为" + root.val); return root; - - // 如果左右子树都没有目标节点, 则返回null - if (left == null && right == null) - return null; + } // 如果目标节点都在左子树,返回左子树匹配的节点 // 如果目标节点都在右子树,返回右子树匹配的节点 - return left != null ? left : right; + //return left != null ? left : right; + + if (left != null) { + System.out.println("公共祖先在左子树, left = " + left.val); + return left; + } + if (right != null) { + System.out.println("公共祖先在右子树, right = " + right.val); + return right; + } + + return null; } } diff --git a/src/main/java/com/study/tree/binarytree/MaximumDepth.java b/src/main/java/com/study/tree/binarytree/MaximumDepth.java new file mode 100644 index 0000000..e5cc071 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/MaximumDepth.java @@ -0,0 +1,132 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 二叉树的最大深度 + *

+ * 给定一个二叉树,找出其最大深度。 + *

+ * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 + *

+ * 说明: 叶子节点是指没有子节点的节点。 + *

+ * 示例: + * 给定二叉树 [3,9,20,null,null,15,7], + *

+ * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回它的最大深度 3 。 + *

+ * 链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree + */ +public class MaximumDepth { + + /** + * 可以通过深度优先进行递归 或者 层级遍历 + * + * @param args + */ + public static void main(String[] args) { + //Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + Integer[] arr = {5, 1, 4, null, null, 3, 6}; + TreeNode root = TreeUtils.buildTree(arr); + TreeUtils.show(root); + + //System.out.println(maxDepthByRecursion(root, "根", root.val)); + //System.out.println(maxDepthByLoop(root)); + System.out.println(maxDepthByRecursion2(root)); + } + + /** + * 使用dfs深度优先的策略(递归)的方式来解决这个问题 + *

+ * 从根递归到最底层叶子节点, 值为1, 然后每往上面一层+1,同时每层会对左右两边的值做对比,取最大的. 如果是算最大深度,那就是取根的左子树和右子树中的最大值 + *

+ * 我们每个结点只访问一次,因此时间复杂度为 O(N), + * + * @param root + * @return + */ + private static int maxDepthByRecursion(TreeNode root, String position, int val) { + if (root == null) { + System.out.println(String.format("%s从%d节点到叶子节点递归终止", position, val)); + return 0; + } + + System.out.println(String.format("%s从%d节点到叶子节点递归", position, val)); + + int left = maxDepthByRecursion(root.left, "左边", root.val); + // 左边递归到了叶子节点, 开始往根节点走 + System.out.println(String.format("左边从叶子节点往根节点开始, 当前节点为: %d left = %d", root.val, left)); + + int right = maxDepthByRecursion(root.right, "右边", root.val); + // 右边递归到了叶子节点, 开始往根节点走 + System.out.println(String.format("右边从叶子节点往根节点开始, 当前节点为: %d right = %d", root.val, right)); + + int result = Math.max(left, right) + 1; + System.out.println(String.format("result = %d, left = %d, right = %d", result, left, right)); + + return result; + } + + /** + * 深度优先遍历, 计算每层左右节点的层数,取最大值 + * + * 时间复杂度为O(n) + * + * @param root + * @return + */ + private static int maxDepthByRecursion2(TreeNode root) { + if (root == null) + return 0; + + int left = maxDepthByRecursion2(root.left); + int right = maxDepthByRecursion2(root.right); + + // 从最底层往根节点回溯的过程中, 计算每层的层数, 取左右两边最大的层数并+1(每层的层数) + // 每次回溯到上一层 都会将当前层左右节点的最大层数+1 带给上一层 + return Math.max(left, right) + 1;// 最底层为0+1=1, 倒数第2层为0+1+1=2 + } + + + /** + * 使用广度优先的方式找出最大深度 遍历+队列 + * 时间复杂度为O(n) + * + * @param root + * @return + */ + private static int maxDepthByLoop(TreeNode root) { + if (root == null) + return 0; + + Queue queue = new LinkedList<>(); + queue.offer(root); + int level = 0; + while (!queue.isEmpty()) { + int size = queue.size(); + + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + level++; + } + // 返回最大层级数, 即最深层级 + return level; + } +} diff --git a/src/main/java/com/study/tree/binarytree/MinimumDepth.java b/src/main/java/com/study/tree/binarytree/MinimumDepth.java new file mode 100644 index 0000000..e6fb509 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/MinimumDepth.java @@ -0,0 +1,145 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; +import sun.reflect.generics.tree.Tree; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 二叉树的最小深度 + *

+ * 给定一个二叉树,找出其最小深度。 + *

+ * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 + *

+ * 说明: 叶子节点是指没有子节点的节点。 + *

+ * 示例: + *

+ * 给定二叉树 [3,9,20,null,null,15,7], + *

+ * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回它的最小深度  2. + *

+ * 链接:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree + */ +public class MinimumDepth { + + public static void main(String[] args) { + //Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + Integer[] arr = {5, 1, 4, null, null, 3}; + //Integer[] arr = {5, 1}; + TreeNode root = TreeUtils.buildTree(arr); + TreeUtils.show(root); + //System.out.println(minDepthByRecursion(root)); + //System.out.println(minDepthByRecursion2(root)); + System.out.println(minDepthByRecursion3(root)); + //System.out.println(minDepthByLoop(root)); + } + + /** + * 使用分治的方法 从左右节点进行递归, + * 注意: 当root节点左右孩子都为空时,返回1 + *

+ * 如果是单边为空或者都为空的节点, 返回另一边+1 这样能够记录所有分支的高度, 最后通过min(跟的左分支,根的右分支) 得到深度最小的 + *

+ * 时间复杂度为O(n) + * + * @param root + * @return + */ + private static int minDepthByRecursion(TreeNode root) { + if (root == null) + return 0; + // 使用分治的方法 从左右节点进行递归 + int left = minDepthByRecursion(root.left); + int right = minDepthByRecursion(root.right); + + // 如果是单边为空或者都为空的节点, 返回另一边+1 这样能够记录所有分支的高度, 最后通过min(跟的左分支,根的右分支) 得到深度最小的 + // 如果左边子节点为空的时候, 那么返回右边不为空的孩子深度+1 + if (left == 0) + return right + 1; + // 如果右边子节点为空的时候, 那么返回左边不为空的孩子深度+1 + if (right == 0) + return left + 1; + + //如果左右子节点都不为空, 返回左右深度最小的那个+1 + return Math.min(left, right) + 1; + } + + + private static int minDepthByRecursion2(TreeNode root) { + if (root == null) + return 0; + + int left = minDepthByRecursion2(root.left); + int right = minDepthByRecursion2(root.right); + + // 记录所有分支的深度, 最后在根节点上取左右最短的一条 + if (left == 0) + return right + 1; + if (right == 0) + return left + 1; + + return Math.min(left, right) + 1; + } + + private static int minDepthByRecursion3(TreeNode root) { + if (root == null) + return 0; + + int left = minDepthByRecursion3(root.left); + int right = minDepthByRecursion3(root.right); + + // 到达叶子节点的时候, 进行回溯的时候计算层级高度 + if (left == 0) + return right + 1; + if (right == 0) + return left + 1; + + return Math.min(left, right) + 1; + } + + /** + * 使用层级遍历的方式: 循环+队列 一层一层遍历并记录层数, 当遇到第一个左右子节点为空的节点, 那这个节点就是最小深度的叶子节点, 返回的层数为最小层数(最小深度). + *

+ * 时间复杂度为O(n), 空间复杂度为O(n) + * + * @param root + * @return + */ + private static int minDepthByLoop(TreeNode root) { + int level = 0; + if (root == null) + return level; + + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + level++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + // 如果节点的左右子树都为空, 那么这个节点为叶子节点, 也就是最小深度 + if (node.left == null && node.right == null) { + return level; + } + // 将左右节点按顺序放入队列中,用于下一层遍历 + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + } + return level; + } +} diff --git a/src/main/java/com/study/binarytree/NodesInALevel.java b/src/main/java/com/study/tree/binarytree/NodesInALevel.java similarity index 59% rename from src/main/java/com/study/binarytree/NodesInALevel.java rename to src/main/java/com/study/tree/binarytree/NodesInALevel.java index 38359fc..7f7a9a5 100644 --- a/src/main/java/com/study/binarytree/NodesInALevel.java +++ b/src/main/java/com/study/tree/binarytree/NodesInALevel.java @@ -1,9 +1,11 @@ -package com.study.binarytree; +package com.study.tree.binarytree; import com.study.utils.TreeUtils; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; /** * 求第k层有多少个节点, 根节点为第0层 @@ -16,16 +18,17 @@ */ public class NodesInALevel { public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; - List treeNodes = TreeUtils.buildTree(arr); - TreeNode root = treeNodes.get(0); + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + + TreeNode root = TreeUtils.buildTree(arr); int k = 2; - System.out.printf("第%d层节点数:%d\r\n", k, k_nodes(root, k)); - System.out.println("分别为:"); - List nodes = k_nodes2(root, k); +// System.out.printf("第%d层节点数:%d\r\n", k, k_nodes(root, k)); +// System.out.println("分别为:"); + //List nodes = k_nodes2(root, k); + List nodes = k_nodes3(root, k); for (TreeNode node : nodes) { - System.out.println(node.val); + System.out.print(node.val + " "); } } @@ -84,24 +87,79 @@ private static List k_nodes2(TreeNode root, int k) { List list = new ArrayList(); if (k < 0) return list; - nodes(root, k, list); + //nodes(root, k, list); + nodes2(root, k, list); return list; } private static void nodes(TreeNode root, int k, List list) { if (root == null) { + System.out.println("节点为空,递归往内中止"); return; } if (k == 0) { // 将第k层节点加入到集合中 list.add(root); + System.out.println("往集合中添加节点" + root.val); } + System.out.println(String.format("递归往内, 当前节点为%d, k=%d", root.val, k)); + // 递归左(右)子树, 找出k=0节点 nodes(root.left, k - 1, list); + System.out.println(String.format("递归左子树往外, 当前节点为%d, k=%d", root.val, k)); // 递归右(左)子树, 找出k=0节点 nodes(root.right, k - 1, list); + System.out.println(String.format("递归右子树往外, 当前节点为%d, k=%d", root.val, k)); + } + + private static void nodes2(TreeNode root, int k, List list) { + if (root == null) + return; + + if (k == 0) { + list.add(root); + } + + // 无所谓左右递归顺序 + nodes2(root.right, k - 1, list); + nodes2(root.left, k - 1, list); + } + + /** + * 使用层级遍历的方式获取第k层节点数 + * + * @param root + * @param k + * @return + */ + private static List k_nodes3(TreeNode root, int k) { + List nodes = new ArrayList<>(); + + if (root == null) + return nodes; + + Queue queue = new LinkedList(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + // 获取第k层节点 + if (k == 0) { + nodes.add(node); + } + if (node.left != null) + queue.offer(node.left); + if (node.right != null) + queue.offer(node.right); + } + if (k == 0) break; + k--; + } + return nodes; } } diff --git a/src/main/java/com/study/binarytree/PostorderTraversal.java b/src/main/java/com/study/tree/binarytree/PostorderTraversal.java similarity index 57% rename from src/main/java/com/study/binarytree/PostorderTraversal.java rename to src/main/java/com/study/tree/binarytree/PostorderTraversal.java index 1d23ef4..bd5924f 100644 --- a/src/main/java/com/study/binarytree/PostorderTraversal.java +++ b/src/main/java/com/study/tree/binarytree/PostorderTraversal.java @@ -1,4 +1,4 @@ -package com.study.binarytree; +package com.study.tree.binarytree; import com.study.utils.TreeUtils; @@ -27,16 +27,22 @@ */ public class PostorderTraversal { public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; - List treeNodes = TreeUtils.buildTree(arr); - TreeNode root = treeNodes.get(0); + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); + + TreeUtils.show(root); //List list = postorderTraversal(root); //List list = postorderTraversal2(root); //List list = postorderTraversal3(root); - List list = postorderTraversal4(root); - for (Integer num : list) { - System.out.println(num); + // List list = postorderTraversal4(root); +// for (Integer num : list) { +// System.out.print(num + " "); +// } + //Stack stack = postorderTraversal5(root); + Stack stack = postorderTraversal5_2(root); + while (!stack.isEmpty()) { + System.out.print(stack.pop() + " "); } } @@ -101,22 +107,94 @@ private static List postorderTraversal3(TreeNode root) { } stack.push(root); + System.out.println(String.format("将根节点%d入栈", root.val)); while (!stack.isEmpty()) { root = stack.pop(); + // 将节点值加到列表顶部 + list.add(0, root.val); + System.out.println(String.format("将节点%d加入到集合顶部", root.val)); + //和前序比那里不一样, 先将左节点入栈 - if (root.left != null) + if (root.left != null) { stack.push(root.left); + System.out.println(String.format("将左节点%d入栈", root.left.val)); + } + //再将右节点入栈 - if (root.right != null) + if (root.right != null) { stack.push(root.right); - - //逆序添加节点值 - list.add(0, root.val); + System.out.println(String.format("将右节点%d入栈", root.right.val)); + } } return list; } + /** + * 通过迭代法+栈 实现后序遍历二叉树 + * 遍历出来的每个二叉树节点再使用栈来存储,最后遍历输出 + * 根节点最先进结果栈,最后出结果栈 + * + * @param root + * @return + */ + private static Stack postorderTraversal5(TreeNode root) { + Stack result = new Stack(); + Stack stack = new Stack(); + + if (root == null) { + return result; + } + + stack.push(root); + System.out.println(String.format("将根节点%d入栈", root.val)); + + while (!stack.isEmpty()) { + root = stack.pop(); + // 因为根节点是最后输出的,所以将根节点首先放入结果栈中(先进后出) + result.push(root.val); + System.out.println(String.format("将节点%d加入到栈中", root.val)); + + //和前序比那里不一样, 先将左节点入栈 + if (root.left != null) { + stack.push(root.left); + System.out.println(String.format("将左节点%d入栈", root.left.val)); + } + + //再将右节点入栈 + if (root.right != null) { + stack.push(root.right); + System.out.println(String.format("将右节点%d入栈", root.right.val)); + } + } + return result; + } + + + private static Stack postorderTraversal5_2(TreeNode root) { + Stack output = new Stack<>(); + Stack input = new Stack<>(); + + if (root == null) { + return output; + } + + input.push(root); + + while (!input.isEmpty()) { + TreeNode node = input.pop(); + output.push(node.val); + + if (node.left != null) + input.push(node.left); + + if (node.right != null) + input.push(node.right); + } + + return output; + } + /** * 迭代遍历 左 右 根 *

diff --git a/src/main/java/com/study/tree/binarytree/PreorderTraversal.java b/src/main/java/com/study/tree/binarytree/PreorderTraversal.java new file mode 100644 index 0000000..f7b3b0c --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/PreorderTraversal.java @@ -0,0 +1,174 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * 144. 二叉树的前序遍历 深度优先遍历dfs + *

+ * 给定一个二叉树,返回它的 前序 遍历。 前序遍历: 根 - 左 - 右 + *

+ * 示例: + *

+ * 输入: [1,null,2,3] + * 1 + * \ + * 2 + * / + * 3 + *

+ * 输出: [1,2,3] + *

+ * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? + *

+ * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ + */ +public class PreorderTraversal { + public static void main(String[] args) { + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); + TreeUtils.show(root); + + //List list = preorderTraversal(root); + List list = preorderTraversalByLoop(root); + // List list = preorderTraversalByLoop2(root); + //List list = preorderTraversalByLoopWithList(root); + for (Integer num : list) { + System.out.print(num + " "); + } + } + + + /** + * 通过递归的方式遍历整个二叉树 时间复杂度O(n) + *

+ * 前序遍历: 根 - 左 - 右 + * + * @param root + * @param list + */ + private static void traversal(TreeNode root, List list) { + if (root != null) { + list.add(root.val); + traversal(root.left, list); + traversal(root.right, list); + } + } + + + public static List preorderTraversal(TreeNode root) { + List list = new ArrayList(); + traversal(root, list); + return list; + } + + /** + * 使用迭代的方式 遍历二叉树 + *

+ * 用到栈后入先出的原理, 将左子树放在右子树后面入栈, 确保左子树先弹出,优先处理左子树,之后再处理右子树 + *

+ * 关于为什么循环中没专门放入根节点, 因为除了最顶层的根节点, 其他层级的根节点都是左右子树 + *

+ * 时间复杂度O(n), 空间复杂度O(n) + * + * @param root + * @return + */ + public static List preorderTraversalByLoop(TreeNode root) { + List list = new ArrayList(); + Stack stack = new Stack(); + + if (root == null) { + return list; + } + + stack.push(root); + System.out.println(String.format("节点%d入栈", root.val)); + + while (!stack.isEmpty()) { + // 每次优先弹出左节点, + // 由于每次左子树都是最后放进去,而每次循环都只pop一次, 存在左子树优先pop左子树,没有再pop右节点 + TreeNode node = stack.pop(); + System.out.println(String.format("节点%d弹出", node.val)); + list.add(node.val); + + // 根据栈后进先出的原理, 把left节点放在right后面放入栈中 + if (node.right != null) { + stack.push(node.right); + System.out.println(String.format("右节点%d入栈", node.right.val)); + } + + if (node.left != null) { + stack.push(node.left); + System.out.println(String.format("左节点%d入栈", node.left.val)); + } + } + return list; + } + + public static List preorderTraversalByLoop2(TreeNode root) { + List nodes = new ArrayList<>(); + + if (root == null) + return nodes; + + Stack stack = new Stack<>(); + stack.push(root); //将节点放到栈顶 + + while (!stack.isEmpty()) { + + // 弹出栈顶的节点 + TreeNode node = stack.pop(); + nodes.add(node.val); + + if (node.right != null) { + stack.push(node.right); + } + + // 根据后进先出的原理,将左子树节点放在后面入栈 + if (node.left != null) { + stack.push(node.left); + } + } + return nodes; + } + + /** + * 使用循环 + 集合 实现前序遍历 + * 每次都将节点放到集合顶部, 并且从集合顶部取出节点 + * + * @param root + * @return + */ + public static List preorderTraversalByLoopWithList(TreeNode root) { + List nodes = new ArrayList<>(); + + if (root == null) + return nodes; + + List list = new ArrayList<>(); + list.add(0, root); //将节点放到集合顶部 + + while (!list.isEmpty()) { + + // 取出并删除集合顶部元素 + TreeNode node = list.remove(0); + nodes.add(node.val); + + if (node.right != null) { + // 将右节点放到集合顶部 + list.add(0, node.right); + } + + // 根据后进先出的原理,将左子树节点放在后面入栈 + if (node.left != null) { + // 将左节点放到集合顶部 + list.add(0, node.left); + } + } + return nodes; + } +} diff --git a/src/main/java/com/study/tree/binarytree/PreorderTraversal2.java b/src/main/java/com/study/tree/binarytree/PreorderTraversal2.java new file mode 100644 index 0000000..59784e4 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/PreorderTraversal2.java @@ -0,0 +1,59 @@ +package com.study.tree.binarytree; + +import com.study.utils.TreeUtils; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 将二叉树的先序遍历的节点 组装成只有右边节点的二叉树 + * 1 + * 2 3 + * 4 5 6 + *

+ * 1 + * 2 + * 4 + * 5 + * 3 + * 6 + */ +public class PreorderTraversal2 { + + /** + * 两种解法: + * 方法一: 将二叉树前序遍历 取出里面每个节点放入集合, 然后遍历集合,取出里面的节点 + * 方法而: 直接在原来的二叉树上做处理 + * + * @param args + */ + public static void main(String[] args) { + Integer[] arr = {1, 2, 3, 4, 5, 6}; + TreeNode root = TreeUtils.buildTree(arr); + TreeUtils.show(root); + + List list = new ArrayList<>(); + traversal(root, list); + TreeNode newRoot = buildNewTree(list); + TreeUtils.show(newRoot); + } + + private static void traversal(TreeNode root, List list) { + if (root != null) { + TreeNode node = new TreeNode(root.val); + list.add(node); + traversal(root.left, list); + traversal(root.right, list); + } + } + + private static TreeNode buildNewTree(List list) { + TreeNode root = list.get(0); + for (int i = 1; i < list.size(); i++) { + root.right = list.get(i); + root = root.right; + } + return list.get(0); + } +} diff --git a/src/main/java/com/study/binarytree/ReverseTree.java b/src/main/java/com/study/tree/binarytree/TraverseTree.java similarity index 95% rename from src/main/java/com/study/binarytree/ReverseTree.java rename to src/main/java/com/study/tree/binarytree/TraverseTree.java index ae7a774..1f082de 100644 --- a/src/main/java/com/study/binarytree/ReverseTree.java +++ b/src/main/java/com/study/tree/binarytree/TraverseTree.java @@ -1,4 +1,4 @@ -package com.study.binarytree; +package com.study.tree.binarytree; import com.study.utils.TreeUtils; @@ -11,11 +11,10 @@ *

* 其中每种遍历 又有多种遍历方法 比如递归, 迭代 */ -public class ReverseTree { +public class TraverseTree { public static void main(String[] args) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; - List treeNodes = TreeUtils.buildTree(arr); - TreeNode root = treeNodes.get(0); + Integer[] arr = {0, 1, 2, 3, 4, 5, 6, 7}; + TreeNode root = TreeUtils.buildTree(arr); //preOrderReverse(root); // 0 1 3 7 4 2 5 6 //preOrderReverse2(root); // 0 1 3 7 4 2 5 6 diff --git a/src/main/java/com/study/binarytree/TreeNode.java b/src/main/java/com/study/tree/binarytree/TreeNode.java similarity index 54% rename from src/main/java/com/study/binarytree/TreeNode.java rename to src/main/java/com/study/tree/binarytree/TreeNode.java index 7a40ec2..8479394 100644 --- a/src/main/java/com/study/binarytree/TreeNode.java +++ b/src/main/java/com/study/tree/binarytree/TreeNode.java @@ -1,11 +1,11 @@ -package com.study.binarytree; +package com.study.tree.binarytree; public class TreeNode { public TreeNode left; public TreeNode right; - public int val; + public Integer val; - public TreeNode(int val) { + public TreeNode(Integer val) { this.val = val; } } diff --git a/src/main/java/com/study/tree/binarytree/TreeNode2.java b/src/main/java/com/study/tree/binarytree/TreeNode2.java new file mode 100644 index 0000000..2b8e4c3 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/TreeNode2.java @@ -0,0 +1,11 @@ +package com.study.tree.binarytree; + +public class TreeNode2 { + public TreeNode2 left; + public TreeNode2 right; + public String val; + + public TreeNode2(String val) { + this.val = val; + } +} diff --git a/src/main/java/com/study/tree/binarytree/binarysearchtree/CRUD.java b/src/main/java/com/study/tree/binarytree/binarysearchtree/CRUD.java new file mode 100644 index 0000000..e205ff6 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/binarysearchtree/CRUD.java @@ -0,0 +1,235 @@ +package com.study.tree.binarytree.binarysearchtree; + +import com.study.tree.binarytree.TreeNode; +import com.study.utils.TreeUtils; + +import java.util.List; + +/** + * 查找二叉搜索树的某个节点 + */ +public class CRUD { + + public static void main(String[] args) { + // 由于二叉搜索树也是二叉树, 所以既可以使用二叉搜索树的方法也可以使用二叉树的方法求最近公共祖先 + int[] arr = {5, 3, 8, 1, 4, 7, 9}; + List treeNodes = TreeUtils.buildTreeAndList(arr); + TreeNode root = treeNodes.get(0); + + TreeUtils.show(root); + + int target = 7; + //int target = 2; + //boolean result = searchByRecursion(root,target); + TreeNode result = searchByRecursion2(root, target); + //TreeNode result = searchByLoop(root, target); + //System.out.println(result != null ? result.val : "未找到"); + + TreeUtils.show(root); + //TreeNode node = insertByRecursion(root, 6); + //TreeNode node = insertByRecursion2(root, 6); + TreeNode node = insertByLoop(root, 6); + System.out.println(node.val); + TreeUtils.show(root); + } + + /** + * 后续遍历查找结果, 需要在递归的时候将参数传进去 + * + * @param root 根节点 + * @param target 目标值 + * @return 是否找到目标 + */ + private static boolean searchByRecursion(TreeNode root, int target) { + if (root == null) { + return false; // 未找到目标 + } + + if (root.val > target) { + // 将找到或者未找到目标不断回溯上去 + return searchByRecursion(root.left, target); + } + + if (root.val < target) { + // 将找到或者未找到目标不断回溯上去 + return searchByRecursion(root.right, target); + } + + // 找到目标 + return true; + } + + /** + * 查找目标节点 + * + * @param root 根节点 + * @param target 目标值 + * @return 目标节点 + */ + private static TreeNode searchByRecursion2(TreeNode root, int target) { + if (root == null) + return null; + + if (root.val > target) { + return searchByRecursion2(root.left, target); + } + + if (root.val < target) { + return searchByRecursion2(root.right, target); + } + + // 后续遍历 查找结果 + return root; + } + + /** + * 先序遍历查找目标节点 + * + * @param root + * @param target + * @return + */ + private static TreeNode searchByRecursion3(TreeNode root, int target) { + // 如果为空, 说明递归到了最底层 叶子节点也没找到 + if (root == null) { + System.out.println("没有找到目标节点"); + return null; + } + + // 如果找到了目标 + if (root.val == target) { + // 一旦找到, 返回结果 + System.out.println("找到了节点" + root.val); + System.out.println("开始回溯"); + return root; + } + + // 如果目标值小于根节点, 往左子树找 + if (root.val > target) { + // 找到目标节点后, 如果曾经从左边找过, 会从左边回溯,并返回结果 + System.out.println("从左子树节点找" + root.left.val); + TreeNode left = searchByRecursion2(root.left, target); + System.out.println("回溯到左子树节点" + root.left.val); + return left; + } else { + // 如果目标值大于根节点, 往右子树找 + System.out.println("从右子树节点找" + root.right.val); + TreeNode right = searchByRecursion2(root.right, target); + System.out.println("回溯到右子树节点" + root.right.val); + return right; + } + } + + /** + * 使用循环的方式,查找目标节点 + * + * @param root + * @param target + * @return + */ + private static TreeNode searchByLoop(TreeNode root, int target) { + while (root != null) { + // 找到目标节点 直接返回 + if (root.val == target) + return root; + + // 目标节点小于根节点 + if (root.val > target) { + // 往左找 + root = root.left; + } else { + // 大于目标节点往右找 + root = root.right; + } + } + // 循环结束的时候,还没找到 返回空 + return null; + } + + /** + * 往树中插入新节点 + *

+ * 首先通过二分查找 找到该节点的位置, 如果节点存在, 直接返回, 如果不存在则创建并返回. + * + * @param root 根节点 + * @param value 要插入的节点 + * @return 根节点 + */ + private static TreeNode insertByRecursion(TreeNode root, int value) { + if (root == null) { + // 如果没找到, 创建该节点 + return new TreeNode(value); + } + + // 从左边找 + if (root.val > value) { + // 将返回的左节点添加到根节点左边, 如果创建了一个新的节点,将被接上 + root.left = insertByRecursion(root.left, value); + } + + if (root.val < value) { + // 从右边找 + // 将返回的右节点添加到根节点右边, 如果创建了一个新的节点,将被接上 + root.right = insertByRecursion(root.right, value); + } + + // 不断回溯到根节点 + return root; + } + + private static TreeNode insertByRecursion2(TreeNode root, int value) { + // 没找到就创建目标节点并返回 + if (root == null) { + return new TreeNode(value); + } + + if (root.val > value) { + // 从左边找 + root.left = insertByRecursion2(root.left, value); + } + if (root.val < value) { + // 从右边找 + root.right = insertByRecursion2(root.right, value); + } + + return root; + } + + /** + * 通过迭代的方式插入新节点 + * + * @param root 根 + * @param val 新节点值 + * @return 根节点 + */ + private static TreeNode insertByLoop(TreeNode root, int val) { + TreeNode result = root; + while (root != null) { + if (root.val > val) { + // 判断左节点是否为空, 为空就说明新节点不存在,则创建 + if (root.left == null) { + root.left = new TreeNode(val); + break; + } + // 往左边找 + root = root.left; + } + if (root.val < val) { + // 判断右节点是否为空, 为空就说明新节点不存在,则创建 + if (root.right == null) { + root.right = new TreeNode(val); + break; + } + // 往右边找 + root = root.right; + } + // 新节点已经存在,停止查找,无需新建 + if (root.val == val) + break; + } + + // 返回根节点 + return result; + } + +} diff --git a/src/main/java/com/study/tree/binarytree/binarysearchtree/KSmallestNode.java b/src/main/java/com/study/tree/binarytree/binarysearchtree/KSmallestNode.java new file mode 100644 index 0000000..2f9fd8b --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/binarysearchtree/KSmallestNode.java @@ -0,0 +1,56 @@ +package com.study.tree.binarytree.binarysearchtree; + +import com.study.tree.binarytree.TreeNode; +import com.study.utils.TreeUtils; + +import java.util.List; + +/** + * 查找二叉搜索树中第k个小的结点 + */ +public class KSmallestNode { + + public static void main(String[] args) { + int[] arr = {5, 3, 7, 1, 4, 6, 8}; + List treeNodes = TreeUtils.buildTreeAndList(arr); + TreeNode root = treeNodes.get(0); + + TreeUtils.show(root); + + int k = 6; + TreeNode kNode = getNodes(root, k); + // 获取第k个节点 + System.out.println(kNode.val); + } + + private static TreeNode getNodes(TreeNode root, int k) { + // 中序遍历获取搜索二叉树 + return helper(root, k); + } + + // 计数器需要设置成全局, 避免放入递归调用栈 + private static int count = 0; + + private static TreeNode helper(TreeNode root, int k) { + if (root == null) { + return null; + } + + TreeNode left = helper(root.left, k); + if (left != null) // 不为空返回是为了继续执行下面的count++ 返回第k个目标root节点, 否则root==null的之后 直接就返回null结束递归了 + return left; + + count++; // 每次遍历完一个节点 k-1, 直到k==0 获取第k个节点 + if (count == k) { + return root; // 返回目标节点, 进行回溯 + } + + TreeNode right = helper(root.right, k); + if (right != null) + return right; + + return null; //没有找到, 可能k不在树中 + } +} + + diff --git a/src/main/java/com/study/binarytree/binarysearchtree/LowestCommonAncestor.java b/src/main/java/com/study/tree/binarytree/binarysearchtree/LowestCommonAncestor.java similarity index 73% rename from src/main/java/com/study/binarytree/binarysearchtree/LowestCommonAncestor.java rename to src/main/java/com/study/tree/binarytree/binarysearchtree/LowestCommonAncestor.java index efdca52..5874628 100644 --- a/src/main/java/com/study/binarytree/binarysearchtree/LowestCommonAncestor.java +++ b/src/main/java/com/study/tree/binarytree/binarysearchtree/LowestCommonAncestor.java @@ -1,11 +1,12 @@ -package com.study.binarytree.binarysearchtree; +package com.study.tree.binarytree.binarysearchtree; +import com.study.tree.binarytree.TreeNode; import com.study.utils.TreeUtils; import java.util.List; /** - * 二叉搜索树的最近公共祖先 + * 二叉搜索树的最近公共祖先 lowest common ancestor *

* 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 *

@@ -37,19 +38,18 @@ public static void main(String[] args) { // 由于二叉搜索树也是二叉树, 所以既可以使用二叉搜索树的方法也可以使用二叉树的方法求最近公共祖先 int[] arr = {5, 3, 7, 1, 4, 6, 8}; - List treeNodes = TreeUtils.buildSearchTree(arr); + List treeNodes = TreeUtils.buildTreeAndList(arr); + TreeNode root = treeNodes.get(0); - SearchTreeNode root = treeNodes.get(0); + TreeUtils.show(root); - SearchTreeNode p = treeNodes.get(5); - SearchTreeNode q = treeNodes.get(6); + TreeNode p = treeNodes.get(5); + TreeNode q = treeNodes.get(6); - //SearchTreeNode lca = getlca(root, p, q); - SearchTreeNode lca = getlca2(root, p, q); + //TreeNode lca = getlca(root, p, q); + TreeNode lca = getlca2(root, p, q); if (lca != null) System.out.println(lca.val); - - } /** @@ -58,7 +58,7 @@ public static void main(String[] args) { * @param q * @return */ - private static SearchTreeNode getlca(SearchTreeNode root, SearchTreeNode p, SearchTreeNode q) { + private static TreeNode getlca(TreeNode root, TreeNode p, TreeNode q) { if (root == null) return null; @@ -67,10 +67,10 @@ private static SearchTreeNode getlca(SearchTreeNode root, SearchTreeNode p, Sear // 递归根左子树, 查找p或q节点, 一旦找到立刻退出, 进行根右子树递归 // 因为根左子树找到1个节点, 那么只要根右子树存在另一个节点, 那最近公共祖先就是根节点, 否则就是左子树找到的节点本身(另一个节点肯定在这个节点下面) - SearchTreeNode left = getlca(root.left, p, q); + TreeNode left = getlca(root.left, p, q); // 递归右子树, 查看右边是否也存在目标节点 - SearchTreeNode right = getlca(root.right, p, q); + TreeNode right = getlca(root.right, p, q); // 如果左右都存在, 直接返回当前节点 if (left != null && right != null) @@ -87,16 +87,20 @@ private static SearchTreeNode getlca(SearchTreeNode root, SearchTreeNode p, Sear /** * 使用二叉搜索树 左子树所有节点 < 父节点 < 右子树所有节点 的原理 * + * 将节点和根节点比较, 从左子树找 或者 右子树找, 不需要全树遍历, 类似二叉查找 + * + * 时间复杂度为O(logN) + * * @param root * @param p * @param q * @return */ - private static SearchTreeNode getlca2(SearchTreeNode root, SearchTreeNode p, SearchTreeNode q) { - // 如果 p和q的值 都小于 root, 直接从树的左边去找 + private static TreeNode getlca2(TreeNode root, TreeNode p, TreeNode q) { + // 如果 p和q的值 都小于 root根节点, 说明两个节点都在左子树, 直接从树的左边去找 if (p.val < root.val && root.val > q.val) return getlca2(root.left, p, q); - // 如果 p和q的值都大于 root, 直接从树的右边去找 + // 如果 p和q的值都大于 root, 说明量两个节点都在右子树, 往树的右边去找 if (p.val > root.val && q.val > root.val) return getlca2(root.right, p, q); // 如果 p.val < root.val < q.val 或 p.val > root.val > q.val 则直接返回root节点 diff --git a/src/main/java/com/study/tree/binarytree/binarysearchtree/ValidateBinarySearchTree.java b/src/main/java/com/study/tree/binarytree/binarysearchtree/ValidateBinarySearchTree.java new file mode 100644 index 0000000..b497c98 --- /dev/null +++ b/src/main/java/com/study/tree/binarytree/binarysearchtree/ValidateBinarySearchTree.java @@ -0,0 +1,237 @@ +package com.study.tree.binarytree.binarysearchtree; + +import com.study.tree.binarytree.TreeNode; +import com.study.utils.TreeUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 验证二叉搜索树 又名 二叉排序树/二叉平衡(查找)树 + *

+ * BinarySearchTree BST + * + *

+ * 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 + *

+ * 假设一个二叉搜索树具有如下特征: + *

+ * 节点的左子树只包含小于当前节点的数。 + * 节点的右子树只包含大于当前节点的数。 + * 所有左子树和右子树自身必须也是二叉搜索树。 + * 示例 1: + *

+ * 输入: + * 2 + * / \ + * 1 3 + * 输出: true + * 示例 2: + *

+ * 输入: + * 5 + * / \ + * 1 4 + * / \ + * 3 6 + * 输出: false + * 解释: 输入为: [5,1,4,null,null,3,6]。 + * 根节点的值为 5 ,但是其右子节点值为 4 。 + *

+ * https://leetcode-cn.com/problems/validate-binary-search-tree/ + */ +public class ValidateBinarySearchTree { + /** + * 节点的左子树只包含小于当前节点的数。 + * 节点的右子树只包含大于当前节点的数。 + * 所有左子树和右子树自身必须也是二叉搜索树。 + * + * @param args + */ + public static void main(String[] args) { + //Integer[] arr = {5, 3, 7, 1, 4, 6, 8}; + //Integer[] arr = {1, 1}; + + Integer[] arr = {5, 1, 6, null, null, 3, 7}; + + //Integer[] arr = {5, 1, 9, null, null, 7, 10, null, null, null, null, 8, 3}; + + //Integer[] arr = {3, 9, 20, null, null, 15, 7}; + + TreeNode root = TreeUtils.buildTree(arr); + System.out.println("中序遍历-----"); + List nodes = new ArrayList(); + inorder(root, nodes); // 验证树是否正确,从小到大输出 + + for (TreeNode node : nodes) { + System.out.print(node.val + " "); + } + System.out.println("\r\n中序遍历-----"); + + System.out.println("打印出树的结构-----"); + // 将刚刚创建的树打印出来 + TreeUtils.show(root); + System.out.println("打印出树的结构-----"); + + // 中序遍历获取所有节点 判断是否为有序节点 + //System.out.println(isValidBST_InOrder(root)); + //System.out.println(isValidBST_InOrder_2(root)); + + // 递归的方式判断是否所有子树 都满足 左子树<节点<右子树 + System.out.println(isValidBST(root)); + //System.out.println(isValidBST_2(root)); + + //System.out.println(isValidBST2(root)); + } + + + /** + * 判断该tree是否为一个有效的二叉搜索树 + *

+ * 先使用中序遍历, 将每个节点放到一个arraylist集合中 + *

+ * 然后遍历集合,对比前后两个元素, 看看是不是后面的元素都比前面元素大,如果是则说明集合是从小到大排列 + *

+ * 同理说明二叉搜索树里的节点也是按左 根 右 从小到大排列的 + * + * @param root + * @return + */ + private static boolean isValidBST_InOrder(TreeNode root) { + List nodes = new ArrayList(); + inorder(root, nodes); // 验证树是否正确,从小到大输出 + + for (int i = 0; i < nodes.size() - 1; i++) { + //如果前一个元素大于后一个元素, 则说明不是有效二叉搜索树 + if (nodes.get(i).val >= nodes.get(i + 1).val) { + return false; + } + } + return true; + } + + private static boolean isValidBST_InOrder_2(TreeNode root) { + List nodes = new ArrayList<>(); + // 中序遍历后, 返回的节点为从小到大有序的 + inorder_2(root, nodes); + + for (int i = 0; i < nodes.size() - 1; i++) { + // 如果后面的节点大于前面的节点 说明它不是二叉搜索树 + if (nodes.get(i).val >= nodes.get(i + 1).val) { + return false; + } + } + return true; + } + + /** + * 验证是否为二叉搜索树, 需要判断左边所有与节点值是否小于根节点, 右边所有节点值 大于 根节点. + * 以及每棵子树 都是 左子树 < 节点 < 右子树 + *

+ * 使用深度优先搜索: 需要把最小值min和最大值max传入, 让左子树所有节点与根节点值min作比较, 让右子树所有节点与根节点值max作比较. + * 对于根节点左边所有子树, 最大值为根节点max; 对于根节点右边所有子树,最小值为根节点min; + * + *

+ * 节点的左子树只包含小于当前节点的数。 + * 节点的右子树只包含大于当前节点的数。 + * 所有左子树和右子树自身必须也是二叉搜索树。 + * + * 时间复杂度 : O(N)。每个结点访问一次。 + * 空间复杂度 : O(N)。我们跟进了整棵树。 + * + * @param root + * @return + */ + private static boolean isValidBST(TreeNode root){ + return helper(root,null,null); + } + + private static boolean helper(TreeNode root, Integer min, Integer max) { + if (root == null) //如果root为空 说明当前节点已经递归到最底层了 或者树本身就是空树 + return true; + //如果root小于最小值, 不是二叉搜索树 + if (min != null && root.val < min) { + System.out.println(String.format("不满足的节点: node: %d, min: %d", root.val, min)); + return false; + } + + //如果root大于最大值, 也不是二叉搜索树 + if (max != null && root.val > max) { + System.out.println(String.format("不满足的节点: node: %d, max: %d", root.val, max)); + return false; + } + + //System.out.print(root.val + " "); + + // 是否左子树的所有节点肯定都比根节点小 右子树的所有节点都比根大 + boolean left = helper(root.left, min, root.val); + boolean right = helper(root.right, root.val, max); + + return left && right; //如果左边和右边都满足条件, 则说明该数为二叉搜索树 + //return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max); + } + + private static boolean isValidBST_2(TreeNode root){ + return helper2(root,null,null); + } + + private static boolean helper2(TreeNode root, Integer min, Integer max) { + if (root == null) + return true; + if (min != null && root.val < min) //跟节点不能小于最小值 + return false; + if (max != null && root.val > max) //跟节点也不能大于最大值 + return false; + + return helper2(root.left, min, root.val) && helper2(root.right, root.val, max); + } + + /* 不对的做法, 这只判断了每个子树 是否满足 左子节点 < 节点 < 右子节点, 没有需要判断 左边子树所有节点 < 根节点 < 右边子树所有节点 */ +// /** +// * 验证是否为二叉搜索树, 需要判断左边所有与节点值是否小于根节点, 右边所有节点值 大于 根节点. +// * 不传入根节点值, 但是需要判断值左右节点值是否为空 +// * +// * @param root +// * @return +// */ +// private static boolean isValidBST2(TreeNode root) { +// if (root == null) +// return true; +// +// if (root.left != null && root.left.val != null && root.val < root.left.val) { +// System.out.println(String.format("不满足的左节点: node: %d, root: %d", root.left.val, root.val)); +// return false; +// } +// +// if (root.right != null && root.right.val != null && root.val > root.right.val) { +// System.out.println(String.format("不满足右的节点: node: %d, root: %d", root.right.val, root.val)); +// return false; +// } +// +// return isValidBST2(root.left) && isValidBST2(root.right); +// } + + /** + * 使用中序遍历 获取二叉树每个节点 + *

+ * 左 根 右 + * + * @param root + */ + private static void inorder(TreeNode root, List nodes) { + if (root != null) { + inorder(root.left, nodes); + nodes.add(root); + inorder(root.right, nodes); + } + } + + + private static void inorder_2(TreeNode root, List nodes) { + if (root != null) { + inorder(root.left, nodes); + nodes.add(root); + inorder(root.right, nodes); + } + } +} diff --git a/src/main/java/com/study/tree/ntree/MaximumDepthOfNAryTree.java b/src/main/java/com/study/tree/ntree/MaximumDepthOfNAryTree.java new file mode 100644 index 0000000..373140b --- /dev/null +++ b/src/main/java/com/study/tree/ntree/MaximumDepthOfNAryTree.java @@ -0,0 +1,49 @@ +package com.study.tree.ntree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * N叉树的最大深度 + * + * 给定一个 N 叉树,找到其最大深度。 + * + * 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 + * + * 例如,给定一个 3叉树 : + * 我们应返回其最大深度,3。 + * + * 3 + * / \ \ + * 9 20 11 + * / \ + * 15 7 + * + * 链接:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree + */ +public class MaximumDepthOfNAryTree { + + public static void main(String[] args) { + + } + + public static int maxDepth(NTreeNode root) { + // 节点为null是返回0 + if(root == null) + return 0; + + // 没有子节点,返回1(有根节点) + if(root.children.isEmpty()) + return 1; + + List depthList = new ArrayList<>(); + // 获取所有子树的深度 + for(NTreeNode node : root.children){ + depthList.add(maxDepth(node)); + } + + // 返回所有子树的最大深度+1 + return Collections.max(depthList) + 1; + } +} diff --git a/src/main/java/com/study/tree/ntree/NAryTreePostorderTraversal.java b/src/main/java/com/study/tree/ntree/NAryTreePostorderTraversal.java new file mode 100644 index 0000000..fc02bbc --- /dev/null +++ b/src/main/java/com/study/tree/ntree/NAryTreePostorderTraversal.java @@ -0,0 +1,35 @@ +package com.study.tree.ntree; + +import java.util.ArrayList; +import java.util.List; + +/** + * N叉树的后序遍历 + *

+ * 给定一个 N 叉树,返回其节点值的后序遍历。 + *

+ * https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/ + */ +public class NAryTreePostorderTraversal { + + /** + * 可以使用 递归 或者 迭代+2个stack的方式 来处理 + * + * @param args + */ + public static void main(String[] args) { + + } + + private List list = new ArrayList(); + + public List postOrder(NTreeNode root) { + if (root != null) { + for (NTreeNode node : root.children) { + postOrder(node); + } + list.add(root.val); + } + return list; + } +} diff --git a/src/main/java/com/study/tree/ntree/NTreeNode.java b/src/main/java/com/study/tree/ntree/NTreeNode.java new file mode 100644 index 0000000..80be5ff --- /dev/null +++ b/src/main/java/com/study/tree/ntree/NTreeNode.java @@ -0,0 +1,21 @@ +package com.study.tree.ntree; + +import java.util.List; + +/** + * N叉树, 具有多颗子节点的N叉树 + */ +public class NTreeNode { + + public int val; + public List children; + + public NTreeNode(int val){ + this.val = val; + } + + public NTreeNode(int val, List children){ + this.val = val; + this.children = children; + } +} diff --git a/src/main/java/com/study/trietree/Trie.java b/src/main/java/com/study/tree/trietree/Trie.java similarity index 51% rename from src/main/java/com/study/trietree/Trie.java rename to src/main/java/com/study/tree/trietree/Trie.java index ab90b3d..c1b4231 100644 --- a/src/main/java/com/study/trietree/Trie.java +++ b/src/main/java/com/study/tree/trietree/Trie.java @@ -1,10 +1,12 @@ -package com.study.trietree; +package com.study.tree.trietree; /** + * 字典树 也叫 trie树 + *

* 基本性质 - * 根节点不包含字符,除根节点外的每一个子节点都包含一个字符 - * 从根节点到某一节点。路径上经过的字符连接起来,就是该节点对应的字符串 - * 每个节点的所有子节点包含的字符都不相同 + * 1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符 + * 2.从根节点到某一节点。路径上经过的字符连接起来,就是该节点对应的字符串 + * 3.每个节点的所有子节点包含的字符都不相同 *

* 应用场景 * 典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),经常被搜索引擎系统用于文本词频统计。 @@ -12,23 +14,42 @@ * 优点 * 利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串比较,查询效率比哈希树高。 *

+ * 遍历算法: bfs深度优先遍历 先根再子节点 + * + *

* https://www.cnblogs.com/xujian2014/p/5614724.html + *

+ * https://leetcode-cn.com/problems/implement-trie-prefix-tree/ */ public class Trie { public static void main(String[] args) { TrieNode root = new TrieNode(); -// String[] strs = {"banana", "band", "bee", "absolute", "acm",}; -// String[] prefix = {"ba", "b", "band", "abc",}; - String[] strs = {"北京大学", "北京科技大学", "南京北京路", "南京大学", "南京金陵十二拆",}; - String[] prefix = {"北", "北京", "北京大学", "南京", "上海浦东新区",}; + String[] strs = {"banana", "band", "bee", "absolute", "acm",}; + String[] prefix = {"ba", "b", "band", "abc",}; +// String[] strs = {"北京大学", "北京科技大学", "南京北京路", "南京大学", "南京金陵十二拆",}; +// String[] prefix = {"北", "北京", "北京大学", "南京", "上海浦东新区",}; for (String str : strs) { insert(str, root); } - //System.out.println(tree.has("abc")); - System.out.println(has("北京", root)); - preTraverse(root); + + System.out.println("------打印字典树中所有单词--------"); + printAllWords(root, "#"); + System.out.println("--------------------------------"); + + // 是否包含这个单词 + System.out.println("------判断是否有ba这个单词--------"); + System.out.println(containsWord("ba", root)); + //System.out.println(containsWord("北京", root)); + System.out.println("--------------------------------"); + + // 打印包含该前缀的单词 + System.out.println("-------打印所有包含ba前缀的单词----"); + System.out.println(hasPrefix("ba", root)); + System.out.println("--------------------------------"); + + //preTraverse(root); + preTraverse2(root); System.out.println(); - //tree.printAllWords(); for (String pre : prefix) { int num = countPrefix(pre, root); System.out.println(pre + " 数量:" + num); @@ -45,16 +66,14 @@ public static void insert(String str, TrieNode root) { return; } TrieNode node = root; - char[] letters = str.toCharArray();//将目标单词转换为字符数组 + char[] letters = str.toCharArray();//将目标单词转换为char数组 for (int i = 0, len = str.length(); i < len; i++) { - //计算每个char的位置 + //计算每个char的位置(哈希值), 这样在当前层级查找该元素或节点的的时间复杂度为O(1) int pos = letters[i] - 'a'; - if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符 - { + if (node.son[pos] == null) { //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符 node.son[pos] = new TrieNode(); node.son[pos].val = letters[i]; - } else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1 - { + } else { //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1 node.son[pos].num++; } node = node.son[pos]; @@ -63,7 +82,7 @@ public static void insert(String str, TrieNode root) { } /** - * 计算单词前缀的数量 + * 计算拥有该前缀的单词 * * @param prefix * @return @@ -86,7 +105,7 @@ public static int countPrefix(String prefix, TrieNode root) { } /** - * 打印指定前缀的单词 + * 找出并打印出拥有指定前缀的所有单词 * * @param prefix * @return @@ -110,7 +129,7 @@ public static String hasPrefix(String prefix, TrieNode root) { } /** - * 遍历经过此节点的单词. + * 打印经过此节点的所有单词. * * @param root * @param prefix @@ -133,7 +152,7 @@ public static void preTraverse(TrieNode root, String prefix) { * @param str * @return */ - public static boolean has(String str, TrieNode root) { + public static boolean containsWord(String str, TrieNode root) { if (str == null || str.length() == 0) { return false; } @@ -152,18 +171,61 @@ public static boolean has(String str, TrieNode root) { } /** - * 前序遍历字典树. + * 打印字典树里面所有字符char *

- * 根 - 左 - 右 + * 前序遍历字典树. bfs深度优先遍历 + * + *

+ * 根 - 所有子节点 * * @param root */ public static void preTraverse(TrieNode root) { - if (root != null) { - System.out.print(root.val + "-"); - for (TrieNode child : root.son) { - preTraverse(child); + if (root == null) { + return; + } + System.out.print(root.val + ":" + root.num + " "); + for (TrieNode child : root.son) { + preTraverse(child); + } + } + + /** + * 打印字典树里面所有字符char + * + * @param root + */ + public static void preTraverse2(TrieNode root) { + if (root == null) { + return; + } + for (TrieNode child : root.son) { + if (child != null) { + System.out.print(child.val + ":" + child.num + " "); + if (child.isEnd) + System.out.println(); } + preTraverse2(child); + } + } + + /** + * 打印字典树里所有的单词 + * 将递归走的每个char进行拼接,直到单词尾部,然后输出这个单词 + * + * @param root 根节点 + * @param prefix 前缀 + */ + private static void printAllWords(TrieNode root, String prefix) { + if (root != null && root.isEnd) { + System.out.println(prefix); + return; + } + if (root == null) + return; + for (TrieNode child : root.son) { + if (child != null) + printAllWords(child, prefix + child.val); } } } \ No newline at end of file diff --git a/src/main/java/com/study/trietree/TrieNode.java b/src/main/java/com/study/tree/trietree/TrieNode.java similarity index 64% rename from src/main/java/com/study/trietree/TrieNode.java rename to src/main/java/com/study/tree/trietree/TrieNode.java index ee42539..aaa51ca 100644 --- a/src/main/java/com/study/trietree/TrieNode.java +++ b/src/main/java/com/study/tree/trietree/TrieNode.java @@ -1,4 +1,4 @@ -package com.study.trietree; +package com.study.tree.trietree; /** * 字典树节点 @@ -8,21 +8,20 @@ public class TrieNode /** * 中文 */ - private int SIZE = 65536; + //private int SIZE = 65536; /** * 英文 一共26个字母 */ - //private int SIZE = 26; + private int SIZE = 256; public int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数 - public TrieNode[] son;// 所有的儿子节点 + public TrieNode[] son = new TrieNode[SIZE];// 所有的儿子节点 public boolean isEnd;// 是不是最后一个节点 - public char val;// 节点的值 + public char val = '#';// 节点的值 public TrieNode() { num = 1; - son = new TrieNode[SIZE]; isEnd = false; } } \ No newline at end of file diff --git a/src/main/java/com/study/utils/LinkedListUtils.java b/src/main/java/com/study/utils/LinkedListUtils.java new file mode 100644 index 0000000..065b4f7 --- /dev/null +++ b/src/main/java/com/study/utils/LinkedListUtils.java @@ -0,0 +1,46 @@ +package com.study.utils; + +import com.study.linkedlist.DoubleLinkedNode; +import com.study.linkedlist.LinkedNode; + +public final class LinkedListUtils { + + public static void printLinkedList(LinkedNode head) { + while (head != null) { + if (head.next == null) { + System.out.printf("%d->NULL", head.val); + } else { + System.out.printf("%d->", head.val); + } + head = head.next; + } + System.out.println(); + } + + public static void printLinkedList(DoubleLinkedNode head) { + + DoubleLinkedNode originalHead = head; + while (head != null) { + if (head.next == null) { + System.out.printf("%d->NULL", head.val); + } else { + System.out.printf("%d->", head.val); + } + head = head.next; + } + + System.out.println(); + + while (originalHead != null) { + if (originalHead.prev == null) { + System.out.printf("NULL<-%d", originalHead.val); + } else { + System.out.printf("<-%d", originalHead.val); + } + originalHead = originalHead.next; + } + + System.out.println(); + } + +} diff --git a/src/main/java/com/study/utils/Printer.java b/src/main/java/com/study/utils/Printer.java deleted file mode 100644 index a2d6699..0000000 --- a/src/main/java/com/study/utils/Printer.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.study.utils; - -import com.study.linkedlist.ListNode; - -public final class Printer { - - public static void printLinkedList(ListNode head) { - while (head != null) { - if (head.next == null) { - System.out.printf("%d->NULL", head.val); - } else { - System.out.printf("%d->", head.val); - } - head = head.next; - } - System.out.println(); - } - -} diff --git a/src/main/java/com/study/utils/TreeUtils.java b/src/main/java/com/study/utils/TreeUtils.java index ead7edb..6149d98 100644 --- a/src/main/java/com/study/utils/TreeUtils.java +++ b/src/main/java/com/study/utils/TreeUtils.java @@ -1,7 +1,7 @@ package com.study.utils; -import com.study.binarytree.TreeNode; -import com.study.binarytree.binarysearchtree.SearchTreeNode; +import com.study.tree.binarytree.TreeNode; +import com.study.tree.binarytree.TreeNode2; import java.util.ArrayList; import java.util.List; @@ -9,18 +9,41 @@ public final class TreeUtils { /** - * 构建二叉树, + * 构建二叉树并返回根节点, + * + * 使用层级遍历的方式 一层层创建 * * @param nodes - * @return 返回二叉树的所有节点, root为第0个 + * @return 返回二叉树的根节点 */ - public static List buildTree(int[] nodes){ +// public static TreeNode buildTree(int[] nodes) { +// List treeNodes = new ArrayList(); +// for (int data : nodes) { +// treeNodes.add(new TreeNode(data)); +// } +// //创建一个二叉树 +// //创建其他节点 每个节点的左子节点都是这个节点的2倍+1, 右子节点都是这个节点的2倍+2 +// for (int i = 0; i < nodes.length / 2; i++) { +// treeNodes.get(i).left = treeNodes.get(i * 2 + 1); +// if (i * 2 + 2 < treeNodes.size()) { +// treeNodes.get(i).right = treeNodes.get(i * 2 + 2); +// } +// } +// return treeNodes.get(0); +// } + + /** + * 构建二叉树并返回所有节点 + * + * @param nodes + * @return 所有节点 + */ + public static List buildTreeAndList(int[] nodes) { List treeNodes = new ArrayList(); for (int data : nodes) { treeNodes.add(new TreeNode(data)); } //创建一个二叉树 - TreeNode root = treeNodes.get(0); //创建其他节点 每个节点的左子节点都是这个节点的2倍+1, 右子节点都是这个节点的2倍+2 for (int i = 0; i < nodes.length / 2; i++) { treeNodes.get(i).left = treeNodes.get(i * 2 + 1); @@ -28,30 +51,188 @@ public static List buildTree(int[] nodes){ treeNodes.get(i).right = treeNodes.get(i * 2 + 2); } } - return treeNodes; } - /** - * 构建二叉搜索树 - * - * @param nodes - * @return 返回二叉搜索树的所有节点, root为第0个 - */ - public static List buildSearchTree(int[] nodes){ - List treeNodes = new ArrayList(); - for (int data : nodes) { - treeNodes.add(new SearchTreeNode(data)); + public static TreeNode buildTree(Integer[] nodes) { + List treeNodes = new ArrayList(); + for (Integer data : nodes) { + treeNodes.add(new TreeNode(data)); } //创建一个二叉树 - SearchTreeNode root = treeNodes.get(0); //创建其他节点 每个节点的左子节点都是这个节点的2倍+1, 右子节点都是这个节点的2倍+2 for (int i = 0; i < nodes.length / 2; i++) { - treeNodes.get(i).left = treeNodes.get(i * 2 + 1); + if (treeNodes.get(i * 2 + 1).val != null) { + treeNodes.get(i).left = treeNodes.get(i * 2 + 1); + } if (i * 2 + 2 < treeNodes.size()) { - treeNodes.get(i).right = treeNodes.get(i * 2 + 2); + if (treeNodes.get(i * 2 + 2).val != null) + treeNodes.get(i).right = treeNodes.get(i * 2 + 2); } } - return treeNodes; + return treeNodes.get(0); + } + + public static TreeNode2 buildTree(String[] nodes) { + List treeNodes = new ArrayList(); + for (String data : nodes) { + treeNodes.add(new TreeNode2(data)); + } + //创建一个二叉树 + //创建其他节点 每个节点的左子节点都是这个节点的2倍+1, 右子节点都是这个节点的2倍+2 + for (int i = 0; i < nodes.length / 2; i++) { + if (treeNodes.get(i * 2 + 1).val != null) { + treeNodes.get(i).left = treeNodes.get(i * 2 + 1); + } + if (i * 2 + 2 < treeNodes.size()) { + if (treeNodes.get(i * 2 + 2).val != null) + treeNodes.get(i).right = treeNodes.get(i * 2 + 2); + } + } + return treeNodes.get(0); + } + + + + // 用于获得树的层数 + private static int getTreeDepth(TreeNode root) { + return root == null ? 0 : (1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right))); + } + + private static int getTreeDepth(TreeNode2 root) { + return root == null ? 0 : (1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right))); + } + + + private static void writeArray(TreeNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) { + // 保证输入的树不为空 + if (currNode == null) return; + // 先将当前节点保存到二维数组中 + res[rowIndex][columnIndex] = String.valueOf(currNode.val); + + // 计算当前位于树的第几层 + int currLevel = ((rowIndex + 1) / 2); + // 若到了最后一层,则返回 + if (currLevel == treeDepth) return; + // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔) + int gap = treeDepth - currLevel - 1; + + // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值 + if (currNode.left != null) { + res[rowIndex + 1][columnIndex - gap] = "/"; + writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth); + } + + // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值 + if (currNode.right != null) { + res[rowIndex + 1][columnIndex + gap] = "\\"; + writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth); + } + } + + private static void writeArray(TreeNode2 currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) { + // 保证输入的树不为空 + if (currNode == null) return; + // 先将当前节点保存到二维数组中 + res[rowIndex][columnIndex] = String.valueOf(currNode.val); + + // 计算当前位于树的第几层 + int currLevel = ((rowIndex + 1) / 2); + // 若到了最后一层,则返回 + if (currLevel == treeDepth) return; + // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔) + int gap = treeDepth - currLevel - 1; + + // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值 + if (currNode.left != null) { + res[rowIndex + 1][columnIndex - gap] = "/"; + writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth); + } + + // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值 + if (currNode.right != null) { + res[rowIndex + 1][columnIndex + gap] = "\\"; + writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth); + } + } + + /** + * 打印出二叉树的结构 + * + * 版权声明:本文为CSDN博主「LenFranky」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 + * 原文链接:https://blog.csdn.net/lenfranky/article/details/89645755 + * + * @param root + * @return + */ + public static void show(TreeNode root) { + if (root == null) System.out.println("EMPTY!"); + // 得到树的深度 + int treeDepth = getTreeDepth(root); + + // 最后一行的宽度为2的(n - 1)次方乘3,再加1 + // 作为整个二维数组的宽度 + int arrayHeight = treeDepth * 2 - 1; + int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1; + // 用一个字符串数组来存储每个位置应显示的元素 + String[][] res = new String[arrayHeight][arrayWidth]; + // 对数组进行初始化,默认为一个空格 + for (int i = 0; i < arrayHeight; i ++) { + for (int j = 0; j < arrayWidth; j ++) { + res[i][j] = " "; + } + } + + // 从根节点开始,递归处理整个树 + // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0'); + writeArray(root, 0, arrayWidth/ 2, res, treeDepth); + + // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可 + for (String[] line: res) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < line.length; i ++) { + sb.append(line[i]); + if (line[i].length() > 1 && i <= line.length - 1) { + i += line[i].length() > 4 ? 2: line[i].length() - 1; + } + } + System.out.println(sb.toString()); + } + } + + + public static void show(TreeNode2 root) { + if (root == null) System.out.println("EMPTY!"); + // 得到树的深度 + int treeDepth = getTreeDepth(root); + + // 最后一行的宽度为2的(n - 1)次方乘3,再加1 + // 作为整个二维数组的宽度 + int arrayHeight = treeDepth * 2 - 1; + int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1; + // 用一个字符串数组来存储每个位置应显示的元素 + String[][] res = new String[arrayHeight][arrayWidth]; + // 对数组进行初始化,默认为一个空格 + for (int i = 0; i < arrayHeight; i ++) { + for (int j = 0; j < arrayWidth; j ++) { + res[i][j] = " "; + } + } + + // 从根节点开始,递归处理整个树 + // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0'); + writeArray(root, 0, arrayWidth/ 2, res, treeDepth); + + // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可 + for (String[] line: res) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < line.length; i ++) { + sb.append(line[i]); + if (line[i].length() > 1 && i <= line.length - 1) { + i += line[i].length() > 4 ? 2: line[i].length() - 1; + } + } + System.out.println(sb.toString()); + } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy