+ * 给定一个整数数组 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
+ * 可以使用如下方法:
+ * 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
+ * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
+ *
+ * 说明:
+ *
+ * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
+ *
+ * 示例 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
+ * 时间复杂度 O(n), 空间复杂度O(n)
+ *
+ * @param nums
+ * @return
+ */
+ private static int singleNumberHashMap(int[] nums) {
+ if (nums.length == 1)
+ return nums[0];
+
+ Map
+ * 遍历数组, 计算数组所有元素值
+ * 使用set来存储不重复的元素, 存储的同时 计算不重复元素值
+ * 使用不重复元素之和的2倍 - 数组元素值之和 = 单个元素
+ *
+ * 时间复杂度 O(n), 空间复杂度O(n)
+ *
+ * @param nums
+ * @return
+ */
+ private static int singleNumberBySum(int[] nums) {
+ Set
+ * 如果我们对 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
- * 前序遍历: 根 - 左 - 右
- *
- * @param root
- * @param list
- */
- private static void traversal(TreeNode root, 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
- * 先使用中序遍历, 将每个节点放到一个arraylist集合中
- *
- * 然后遍历集合,对比前后两个元素, 看看是不是后面的元素都比前面元素大,如果是则说明集合是从小到大排列
- *
- * 同理说明二叉搜索树里的节点也是按左 根 右 从小到大排列的
- *
- * @param root
- * @return
- */
- private static boolean isValidBST(SearchTreeNode root) {
- List
- * 左 根 右
- *
- * @param root
- */
- private static void inorder(SearchTreeNode root, List
+ * 获取数据 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
- * 问题: 有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
+ * 从后往前推, 修改每个点的值为其最小路径和, 一层层往上,最终顶点的最短路径 = 顶点值 + 第二层最短路径的那个点的值
+ *
+ * 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
+ * 每个节点都有直接前驱后直接后继, 除了第一个节点没有直接前驱和最后一个节点没有直接后继
+ * 结构如下
+ *
+ * 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
* 存储前判断是否已存在该节点,存在则返回该节点.
*/
- private static ListNode detectCycle3(ListNode head) {
- Set
+ * 示例:
+ *
+ * 输入: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
+ * 给定一个数组,它的第 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
+ * 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
+ * 由于排序,所以最快的时间复杂度也要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
+ * 时间复杂度O^3
+ *
+ * @param nums
+ * @return
+ */
+ private static List
+ * 题目描述:给定一个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
+ * 找到第一个相同的元素(存在重复的元素)
+ * 找到最后一个相同的元素(存在重复的元素)
+ * 找到第一个大于等于的元素(存在重复的元素)
+ * 找到最后一个小于等于的元素(存在重复的元素)
+ */
+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
+ * 需要借助两个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
+ * 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
+ *
+ * 例如,给出 n = 3,生成结果为:
+ *
+ * [
+ * "((()))",
+ * "(()())",
+ * "(())()",
+ * "()(())",
+ * "()()()"
+ * ]
+ *
+ * 链接:https://leetcode-cn.com/problems/generate-parentheses
+ */
+public class GenerateParentheses {
+
+ /**
+ * 有几个需要注意的条件:
+ * 1. 左括号和右括号数量必须一致
+ * 2. 必须先有左括号
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ //List
+ * 为了枚举某些内容,我们通常希望将其表示为更容易计算的不相交子集的总和。
+ *
+ * 考虑有效括号序列 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
+ * 时间复杂度O
+ *
+ * @param left
+ * @param right
+ * @param n
+ * @param result
+ * @param resultList
+ */
+ private static void gen(int left, int right, int n, String result, List
+ * 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
+ *
+ * 在构造过程中,请注意区分大小写。比如 "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
+ * 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 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
+ * 皇后问题研究的是如何将 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
+ * 本题中,一棵高度平衡二叉树定义为:
+ *
+ * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过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
+ * 翻转一棵二叉树。
+ *
+ * 示例:
+ *
+ * 输入:
+ *
+ * 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
+ * 解题思路,
+ * 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
+ * 使用迭代的方式
+ *
+ * 思路: 使用一个队列来装载二叉树的每一层节点, 通过先进先出的原理使得取出来的节点可以根据装入的节点顺序一样(根 左 右)
+ *
+ * @param root
+ * @return
+ */
+ private static List
+ * 先从左子树进行查找,如果找到第一个目标节点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
+ * 给定一个二叉树,找出其最小深度。
+ *
+ * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
+ *
+ * 说明: 叶子节点是指没有子节点的节点。
+ *
+ * 示例:
+ *
+ * 给定二叉树 [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
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> 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));
+ }
+
+ /**
+ * 动态规划法
+ *
> 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
> resultList = threeSum(nums);
+ List
> resultList = threeSum_2(nums);
+ for (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
> 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
> allNodes = new ArrayList<>();
+
+ List
> lists = levelOrder(root);
+ for (List
> levelOrder(TreeNode root) {
+ // 创建一个2维的节点集合, 第1维度为层集合,第2维度为每层的节点集合
+ List
> levels = new ArrayList
>();
+ //如果根节点为空,直接返回空列表
+ if (root == null) return levels;
+
+ // 创建一个当前层的队列
+ Queue
> visitedNodes = searchByLoop(root);
+ // List
> visitedNodes = searchByRecursion(root);
+ for (List
> searchByLoop(TreeNode root) {
+ // 用于存储访问过的节点
+ List
> visited = new ArrayList<>();
+
+ if (root == null)
+ return visited;
+
+ // 记录节点层级
+ int level = 0;
+
+ // 利用队列先进先出的特性,实现层级的从左往右遍历
+ Queue
> 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
> allNodes = levelOrder(root);
+ List
> allNodes = levelOrder2(root);
+
+ for (List
> levelOrder(TreeNode root) {
+ // 创建一个2维的节点集合, 第1维度为层集合,第2维度为每层的节点集合
+ List
> levels = new ArrayList
>();
+ //如果根节点为空,直接返回空列表
+ if (root == null) return levels;
+
+ // 创建一个当前层的队列
+ Queue
> levelOrder2(TreeNode root) {
+ List
> nodes = new ArrayList
>();
+
+ if (root == null)
+ return nodes;
+
+ Queue
> 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
> levelOrder3(TreeNode root) {
+ List
> nodes = new ArrayList
>();
+
+ if (root == null)
+ return nodes;
+
+ int level = 0;
+ Queue
> list = getTreeList(root);
+
+ int level = 1;
+ for (List
> getTreeList(TreeNode2 root) {
+ List
> list = new ArrayList<>();
+
+ Queue