diff --git a/src/main/java/com/chen/Test.java b/src/main/java/com/chen/Test.java deleted file mode 100644 index ce3beb4..0000000 --- a/src/main/java/com/chen/Test.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.chen; - -/** - * @author : chen weijie - * @Date: 2020-09-18 20:03 - */ -public class Test { - - - -} diff --git a/src/main/java/com/chen/algorithm/sort/standrd/QuickSort.java b/src/main/java/com/chen/algorithm/sort/standrd/QuickSort.java index 89a81f7..f959948 100644 --- a/src/main/java/com/chen/algorithm/sort/standrd/QuickSort.java +++ b/src/main/java/com/chen/algorithm/sort/standrd/QuickSort.java @@ -2,6 +2,8 @@ import org.junit.Test; +import java.util.Arrays; + /** * 如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。 * 我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分, @@ -17,11 +19,9 @@ public class QuickSort { @Test public void quickSort() { - int[] nums = {3, 7, 2, 10, -1, 4}; + int[] nums = {3, 7, 2, 10, -1, 6}; sort(0, nums.length - 1, nums); - for (int num : nums) { - System.out.println(num); - } + System.out.println(Arrays.toString(nums)); } diff --git a/src/main/java/com/chen/algorithm/study/offer/test29/Solution.java b/src/main/java/com/chen/algorithm/study/offer/test29/Solution.java index bda3fd1..1c3a01a 100644 --- a/src/main/java/com/chen/algorithm/study/offer/test29/Solution.java +++ b/src/main/java/com/chen/algorithm/study/offer/test29/Solution.java @@ -9,79 +9,79 @@ */ public class Solution { - public static int[] spiralOrder(int[][] matrix) { - if (matrix.length == 0) { - return new int[0]; - } - int[] res = new int[matrix.length * matrix[0].length]; - int u = 0, d = matrix.length - 1, l = 0, r = matrix[0].length - 1; - int idx = 0; - while (true) { - for (int i = l; i <= r; i++) { - res[idx++] = matrix[u][i]; - } - if (++u > d) { - break; - } - for (int i = u; i <= d; i++) { - res[idx++] = matrix[i][r]; - } - if (--r < l) { - break; - } - for (int i = r; i >= l; i--) { - res[idx++] = matrix[d][i]; - } - if (--d < u) { - break; - } - for (int i = d; i >= u; i--) { - res[idx++] = matrix[i][l]; - } - if (++l > r) { - break; - } - } - return res; - } + //public static int[] spiralOrder(int[][] matrix) { + // if (matrix.length == 0) { + // return new int[0]; + // } + // int[] res = new int[matrix.length * matrix[0].length]; + // int u = 0, d = matrix.length - 1, l = 0, r = matrix[0].length - 1; + // int idx = 0; + // while (true) { + // for (int i = l; i <= r; i++) { + // res[idx++] = matrix[u][i]; + // } + // if (++u > d) { + // break; + // } + // for (int i = u; i <= d; i++) { + // res[idx++] = matrix[i][r]; + // } + // if (--r < l) { + // break; + // } + // for (int i = r; i >= l; i--) { + // res[idx++] = matrix[d][i]; + // } + // if (--d < u) { + // break; + // } + // for (int i = d; i >= u; i--) { + // res[idx++] = matrix[i][l]; + // } + // if (++l > r) { + // break; + // } + // } + // return res; + //} + // + //private static int[] spiralOrder1(int[][] matrix) { + // if (matrix == null || matrix.length == 0) return new int[0]; + // + // int numEle = matrix.length * matrix[0].length; + // int[] result = new int[numEle]; + // int idx = 0; + // int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1; + // + // while (numEle >= 1) { + // for (int i = left; i <= right && numEle >= 1; i++) { + // result[idx++] = matrix[top][i]; + // numEle--; + // } + // top++; + // for (int i = top; i <= bottom && numEle >= 1; i++) { + // result[idx++] = matrix[i][right]; + // numEle--; + // } + // right--; + // for (int i = right; i >= left && numEle >= 1; i--) { + // result[idx++] = matrix[bottom][i]; + // numEle--; + // } + // bottom--; + // for (int i = bottom; i >= top && numEle >= 1; i--) { + // result[idx++] = matrix[i][left]; + // numEle--; + // } + // left++; + // } + // return result; + //} - private static int[] spiralOrder1(int[][] matrix) { - if (matrix == null || matrix.length == 0) return new int[0]; - - int numEle = matrix.length * matrix[0].length; - int[] result = new int[numEle]; - int idx = 0; - int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1; - - while (numEle >= 1) { - for (int i = left; i <= right && numEle >= 1; i++) { - result[idx++] = matrix[top][i]; - numEle--; - } - top++; - for (int i = top; i <= bottom && numEle >= 1; i++) { - result[idx++] = matrix[i][right]; - numEle--; - } - right--; - for (int i = right; i >= left && numEle >= 1; i--) { - result[idx++] = matrix[bottom][i]; - numEle--; - } - bottom--; - for (int i = bottom; i >= top && numEle >= 1; i--) { - result[idx++] = matrix[i][left]; - numEle--; - } - left++; - } - return result; - } - - public static void main(String[] args) { - int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; - int[] res = spiralOrder1(matrix); - System.out.println(Arrays.toString(res)); - } + //public static void main(String[] args) { + // int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; + // int[] res = spiralOrder1(matrix); + // System.out.println(Arrays.toString(res)); + //} } diff --git a/src/main/java/com/chen/algorithm/study/test111/Solution2.java b/src/main/java/com/chen/algorithm/study/test111/Solution2.java index 2d59b66..6cdb1ff 100644 --- a/src/main/java/com/chen/algorithm/study/test111/Solution2.java +++ b/src/main/java/com/chen/algorithm/study/test111/Solution2.java @@ -16,6 +16,6 @@ public int minDepth(TreeNode root) { int left = minDepth(root.left); int right = minDepth(root.right); - return (left == 0 || right == 0) ? 1 : Math.min(left, right) + 1; + return (left == 0 || right == 0) ? left + right + 1 : Math.min(left, right) + 1; } } diff --git a/src/main/java/com/chen/algorithm/study/test141/Solution3.java b/src/main/java/com/chen/algorithm/study/test141/Solution3.java deleted file mode 100644 index 75c18d8..0000000 --- a/src/main/java/com/chen/algorithm/study/test141/Solution3.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.chen.algorithm.study.test141; - -import java.util.HashSet; -import java.util.Set; - -/** - * @Auther: zhunn - * @Date: 2020/10/23 16:26 - * @Description: 环形链表一:判断链表是否有环 - * 方法:1-哈希表,2-快慢指针 - */ -public class Solution3 { - class ListNode { - int val; - ListNode next; - - ListNode(int x) { - val = x; - next = null; - } - } - - - /** - * 2-快慢指针 - * @param head - * @return - */ - public boolean hasCycle1(ListNode head) { - if (head == null || head.next == null) { - return false; - } - - ListNode slow = head; - ListNode fast = head; - - while (fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - - if (slow == fast) { - return true; - } - } - return false; - } - - /** - * 1-哈希表 - * @param head - * @return - */ - public boolean hasCycle2(ListNode head) { - if (head == null || head.next == null) { - return false; - } - - ListNode dummy = head; - Set visited = new HashSet<>(); - while (dummy != null) { - if (visited.contains(dummy)) { - return true; - } else { - visited.add(dummy); - } - dummy = dummy.next; - } - return false; - } - - /** - * 1-哈希表简易版 - * @param head - * @return - */ - public boolean hasCycle3(ListNode head) { - Set seen = new HashSet<>(); - while (head != null) { - if (!seen.add(head)) { - return true; - } - head = head.next; - } - return false; - } - -} diff --git a/src/main/java/com/chen/algorithm/study/test142/Solution4.java b/src/main/java/com/chen/algorithm/study/test142/Solution4.java deleted file mode 100644 index 5d283c5..0000000 --- a/src/main/java/com/chen/algorithm/study/test142/Solution4.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.chen.algorithm.study.test142; - - -import java.util.HashSet; -import java.util.Set; - -/** - * @Auther: zhunn - * @Date: 2020/10/23 16:10 - * @Description: 环形链表二,找出入环点 - * 方法:1-哈希表,2-快慢指针 - */ -public class Solution4 { - - /** - * 1-哈希表 - * @param head - * @return - */ - public ListNode detectCycle1(ListNode head) { - if (head == null || head.next == null) { - return null; - } - ListNode dummy = head; - Set visited = new HashSet<>(); - while (dummy != null) { - if (visited.contains(dummy)) { - return dummy; - } else { - visited.add(dummy); - } - dummy = dummy.next; - } - return null; - } - - /** - * 2-快慢指针 - * @param head - * @return - */ - public ListNode detectCycle2(ListNode head) { - if (head == null || head.next == null) { - return null; - } - - ListNode slow = head; - ListNode fast = head; - while (fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - if (slow == fast) { - break; - } - } - - ListNode ptr = head; - while (slow != ptr) { - slow = slow.next; - ptr = ptr.next; - } - return slow; - } - -} diff --git a/src/main/java/com/chen/algorithm/study/test206/Solution5.java b/src/main/java/com/chen/algorithm/study/test206/Solution5.java deleted file mode 100644 index 3e4e9de..0000000 --- a/src/main/java/com/chen/algorithm/study/test206/Solution5.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.chen.algorithm.study.test206; - -import org.junit.Test; - -/** - * @Auther: zhunn - * @Date: 2020/10/22 17:23 - * @Description: 反转链表:1-迭代法,2-递归 - */ -public class Solution5 { - - class ListNode { - int val; - ListNode next; - - public ListNode() { - } - - public ListNode(int val) { - this.val = val; - } - - public ListNode(int val, ListNode next) { - this.val = val; - this.next = next; - } - - } - - public ListNode reverseList1(ListNode head) { - if (head == null || head.next == null) { - return head; - } - - ListNode pre = null; - ListNode curr = head; - while (curr != null) { - ListNode nextTemp = curr.next; - curr.next = pre; - pre = curr; - curr = nextTemp; - } - - return pre; - } - - public ListNode reverseList2(ListNode head){ - if(head == null || head.next == null){ - return head; - } - ListNode p = reverseList2(head.next); - head.next.next = head; - head.next = null; - return p; - } - - @Test - public void test() { - ListNode l1_4 = new ListNode(18); - ListNode l1_3 = new ListNode(9, l1_4); - ListNode l1_2 = new ListNode(6, l1_3); - ListNode l1_1 = new ListNode(7, l1_2); - - ListNode result = reverseList2(l1_1); - System.out.println(result.val); - System.out.println(result.next.val); - System.out.println(result.next.next.val); - System.out.println(result.next.next.next.val); - } - - -} diff --git a/src/main/java/com/chen/algorithm/study/test208/Trie.java b/src/main/java/com/chen/algorithm/study/test208/Trie.java index 43a4bd7..d8a712d 100644 --- a/src/main/java/com/chen/algorithm/study/test208/Trie.java +++ b/src/main/java/com/chen/algorithm/study/test208/Trie.java @@ -77,8 +77,7 @@ public TrieNode() { } TrieNode(char c) { - TrieNode node = new TrieNode(); - node.val = c; + this.val = c; } } diff --git a/src/main/java/com/chen/algorithm/study/test25/Solution5.java b/src/main/java/com/chen/algorithm/study/test25/Solution5.java deleted file mode 100644 index 7f02d9d..0000000 --- a/src/main/java/com/chen/algorithm/study/test25/Solution5.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.chen.algorithm.study.test25; - -/** - * @Auther: zhunn - * @Date: 2020/10/24 17:23 - * @Description: K个一组翻转链表 - */ -public class Solution5 { - - /** - * 官网解法 - * @param head - * @param k - * @return - */ - public ListNode reverseKGroup(ListNode head, int k) { - ListNode hair = new ListNode(0); - hair.next = head; - ListNode pre = hair; - - while (head != null) { - ListNode tail = pre; - // 查看剩余部分长度是否大于等于 k - for (int i = 0; i < k; ++i) { - tail = tail.next; - if (tail == null) { - return hair.next; - } - } - ListNode nextTemp = tail.next; - ListNode[] reverse = myReverse(head, tail); - head = reverse[0]; - tail = reverse[1]; - // 把子链表重新接回原链表 - pre.next = head; - tail.next = nextTemp; - pre = tail; - head = tail.next; - } - - return hair.next; - } - - private ListNode[] myReverse(ListNode head, ListNode tail) { - ListNode prev = tail.next; - ListNode p = head; - while (prev != tail) { - ListNode nex = p.next; - p.next = prev; - prev = p; - p = nex; - } - return new ListNode[]{tail, head}; - } - - - public ListNode reverseKGroup1(ListNode head, int k) { - ListNode dummy = new ListNode(0); - dummy.next = head; - - ListNode pre = dummy; - ListNode end = dummy; - - while (end.next != null) { - for (int i = 0; i < k && end != null; i++){end = end.next;} - if (end == null) {break;} - ListNode start = pre.next; - ListNode next = end.next; - end.next = null; - pre.next = reverse(start); - start.next = next; - pre = start; - - end = pre; - } - return dummy.next; - } - - private ListNode reverse(ListNode head) { - ListNode pre = null; - ListNode curr = head; - while (curr != null) { - ListNode next = curr.next; - curr.next = pre; - pre = curr; - curr = next; - } - return pre; - } - - -} diff --git a/src/main/java/com/chen/algorithm/study/test27/Solution2.java b/src/main/java/com/chen/algorithm/study/test27/Solution2.java deleted file mode 100644 index 24cbd15..0000000 --- a/src/main/java/com/chen/algorithm/study/test27/Solution2.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.chen.algorithm.study.test27; - -import org.junit.Test; - -import java.util.Arrays; - -/** - * @Auther: zhunn - * @Date: 2020/10/10 16:40 - * @Description: 移除元素,双指针法 - */ -public class Solution2 { - - public int removeElement(int[] nums, int val) { - if (nums == null || nums.length == 0) return 0; - - int i = 0; - for (int j = 0; j < nums.length; j++) { - if (nums[j] != val) { - nums[i] = nums[j]; - i++; - } - } - System.out.println(Arrays.toString(nums)); - return i; - } - - @Test - public void test() { - System.out.println(removeElement(new int[]{1, 1, 2, 2, 3, 5, 6, 7, 8}, 2)); - } -} diff --git a/src/main/java/com/chen/algorithm/study/test279/Solution.java b/src/main/java/com/chen/algorithm/study/test279/Solution.java index 0649d49..d0d4338 100644 --- a/src/main/java/com/chen/algorithm/study/test279/Solution.java +++ b/src/main/java/com/chen/algorithm/study/test279/Solution.java @@ -8,11 +8,11 @@ public class Solution { public int numSquares(int n) { - int[] dp = new int[n + 1]; + for (int i = 1; i <= n; i++) { dp[i] = i; - for (int j = 0; i - j * j >= 0; j++) { + for (int j = 1; i - j * j >= 0; j++) { dp[i] = Math.min(dp[i], dp[i - j * j] + 1); } } diff --git a/src/main/java/com/chen/algorithm/study/test283/Solution2.java b/src/main/java/com/chen/algorithm/study/test283/Solution2.java index e066b48..84c34f4 100644 --- a/src/main/java/com/chen/algorithm/study/test283/Solution2.java +++ b/src/main/java/com/chen/algorithm/study/test283/Solution2.java @@ -43,28 +43,4 @@ public void testCase() { } - - /** - * @author: zhunn - * @param nums - * @return - */ - public int[] moveZeroes1(int[] nums) { - if (nums == null || nums.length == 0) { - return nums; - } - - int i = 0; - for (int j = 0; j < nums.length; j++) { - if (nums[j] != 0) { - nums[i] = nums[j]; - i++; - } - } - - for (int k = i; k < nums.length; k++) { - nums[k] = 0; - } - return nums; - } } diff --git a/src/main/java/com/chen/algorithm/study/test3/Solution4.java b/src/main/java/com/chen/algorithm/study/test3/Solution4.java index 3e6daf1..16ba0ae 100644 --- a/src/main/java/com/chen/algorithm/study/test3/Solution4.java +++ b/src/main/java/com/chen/algorithm/study/test3/Solution4.java @@ -27,9 +27,9 @@ public int lengthOfLongestSubstring(String s) { char c = s.charAt(i); if (map.containsKey(c)) { - left = Math.max(left, map.get(c)); + left = Math.max(left, map.get(c) + 1); } - max = Math.max(max, i - left); + max = Math.max(max, i - left + 1); map.put(c, i); } return max; diff --git a/src/main/java/com/chen/algorithm/study/test39/Solution2.java b/src/main/java/com/chen/algorithm/study/test39/Solution2.java index ef9cac8..b96af42 100644 --- a/src/main/java/com/chen/algorithm/study/test39/Solution2.java +++ b/src/main/java/com/chen/algorithm/study/test39/Solution2.java @@ -1,9 +1,11 @@ package com.chen.algorithm.study.test39; +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Stack; /** * https://leetcode-cn.com/problems/combination-sum/solution/di-gui-hui-su-tu-wen-fen-xi-ji-bai-liao-9987de-yon/ @@ -24,7 +26,7 @@ public List> combinationSum(int[] candidates, int target) { public void backtrack(List> res, int[] candidates, int target, List curList, int start) { if (target == 0) { - res.add(new ArrayList<>(new Stack<>())); + res.add(new ArrayList<>(curList)); return; } @@ -39,4 +41,11 @@ public void backtrack(List> res, int[] candidates, int target, Lis } } + @Test + public void test() { + int[] candidates = {2, 3, 6, 7}; + int target = 7; + + System.out.println(combinationSum(candidates, target)); + } } diff --git a/src/main/java/com/chen/algorithm/study/test703/ObjectPriorityQueue.java b/src/main/java/com/chen/algorithm/study/test703/ObjectPriorityQueue.java index da4640e..16c3f60 100644 --- a/src/main/java/com/chen/algorithm/study/test703/ObjectPriorityQueue.java +++ b/src/main/java/com/chen/algorithm/study/test703/ObjectPriorityQueue.java @@ -26,7 +26,7 @@ public int add(int num) { if (queue.size() < limit) { queue.offer(num); - } else if (queue.peek() > num) { + } else if (queue.peek() < num) { queue.poll(); queue.offer(num); } diff --git a/src/main/java/com/chen/algorithm/study/test704/Solution.java b/src/main/java/com/chen/algorithm/study/test704/Solution.java index 8dda25c..143871f 100644 --- a/src/main/java/com/chen/algorithm/study/test704/Solution.java +++ b/src/main/java/com/chen/algorithm/study/test704/Solution.java @@ -10,58 +10,58 @@ public class Solution { /** * 适用于升序数组,可做相应调整适用降序数组 */ - public static int bsearch(int[] array, int target) { - if (array == null || array.length == 0 - /*|| target < array[0] || target > array[array.length - 1]*/) { - return -1; - } - - int left = 0; - int right = array.length - 1; - //这里必须是 >=,切记勿遗漏 = - while (right >= left) { - // 当start=Integer.MAX_VALUE时,给它加个1都会溢出。安全的写法是:mid = start + (end-start)/2,但是会造成死循环,弃用 - //int mid = min + (max - min) >> 1; - int mid = (left + right) >> 1; - if (target == array[mid]) { - return mid; - } - if (target < array[mid]) { - right = mid - 1; - } - if (target > array[mid]) { - left = mid + 1; - } - } - return -1; - } + //public static int bsearch(int[] array, int target) { + // if (array == null || array.length == 0 + // /*|| target < array[0] || target > array[array.length - 1]*/) { + // return -1; + // } + // + // int left = 0; + // int right = array.length - 1; + // //这里必须是 >=,切记勿遗漏 = + // while (right >= left) { + // // 当start=Integer.MAX_VALUE时,给它加个1都会溢出。安全的写法是:mid = start + (end-start)/2,但是会造成死循环,弃用 + // //int mid = min + (max - min) >> 1; + // int mid = (left + right) >> 1; + // if (target == array[mid]) { + // return mid; + // } + // if (target < array[mid]) { + // right = mid - 1; + // } + // if (target > array[mid]) { + // left = mid + 1; + // } + // } + // return -1; + //} /** * 查找第一个与target相等的元素 * 当key=array[mid]时, 往左边一个一个逼近,right = mid -1; 返回left */ - public static int bsearch1(int[] array, int target) { - if (array == null || array.length == 0 - /*|| target < array[0] || target > array[array.length - 1]*/) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] >= target) { - right = mid - 1; - } else { - left = mid + 1; - } - } - - if (left < array.length && array[left] == target) { - return left; - } - return -1; - - } + //public static int bsearch1(int[] array, int target) { + // if (array == null || array.length == 0 + // /*|| target < array[0] || target > array[array.length - 1]*/) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] >= target) { + // right = mid - 1; + // } else { + // left = mid + 1; + // } + // } + // + // if (left < array.length && array[left] == target) { + // return left; + // } + // return -1; + // + //} /** * 查找最后一个与target相等的元素 @@ -69,26 +69,26 @@ public static int bsearch1(int[] array, int target) { * * @return */ - public static int bsearch2(int[] array, int target) { - if (array == null || array.length == 0 /*|| target < array[0] || target > array[array.length - 1]*/) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] <= target) { - left = mid + 1; - } else { - right = mid - 1; - } - } - - if (right >= 0 && array[right] == target) { - return right; - } - return -1; - } + //public static int bsearch2(int[] array, int target) { + // if (array == null || array.length == 0 /*|| target < array[0] || target > array[array.length - 1]*/) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] <= target) { + // left = mid + 1; + // } else { + // right = mid - 1; + // } + // } + // + // if (right >= 0 && array[right] == target) { + // return right; + // } + // return -1; + //} // 二分查找变种说明 //public void test(int[] array, int target){ @@ -112,86 +112,86 @@ public static int bsearch2(int[] array, int target) { /** * 查找第一个等于或者大于key的元素 */ - public static int bsearch3(int[] array, int target) { - if (array == null || array.length == 0) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] >= target) { - right = mid - 1; - } else { - left = mid + 1; - } - } - return left; - } + //public static int bsearch3(int[] array, int target) { + // if (array == null || array.length == 0) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] >= target) { + // right = mid - 1; + // } else { + // left = mid + 1; + // } + // } + // return left; + //} /** * 查找第一个大于key的元素 */ - public static int bsearch4(int[] array, int target) { - if (array == null || array.length == 0) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] > target) { - right = mid - 1; - } else { - left = mid + 1; - } - } - return left; - } + //public static int bsearch4(int[] array, int target) { + // if (array == null || array.length == 0) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] > target) { + // right = mid - 1; + // } else { + // left = mid + 1; + // } + // } + // return left; + //} /** * 查找最后一个等于或者小于key的元素 */ - public static int bsearch5(int[] array, int target) { - if (array == null || array.length == 0) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] <= target) { - left = mid + 1; - } else { - right = mid - 1; - } - } - return right; - } + //public static int bsearch5(int[] array, int target) { + // if (array == null || array.length == 0) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] <= target) { + // left = mid + 1; + // } else { + // right = mid - 1; + // } + // } + // return right; + //} /** * 查找最后一个小于key的元素 */ - public static int bsearch6(int[] array, int target) { - if (array == null || array.length == 0) { - return -1; - } - int left = 0; - int right = array.length - 1; - while (right >= left) { - int mid = (left + right) >> 1; - if (array[mid] < target) { - left = mid + 1; - } else { - right = mid - 1; - } - } - return right; - } + //public static int bsearch6(int[] array, int target) { + // if (array == null || array.length == 0) { + // return -1; + // } + // int left = 0; + // int right = array.length - 1; + // while (right >= left) { + // int mid = (left + right) >> 1; + // if (array[mid] < target) { + // left = mid + 1; + // } else { + // right = mid - 1; + // } + // } + // return right; + //} - public static void main(String[] args) { - int[] array = {1, 1, 3, 6, 6, 6, 7, 9, 17, 17}; - int index = bsearch6(array, 0); - System.out.println(index); - } + //public static void main(String[] args) { + // int[] array = {1, 1, 3, 6, 6, 6, 7, 9, 17, 17}; + // int index = bsearch6(array, 0); + // System.out.println(index); + //} } diff --git a/src/main/java/com/chen/algorithm/study/test98/Solution.java b/src/main/java/com/chen/algorithm/study/test98/Solution.java index b7b37f1..c6ea865 100644 --- a/src/main/java/com/chen/algorithm/study/test98/Solution.java +++ b/src/main/java/com/chen/algorithm/study/test98/Solution.java @@ -29,7 +29,7 @@ public boolean isValidBST(TreeNode root) { } - public boolean isValid(TreeNode root, Integer max, Integer min) { + public boolean isValid(TreeNode root, Integer min, Integer max) { if (root == null) { return true; diff --git a/src/main/java/com/chen/algorithm/znn/InterviewTest.java b/src/main/java/com/chen/algorithm/znn/InterviewTest.java new file mode 100644 index 0000000..5b4ce13 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/InterviewTest.java @@ -0,0 +1,128 @@ +package com.chen.algorithm.znn; + + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InterviewTest { + + /* + [ +{id:1, name:xx1, pid:0}, +{id:2, name:xx2, pid:0}, +{id:3, name:xx3, pid:1}, +{id:4, name:xx4, pid:3}, +{id:5, name:xx5, pid:4}, +] + */ + + class Node{ + private int id; + private String name; + private int pid; + private List children; + public Node(){} + public Node(int pid){ + this.pid = pid; + } + + public Node(int id,String name, int pid){ + this.id = id; + this.name = name; + this.pid = pid; + } + } + + public Node getNode(List nodeList){ + if(nodeList == null || nodeList.size() == 0){ + return null; + } + Map> nodeMap = new HashMap<>(); + Map originNodeMap = new HashMap<>(); + + for(int i =0;i{ + Node fNode = originNodeMap.get(pid); + if(fNode == null){ + fNode = new Node(pid,"xxxpid",-1); + if(root.children == null){ + root.children = Arrays.asList(fNode); + }else{ + root.children.add(fNode); + } + } + fNode.children = nodeMap.get(pid); + }); + return root; + } + + @Test + public void test(){ + +// [ +// {id:1, name:xx1, pid:0}, +// {id:2, name:xx2, pid:0}, +// {id:3, name:xx3, pid:1}, +// {id:4, name:xx4, pid:3}, +// {id:5, name:xx5, pid:4}, +//] + System.out.println(isHuiwen("12321")); + } + + public boolean isPalling(String s){ + if(s == null || s.length() == 0){ + return false; + } + + int len = s.length(); + boolean[][] dp = new boolean[len][len]; + for(int x = 0;x d) { + break; + } + for (int i = u; i <= d; i++) { + res[idx++] = matrix[i][r]; + } + if (--r < l) { + break; + } + for (int i = r; i >= l; i--) { + res[idx++] = matrix[d][i]; + } + if (--d < u) { + break; + } + for (int i = d; i >= u; i--) { + res[idx++] = matrix[i][l]; + } + if (++l > r) { + break; + } + } + return res; + } + + public int[] spiralOrder1(int[][] matrix) { + if (matrix == null || matrix.length == 0) return new int[0]; + + int numEle = matrix.length * matrix[0].length; + int[] result = new int[numEle]; + int idx = 0; + int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1; + + while (numEle >= 1) { + for (int i = left; i <= right && numEle >= 1; i++) { //注意边界 >=,<= + result[idx++] = matrix[top][i]; + numEle--; + } + top++; //注意边界 >=,<=,因为这里都做了递增和递减处理,所以遍历需要带等号 + for (int i = top; i <= bottom && numEle >= 1; i++) { + result[idx++] = matrix[i][right]; + numEle--; + } + right--; + for (int i = right; i >= left && numEle >= 1; i--) { + result[idx++] = matrix[bottom][i]; + numEle--; + } + bottom--; + for (int i = bottom; i >= top && numEle >= 1; i--) { + result[idx++] = matrix[i][left]; + numEle--; + } + left++; + } + return result; + } + + @Test + public void test() { + int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; + int[] res = spiralOrder1(matrix); + System.out.println(Arrays.toString(res)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/offer/test57/Solution.java b/src/main/java/com/chen/algorithm/znn/array/offer/test57/Solution.java new file mode 100644 index 0000000..201c775 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/offer/test57/Solution.java @@ -0,0 +1,94 @@ +package com.chen.algorithm.znn.array.offer.test57; + +import com.alibaba.fastjson.JSON; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/ + * 剑指 Offer 57 - II. 和为s的连续正数序列: + * 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 + * 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 + * 示例 1: + * 输入:target = 9 + * 输出:[[2,3,4],[4,5]] + * 示例 2: + * 输入:target = 15 + * 输出:[[1,2,3,4,5],[4,5,6],[7,8]] + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 剑指 Offer 57 - II. 和为s的连续正数序列:滑动窗口法 + */ +public class Solution { + + //public int[][] findContinuousSequence(int target) { + // int i = 1; // 滑动窗口的左边界 + // int j = 1; // 滑动窗口的右边界 + // int sum = 0; // 滑动窗口中数字的和 + // List res = new ArrayList<>(); + // + // while (i <= target / 2) { + // if (sum < target) { + // // 右边界向右移动 + // sum += j; + // j++; + // } else if (sum > target) { + // // 左边界向右移动 + // sum -= i; + // i++; + // } else { + // // 记录结果 + // int[] arr = new int[j - i]; + // for (int k = i; k < j; k++) { + // arr[k - i] = k; + // } + // res.add(arr); + // // 左边界向右移动 + // sum -= i; + // i++; + // } + // } + // + // return res.toArray(new int[res.size()][]); + //} + + /** + * 滑动窗口法 + * + * @param target + * @return + */ + public int[][] findContinuousSequence(int target) { + int left = 1, right = 1; + int sum = 0; + List res = new ArrayList<>(); + + while (left <= target / 2) { // 假设窗口左边界i=target/2, i+1=target/2 +1,那么i + (i+1) = target+1,即最小的窗口的元素之和就已经比target大了,所以当i>=target/2时不可能有一个窗口长度>=2的答案。 这也归因于本题的特殊要求:答案数组至少包含两个元素。 如果没有上述要求,那么在i>=target/2的范围内还存在当i == target时的结果,窗口长度==1。 + if (sum < target) { + sum += right; + right++; + } else if (sum > target) { + sum -= left; + left++; + } else { + int[] arr = new int[right - left]; + for (int i = left; i < right; i++) { // 因为当sum=target的时候,此时sum加的是j,而j此时又进行了++操作,相当于j右移了一下,不在sum计算范围内,所以是左闭右开 + arr[i - left] = i; + } + res.add(arr); + + sum -= left; + left++; + } + } + return res.toArray(new int[res.size()][]); + } + + @Test + public void test() { + System.out.println(JSON.toJSONString(findContinuousSequence(15))); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test1/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test1/Solution.java new file mode 100644 index 0000000..596dbba --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test1/Solution.java @@ -0,0 +1,59 @@ +package com.chen.algorithm.znn.array.test1; + +import com.alibaba.fastjson.JSON; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1. 两数之和 + * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + * 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + * 示例: + * 给定 nums = [2, 7, 11, 15], target = 9 + * 因为 nums[0] + nums[1] = 2 + 7 = 9 + * 所以返回 [0, 1] + * + * @Auther: zhunn + * @Date: 2020/10/22 19:00 + * @Description: 两数之和 + */ +public class Solution { + + public int[] twoSum(int[] nums, int target) { + int n = nums.length; + for (int i = 0; i < n; ++i) { + for (int j = i + 1; j < n; ++j) { + if (nums[i] + nums[j] == target) { + return new int[]{i, j}; + } + } + } + return new int[0]; + } + + public int[] twoSum2(int[] nums, int target) { + if (nums == null || nums.length < 2) { + return new int[0]; + } + + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; ++i) { + if (map.containsKey(target - nums[i])) { + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); + } + return new int[0]; + } + + @Test + public void testCase() { + int[] array = {4, 5, 6, 7, 2}; + + int[] result = twoSum2(array, 9); + System.out.println(JSON.toJSONString(result)); + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test11/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test11/Solution.java new file mode 100644 index 0000000..bb659fe --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test11/Solution.java @@ -0,0 +1,51 @@ +package com.chen.algorithm.znn.array.test11; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/ + * 11. 盛最多水的容器 + * 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 + * 说明:你不能倾斜容器。 + * 示例 1: + * 输入:[1,8,6,2,5,4,8,3,7] + * 输出:49 + * 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 + * 示例 2: + * 输入:height = [1,1] + * 输出:1 + * 示例 3: + * 输入:height = [4,3,2,1,4] + * 输出:16 + * 示例 4: + * 输入:height = [1,2,1] + * 输出:2 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 盛最多水的容器:双指针法 + */ +public class Solution { + + public int maxArea(int[] height) { + int left = 0, right = height.length - 1; + int res = 0; + + while (left < right) { + int area = Math.min(height[left], height[right]) * (right - left); + res = Math.max(res, area); + if (height[left] <= height[right]) { + left++; + } else { + right--; + } + } + return res; + } + + @Test + public void test() { + int[] height = {1, 8, 6, 2, 5, 4, 8, 3, 7}; + System.out.println(maxArea(height)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test1712/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test1712/Solution.java new file mode 100644 index 0000000..9c4b1ae --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test1712/Solution.java @@ -0,0 +1,15 @@ +package com.chen.algorithm.znn.array.test1712; + +/** + * https://leetcode-cn.com/problems/ways-to-split-array-into-three-subarrays/ + * 我们称一个分割整数数组的方案是 好的,当它满足: + * + * 数组被分成三个 非空连续子数组,从左至右分别命名为left,mid,right。 + * left中元素和小于等于mid中元素和,mid中元素和小于等于right中元素和。 + * 给你一个 非负 整数数组nums,请你返回好的 分割 nums方案数目。由于答案可能会很大,请你将结果对 109+ 7取余后返回。 + * + * + * + */ +public class Solution { +} diff --git a/src/main/java/com/chen/algorithm/study/test238/Solution1.java b/src/main/java/com/chen/algorithm/znn/array/test238/Solution.java similarity index 85% rename from src/main/java/com/chen/algorithm/study/test238/Solution1.java rename to src/main/java/com/chen/algorithm/znn/array/test238/Solution.java index c2028f2..0f56b46 100644 --- a/src/main/java/com/chen/algorithm/study/test238/Solution1.java +++ b/src/main/java/com/chen/algorithm/znn/array/test238/Solution.java @@ -1,13 +1,21 @@ -package com.chen.algorithm.study.test238; +package com.chen.algorithm.znn.array.test238; import com.alibaba.fastjson.JSON; /** + * 238. 除自身以外数组的乘积 + * 给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 + * 示例: + * 输入: [1,2,3,4] + * 输出: [24,12,8,6] + * 提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。 + * 说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 + * * @Auther: zhunn * @Date: 2020/10/16 14:30 * @Description: 除自身以外数组的乘积 */ -public class Solution1 { +public class Solution { /** * 时间复杂度O(N),空间复杂度O(N) diff --git a/src/main/java/com/chen/algorithm/znn/array/test240/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test240/Solution.java new file mode 100644 index 0000000..8d60afb --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test240/Solution.java @@ -0,0 +1,88 @@ +package com.chen.algorithm.znn.array.test240; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/search-a-2d-matrix-ii/solution/er-fen-fa-pai-chu-fa-python-dai-ma-java-dai-ma-by-/ + * 240. 搜索二维矩阵 II + * 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性: + * 每行的元素从左到右升序排列。 + * 每列的元素从上到下升序排列。 + * 示例: + * 现有矩阵 matrix 如下: + * [ + * [1, 4, 7, 11, 15], + * [2, 5, 8, 12, 19], + * [3, 6, 9, 16, 22], + * [10, 13, 14, 17, 24], + * [18, 21, 23, 26, 30] + * ] + * 给定 target = 5,返回 true。 + * 给定 target = 20,返回 false。 + * + * @Author: zhunn + * @Date: 2020-10-22 14:26 + * @Description: 搜索二维矩阵 II + */ +public class Solution { + + + /** + * 从左下角开始 + *

+ * 如果当前数比目标元素小,当前列就不可能存在目标值,“指针”就向右移一格(纵坐标加 11); + * 如果当前数比目标元素大,当前行就不可能存在目标值,“指针”就向上移一格(横坐标减 11)。 + * + * @param matrix + * @param target + * @return + */ + public boolean searchMatrix(int[][] matrix, int target) { + + if (matrix == null) { + return false; + } + + int rows = matrix.length; + if (rows == 0) { + return false; + } + + int col = matrix[0].length; + + if (col == 0) { + return false; + } + + int x = rows - 1; + int y = 0; + int value; + + while (x >= 0 && y < col) { + value = matrix[x][y]; + if (value > target) { + x--; + } else if (value < target) { + y++; + } else { + return true; + } + } + + return false; + } + + + @Test + public void test() { + int[][] matrix = { + {1, 4, 7, 11, 15}, + {2, 5, 8, 12, 19}, + {3, 6, 9, 16, 22}, + {10, 13, 14, 17, 24}, + {18, 21, 23, 26, 30} + }; + System.out.println(searchMatrix(matrix, 5)); + System.out.println(searchMatrix(matrix, 20)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test26/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test26/Solution.java new file mode 100644 index 0000000..c711688 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test26/Solution.java @@ -0,0 +1,48 @@ +package com.chen.algorithm.znn.array.test26; + +import org.junit.Test; + +/** + * 26. 删除排序数组中的重复项 + * 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + * 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 + * 示例 1: + * 给定数组 nums = [1,1,2], + * 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 + * 你不需要考虑数组中超出新长度后面的元素。 + * 示例 2: + * 给定 nums = [0,0,1,1,1,2,2,3,3,4], + * 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 + * 你不需要考虑数组中超出新长度后面的元素。 + * + * @Auther: zhunn + * @Date: 2020/10/10 16:23 + * @Description: 删除排序数组中的重复项,返回新数组长度。双指针法 + */ +public class Solution { + + /** + * 双指针法 ,数组是一个引用 + * + * @param nums + * @return + */ + public int removeDuplicates(int[] nums) { + if (nums == null || nums.length == 0) return 0; + + int i = 0; + for (int j = 0; j < nums.length; j++) { + if (nums[j] != nums[i]) { // 不相等的重新放入数组中,重新组成无重复数组 + i++; + nums[i] = nums[j]; + } + } + //System.out.println(Arrays.toString(nums)); + return i + 1; + } + + @Test + public void test() { + System.out.println(removeDuplicates(new int[]{0, 0, 1, 1, 2, 3, 4, 5, 5, 6})); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test27/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test27/Solution.java new file mode 100644 index 0000000..8625f95 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test27/Solution.java @@ -0,0 +1,46 @@ +package com.chen.algorithm.znn.array.test27; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * 27. 移除元素 + * 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 + * 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 + * 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 + * 示例 1: + * 给定 nums = [3,2,2,3], val = 3, + * 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 + * 你不需要考虑数组中超出新长度后面的元素。 + * 示例 2: + * 给定 nums = [0,1,2,2,3,0,4,2], val = 2, + * 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 + * 注意这五个元素可为任意顺序。 + * 你不需要考虑数组中超出新长度后面的元素。 + * + * @Auther: zhunn + * @Date: 2020/10/10 16:40 + * @Description: 移除元素,双指针法 + */ +public class Solution { + + public int removeElement(int[] nums, int val) { + if (nums == null || nums.length == 0) return 0; + + int i = 0; + for (int j = 0; j < nums.length; j++) { + if (nums[j] != val) { + nums[i] = nums[j]; + i++; + } + } + System.out.println(Arrays.toString(nums)); + return i; + } + + @Test + public void test() { + System.out.println(removeElement(new int[]{1, 1, 2, 2, 3, 5, 6, 7, 8}, 2)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test283/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test283/Solution.java new file mode 100644 index 0000000..6296c38 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test283/Solution.java @@ -0,0 +1,53 @@ +package com.chen.algorithm.znn.array.test283; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * 283. 移动零 + * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 + * 示例: + * 输入: [0,1,0,3,12] + * 输出: [1,3,12,0,0] + * + * @author: zhunn + * @Date: 2019-11-03 18:44 + * @Description: 移动0至末尾 + */ +public class Solution { + + /** + * @param nums + * @return + */ + public int[] moveZeroes(int[] nums) { + if (nums == null || nums.length == 0) { + return nums; + } + + int i = 0; + for (int j = 0; j < nums.length; j++) { + if (nums[j] != 0) { + nums[i] = nums[j]; + i++; + } + } + + for (int k = i; k < nums.length; k++) { + nums[k] = 0; + } + return nums; + } + + @Test + public void testCase() { + + int[] n = {0, 1, 0, 3, 12}; + moveZeroes(n); + System.out.println(Arrays.toString(n)); + + + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test287/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test287/Solution.java new file mode 100644 index 0000000..ee21fdd --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test287/Solution.java @@ -0,0 +1,74 @@ +package com.chen.algorithm.znn.array.test287; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * 287. 寻找重复数 + * 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 + * 示例 1: + * 输入: [1,3,4,2,2] + * 输出: 2 + * 示例 2: + * 输入: [3,1,3,4,2] + * 输出: 3 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 寻找重复数:1-排序迭代;2-快慢指针 + * 将i 指向 nums[i],如果有重复,就会形成环状 + */ +public class Solution { + + /** + * 1-排序迭代 + * + * @param nums + * @return + */ + public int findDuplicate(int[] nums) { + if (nums == null || nums.length == 0) { + return -1; + } + Arrays.sort(nums); + for (int i = 1; i < nums.length; i++) { + if (nums[i] == nums[i - 1]) { + return nums[i]; + } + } + return -1; + } + + /** + * 2-快慢指针 + * + * @param nums + * @return + */ + public int findDuplicate2(int[] nums) { + if (nums == null || nums.length == 0) { + return -1; + } + + int slow = 0, fast = 0; + do { + slow = nums[slow]; + fast = nums[nums[fast]]; + } while (slow != fast); + + slow = 0; + while (slow != fast) { + slow = nums[slow]; + fast = nums[fast]; + } + return slow; + } + + @Test + public void test() { + int[] nums = {1, 6, 7, 7, 7, 2, 3, 5}; + System.out.println(findDuplicate(nums)); + System.out.println(findDuplicate2(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/study/test34/Solution4.java b/src/main/java/com/chen/algorithm/znn/array/test34/Solution.java similarity index 78% rename from src/main/java/com/chen/algorithm/study/test34/Solution4.java rename to src/main/java/com/chen/algorithm/znn/array/test34/Solution.java index bf8ea9c..56076e3 100644 --- a/src/main/java/com/chen/algorithm/study/test34/Solution4.java +++ b/src/main/java/com/chen/algorithm/znn/array/test34/Solution.java @@ -1,16 +1,27 @@ -package com.chen.algorithm.study.test34; +package com.chen.algorithm.znn.array.test34; import org.junit.Test; import java.util.Arrays; /** + * 34. 在排序数组中查找元素的第一个和最后一个位置 + * 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 + * 你的算法时间复杂度必须是 O(log n) 级别。 + * 如果数组中不存在目标值,返回 [-1, -1]。 + * 示例 1: + * 输入: nums = [5,7,7,8,8,10], target = 8 + * 输出: [3,4] + * 示例 2: + * 输入: nums = [5,7,7,8,8,10], target = 6 + * 输出: [-1,-1] + * * @Auther: zhunn * @Date: 2020/10/10 17:04 * @Description: 在排序数组中查找元素的第一个和最后一个位置 * (可使用二分查找到第一个与target相等元素和最后一个与target相等元素 方式,返回下标) */ -public class Solution4 { +public class Solution { /** * 当key=array[mid]时, 往左边一个一个逼近,right = mid -1; 返回left diff --git a/src/main/java/com/chen/algorithm/znn/array/test448/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test448/Solution.java new file mode 100644 index 0000000..97233be --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test448/Solution.java @@ -0,0 +1,96 @@ +package com.chen.algorithm.znn.array.test448; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/solution/e-wai-liang-ge-intkong-jian-shi-jian-fu-za-du-jin-/ + * 448. 找到所有数组中消失的数字 + * 给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 + * 找到所有在 [1, n] 范围之间没有出现在数组中的数字。 + * 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。 + * 示例: + * 输入: + * [4,3,2,7,8,2,3,1] + * 输出: + * [5,6] + * + * @author: zhunn + * @Date: 2020-10-04 00:06 + * @@Description: zhunn 找到所有数组中消失的数字:迭代并赋值为负数 + */ +public class Solution { + + + public List findDisappearedNumbers(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + + for (int i = 0; i < nums.length; i++) { + if (nums[i] < 0) { + continue; + } + + int temp = nums[i]; + while (temp > 0) { + int nextIndex = nums[temp - 1]; + nums[temp - 1] = -1; + temp = nextIndex; + } + + } + + List result = new ArrayList<>(); + + for (int i = 0; i < nums.length; i++) { + if (nums[i] > 0) { + result.add(i + 1); + } + } + + return result; + } + + /** + * 官方解法 + * + * @param nums + * @return + */ + public List findDisappearedNumbers1(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + + for (int i = 0; i < nums.length; i++) { + int newIndex = Math.abs(nums[i]) - 1; + if (nums[newIndex] > 0) { + nums[newIndex] *= -1; + } + } + + List result = new ArrayList<>(); + for (int i = 1; i <= nums.length; i++) { + if (nums[i - 1] > 0) { + result.add(i); + } + } + + return result; + } + + + @Test + public void testCase() { + + int[] nums = {4, 3, 2, 7, 8, 2, 3, 1}; + + List list = findDisappearedNumbers(nums); + list.forEach(System.out::println); + + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test56/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test56/Solution.java new file mode 100644 index 0000000..2a8b683 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test56/Solution.java @@ -0,0 +1,104 @@ +package com.chen.algorithm.znn.array.test56; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/merge-intervals/solution/pai-xu-by-powcai/ + * 56. 合并区间 + * 给出一个区间的集合,请合并所有重叠的区间。 + * 示例 1: + * 输入: intervals = [[1,3],[2,6],[8,10],[15,18]] + * 输出: [[1,6],[8,10],[15,18]] + * 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. + * 示例 2: + * 输入: intervals = [[1,4],[4,5]] + * 输出: [[1,5]] + * 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 + * + * @author: zhunn + * @Date: 2020-10-14 00:22 + * @Description: 合并区间 + */ +public class Solution { + + + /** + * 先按首位置进行排序; + *

+ * 接下来,如何判断两个区间是否重叠呢?比如 a = [1,4],b = [2,3] + * 当 a[1] >= b[0] 说明两个区间有重叠. + * 但是如何把这个区间找出来呢? + * 左边位置一定是确定,就是 a[0],而右边位置是 max(a[1], b[1]) + * 所以,我们就能找出整个区间为:[1,4] + * + * @param intervals + * @return + */ + + public int[][] merge(int[][] intervals) { + + List inner = Arrays.asList(intervals); + List newInner = new ArrayList<>(inner); + newInner.sort((o1, o2) -> o1[0] - o2[0]); + + List res = new ArrayList<>(); + + for (int i = 0; i < newInner.size(); ) { + int t = newInner.get(i)[1]; + int j = i + 1; + while (j < newInner.size() && newInner.get(j)[0] <= t) { + t = Math.max(t, newInner.get(j)[1]); + j++; + } + res.add(new int[]{newInner.get(i)[0], t}); + i = j; + } + + int[][] array = new int[res.size()][2]; + + for (int i = 0; i < res.size(); i++) { + array[i][0] = res.get(i)[0]; + array[i][1] = res.get(i)[1]; + } + return array; + } + + public int[][] merge1(int[][] intervals) { + if (intervals == null || intervals.length == 0) { + return null; + } + + //Arrays.sort(intervals, new Comparator() { + // @Override + // public int compare(int[] o1, int[] o2) { + // return o1[0] - o2[0]; + // } + //}); + Arrays.sort(intervals, Comparator.comparingInt(o -> o[0])); + + List merged = new ArrayList<>(); + for (int i = 0; i < intervals.length; i++) { + int L = intervals[i][0], R = intervals[i][1]; + if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) { + merged.add(new int[]{L, R}); + } else { + merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R); // 已经排过序,不需要比对起始值 + } + } + return merged.toArray(new int[merged.size()][]); + } + + @Test + public void testCase() { + + int[][] n = {{1, 3}, {2, 6}, {15, 18}, {8, 10}}; + System.out.println(JSONObject.toJSONString(merge1(n))); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test561/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test561/Solution.java new file mode 100644 index 0000000..6e9190b --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test561/Solution.java @@ -0,0 +1,46 @@ +package com.chen.algorithm.znn.array.test561; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/array-partition-i/solution/shu-zu-chai-fen-i-by-leetcode/ + * 给定长度为2n的整数数组 nums ,你的任务是将这些数分成n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到n 的 min(ai, bi) 总和最大。 + * 返回该 最大总和 。 + * 示例 1: + * 输入:nums = [1,4,3,2] + * 输出:4 + * 解释:所有可能的分法(忽略元素顺序)为: + * 1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3 + * 2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3 + * 3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4 + * 所以最大总和为 4 + * + * @Auther: zhunn + * @Date: 2020/11/5 18:46 + * @Description: 数组拆分 I + */ +public class Solution { + + public int arrayPairSum(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + //初始一个结果 + int sum = 0; + //对数组进行升序排序 + Arrays.sort(nums); + //循环数组,选择索引为0、2、4、6、8...这样可以保证获取每两个元素的最小值 + for (int i = 0; i < nums.length; i += 2) { + sum += nums[i]; //将最小值加起来 + } + return sum; + } + + @Test + public void test() { + int[] nums = new int[]{1, 4, 3, 2}; + System.out.println(arrayPairSum(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test581/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test581/Solution.java new file mode 100644 index 0000000..bfff8c5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test581/Solution.java @@ -0,0 +1,70 @@ +package com.chen.algorithm.znn.array.test581; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/solution/zui-duan-wu-xu-lian-xu-zi-shu-zu-by-leetcode/ + * 581. 最短无序连续子数组 + * 给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 + * 你找到的子数组应是最短的,请输出它的长度。 + * 示例 1: + * 输入: [2, 6, 4, 8, 10, 9, 15] + * 输出: 5 + * 解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。 + * + * @author: zhunn + * @Date: 2020-10-07 23:34 + * @Description: 最短无序连续子数组:排序对比原数组 + */ +public class Solution { + + + public int findUnsortedSubarray(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int[] snums = nums.clone(); + Arrays.sort(snums); + int start = snums.length, end = 0; + + for (int i = 0; i < snums.length; i++) { + if (snums[i] == nums[i]) { + continue; + } + start = Math.min(start, i); + end = Math.max(end, i); + } + return (end - start >= 0 ? end - start + 1 : 0); + } + + public int findUnsortedSubarray2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int[] snums = nums.clone(); + Arrays.sort(snums); + int start = snums.length, end = 0; + + for (int i = 0; i < snums.length; i++) { + if (snums[i] != nums[i]) { + start = Math.min(start, i); + end = Math.max(end, i); + } + } + return (end - start) < 0 ? 0 : end - start + 1; + } + + @Test + public void testCase() { + int[] n = {2, 6, 4, 8, 10, 9, 15}; + + System.out.println(findUnsortedSubarray(n)); + System.out.println(findUnsortedSubarray2(n)); + } + + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test674/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test674/Solution.java new file mode 100644 index 0000000..aa60736 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test674/Solution.java @@ -0,0 +1,49 @@ +package com.chen.algorithm.znn.array.test674; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/solution/zui-chang-lian-xu-di-zeng-xu-lie-by-leetcode/ + * 674. 最长连续递增序列 + * 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 + * 连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。 + * 示例 1: + * 输入:nums = [1,3,5,4,7] + * 输出:3 + * 解释:最长连续递增序列是 [1,3,5], 长度为3。 + * 尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 + * 示例 2: + * 输入:nums = [2,2,2,2,2] + * 输出:1 + * 解释:最长连续递增序列是 [2], 长度为1。 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 最长连续递增序列:滑动窗口 + */ +public class Solution { + + public int findLengthOfLCIS(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int res = 0, start = 0; + + for (int i = 0; i < nums.length; i++) { + if (i > 0 && nums[i - 1] >= nums[i]) { + start = i; + } + res = Math.max(res, i - start + 1); + } + return res; + } + + @Test + public void test() { + int[] nums = {1, 3, 5, 4, 7}; + int[] nums2 = {2, 2, 2, 2, 2}; + System.out.println(findLengthOfLCIS(nums)); + System.out.println(findLengthOfLCIS(nums2)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test69/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test69/Solution.java new file mode 100644 index 0000000..3961ff1 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test69/Solution.java @@ -0,0 +1,53 @@ +package com.chen.algorithm.znn.array.test69; + +import org.junit.Test; + +/** + * 69. x 的平方根 + * 实现 int sqrt(int x) 函数。 + * 计算并返回 x 的平方根,其中 x 是非负整数。 + * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 + * 示例 1: + * 输入: 4 + * 输出: 2 + * 示例 2: + * 输入: 8 + * 输出: 2 + * 说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 + * + * @author: zhunn + * @Date: 2020-10-03 09:39 + * @Description: x的平方根,舍弃小数 + */ +public class Solution { + + + public int mySqrt(int x) { + + if (x < 2) { + return x; + } + + int left = 1, right = x / 2, mid; + long result; + + while (right >= left) { + mid = (right - left) / 2 + left; + result = (long) mid * mid; + if (result > x) { + right = mid - 1; + } else if (result < x) { + left = mid + 1; + } else { + return mid; + } + } + return right; + } + + @Test + public void test() { + System.out.println(mySqrt(2147395599)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test704/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test704/Solution.java new file mode 100644 index 0000000..1fce82d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test704/Solution.java @@ -0,0 +1,211 @@ +package com.chen.algorithm.znn.array.test704; + +import org.junit.Test; + +/** + * 704. 二分查找 + * 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 + * 示例 1: + * 输入: nums = [-1,0,3,5,9,12], target = 9 + * 输出: 4 + * 解释: 9 出现在 nums 中并且下标为 4 + * 示例 2: + * 输入: nums = [-1,0,3,5,9,12], target = 2 + * 输出: -1 + * 解释: 2 不存在 nums 中因此返回 -1 + * + * @Auther: zhunn + * @Date: 2020/10/9 15:31 + * @Description: 二分查找及其变种 https://blog.csdn.net/Lngxling/article/details/78217619 + */ +public class Solution { + + /** + * 适用于升序数组,可做相应调整适用降序数组 + */ + public int bsearch(int[] array, int target) { + if (array == null || array.length == 0 + /*|| target < array[0] || target > array[array.length - 1]*/) { + return -1; + } + + int left = 0; + int right = array.length - 1; + //这里必须是 >=,切记勿遗漏 = + while (right >= left) { + // 当start=Integer.MAX_VALUE时,给它加个1都会溢出。安全的写法是:mid = start + (end-start)/2,但是会造成死循环,弃用 + //int mid = min + (max - min) >> 1; + int mid = (left + right) >> 1; + if (target == array[mid]) { + return mid; + } + if (target < array[mid]) { + right = mid - 1; + } + if (target > array[mid]) { + left = mid + 1; + } + } + return -1; + } + + /** + * 查找第一个与target相等的元素 + * 当key=array[mid]时, 往左边一个一个逼近,right = mid -1; 返回left + */ + public int bsearch1(int[] array, int target) { + if (array == null || array.length == 0 + /*|| target < array[0] || target > array[array.length - 1]*/) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] >= target) { + right = mid - 1; + } else { + left = mid + 1; + } + } + + if (left < array.length && array[left] == target) { + return left; + } + return -1; + + } + + /** + * 查找最后一个与target相等的元素 + * 当key=array[mid]时, 往右边一个一个逼近,left = mid + 1; 返回right + * + * @return + */ + public int bsearch2(int[] array, int target) { + if (array == null || array.length == 0 /*|| target < array[0] || target > array[array.length - 1]*/) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] <= target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + if (right >= 0 && array[right] == target) { + return right; + } + return -1; + } + + // 二分查找变种说明 + //public void test(int[] array, int target){ + // + // int left = 0; + // int right = array.length - 1; + // // 这里必须是 <= + // while (left <= right) { + // int mid = (left + right) / 2; + // if (array[mid] ? key) { + // //... right = mid - 1; + // } + // else { + // // ... left = mid + 1; + // } + // } + // return xxx; + //} + + + /** + * 查找第一个等于或者大于key的元素 + */ + public int bsearch3(int[] array, int target) { + if (array == null || array.length == 0) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] >= target) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + /** + * 查找第一个大于key的元素 + */ + public int bsearch4(int[] array, int target) { + if (array == null || array.length == 0) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] > target) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + /** + * 查找最后一个等于或者小于key的元素 + */ + public int bsearch5(int[] array, int target) { + if (array == null || array.length == 0) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] <= target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return right; + } + + /** + * 查找最后一个小于key的元素 + */ + public int bsearch6(int[] array, int target) { + if (array == null || array.length == 0) { + return -1; + } + int left = 0; + int right = array.length - 1; + while (right >= left) { + int mid = (left + right) >> 1; + if (array[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return right; + } + + @Test + public void test() { + int[] array = {1, 1, 3, 6, 6, 6, 7, 9, 17, 17}; + int index = bsearch6(array, 6); + System.out.println(index); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/array/test88/Solution.java b/src/main/java/com/chen/algorithm/znn/array/test88/Solution.java new file mode 100644 index 0000000..ae7229e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/array/test88/Solution.java @@ -0,0 +1,50 @@ +package com.chen.algorithm.znn.array.test88; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/merge-sorted-array/solution/leetcode88-he-bing-liang-ge-you-xu-shu-zu-by-ma-xi/ + * 88. 合并两个有序数组 + * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 + * 说明: + * 初始化 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] + * + * @author: zhunn + * @Date: 2020-10-20 11:33 + * @Description: 合并两个有序数组 + */ +public class Solution { + + public void merge(int[] nums1, int m, int[] nums2, int n) { + + int p1 = m - 1, p2 = n - 1, p3 = m + n - 1; + while (p2 >= 0) { + if (p1 >= 0 && nums1[p1] > nums2[p2]) { + nums1[p3--] = nums1[p1--]; + } else { + nums1[p3--] = nums2[p2--]; + } + } + } + + + @Test + public void testCase() { + //int[] m = {1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0}; + //int[] n = {0, 2, 5, 6, 8, 9, 25}; + //merge(m, 4, n, n.length); + int[] m = {1, 2, 3, 0, 0, 0}; + int[] n = {2, 5, 6}; + merge(m, 3, n, n.length); + System.out.println(Arrays.toString(m)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/Test.java b/src/main/java/com/chen/algorithm/znn/backtrack/Test.java new file mode 100644 index 0000000..a007d19 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/Test.java @@ -0,0 +1,24 @@ +package com.chen.algorithm.znn.backtrack; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 回溯相关 + */ +public class Test { + + /** + * 回溯算法的框架: + * + * result = [] + * def backtrack(路径, 选择列表): + * if 满足结束条件: + * result.add(路径) + * return + * + * for 选择 in 选择列表: + * 做选择 + * backtrack(路径, 选择列表) + * 撤销选择 + */ +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/ZeroOneBag.java b/src/main/java/com/chen/algorithm/znn/backtrack/ZeroOneBag.java new file mode 100644 index 0000000..b9e2888 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/ZeroOneBag.java @@ -0,0 +1,122 @@ +package com.chen.algorithm.znn.backtrack; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/ + * 作为「0-1 背包问题」,它的特点是:「每个数只能用一次」。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想,特别重要。 + * 在实际生活中,我们也是这样做的,一个一个地尝试把候选物品放入「背包」,通过比较得出一个物品要不要拿走。 + * + * 具体做法是:画一个 len 行,target + 1 列的表格。这里 len 是物品的个数,target 是背包的容量。len 行表示一个一个物品考虑,target + 1多出来的那 1 列,表示背包容量从 0 开始考虑。很多时候,我们需要考虑这个容量为 0 的数值。 + * + * 我们有一个背包,背包总的承载重量是Wkg。现在我们有n个物品,每个物品的重量不等,并且不可分割。我们现在期望选择几件物品, + * 装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大? + * + * @Auther: zhunn + * @Date: 2020/11/3 14:53 + * @Description: 0-1背包问题:1-回溯;2-动态规划 + */ +public class ZeroOneBag { + + private int maxW = Integer.MAX_VALUE; + private int[] items = {2, 2, 4, 6, 3}; // 物品重量 + private int n = 5; // 物品个数 + private int w = 9; // 背包承受的最大重量 + + /** + * 1-回溯 + * // 假设背包可承受重量100,物品个数10,物品重量存储在数组a中,那可以这样调用函数: + * // f(0, 0, a, 10, 100) + *

+ * 我们可以把物品依次排列,整个问题就分解为了 n 个阶段,每个阶段对应一个物品怎么选择。 + * 先对第一个物品进行处理,选择装进去或者不装进去,然后再递归地处理剩下的物品。 + * + * @param i 表示考察到哪个物品了 + * @param cw 表示当前已经装进去的物品的重量和 + * items 表示每个物品的重量 + * n 表示物品个数 + * w 背包重量 + */ + public void bag1(int i, int cw) { + // cw==w表示装满了;i==n表示已经考察完所有的物品 + if (cw == w || i == n) { + if (cw > maxW) { + maxW = cw; + } + return; + } + bag1(i + 1, cw); + // 已经超过可以背包承受的重量的时候,就不要再装了 + if (cw + items[i] <= w) { + bag1(i + 1, cw + items[i]); + } + } + + private boolean[][] memo = new boolean[5][10]; + + public void bag2(int i, int cw) { + if (cw == w || i == n) { // cw==w表示装满了,i==n表示物品都考察完了 + if (cw > maxW) { + maxW = cw; + } + return; + } + if (memo[i][cw]) { + return; // 重复状态 + } + memo[i][cw] = true; // 记录(i, cw)这个状态 + bag2(i + 1, cw); // 选择不装第i个物品 + if (cw + items[i] <= w) { + bag2(i + 1, cw + items[i]); // 选择装第i个物品 + } + } + + /** + * 2-动态规划 + * 我们把整个求解过程分为n个阶段,每个阶段会决策一个物品是否放到背包中。每个物品决策(放入或者不放入背包)完之后,背包中的物品的重量会有多种情况,也就是说,会达到多种不同的状态,对应到递归树中,就是有很多不同的节点。 + * 我们把每一层重复的状态(节点)合并,只记录不同的状态,然后基于上一层的状态集合,来推导下一层的状态集合。我们可以通过合并每一层重复的状态,这样就保证每一层不同状态的个数都不会超过w个(w表示背包的承载重量),也就是例子中的9。于是,我们就成功避免了每层状态个数的指数级增长。 + * 我们用一个二维数组states[n][w+1],来记录每层可以达到的不同状态。 + * 第0个(下标从0开始编号)物品的重量是2,要么装入背包,要么不装入背包,决策完之后,会对应背包的两种状态,背包中物品的总重量是0或者2。我们用states[0][0]=true和states[0][2]=true来表示这两种状态。 + * 第1个物品的重量也是2,基于之前的背包状态,在这个物品决策完之后,不同的状态有3个,背包中物品总重量分别是0(0+0),2(0+2 or 2+0),4(2+2)。我们用states[1][0]=true,states[1][2]=true,states[1][4]=true来表示这三种状态。 + * 以此类推,直到考察完所有的物品后,整个states状态数组就都计算好了。我把整个计算的过程画了出来,你可以看看。图中0表示false,1表示true。我们只需要在最后一层,找一个值为true的最接近w(这里是9)的值,就是背包中物品总重量的最大值。 + * + * @param weight weight:物品重量(2、2、4、6、3) + * @param n n:物品个数(0-5) + * @param w w:背包可承载重量(0-9) + * @return + * 状态定义:第一维n表示放了几个物品,第二维w表示放或放入物品后,背包里的总重量 + * 状态转移方程: + * 思路:物品一个一个尝试,容量一点一点尝试,每个物品分类讨论的标准是:选与不选。 + */ + public int knapsack(int[] weight, int n, int w) { + // 状态定义:第一维表示放了几个物品,第二维表示放或放入物品后,背包里的总重量 + boolean[][] states = new boolean[n][w + 1]; //默认值false + states[0][0] = true; //初始化,第一行的数据要特殊处理,可以利用哨兵优化 + states[0][weight[0]] = true; + + for (int i = 1; i < n; i++) { // 动态规划状态转移 + for (int j = 0; j <= w; j++) { // 不把第i个物品放入背包 + if (states[i - 1][j]) { + states[i][j] = states[i - 1][j]; + } + } + for (int j = 0; j + weight[i] <= w; j++) { //把第i个物品放入背包 + if (states[i - 1][j]) { + states[i][j + weight[i]] = true; + } + } + } + for (int i = w; i >= 0; i--) { // 输出结果 + if (states[n - 1][i]) { + return i; + } + } + return 0; + } + + @Test + public void test() { + int cw = knapsack(items, n, w); + System.out.println(cw); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test36/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test36/Solution.java new file mode 100644 index 0000000..475f0aa --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test36/Solution.java @@ -0,0 +1,162 @@ +package com.chen.algorithm.znn.backtrack.test36; + +import org.junit.Test; + +import java.util.HashSet; + +/** + * https://leetcode-cn.com/problems/valid-sudoku/solution/javawei-yun-suan-1ms-100-li-jie-fang-ge-suo-yin-by/ + * 36. 有效的数独 + * 判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。 + * 数字 1-9 在每一行只能出现一次。 + * 数字 1-9 在每一列只能出现一次。 + * 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 + * 示例 1: + * 输入: + * [ + * ["5","3",".",".","7",".",".",".","."], + * ["6",".",".","1","9","5",".",".","."], + * [".","9","8",".",".",".",".","6","."], + * ["8",".",".",".","6",".",".",".","3"], + * ["4",".",".","8",".","3",".",".","1"], + * ["7",".",".",".","2",".",".",".","6"], + * [".","6",".",".",".",".","2","8","."], + * [".",".",".","4","1","9",".",".","5"], + * [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: true + * 示例 2: + * 输入: + * [ + * ["8","3",".",".","7",".",".",".","."], + * ["6",".",".","1","9","5",".",".","."], + * [".","9","8",".",".",".",".","6","."], + * ["8",".",".",".","6",".",".",".","3"], + * ["4",".",".","8",".","3",".",".","1"], + * ["7",".",".",".","2",".",".",".","6"], + * [".","6",".",".",".",".","2","8","."], + * [".",".",".","4","1","9",".",".","5"], + * [".",".",".",".","8",".",".","7","9"] + * ] + * 输出: false + * 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 + * 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。 + * + * @Auther: zhunn + * @Date: 2020/11/3 13:54 + * @Description: 有效的数独:1-哈希数组(没有空间要求也可以);2-位运算数组;3-二维数组(推荐) + */ +public class Solution { + + /** + * 1-哈希数组 + * + * @param board + * @return + */ + public boolean isValidSudoku(char[][] board) { + HashSet[] rows = new HashSet[9]; + HashSet[] cols = new HashSet[9]; + HashSet[] boxes = new HashSet[9]; + for (int i = 0; i < 9; i++) { + rows[i] = new HashSet<>(); + cols[i] = new HashSet<>(); + boxes[i] = new HashSet<>(); + } + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] == '.') + continue; + int tmp = board[i][j] - '0'; + if (rows[i].contains(tmp) //本行中已有数字 + || cols[j].contains(tmp) //本列中已有数字 + || boxes[(i / 3) * 3 + j / 3].contains(tmp)) //本方格中已有数字 + return false; + rows[i].add(tmp); //添加到本行 + cols[j].add(tmp); //添加到本列 + boxes[(i / 3) * 3 + j / 3].add(tmp); //添加到本方格 + } + } + return true; + } + + /** + * 2-位运算数组 + */ + public boolean isValidSudoku2(char[][] board) { + int[] rows = new int[9]; //行的位运算数组 + int[] cols = new int[9]; //列的位运算数组 + int[] boxes = new int[9]; //方格的位运算数组 + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] == '.') + continue; + int tmp = board[i][j] - '0'; + int boxIndex = i / 3 * 3 + j / 3; + if ((rows[i] >> tmp & 1) == 1 //rows[i] >> tmp & 1取出第i行的tmp数字,看是否已填,如果等于1,代表已填 + || (cols[j] >> tmp & 1) == 1 //cols[j] >> tmp & 1取出第j列的tmp数字,看是否已填,如果等于1,代表已填 + || (boxes[boxIndex] >> tmp & 1) == 1) //boxes[boxIndex] >> tmp & 1取出第boxIndex个方格的tmp数字,看是否已填,如果等于1,代表已填 + return false; + rows[i] = rows[i] | (1 << tmp); //将tmp数字加入到第i行的位运算数组 + cols[j] = cols[j] | (1 << tmp); //将tmp数字加入到第j列的位运算数组 + boxes[boxIndex] = boxes[boxIndex] | (1 << tmp); //将tmp数字加入到第boxIndex个方格的位运算数组 + } + } + return true; + } + + /** + * 二维数组,第一维标识位置,第二维标识1-9数字 + * + * @param board + * @return + */ + public boolean isValidSudoku1(char[][] board) { + int[][] row = new int[9][10]; // 哈希表存储每一行的每个数是否出现过,默认初始情况下,每一行每一个数都没有出现过 + // 整个board有9行,第二维的维数10是为了让下标有9,和数独中的数字9对应。 + int[][] col = new int[9][10]; // 存储每一列的每个数是否出现过,默认初始情况下,每一列的每一个数都没有出现过 + int[][] box = new int[9][10]; // 存储每一个box的每个数是否出现过,默认初始情况下,在每个box中,每个数都没有出现过。整个board有9个box。 + for (int i = 0; i < 9; i++) { //行 + for (int j = 0; j < 9; j++) {//列 + if (board[i][j] == '.') { + continue; + } + + // 遍历到第i行第j列的那个数,我们要判断这个数在其所在的行有没有出现过, + // 同时判断这个数在其所在的列有没有出现过 + // 同时判断这个数在其所在的box中有没有出现过 + int curNum = board[i][j] - '0'; + if (row[i][curNum] == 1) { + return false; + } + if (col[j][curNum] == 1) { + return false; + } + if (box[j / 3 + (i / 3) * 3][curNum] == 1) { + return false; + } + row[i][curNum] = 1; // 之前都没出现过,现在出现了,就给它置为1,下次再遇见就能够直接返回false了。 + col[j][curNum] = 1; + box[j / 3 + (i / 3) * 3][curNum] = 1; + } + } + return true; + } + + @Test + public void test() { + char[][] board = { + {'5', '3', '.', '.', '7', '.', '.', '.', '.'}, + {'6', '.', '.', '1', '9', '5', '.', '.', '.'}, + {'.', '9', '8', '.', '.', '.', '.', '6', '.'}, + {'8', '.', '.', '.', '6', '.', '.', '.', '3'}, + {'4', '.', '.', '8', '.', '3', '.', '.', '1'}, + {'7', '.', '.', '.', '2', '.', '.', '.', '6'}, + {'.', '6', '.', '.', '.', '.', '2', '8', '.'}, + {'.', '.', '.', '4', '1', '9', '.', '.', '5'}, + {'.', '.', '.', '.', '8', '.', '.', '7', '9'} + }; + boolean res = isValidSudoku(board); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test39/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test39/Solution.java new file mode 100644 index 0000000..ff2c5ae --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test39/Solution.java @@ -0,0 +1,94 @@ +package com.chen.algorithm.znn.backtrack.test39; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/combination-sum/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-2/ + * 39. 组合总和 + * 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 + * candidates 中的数字可以无限制重复被选取。 + * 说明: + * 所有数字(包括 target)都是正整数。 + * 解集不能包含重复的组合。 + * 示例 1: + * 输入:candidates = [2,3,6,7], target = 7, + * 所求解集为: + * [ + * [7], + * [2,2,3] + * ] + * 示例 2: + * 输入:candidates = [2,3,5], target = 8, + * 所求解集为: + * [ + * [2,2,2,2], + * [2,3,3], + * [3,5] + * ] + * + * @Auther: zhunn + * @Date: 2020/11/3 15:27 + * @Description: 组合总和 + */ +public class Solution { + + public List> combinationSum(int[] candidates, int target) { + if (candidates == null || candidates.length == 0) { + return null; + } + List> res = new ArrayList<>(); + // 排序是剪枝的前提 + //Arrays.sort(candidates); + backtrack(0, target, candidates, new ArrayList<>(), res); + return res; + } + + private void backtrack(int start, int target, int[] candidates, List curList, List> res) { + if (target == 0) { + res.add(new ArrayList<>(curList)); + return; + } + for (int i = start; i < candidates.length; i++) { + if (candidates[i] > target) { + continue; + } + curList.add(candidates[i]); + //System.out.println("递归之前 => " + curList + ",剩余 = " + (target - candidates[i])); + backtrack(i, target - candidates[i], candidates, curList, res); + curList.remove(curList.size() - 1); + //System.out.println("递归之后 => " + curList); + } + } + //private void backtrack(int start, int target, int[] candidates, List curList, List> res) { + // // 由于进入更深层的时候,小于0的部分被剪枝,因此递归终止条件值只判断等于0的情况 + // //if (target < 0) { + // // return; + // //} + // if (target == 0) { + // res.add(new ArrayList<>(curList)); + // return; + // } + // // 重点理解这里从start开始搜索的语意 + // for (int i = start; i < candidates.length; i++) { + // if (candidates[i] > target) { // 重点理解这里剪枝,前提是候选数组已经有序 + // continue; + // } + // curList.add(candidates[i]); + // backtrack(i, target - candidates[i], candidates, curList, res);// 注意每个元素可以重复使用,下一轮搜索的起点依然是 i + // curList.remove(curList.size() - 1); //状态重置 + // } + //} + + @Test + public void test() { + int[] candidates = {2, 3, 6, 7}; + int target = 7; + + System.out.println(JSONObject.toJSONString(combinationSum(candidates, target))); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test40/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test40/Solution.java new file mode 100644 index 0000000..e075d89 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test40/Solution.java @@ -0,0 +1,85 @@ +package com.chen.algorithm.znn.backtrack.test40; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/combination-sum-ii/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3/ + * 40. 组合总和 II + * 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 + * candidates 中的每个数字在每个组合中只能使用一次。 + * 说明: + * 所有数字(包括目标数)都是正整数。 + * 解集不能包含重复的组合。 + * 示例 1: + * 输入: candidates = [10,1,2,7,6,1,5], target = 8, + * 所求解集为: + * [ + * [1, 7], + * [1, 2, 5], + * [2, 6], + * [1, 1, 6] + * ] + * 示例 2: + *

+ * 输入: candidates = [2,5,2,1,2], target = 5, + * 所求解集为: + * [ + * [1,2,2], + * [5] + * ] + * + * @Auther: zhunn + * @Date: 2020/11/3 15:28 + * @Description: 组合总和 II + */ +public class Solution { + + public List> combinationSum2(int[] nums, int target) { + if (nums == null || nums.length == 0) { + return null; + } + // 排序是剪枝的前提 + Arrays.sort(nums); + List> res = new ArrayList<>(); + backtrack(0, target, nums, new ArrayList<>(), res); + return res; + } + + private void backtrack(int start, int target, int[] nums, List curList, List> res) { + // 由于进入更深层的时候,小于0的部分被剪枝,因此递归终止条件值只判断等于0的情况 + //if (target < 0) { + // return; + //} + if (target == 0) { + res.add(new ArrayList<>(curList)); + return; + } + for (int i = start; i < nums.length; i++) { + if (nums[i] > target) { + break; + } + if (i > start && nums[i - 1] == nums[i]) { + continue; + } + curList.add(nums[i]); + backtrack(i + 1, target - nums[i], nums, curList, res); + curList.remove(curList.size() - 1); + } + } + + @Test + public void testCase() { + int[] nums = {10, 1, 2, 7, 6, 1, 5}; + int target = 8; + + List> res = combinationSum2(nums, target); + System.out.println(JSONObject.toJSONString(res)); + + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test46/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test46/Solution.java new file mode 100644 index 0000000..ebecb1a --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test46/Solution.java @@ -0,0 +1,65 @@ +package com.chen.algorithm.znn.backtrack.test46; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/ + * 46. 全排列 + * 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 + * 示例: + * 输入: [1,2,3] + * 输出: + * [ + * [1,2,3], + * [1,3,2], + * [2,1,3], + * [2,3,1], + * [3,1,2], + * [3,2,1] + * ] + * + * @Auther: zhunn + * @Date: 2020/11/3 17:52 + * @Description: 全排列 + */ +public class Solution { + + public List> permute(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + List> res = new ArrayList<>(); + boolean[] used = new boolean[nums.length]; + backtrack(0, nums.length, nums, used, new ArrayList<>(), res); + return res; + } + + private void backtrack(int depth, int len, int[] nums, boolean[] used, List curList, List> res) { + if (depth == len) { + res.add(new ArrayList<>(curList)); + return; + } + for (int i = 0; i < len; i++) { + if (used[i]) { + continue; + } + + curList.add(nums[i]); + used[i] = true; + backtrack(depth + 1, len, nums, used, curList, res); + // 注意:这里是状态重置,是从深层结点回到浅层结点的过程,代码在形式上和递归之前是对称的 + curList.remove(curList.size() - 1); + used[i] = false; + } + } + + @Test + public void testCase() { + int[] nums = {1, 2, 3}; + System.out.println(JSONObject.toJSONString(permute(nums))); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test47/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test47/Solution.java new file mode 100644 index 0000000..f74da78 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test47/Solution.java @@ -0,0 +1,104 @@ +package com.chen.algorithm.znn.backtrack.test47; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.*; + +/** + * 47. 全排列 II + * 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 + * 示例 1: + * 输入:nums = [1,1,2] + * 输出: + * [[1,1,2], + * [1,2,1], + * [2,1,1]] + * 示例 2: + * 输入:nums = [1,2,3] + * 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] + * + * @Auther: zhunn + * @Date: 2020/11/3 19:39 + * @Description: 全排列 II + */ +public class Solution { + + public List> permuteUnique(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + + List> res = new ArrayList<>(); + // 排序(升序或者降序都可以),排序是剪枝的前提 + Arrays.sort(nums); + + boolean[] used = new boolean[nums.length]; + backtrack(0, nums.length, nums, used, new ArrayList<>(), res); + return res; + } + + private void backtrack(int depth, int len, int[] nums, boolean[] used, List curList, List> res) { + if (depth == len) { + res.add(new ArrayList<>(curList)); + return; + } + + for (int i = 0; i < len; i++) { + if (used[i]) { + continue; + } + + // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义 + // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择 + if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { + continue; + } + + curList.add(nums[i]); + used[i] = true; + backtrack(depth + 1, len, nums, used, curList, res); + // 回溯部分的代码,和 dfs 之前的代码是对称的 + used[i] = false; + curList.remove(curList.size() - 1); + } + } + + @Test + public void testCase() { + int[] nums = {1, 1, 2}; + System.out.println(JSONObject.toJSONString(permuteUnique(nums))); + System.out.println(JSONObject.toJSONString(permuteUnique2(nums))); + } + + public List> permuteUnique2(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + Arrays.sort(nums); + List> res = new ArrayList<>(); + boolean[] used = new boolean[nums.length]; + dfs(0, nums.length, nums, used, new ArrayList<>(), res); + return res; + } + + private void dfs(int depth, int len, int[] nums, boolean[] used, List curList, List> res) { + if (depth == len) { + res.add(new ArrayList<>(curList)); + return; + } + for (int i = 0; i < len; i++) { + if (used[i]) { + continue; + } + if (i > 0 && nums[i - 1] == nums[i] && !used[i - 1]) { + continue; + } + used[i] = true; + curList.add(nums[i]); + dfs(depth + 1, len, nums, used, curList, res); + curList.remove(curList.size() - 1); + used[i] = false; + } + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test51/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test51/Solution.java new file mode 100644 index 0000000..8f39ebc --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test51/Solution.java @@ -0,0 +1,103 @@ +package com.chen.algorithm.znn.backtrack.test51; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/n-queens/solution/gen-ju-di-46-ti-quan-pai-lie-de-hui-su-suan-fa-si-/ + * 51. N 皇后 + * n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + * 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 + * 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 + * 示例: + * 输入:4 + * 输出:[ + * [".Q..", // 解法 1 + * "...Q", + * "Q...", + * "..Q."], + *

+ * ["..Q.", // 解法 2 + * "Q...", + * "...Q", + * ".Q.."] + * ] + * 解释: 4 皇后问题存在两个不同的解法。 + * + * @Auther: zhunn + * @Date: 2020/11/02 21:23 + * @Description: N皇后问题 + */ +public class Solution { + + private boolean[] col; // 列 + private boolean[] master; // 主对角线 + private boolean[] slave; //副对角线 + private int n; // 行数 + private List> res; //结果集 + + public List> solveNQueues(int n) { + this.n = n; + res = new ArrayList<>(); + if (n == 0) { + return res; + } + col = new boolean[n]; + master = new boolean[2 * n - 1]; + slave = new boolean[2 * n - 1]; + Stack stack = new Stack<>(); + helper(0, stack); + return res; + } + + private void helper(int row, Stack stack) { + if (row == n) { + List board = convert2board(stack, n); + res.add(board); + return; + } + + // 针对每一列,尝试是否可以放置 + for (int i = 0; i < n; i++) { + if (col[i] || master[row + i] || slave[row - i + n - 1]) { + continue; + } + stack.push(i); + col[i] = true; + master[row + i] = true; + slave[row - i + n - 1] = true; + helper(row + 1, stack); + slave[row - i + n - 1] = false; + master[row + i] = false; + col[i] = false; + stack.pop(); + } + } + + private List convert2board(Stack stack, int n) { + List board = new ArrayList<>(); + for (Integer num : stack) { + char[] row = new char[n]; + for (int i = 0; i < n; i++) { + row[i] = '*'; + } + row[num] = 'Q'; + board.add(row.toString()); + } + return board; + } + + @Test + public void testCase() { + List> res = solveNQueues(4); + + for (List re : res) { + System.out.println("====" + JSONObject.toJSONString(re)); + } + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test78/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test78/Solution.java new file mode 100644 index 0000000..1e54951 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test78/Solution.java @@ -0,0 +1,55 @@ +package com.chen.algorithm.znn.backtrack.test78; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/subsets/solution/hui-su-suan-fa-by-powcai-5/ + * 78. 子集 + * 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 + * 说明:解集不能包含重复的子集。 + * 示例: + * 输入: nums = [1,2,3] + * 输出: + * [ + * [3], + * [1], + * [2], + * [1,2,3], + * [1,3], + * [2,3], + * [1,2], + * [] + * ] + * + * @Auther: zhunn + * @Date: 2020/11/3 19:23 + * @Description: 子集 + */ +public class Solution { + + public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + backtrack(0, nums, new ArrayList<>(), res); + return res; + } + + private void backtrack(int start, int[] nums, List curList, List> res) { + res.add(new ArrayList<>(curList)); + for (int i = start; i < nums.length; i++) { + curList.add(nums[i]); + backtrack(i + 1, nums, curList, res); + curList.remove(curList.size() - 1); + } + } + + @Test + public void testCase() { + int[] n = {1, 2, 3}; + System.out.println(subsets(n)); + + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test79/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test79/Solution.java new file mode 100644 index 0000000..98eb479 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test79/Solution.java @@ -0,0 +1,80 @@ +package com.chen.algorithm.znn.backtrack.test79; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/word-search/solution/zai-er-wei-ping-mian-shang-shi-yong-hui-su-fa-pyth/ + * 79. 单词搜索 + * 给定一个二维网格和一个单词,找出该单词是否存在于网格中。 + * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 + * 示例: + * board = + * [ + * ['A','B','C','E'], + * ['S','F','C','S'], + * ['A','D','E','E'] + * ] + * 给定 word = "ABCCED", 返回 true + * 给定 word = "SEE", 返回 true + * 给定 word = "ABCB", 返回 false + * + * @Auther: zhunn + * @Date: 2020/11/3 18:31 + * @Description: 单词搜索 + */ +public class Solution { + + private boolean[][] visited; + + public boolean exist(char[][] board, String word) { + if (board == null || board.length == 0) { + return false; + } + int m = board.length; + int n = board[0].length; + visited = new boolean[m][n]; + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (dfs(0, word, board, i, j)) { + return true; + } + } + } + return false; + } + + private boolean dfs(int index, String word, char[][] board, int x, int y) { + if (x < 0 || y < 0 || x >= board.length || y >= board[0].length //越界 + || word.charAt(index) != board[x][y] // 字符不相等 + || visited[x][y]) { // 被访问过 + return false; + } + if (index == word.length() - 1) { // 已验证到单词结尾 + return true; + } + + visited[x][y] = true; + if (dfs(index + 1, word, board, x - 1, y) || dfs(index + 1, word, board, x + 1, y) + || dfs(index + 1, word, board, x, y - 1) || dfs(index + 1, word, board, x, y + 1)) { + return true; + } + visited[x][y] = false; + return false; + } + + @Test + public void test() { + char[][] board = + { + {'A', 'B', 'C', 'E'}, + {'S', 'F', 'C', 'S'}, + {'A', 'D', 'E', 'E'} + }; + + String word = "ABCCED"; + + System.out.println(exist(board, word)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/backtrack/test90/Solution.java b/src/main/java/com/chen/algorithm/znn/backtrack/test90/Solution.java new file mode 100644 index 0000000..64d90e6 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/backtrack/test90/Solution.java @@ -0,0 +1,56 @@ +package com.chen.algorithm.znn.backtrack.test90; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/subsets/solution/hui-su-suan-fa-by-powcai-5/ + * 90. 子集 II + * 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 + * 说明:解集不能包含重复的子集。 + * 示例: + * 输入: [1,2,2] + * 输出: + * [ + * [2], + * [1], + * [1,2,2], + * [2,2], + * [1,2], + * [] + * ] + * + * @Auther: zhunn + * @Date: 2020/11/3 19:41 + * @Description: 子集 II + */ +public class Solution { + public List> subsetsWithDup(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); //排序 + backtrack(0, nums, new ArrayList<>(), res); + return res; + } + + private void backtrack(int start, int[] nums, List curList, List> res) { + res.add(new ArrayList<>(curList)); + for (int i = start; i < nums.length; i++) { + //和上个数字相等就跳过 + if (i > start && nums[i] == nums[i - 1]) { + continue; + } + curList.add(nums[i]); + backtrack(i + 1, nums, curList, res); + curList.remove(curList.size() - 1); + } + } + + @Test + public void testCase() { + int[] nums = {1, 2, 2}; + System.out.println(subsetsWithDup(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/bfs/Test.java b/src/main/java/com/chen/algorithm/znn/bfs/Test.java new file mode 100644 index 0000000..d520e05 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bfs/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.bfs; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 广度优先搜索相关,详见二叉树 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/Test.java b/src/main/java/com/chen/algorithm/znn/bitmap/Test.java new file mode 100644 index 0000000..6700ac6 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/Test.java @@ -0,0 +1,18 @@ +package com.chen.algorithm.znn.bitmap; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 位运算相关 + */ +public class Test { + /* + 位运算操作: + 1、 x & 1 == 1 or x & 1 == 0 判断奇偶(x % 2 == 1) + 2、 x = x & (x-1) =>清零最低位的 1,即将最低位的1设置为0 + 3、 x & -x => 得到最低位的 1 + 4、任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。 + 5、任何数和其自身做异或运算,结果是 0,a⊕a=0。 + 6、异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。 + */ +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/test136/Solution.java b/src/main/java/com/chen/algorithm/znn/bitmap/test136/Solution.java new file mode 100644 index 0000000..c766b5e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/test136/Solution.java @@ -0,0 +1,50 @@ +package com.chen.algorithm.znn.bitmap.test136; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-zi-by-leetcode-solution/ + * 136. 只出现一次的数字 + * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 + * 说明: + * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? + * 示例 1: + * 输入: [2,2,1] + * 输出: 1 + * 示例 2: + * 输入: [4,1,2,1,2] + * 输出: 4 + * + * @Auther: zhunn + * @Date: 2020/11/07 17:23 + * @Description: 只出现一次的数字:1-hash表存储;2-位运算 + * 1、任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。 + * 2、任何数和其自身做异或运算,结果是 0,a⊕a=0。 + * 3、异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。 + */ +public class Solution { + + /** + * 2-位运算 + * + * @param nums + * @return + */ + public int singleNumber(int[] nums) { + if (nums == null || nums.length == 0) { + return -1; + } + + int single = 0; + for (int num : nums) { + single = single ^ num; + } + return single; + } + + @Test + public void test() { + int[] nums = {7, 1, 2, 1, 2}; + System.out.println(singleNumber(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/test191/Solution.java b/src/main/java/com/chen/algorithm/znn/bitmap/test191/Solution.java new file mode 100644 index 0000000..b71d5e9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/test191/Solution.java @@ -0,0 +1,61 @@ +package com.chen.algorithm.znn.bitmap.test191; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/number-of-1-bits/solution/wei-1de-ge-shu-by-leetcode/ + * 191. 位1的个数 + * 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 + * 示例 1: + * 输入:00000000000000000000000000001011 + * 输出:3 + * 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 + * 示例 2: + * 输入:00000000000000000000000010000000 + * 输出:1 + * 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 + * 示例 3: + * 输入:11111111111111111111111111111101 + * 输出:31 + * 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 + * + * @Auther: zhunn + * @Date: 2020/11/07 21:23 + * @Description: 位1的个数:1-位运算;2-循环和位移动 + * 对于任意数字 n ,将 n 和 n−1 做与运算,会把最后一个 1 的位变成 0 + */ +public class Solution { + + /** + * 1-位运算 + * + * @param n + * @return + */ + public int hammingWeight(int n) { + int sum = 0; + while (n != 0) { + sum++; + n = n & (n - 1); // 将数字n最后一位1变为0 + } + return sum; + } + + public int hammingWeight2(int n) { + + int sum = 0; + int mask = 1; + for (int i = 0; i < 32; i++) { + if ((n & mask) != 0) { + sum++; + } + mask = mask << 1; + } + return sum; + } + + @Test + public void test() { + System.out.println(hammingWeight(-3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/test231/Solution.java b/src/main/java/com/chen/algorithm/znn/bitmap/test231/Solution.java new file mode 100644 index 0000000..2ba6bf9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/test231/Solution.java @@ -0,0 +1,61 @@ +package com.chen.algorithm.znn.bitmap.test231; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/power-of-two/solution/2de-mi-by-leetcode/ + * 231. 2的幂 + * 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 + * 示例 1: + * 输入: 1 + * 输出: true + * 解释: 20 = 1 + * 示例 2: + * 输入: 16 + * 输出: true + * 解释: 24 = 16 + * 示例 3: + * 输入: 218 + * 输出: false + * + * @Auther: zhunn + * @Date: 2020/11/07 22:23 + * @Description: 2的幂:1-位运算-获取二进制中最右边的 1(x&(-x));2-位运算-去除二进制中最右边的 1 (x&(x-1)) + * 2 的幂二进制表示只含有一个 1。 + */ +public class Solution { + + /** + * 1-位运算-获取二进制中最右边的 1(x&(-x)) + * + * @param n + * @return + */ + public boolean isPowerOfTwo(int n) { + if (n == 0) { + return false; + } + + long x = (long) n; + return (x & (-x)) == x; + } + + /** + * 2-位运算-去除二进制中最右边的 1 (x&(x-1)) + * + * @param n + * @return + */ + public boolean isPowerOfTwo2(int n) { + if (n == 0) { + return false; + } + long x = (long) n; + return (x & (x - 1)) == 0; + } + + @Test + public void test() { + System.out.println(isPowerOfTwo(16)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/test338/Solution.java b/src/main/java/com/chen/algorithm/znn/bitmap/test338/Solution.java new file mode 100644 index 0000000..a1e4745 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/test338/Solution.java @@ -0,0 +1,66 @@ +package com.chen.algorithm.znn.bitmap.test338; + +import com.alibaba.fastjson.JSON; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/ + * 338. 比特位计数 + * 给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 + * 示例 1: + * 输入: 2 + * 输出: [0,1,1] + * 示例 2: + * 输入: 5 + * 输出: [0,1,1,2,1,2] + * + * @Auther: zhunn + * @Date: 2020/11/07 22:23 + * @Description: 比特位计数:1-位运算-Pop count;2-动态规划+最后设置位;3-动态规划+最高有效位;4-动态规划+最低有效位; + */ +public class Solution { + + /** + * 1-位运算-Pop count + * + * @param num + * @return + */ + public int[] countBits(int num) { + int[] res = new int[num + 1]; + + for (int i = 1; i <= num; i++) { + res[i] = popCount(i); + } + return res; + } + + private int popCount(int n) { + int count = 0; + while (n != 0) { + count++; + n = n & (n - 1); + } + return count; + } + + /** + * 2-动态规划+最后设置位 + * + * @param num + * @return + */ + public int[] countBits2(int num) { + int[] res = new int[num + 1]; + for (int i = 1; i <= num; i++) { + res[i] = res[i & (i - 1)] + 1; + } + return res; + } + + @Test + public void test() { + System.out.println(JSON.toJSONString(countBits(2))); + System.out.println(JSON.toJSONString(countBits2(5))); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/bitmap/test461/Solution.java b/src/main/java/com/chen/algorithm/znn/bitmap/test461/Solution.java new file mode 100644 index 0000000..9ee031e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/bitmap/test461/Solution.java @@ -0,0 +1,97 @@ +package com.chen.algorithm.znn.bitmap.test461; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/hamming-distance/solution/yi-ming-ju-chi-by-leetcode/ + * 461. 汉明距离 + * 两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。 + * 给出两个整数 x 和 y,计算它们之间的汉明距离。 + * 注意: + * 0 ≤ x, y < 231. + * 示例: + * 输入: x = 1, y = 4 + * 输出: 2 + * 解释: + * 1 (0 0 0 1) + * 4 (0 1 0 0) + * ↑ ↑ + * 上面的箭头指出了对应二进制位不同的位置。 + * + * @Auther: zhunn + * @Date: 2020/11/07 22:23 + * @Description: 汉明距离:0-首选-位运算;1-内置位计数功能;2-移位;3-布赖恩·克尼根算法(x&(x-1)) + */ +public class Solution { + + /** + * @param x + * @param y + * @return + */ + public int hammingDistance(int x, int y) { + int dis = x ^ y; + int res = 0; + while (dis != 0) { + res++; + dis = dis & (dis - 1); + } + return res; + } + + ///** + // * 1-内置位计数功能 + // * + // * @param x + // * @param y + // * @return + // */ + //public int hammingDistance1(int x, int y) { + // return Integer.bitCount(x ^ y); + //} + // + ///** + // * 2-移位 + // * + // * @param x + // * @param y + // * @return + // */ + //public int hammingDistance2(int x, int y) { + // int dis = x ^ y; + // int res = 0; + // + // while (dis != 0) { + // if ((dis & 1) == 1) { + // res++; + // } + // dis = dis >> 1; + // } + // return res; + //} + // + ///** + // * 3-布赖恩·克尼根算法(x&(x-1)) + // * + // * @param x + // * @param y + // * @return + // */ + //public int hammingDistance3(int x, int y) { + // int dis = x ^ y; + // int res = 0; + // while (dis != 0) { + // res++; + // dis = dis & (dis - 1); + // } + // return res; + //} + + @Test + public void test() { + System.out.println(hammingDistance(1, 7)); + //System.out.println(hammingDistance1(1, 7)); + //System.out.println(hammingDistance2(1, 7)); + //System.out.println(hammingDistance3(1, 7)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dfs/Test.java b/src/main/java/com/chen/algorithm/znn/dfs/Test.java new file mode 100644 index 0000000..63d1100 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dfs/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.dfs; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 深度优先搜索相关,详见二叉树 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution.java b/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution.java new file mode 100644 index 0000000..46900d9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution.java @@ -0,0 +1,67 @@ +package com.chen.algorithm.znn.dfs.test22; + +import com.alibaba.fastjson.JSON; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/ + * 22. 括号生成 + * 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 + * 示例: + * 输入:n = 3 + * 输出:[ + * "((()))", + * "(()())", + * "(())()", + * "()(())", + * "()()()" + * ] + * + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 括号生成:1-dfs(推荐) + */ +public class Solution { + + List res = new ArrayList<>(); + + public List generateParenthesis(int n) { + dfs(n, n, ""); + return res; + } + + /** + * @param left 左边剩余的括号数 + * @param right 右边剩余的括号数 + * @param curStr 括号结果 + */ + private void dfs(int left, int right, String curStr) { + // 左右括号都不剩余了,递归终止 + if (left == 0 && right == 0) { + res.add(curStr); + return; + } + + // 剪枝 + if (left > right) { + return; + } + // 如果左括号还剩余的话,可以拼接左括号 + if (left > 0) { + dfs(left - 1, right, curStr + "("); + } + // 如果右括号剩余多于左括号剩余的话,可以拼接右括号 + if (right > left) { + dfs(left, right - 1, curStr + ")"); + } + } + + @Test + public void test() { + List res = generateParenthesis(3); + System.out.println(JSON.toJSONString(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution2.java b/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution2.java new file mode 100644 index 0000000..a006815 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dfs/test22/Solution2.java @@ -0,0 +1,56 @@ +package com.chen.algorithm.znn.dfs.test22; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/ + * + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 括号生成:2-bfs + */ +public class Solution2 { + class Node { + private String str; // 当前得到的字符串 + private int left; // 剩余的左括号数量 + private int right; // 剩余的右括号数量 + + public Node(String str, int left, int right) { + this.str = str; + this.left = left; + this.right = right; + } + } + + /** + * 2-bfs + * @param n + * @return + */ + public List generateParenthesis(int n) { + if (n == 0) { + return null; + } + Queue queue = new LinkedList<>(); + queue.add(new Node("", n, n)); + List res = new ArrayList<>(); + + while (!queue.isEmpty()) { + Node node = queue.poll(); + + if (node.left == 0 && node.right == 0) { + res.add(node.str); + } + if (node.left > 0) { + queue.add(new Node(node.str + "(", node.left - 1, node.right)); + } + if (node.right > 0 && node.left < node.right) { + queue.add(new Node(node.str + ")", node.left, node.right - 1)); + } + } + return res; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/divide/Test.java b/src/main/java/com/chen/algorithm/znn/divide/Test.java new file mode 100644 index 0000000..d734a6b --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/divide/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.divide; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 分治法相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/divide/test169/Solution.java b/src/main/java/com/chen/algorithm/znn/divide/test169/Solution.java new file mode 100644 index 0000000..42a3d70 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/divide/test169/Solution.java @@ -0,0 +1,138 @@ +package com.chen.algorithm.znn.divide.test169; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 169. 多数元素 + * 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 + * 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 + * 示例 1: + * 输入: [3,2,3] + * 输出: 3 + * 示例 2: + * 输入: [2,2,1,1,1,2,2] + * 输出: 2 + * + * @Auther: zhunn + * @Date: 2020/11/2 16:23 + * @Description: 多数元素:1-排序;2-哈希表(优选);3-分治 + */ +public class Solution { + + /** + * 1-排序 + * + * @param nums + * @return + */ + public int majorityElement1(int[] nums) { + Arrays.sort(nums); + return nums[nums.length / 2]; + } + + /** + * 2-哈希表(优化后) + * + * @param nums + * @return + */ + public int majorityElement2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + Map map = new HashMap<>(); + for (int num : nums) { + if (!map.containsKey(num)) { + map.put(num, 1); + continue; + } + + int count = map.get(num) + 1; + if (count > nums.length / 2) { + return num; + } + map.put(num, count); + } + return 0; + } + + /** + * 2-哈希表(直观) + * + * @param nums + * @return + */ + //public int majorityElement2(int[] nums) { + // if (nums == null || nums.length == 0) { + // return 0; + // } + // Map map = new HashMap<>(); + // for (int i = 0; i < nums.length; i++) { + // if (map.containsKey(nums[i])) { + // int count = map.get(nums[i]) + 1; + // if (count > nums.length / 2) { + // return nums[i]; + // } + // map.put(nums[i], count); + // } else { + // map.put(nums[i], 1); + // } + // } + // return 0; + //} + + /** + * 3-分治:官方解答 + * + * @param nums + * @return + */ + public int majorityElement3(int[] nums) { + return majorityElementRec(nums, 0, nums.length - 1); + } + + private int countInRange(int[] nums, int num, int lo, int hi) { + int count = 0; + for (int i = lo; i <= hi; i++) { + if (nums[i] == num) { + count++; + } + } + return count; + } + + private int majorityElementRec(int[] nums, int lo, int hi) { + // base case; the only element in an array of size 1 is the majority + // element. + if (lo == hi) { + return nums[lo]; + } + + // recurse on left and right halves of this slice. + int mid = (hi - lo) / 2 + lo; + int left = majorityElementRec(nums, lo, mid); + int right = majorityElementRec(nums, mid + 1, hi); + + // if the two halves agree on the majority element, return it. + if (left == right) { + return left; + } + + // otherwise, count each element and return the "winner". + int leftCount = countInRange(nums, left, lo, hi); + int rightCount = countInRange(nums, right, lo, hi); + + return leftCount > rightCount ? left : right; + } + + @Test + public void test() { + int[] nums = {2, 2, 1, 1, 1, 2, 2}; + System.out.println(majorityElement2(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/divide/test50/Solution.java b/src/main/java/com/chen/algorithm/znn/divide/test50/Solution.java new file mode 100644 index 0000000..b402be2 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/divide/test50/Solution.java @@ -0,0 +1,106 @@ +package com.chen.algorithm.znn.divide.test50; + +import org.junit.Test; + +/** + * 50. Pow(x, n) + * 实现 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 + * + * @Auther: zhunn + * @Date: 2020/11/2 16:13 + * @Description: Pow(x, n):1-递归;2-迭代(推荐) + */ +public class Solution { + + /** + * 1-递归 + * + * @param x + * @param n + * @return + */ + public double myPow1(double x, int n) { + if (n == 0) { + return 1.0d; + } + int N = n; + if (N < 0) { + N = -N; + x = 1 / x; + } + return quick(x, N); + } + + private double quick(double x, int n) { + if (n == 0) { + return 1.0d; + } + double y = quick(x, n / 2); + return n % 2 == 1 ? y * y * x : y * y; + } + + /** + * 2-迭代 + * + * @param x + * @param n + * @return + */ + public double myPow2(double x, int n) { + if (n == 0) { + return 1d; + } + + int N = n; + if (N < 0) { + x = 1 / x; + N = -N; + } + + double res = 1d; + double x_contribute = x; + while (N > 0) { + if (N % 2 == 1) { + res = res * x_contribute; + } + x_contribute = x_contribute * x_contribute; + N = N / 2; + } + return res; + } + + @Test + public void test() { + System.out.println(myPow(2.0, 10)); + } + + public double myPow(double x, int n) { + if (n == 0) { + return 1.0; + } + if (n < 0) { + n = -n; + x = 1 / x; + } + double res = 1d; + double x_con = x; + while (n > 0) { + if (n % 2 == 1) { + res = res * x_con; + } + x_con = x_con * x_con; + n = n / 2; + } + return res; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/Test.java b/src/main/java/com/chen/algorithm/znn/dynamic/Test.java new file mode 100644 index 0000000..9408d3e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/Test.java @@ -0,0 +1,129 @@ +package com.chen.algorithm.znn.dynamic; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/tan-xin-suan-fa-by-liweiwei1419-2/ + * + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: DP动态规划相关 + */ +public class Test { + + /** + * 买卖股票的最佳时机相关题: + * 121(最多买卖一次)、122(贪心,最多买卖多次,不限次)、123(最多买卖两次)、188(最多买卖K次)、 + * 309(可以买卖多次,有冷冻期)、714(可以买卖多次,一次买卖含一次手续费) + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票) + * + * 理解状态转移方程: + * + * 人为规定:如果当天买入股票的时候记录「交易发生一次」,如果当天卖出股票,不增加交易次数; + * 买入股票,手上持有的现金数减少(减去当天股价),相应地,卖出股票,手上持有的现金数增加(加上当天股价); + * 难点:还没发生的交易,并且还规定了当天必须持股的状态值应该设置为负无穷 + * + */ + + /** 121. 买卖股票的最佳时机 **/ + //给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + //如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 + //注意:你不能在买入股票前卖出股票。 + // + //示例 1: + //输入: [7,1,5,3,6,4] + //输出: 5 + //解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 + //注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 + //示例 2: + //输入: [7,6,4,3,1] + //输出: 0 + //解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + + + /**122. 买卖股票的最佳时机 II **/ + //给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + //设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + //注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + // + //示例 1: + //输入: [7,1,5,3,6,4] + //输出: 7 + //解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + //随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + //示例 2: + //输入: [1,2,3,4,5] + //输出: 4 + //解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + //注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + //因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + //示例 3: + //输入: [7,6,4,3,1] + //输出: 0 + //解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + + + /** 123. 买卖股票的最佳时机 III **/ + //给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 + //设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 + //注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + // + //示例 1: + //输入: [3,3,5,0,0,3,1,4] + //输出: 6 + //解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 + //随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 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。 + + + /** 188. 买卖股票的最佳时机 IV **/ + //给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 + //设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 + //注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + //   + //示例 1: + //输入:k = 2, prices = [2,4,1] + //输出:2 + //解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。 + //示例 2: + //输入:k = 2, prices = [3,2,6,5,0,3] + //输出:7 + //解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。 + //随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。 + + + /** 309. 最佳买卖股票时机含冷冻期 **/ + //给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​ + //设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + //你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + //卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 + //示例: + //输入: [1,2,3,0,2] + //输出: 3 + //解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出] + + + /** 714. 买卖股票的最佳时机含手续费 **/ + //给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 + //你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + //返回获得利润的最大值。 + //注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。 + // + //示例 1: + //输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 + //输出: 8 + //解释: 能够达到的最大利润: + //在此处买入 prices[0] = 1 + //在此处卖出 prices[3] = 8 + //在此处买入 prices[4] = 4 + //在此处卖出 prices[5] = 9 + //总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8. + +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test120/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test120/Solution.java new file mode 100644 index 0000000..4843372 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test120/Solution.java @@ -0,0 +1,107 @@ +package com.chen.algorithm.znn.dynamic.test120; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/triangle/solution/di-gui-ji-yi-hua-dp-bi-xu-miao-dong-by-sweetiee/ + * 120. 三角形最小路径和 + * 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 + * 相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。 + * 例如,给定三角形: + * [ + * [2], + * [3,4], + * [6,5,7], + * [4,1,8,3] + * ] + * 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 + * + * @Auther: zhunn + * @Date: 2020/11/3 22:02 + * @Description: 三角形最小路径和:1-动态规划;2-动态规划-空间优化(推荐) + * 第i行的列数等于 i+1,即行数加一 + */ +public class Solution { + + /** + * 1-动态规划:时间和空间复杂度都是O(n^2) + * + * @param triangle + * @return + */ + public int minimumTotal(List> triangle) { + if (triangle == null || triangle.size() == 0) { + return 0; + } + + int n = triangle.size(); // 总行数 + int m = triangle.get(n - 1).size(); // 总列数 + int[][] dp = new int[n + 1][m + 1]; // dp[i][j] 表示从点 (i, j) 到底边的最小路径和。 由于存储最高层的数据,所以需要数值上加1 + + for (int i = n - 1; i >= 0; i--) { // 从三角形的最后一行开始递推。 + for (int j = 0; j <= i; j++) { + dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j); + } + } + + return dp[0][0]; + } + + /** + * 2-动态规划-空间优化(推荐):时间复杂度O(n^2)、空间复杂度是O(n) + * + * @param triangle + * @return + */ + public int minimumTotal2(List> triangle) { + if (triangle == null || triangle.size() == 0) { + return 0; + } + + int n = triangle.size(); + int m = triangle.get(n - 1).size(); + int[] dp = new int[m + 1]; + + for (int i = n - 1; i >= 0; i--) { + for (int j = 0; j <= i; j++) { + dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); + } + } + + return dp[0]; + } + + @Test + public void testCase() { + List> triangle = new ArrayList<>(); + triangle.add(Arrays.asList(1)); + triangle.add(Arrays.asList(3, 4)); + triangle.add(Arrays.asList(6, 5, 7)); + triangle.add(Arrays.asList(4, 1, 8, 3)); + + System.out.println(minimumTotal2(triangle)); + System.out.println(minimumTotal3(triangle)); + + } + + public int minimumTotal3(List> triangle) { + if (triangle == null || triangle.size() == 0) { + return 0; + } + + int n = triangle.size(); + int m = triangle.get(n - 1).size(); + int[] dp = new int[m + 1]; + + for (int i = n - 1; i >= 0; i--) { + for (int j = 0; j <= i; j++) { + dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); + } + } + return dp[0]; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test121/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test121/Solution.java new file mode 100644 index 0000000..a3e7d9d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test121/Solution.java @@ -0,0 +1,47 @@ +package com.chen.algorithm.znn.dynamic.test121; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/kan-yi-bian-jiu-wang-bu-diao-de-jie-ti-si-lu-bu-di/ + * 121. 买卖股票的最佳时机 + * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + * 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 + * 注意:你不能在买入股票前卖出股票。 + * 示例 1: + * 输入: [7,1,5,3,6,4] + * 输出: 5 + * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 + * 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 + * 示例 2: + * 输入: [7,6,4,3,1] + * 输出: 0 + * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + * + * @Auther: zhunn + * @Date: 2020/11/3 19:59 + * @Description: 买卖股票的最佳时机:双指针法 买入算一笔交易 + * 121(最多买卖一次)、122(贪心,最多买卖多次,不限次)、123(最多买卖两次)、188(最多买卖K次)、309(可以买卖多次,有冷冻期)、714(可以买卖多次,一次买卖含一次手续费) + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票) + */ +public class Solution { + + public int maxProfit(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + int min = nums[0]; + int max = 0; + for (int i = 0; i < nums.length; i++) { + min = Math.min(nums[i], min); + max = Math.max(max, nums[i] - min); + } + return max; + } + + @Test + public void test() { + int[] nums = {7, 1, 5, 3, 6, 4}; + System.out.println(maxProfit(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test123/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test123/Solution.java new file mode 100644 index 0000000..b8a1bfe --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test123/Solution.java @@ -0,0 +1,132 @@ +package com.chen.algorithm.znn.dynamic.test123; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/solution/dong-tai-gui-hua-by-liweiwei1419-7/ + * 123. 买卖股票的最佳时机 III + * 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 + * 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * 示例 1: + * 输入: [3,3,5,0,0,3,1,4] + * 输出: 6 + * 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 + * 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 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。 + * + * @Auther: zhunn + * @Date: 2020/11/3 21:06 + * @Description: 买卖股票的最佳时机 III:1-动态规划;2-动态规划-优化空间 + */ +public class Solution { + + /** + * 1-动态规划 dp[i][j][k]: i 表示第几天(0-n),j表示交易了几次(0,1,2),k表示是否持股(0-不持股,1-持股) + * + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + int len = prices.length; + if (len < 2) { + return 0; + } + + // 第 2 维的 0 没有意义,1 表示交易进行了 1 次,2 表示交易进行了 2 次 + // 为了使得第 2 维的数值 1 和 2 有意义,这里将第 2 维的长度设置为 3 + int[][][] dp = new int[len][3][2]; + + // 理解如下初始化 + // 第 3 维规定了必须持股,因此是 -prices[0] + dp[0][1][1] = -prices[0]; + // 还没发生的交易,持股的时候应该初始化为负无穷 + dp[0][2][1] = Integer.MIN_VALUE; + + for (int i = 1; i < len; i++) { + // 转移顺序先持股,再卖出 + dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][0][0] - prices[i]); + dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i]); + dp[i][2][1] = Math.max(dp[i - 1][2][1], dp[i - 1][1][0] - prices[i]); + dp[i][2][0] = Math.max(dp[i - 1][2][0], dp[i - 1][2][1] + prices[i]); + } + return Math.max(dp[len - 1][1][0], dp[len - 1][2][0]); + } + + /** + * 2-动态规划-优化空间 + * (由于今天只参考了昨天的状态,所以直接去掉第一维度不会影响状态转移的正确性) + * + * @param prices + * @return dp[i][j][k]: i 表示第几天(0-n),j表示交易了几次(0,1,2),k表示是否持股(0-不持股,1-持股) + * 此题可以用dp[j][k] 即可 + */ + public int maxProfit2(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + int[][] dp = new int[3][2]; + dp[0][0] = 0; + dp[1][1] = -prices[0]; + dp[2][1] = Integer.MIN_VALUE; // 还没发生的交易,持股的时候应该初始化为负无穷 + + for (int i = 1; i < prices.length; i++) { + dp[1][1] = Math.max(dp[1][1], dp[0][0] - prices[i]); + dp[1][0] = Math.max(dp[1][0], dp[1][1] + prices[i]); + dp[2][1] = Math.max(dp[2][1], dp[1][0] - prices[i]); + dp[2][0] = Math.max(dp[2][0], dp[2][1] + prices[i]); + } + return Math.max(dp[1][0], dp[2][0]); + } + + ///** + // * 3-动态规划 + // * + // * @param + // * @return + // */ + //public int maxProfit3(int[] prices) { + // if (prices == null || prices.length == 0) { + // return 0; + // } + // // dp[i][j] ,表示 [0, i] 区间里,状态为 j 的最大收益 + // // j = 0:什么都不操作 + // // j = 1:第 1 次买入一支股票 + // // j = 2:第 1 次卖出一支股票 + // // j = 3:第 2 次买入一支股票 + // // j = 4:第 2 次卖出一支股票 + // + // int n = prices.length; + // int[][] dp = new int[n][5]; + // dp[0][0] = 0; + // dp[0][1] = -prices[0]; + // for (int i = 0; i < n; i++) { + // dp[i][3] = Integer.MIN_VALUE; + // } + // + // for (int i = 1; i < n; i++) { + // dp[i][0] = 0; + // dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + // dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]); + // dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]); + // dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]); + // } + // return Math.max(Math.max(dp[n - 1][0], dp[n - 1][2]), dp[n - 1][4]); + //} + + @Test + public void test() { + int[] prices = {3, 3, 5, 0, 0, 3, 1, 4}; + System.out.println(maxProfit(prices)); + System.out.println(maxProfit2(prices)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test152/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test152/Solution.java new file mode 100644 index 0000000..7f0dfa6 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test152/Solution.java @@ -0,0 +1,87 @@ +package com.chen.algorithm.znn.dynamic.test152; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/maximum-product-subarray/solution/dong-tai-gui-hua-li-jie-wu-hou-xiao-xing-by-liweiw/ + * 152. 乘积最大子数组 + * 给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 + * 示例 1: + * 输入: [2,3,-2,4] + * 输出: 6 + * 解释: 子数组 [2,3] 有最大乘积 6。 + * 示例 2: + * 输入: [-2,0,-1] + * 输出: 0 + * 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 + * + * @Auther: zhunn + * @Date: 2020/11/3 22:02 + * @Description: 乘积最大子数组:1-动态规划;2-动态规划-优化空间 + */ +public class Solution { + + /** + * 1-动态规划 + * + * @param nums + * @return + */ + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + // dp[i][0]:以 nums[i] 结尾的连续子数组的最小值 + // dp[i][1]:以 nums[i] 结尾的连续子数组的最大值 + int len = nums.length; + int[][] dp = new int[len][2]; + dp[0][0] = nums[0]; + dp[0][1] = nums[0]; + + for (int i = 1; i < len; i++) { + dp[i][0] = Math.min(nums[i], Math.min(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i])); + dp[i][1] = Math.max(nums[i], Math.max(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i])); + } + + // 只关心最大值,需要遍历 + int res = dp[0][1]; + for (int i = 1; i < len; i++) { + res = Math.max(dp[i][1], res); + } + return res; + } + + /** + * 2-动态规划-优化空间 + * + * @param nums + * @return + */ + public int maxProduct2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int len = nums.length; + int[] dp = new int[2]; + dp[0] = nums[0]; + dp[1] = nums[0]; + int res = nums[0]; + + for (int i = 1; i < len; i++) { + int min = dp[0], max = dp[1]; + + dp[0] = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); + dp[1] = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); + res = Math.max(res, dp[1]); + } + return res; + } + + @Test + public void test() { + int[] nums = {2, 3, -2, 4}; + System.out.println(maxProduct2(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test188/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test188/Solution.java new file mode 100644 index 0000000..3c2bdc6 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test188/Solution.java @@ -0,0 +1,158 @@ +package com.chen.algorithm.znn.dynamic.test188; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/solution/dong-tai-gui-hua-by-liweiwei1419-4/ + * 188. 买卖股票的最佳时机 IV + * 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * 示例 1: + * 输入:k = 2, prices = [2,4,1] + * 输出:2 + * 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。 + * 示例 2: + * 输入:k = 2, prices = [3,2,6,5,0,3] + * 输出:7 + * 解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。 + * 随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。 + * + * @Auther: zhunn + * @Date: 2020/11/3 22:03 + * @Description: 买卖股票的最佳时机 IV :1-动态规划-三维超出内存限制;2-动态规划-优化空间,降维,使用二维 + *

+ * 理解状态转移方程: + * 人为规定:如果当天买入股票的时候记录「交易发生一次」,如果当天卖出股票,不增加交易次数; + * 买入股票,手上持有的现金数减少(减去当天股价),相应地,卖出股票,手上持有的现金数增加(加上当天股价); + * 难点:还没发生的交易,并且还规定了当天必须持股的状态值应该设置为负无穷 + */ +public class Solution { + + // 超出内存限制 + //public int maxProfit(int k, int[] prices) { + // int len = prices.length; + // // 特殊判断 + // if (k == 0 || len < 2) { + // return 0; + // } + // // 特殊判断,因为交易一次需要 2 天,如果 k >= len / 2,相当于没有限制 + // // 转换为「力扣」第 122 题,使用贪心算法 + // if (k >= len / 2) { + // return greedy(prices, len); + // } + // + // // 状态转移方程里下标有 -1 的时候,为了防止数组下标越界,多开一行,因此第一维的长度是 len + 1 + // // 第二维表示交易次数,从 0 开始,因此第二维的长度是 k + 1 + // // 第三维表示是否持股,0:不持股,1:持股 + // int[][][] dp = new int[len + 1][k + 1][2]; + // + // // 初始化:把持股的部分都设置为一个较小的负值 + // // 注意:如果使用默认值 0,状态转移的过程中会做出错误的决策 + // for (int i = 0; i <= len; i++) { + // for (int j = 0; j <= k; j++) { + // dp[i][j][1] = Integer.MIN_VALUE; + // } + // } + // + // // 注意:i 和 j 都有 1 个位置的偏移 + // for (int i = 1; i <= len; i++) { + // for (int j = 1; j <= k; j++) { + // dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]); + // dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]); + // } + // } + // // 说明:第一维和第二维状态都具有前缀性质的,输出最后一个状态即可 + // return dp[len][k][0]; + //} + //private int greedy(int[] prices, int len) { + // // 转换为股票系列的第 2 题,使用贪心算法完成,思路是只要有利润,就交易 + // int res = 0; + // for (int i = 1; i < len; i++) { + // if (prices[i] > prices[i - 1]) { + // res += prices[i] - prices[i - 1]; + // } + // } + // return res; + //} + + /** + * 1-动态规划-三维超出内存限制 + * + * @param prices + * @param k + * @return + */ + public int maxProfit(int[] prices, int k) { + if (prices == null || prices.length == 0 || k == 0) { + return 0; + } + int len = prices.length; + if (k >= len / 2) { + return greedy(prices); + } + + int[][][] dp = new int[len + 1][k + 1][2]; + for (int i = 0; i <= len; i++) { + for (int j = 0; j <= k; j++) { + dp[i][j][1] = Integer.MIN_VALUE; + } + } + + // 注意:i 和 j 都有 1 个位置的偏移 + for (int i = 1; i <= len; i++) { + for (int j = 1; j <= k; j++) { + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]); + } + } + + + return dp[len][k][0]; + } + + private int greedy(int[] prices) { + int res = 0; + for (int i = 1; i < prices.length; i++) { + if (prices[i] > prices[i - 1]) { + res += prices[i] - prices[i - 1]; + } + } + return res; + } + + /** + * 2-动态规划-优化空间,降维,使用二维 + * + * @param prices + * @return + */ + public int maxProfit2(int[] prices, int k) { + if (prices == null || prices.length == 0 || k == 0) { + return 0; + } + int len = prices.length; + if (k >= len / 2) { + return greedy(prices); + } + + int[][] dp = new int[k + 1][2]; + for (int i = 0; i <= k; i++) { + dp[i][1] = Integer.MIN_VALUE; + } + for (int price : prices) { + for (int j = 1; j <= k; j++) { + dp[j][0] = Math.max(dp[j][0], dp[j][1] + price); + dp[j][1] = Math.max(dp[j][1], dp[j - 1][0] - price); + } + } + return dp[k][0]; + } + + @Test + public void test() { + int[] prices = {3, 2, 6, 5, 0, 3}; + System.out.println(maxProfit(prices, 2)); + System.out.println(maxProfit2(prices, 2)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test198/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test198/Solution.java new file mode 100644 index 0000000..33d78b8 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test198/Solution.java @@ -0,0 +1,76 @@ +package com.chen.algorithm.znn.dynamic.test198; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/ + * 198. 打家劫舍 + * 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 + * 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 + * 示例 1: + * 输入:[1,2,3,1] + * 输出:4 + * 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 + * 偷窃到的最高金额 = 1 + 3 = 4 。 + * 示例 2: + * 输入:[2,7,9,3,1] + * 输出:12 + * 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 + * + * @Auther: zhunn + * @Date: 2020/11/6 17:16 + * @Description: 打家劫舍:1-动态规划;2-动态规划 + 滚动数组 + */ +public class Solution { + + /** + * 1-动态规划 + * 1. 偷窃第 k 间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。 + * 2. 不偷窃第 k 间房屋,偷窃总金额为前 k−1 间房屋的最高总金额。 + * + * @param nums + * @return + */ + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + int len = nums.length; + if (len == 1) { + return nums[0]; + } + + int[] dp = new int[len]; // dp[i] 表示前 i 间房屋能偷窃到的最高总金额 + dp[0] = nums[0]; + dp[1] = Math.max(nums[0], nums[1]); + + for (int i = 2; i < len; i++) { // 第i间房偷dp[i - 2] + nums[i];第i间房不偷dp[i - 1] + dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); + } + return dp[len - 1]; + } + + public int rob2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + int len = nums.length; + if (len == 1) { + return nums[0]; + } + + int first = nums[0], second = Math.max(nums[0], nums[1]); + for (int i = 2; i < len; i++) { + int temp = second; + second = Math.max(first + nums[i], temp); + first = temp; + } + return second; + } + + @Test + public void test() { + int[] nums = {2, 7, 9, 3, 1}; + System.out.println(rob2(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test213/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test213/Solution.java new file mode 100644 index 0000000..e2cfa4a --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test213/Solution.java @@ -0,0 +1,70 @@ +package com.chen.algorithm.znn.dynamic.test213; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * 213. 打家劫舍 II + * 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 + * 给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。 + * 示例 1: + * 输入:nums = [2,3,2] + * 输出:3 + * 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 + * 示例 2: + * 输入:nums = [1,2,3,1] + * 输出:4 + * 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 + * 偷窃到的最高金额 = 1 + 3 = 4 。 + * 示例 3: + * 输入:nums = [0] + * 输出:0 + * + * @Auther: zhunn + * @Date: 2020/11/6 17:45 + * @Description: 打家劫舍 II:在198题的基础上,选择不偷第一家或不偷最后一家 + */ +public class Solution { + + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + int len = nums.length; + if (len == 1) { + return nums[0]; + } + return Math.max(myRob2(Arrays.copyOfRange(nums, 0, len - 1)), + myRob2(Arrays.copyOfRange(nums, 1, len))); + + } + + private int myRob2(int[] nums) { + int len = nums.length; + int[] dp = new int[len]; + dp[0] = nums[0]; + dp[1] = Math.max(nums[0], nums[1]); + + for (int i = 2; i < len; i++) { + dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); + } + return dp[len - 1]; + } + + private int myRob(int[] nums) { + int first = nums[0], second = nums[1]; + for (int i = 2; i < nums.length; i++) { + int temp = second; + second = Math.max(first + nums[i], temp); + first = temp; + } + return second; + } + + @Test + public void test() { + int[] nums = {2, 3, 2}; + System.out.println(rob(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test279/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test279/Solution.java new file mode 100644 index 0000000..b854cc6 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test279/Solution.java @@ -0,0 +1,41 @@ +package com.chen.algorithm.znn.dynamic.test279; + +import org.junit.Test; + +/** + * 279. 完全平方数 + * 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 + * 示例 1: + * 输入: n = 12 + * 输出: 3 + * 解释: 12 = 4 + 4 + 4. + * 示例 2: + * 输入: n = 13 + * 输出: 2 + * 解释: 13 = 4 + 9. + * + * @Auther: zhunn + * @Date: 2020/11/5 18:00 + * @Description: 完全平方数:1-动态规划 + */ +public class Solution { + + public int numSquares(int n) { + + int[] dp = new int[n + 1]; + //dp[0] = 0; //题意是给定正整数,不用考虑0 + + for (int i = 1; i <= n; i++) { + dp[i] = i; // 赋初始值,最多是有它本身这么多 + for (int j = 1; i - j * j >= 0; j++) { + dp[i] = Math.min(dp[i], dp[i - j * j] + 1); + } + } + return dp[n]; + } + + @Test + public void test() { + System.out.println(numSquares(12)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test300/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test300/Solution.java new file mode 100644 index 0000000..7dcf298 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test300/Solution.java @@ -0,0 +1,55 @@ +package com.chen.algorithm.znn.dynamic.test300; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-er-fen-cha-zhao-tan-xin-suan-fa-p/ + * 300. 最长上升子序列 + * 给定一个无序的整数数组,找到其中最长上升子序列的长度。 + * 示例: + * 输入: [10,9,2,5,3,7,101,18] + * 输出: 4 + * 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 + * + * @Auther: zhunn + * @Date: 2020/11/4 21:03 + * @Description: 最长上升子序列:1-动态规划(推荐);2-动态规划-优化空间(贪心+二分查找) + */ +public class Solution { + + /** + * 1-动态规划(推荐) + * + * @param nums + * @return + */ + public int lengthOfLIS(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int len = nums.length; + int[] dp = new int[len]; // dp[i]标识以nums[i]结尾的[上升子序列]的长度 + Arrays.fill(dp, 1); + + int res = 0; + for (int i = 1; i < len; i++) { + for (int j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + res = Math.max(res, dp[i]); + } + + return res; + } + + @Test + public void test() { + int[] nums = {10, 9, 2, 5, 3, 7, 8, 101, 18}; + System.out.println(lengthOfLIS(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test309/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test309/Solution.java new file mode 100644 index 0000000..42f19c7 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test309/Solution.java @@ -0,0 +1,89 @@ +package com.chen.algorithm.znn.dynamic.test309; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/dong-tai-gui-hua-by-liweiwei1419-5/ + * 309. 最佳买卖股票时机含冷冻期 + * 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​ + * 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + * 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 + * 示例: + * 输入: [1,2,3,0,2] + * 输出: 3 + * 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出] + * + * @Auther: zhunn + * @Date: 2020/11/3 22:03 + * @Description: 最佳买卖股票时机含冷冻期:1-动态规划(推荐);2-动态规划-优化空间 + */ +public class Solution { + + /** + * 1-动态规划 + * + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + int len = prices.length; + if (len < 2) { + return 0; + } + + int[][] dp = new int[len][3]; + dp[0][0] = 0; + dp[0][1] = -prices[0]; + dp[0][2] = 0; + + // dp[i][0]: 手上不持有股票,并且今天不是由于卖出股票而不持股,我们拥有的现金数 + // dp[i][1]: 手上持有股票时,我们拥有的现金数 + // dp[i][2]: 手上不持有股票,并且今天是由于卖出股票而不持股,我们拥有的现金数 + for (int i = 1; i < len; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + dp[i][2] = dp[i - 1][1] + prices[i]; + } + return Math.max(dp[len - 1][0], dp[len - 1][2]); + } + + /** + * 2-动态规划-优化空间 + * + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + int len = prices.length; + if (len < 2) { + return 0; + } + + int[] dp = new int[3]; + + dp[0] = 0; + dp[1] = -prices[0]; + dp[2] = 0; + + int pre0 = dp[0]; + int pre2 = dp[2]; + + for (int i = 1; i < len; i++) { + dp[0] = Math.max(dp[0], pre2); + dp[1] = Math.max(dp[1], pre0 - prices[i]); + dp[2] = dp[1] + prices[i]; + + pre0 = dp[0]; + pre2 = dp[2]; + } + return Math.max(dp[0], dp[2]); + } + + @Test + public void test() { + int[] prices = {1, 2, 3, 0, 2}; + System.out.println(maxProfit(prices)); + System.out.println(maxProfit2(prices)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test322/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test322/Solution.java new file mode 100644 index 0000000..a2e1388 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test322/Solution.java @@ -0,0 +1,72 @@ +package com.chen.algorithm.znn.dynamic.test322; + +import org.junit.Test; + +import java.util.Arrays; + + +/** + * https://leetcode-cn.com/problems/coin-change/solution/dong-tai-gui-hua-shi-yong-wan-quan-bei-bao-wen-ti-/ + * 官方解答:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/ + * 322. 零钱兑换 + * 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 + * 你可以认为每种硬币的数量是无限的。 + * 示例 1: + * 输入:coins = [1, 2, 5], amount = 11 + * 输出:3 + * 解释:11 = 5 + 5 + 1 + * 示例 2: + * 输入:coins = [2], amount = 3 + * 输出:-1 + * 示例 3: + * 输入:coins = [1], amount = 0 + * 输出:0 + * 示例 4: + * 输入:coins = [1], amount = 1 + * 输出:1 + * 示例 5: + * 输入:coins = [1], amount = 2 + * 输出:2 + * + * @Auther: zhunn + * @Date: 2020/11/4 22:03 + * @Description: 零钱兑换:1-动态规划;2-动态规划-优化空间 + * 返回所需的最少的硬币个数 + */ +public class Solution { + + /** + * 动态规划-优化空间(推荐) + * + * @param coins + * @param amount + * @return + */ + public int coinChange(int[] coins, int amount) { + if (coins == null || coins.length == 0) { + if(amount == 0){ + return 0; + } + return -1; + } + + int[] dp = new int[amount + 1]; // 状态转移方程-凑成amount数值需要的最少硬币数,给 0 占位 + Arrays.fill(dp, amount + 1); // 注意:因为要比较的是最小值,这个不可能的值就得赋值成为一个最大值 + dp[0] = 0; // 理解 dp[0] = 0 的合理性,单独一枚硬币如果能够凑出面值,符合最优子结构 + + for (int coin : coins) { + for (int i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + + return dp[amount] > amount ? -1 : dp[amount]; + } + + @Test + public void test() { + int[] nums = {1, 2, 5}; + int amount = 10; + System.out.println(coinChange(nums, amount)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test337/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test337/Solution.java new file mode 100644 index 0000000..feef2cd --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test337/Solution.java @@ -0,0 +1,63 @@ +package com.chen.algorithm.znn.dynamic.test337; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/house-robber-iii/solution/shu-xing-dp-ru-men-wen-ti-by-liweiwei1419/ + * 337. 打家劫舍 III + * 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 + * 计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。 + * 示例 1: + * 输入: [3,2,3,null,3,null,1] + * 3 + * / \ + * 2 3 + * \ \ + * 3 1 + * 输出: 7 + * 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7. + * 示例 2: + * 输入: [3,4,5,1,3,null,1] + * 3 + * / \ + * 4 5 + * / \ \ + * 1 3 1 + * 输出: 9 + * 解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9. + * + * @Auther: zhunn + * @Date: 2020/11/6 18:35 + * @Description: 打家劫舍 III:1-树的后序遍历; + */ +public class Solution { + + public int rob(TreeNode root) { + int[] res = dfs(root); // 树的后序遍历 + return Math.max(res[0], res[1]); + } + + private int[] dfs(TreeNode root) { + if (root == null) { + return new int[]{0, 0}; + } + + int[] left = dfs(root.left); // 分类讨论的标准是:当前结点偷或者不偷 + int[] right = dfs(root.right); // 由于需要后序遍历,所以先计算左右子结点,然后计算当前结点的状态值 + + + int[] res = new int[2]; // dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷; dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷 + res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); + res[1] = root.val + left[0] + right[0]; + return res; + } + + @Test + public void test() { + TreeNode left = new TreeNode(2, null, new TreeNode(3)); + TreeNode right = new TreeNode(3, null, new TreeNode(1)); + TreeNode root = new TreeNode(3, left, right); + System.out.println(rob(root)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test343/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test343/Solution.java new file mode 100644 index 0000000..95c3aee --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test343/Solution.java @@ -0,0 +1,49 @@ +package com.chen.algorithm.znn.dynamic.test343; + +import org.junit.Test; + +/** + * 343. 整数拆分 + * 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 + * 示例 1: + * 输入: 2 + * 输出: 1 + * 解释: 2 = 1 + 1, 1 × 1 = 1。 + * 示例 2: + * 输入: 10 + * 输出: 36 + * 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 + * + * @Auther: zhunn + * @Date: 2020/11/5 18:46 + * @Description: 整数拆分 + */ +public class Solution { + + public int integerBreak(int n) { + if (n < 2) { + return n; + } + + int[] dp = new int[n + 1]; // dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积 + dp[0] = 0; + dp[1] = 1; + + for (int i = 2; i <= n; i++) { + for (int j = 1; j < i; j++) { + // 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j); + // 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]。 + dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, (i - j) * j)); + } + } + return dp[n]; + } + + + @Test + public void test() { + System.out.println(integerBreak(2)); + System.out.println(integerBreak(10)); + System.out.println(integerBreak(58)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test416/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test416/Solution.java new file mode 100644 index 0000000..242b8ae --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test416/Solution.java @@ -0,0 +1,156 @@ +package com.chen.algorithm.znn.dynamic.test416; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/ + * https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/fen-ge-deng-he-zi-ji-by-leetcode-solution/ + * 416. 分割等和子集 + * 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 + * 注意: + * 每个数组中的元素不会超过 100 + * 数组的大小不会超过 200 + * 示例 1: + * 输入: [1, 5, 11, 5] + * 输出: true + * 解释: 数组可以分割成 [1, 5, 5] 和 [11]. + * 示例 2: + * 输入: [1, 2, 3, 5] + * 输出: false + * 解释: 数组不能分割成两个元素和相等的子集. + * + * @Auther: zhunn + * @Date: 2020/11/5 18:27 + * @Description: 分割等和子集:1-动态规划, 0-1背包问题;2-动态规划-剪枝(推荐);3-动态规划-优化空间(推荐2演化到3) + */ +public class Solution { + + /** + * 1-动态规划, 0-1背包问题 + * + * @param nums + * @return + */ + public boolean canPartition(int[] nums) { + if (nums == null || nums.length == 0) { + return false; + } + + int len = nums.length; + int sum = 0; + for (int i = 0; i < len; i++) { + sum += nums[i]; + } + if ((sum & 1) == 1) { // 特判:如果是奇数,就不符合要求 + return false; + } + + int target = sum / 2; + boolean[][] dp = new boolean[len][target + 1]; // 创建二维数组,行:物品索引,列:容量(包括0) + //dp[0][0] = true; // 初始化成为 true 虽然不符合状态定义,但是从状态转移来说是完全可以的 + + if (nums[0] <= target) { // 先填表格第0行,第 1 个数只能让容积为它自己的背包恰好装满 + dp[0][nums[0]] = true; + } + + for (int i = 1; i < len; i++) { //再填表格后面几行 + for (int j = 0; j <= target; j++) { + dp[i][j] = dp[i - 1][j]; //如果不选取nums[i],则dp[i][j]=dp[i−1][j]; + if (nums[i] <= j) { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; + } + } + } + + return dp[len - 1][target]; + } + + /** + * 2-动态规划-剪枝 + * + * @param nums + * @return + */ + public boolean canPartition2(int[] nums) { + if (nums == null || nums.length == 0) { + return false; + } + + int len = nums.length; + int sum = 0; + for (int num : nums) { + sum += num; + } + if ((sum & 1) == 1) { + return false; + } + + int target = sum / 2; + boolean[][] dp = new boolean[len][target + 1]; + dp[0][0] = true; // 初始化成为 true 虽然不符合状态定义,但是从状态转移来说是完全可以的 + + if (nums[0] <= target) { + dp[0][nums[0]] = true; + } + + for (int i = 1; i < len; i++) { + for (int j = 0; j <= target; j++) { + dp[i][j] = dp[i - 1][j]; //如果不选取nums[i],则dp[i][j]=dp[i−1][j]; + if (nums[i] <= j) { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; //如果选取nums[i],则dp[i][j]=dp[i−1][j−nums[i]]。选取后j - nums[i]+nums[i]刚好等于j + } + + if (dp[i][target]) { // 由于状态转移方程的特殊性,提前结束,可以认为是剪枝操作 + return true; + } + } + } + return dp[len - 1][target]; + } + + /** + * 3-动态规划-优化空间 + * + * @param nums + * @return + */ + public boolean canPartition3(int[] nums) { + if (nums == null || nums.length == 0) { + return false; + } + + int len = nums.length; + int sum = 0; + for (int num : nums) { + sum += num; + } + if ((sum & 1) == 1) { + return false; + } + + int target = sum / 2; + boolean[] dp = new boolean[target + 1]; + dp[0] = true; + + if (nums[0] <= target) { + dp[nums[0]] = true; + } + + for (int i = 1; i < len; i++) { + for (int j = target; j >= nums[i]; j--) { // 使用一维表格,从后向前 + if (dp[target]) { + return true; + } + dp[j] = dp[j] || dp[j - nums[i]]; + } + } + return dp[target]; + } + + @Test + public void test() { + int[] nums = {1, 5, 11, 5}; + System.out.println(canPartition2(nums)); + System.out.println(canPartition3(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test474/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test474/Solution.java new file mode 100644 index 0000000..191eed5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test474/Solution.java @@ -0,0 +1,103 @@ +package com.chen.algorithm.znn.dynamic.test474; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/ones-and-zeroes/solution/dong-tai-gui-hua-zhuan-huan-wei-0-1-bei-bao-wen-ti/ + * 474. 一和零 + * 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 + * 请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。 + * 如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。 + * 示例 1: + * 输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 + * 输出:4 + * 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 + * 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。 + * 示例 2: + * 输入:strs = ["10", "0", "1"], m = 1, n = 1 + * 输出:2 + * 解释:最大的子集是 {"0", "1"} ,所以答案是 2 。 + * + * @Auther: zhunn + * @Date: 2020/11/6 14:54 + * @Description: 一和零:1-动态规划,0-1背包问题;2-动态规划优化空间(滚动数组,从后往前) + */ +public class Solution { + + //public int findMaxForm(String[] strs, int m, int n) { + // int[][] dp = new int[m + 1][n + 1]; + // for (String s : strs) { + // int[] count = countzeroesones(s); + // for (int zeroes = m; zeroes >= count[0]; zeroes--) + // for (int ones = n; ones >= count[1]; ones--) + // dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]); + // } + // return dp[m][n]; + //} + + public int[] countzeroesones(String s) { + int[] cnt = new int[2]; + for (int i = 0; i < s.length(); i++) { + cnt[s.charAt(i) - '0']++; + } + return cnt; + } + + /** + * 1-动态规划,0-1背包问题 + * 定义状态:尝试题目问啥,就把啥定义成状态。dp[i][j][k] 表示输入字符串在子区间 [0, i] 能够使用 j 个 0 和 k 个 1 的字符串的最大数量。 + * + * @param strs + * @param m 0的个数 + * @param n 1的个数 + * @return + */ + public int findMaxForm1(String[] strs, int m, int n) { + int len = strs.length; + int[][][] dp = new int[len + 1][m + 1][n + 1]; + + for (int i = 1; i <= len; i++) { + int[] cnt = countzeroesones(strs[i - 1]); // 注意:有一位偏移 + for (int j = 0; j <= m; j++) { + for (int k = 0; k <= n; k++) { + dp[i][j][k] = dp[i - 1][j][k]; // 先把上一行抄下来 + + if (j >= cnt[0] && k >= cnt[1]) { // 超过最大0和1的限制,不选此字符串 + dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - cnt[0]][k - cnt[1]] + 1); // dp[i - 1][j][k],不选;dp[i][j - cnt[0]][k - cnt[1]] + 1,选 + } + } + } + } + return dp[len][m][n]; + } + + /** + * 2-动态规划优化空间(滚动数组,从后往前) + * + * @param strs + * @param m 0的个数 + * @param n 1的个数 + * @return + */ + public int findMaxForm2(String[] strs, int m, int n) { + int[][] dp = new int[m + 1][n + 1]; + //dp[0][0] = 0; + + for (String str : strs) { + int[] cnt = countzeroesones(str); + for (int i = m; i >= cnt[0]; i--) { + for (int j = n; j >= cnt[1]; j--) { + dp[i][j] = Math.max(dp[i][j], dp[i - cnt[0]][j - cnt[1]] + 1); + } + } + } + return dp[m][n]; + } + + @Test + public void test() { + String[] strs = {"10", "0001", "111001", "1", "0"}; + System.out.println(findMaxForm1(strs, 5, 3)); + System.out.println(findMaxForm2(strs, 5, 3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test494/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test494/Solution.java new file mode 100644 index 0000000..a54f3be --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test494/Solution.java @@ -0,0 +1,141 @@ +package com.chen.algorithm.znn.dynamic.test494; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/target-sum/solution/huan-yi-xia-jiao-du-ke-yi-zhuan-huan-wei-dian-xing/ + * 494. 目标和 + * 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。 + * 返回可以使最终数组和为目标数 S 的所有添加符号的方法数。 + * 示例: + * 输入:nums: [1, 1, 1, 1, 1], S: 3 + * 输出:5 + * 解释: + * -1+1+1+1+1 = 3 + * +1-1+1+1+1 = 3 + * +1+1-1+1+1 = 3 + * +1+1+1-1+1 = 3 + * +1+1+1+1-1 = 3 + * 一共有5种方法让最终目标和为3。 + * + * @Auther: zhunn + * @Date: 2020/11/6 16:03 + * @Description: 目标和:1-枚举;2-动态规划,0-1背包问题;3-动态规划-优化空间 + */ +public class Solution { + + + int count = 0; + + /** + * 1-暴力枚举 + * 时间复杂度:O(2^n) + * 空间复杂度:O(n) + * + * @param nums + * @param S + * @return + */ + public int findTargetSumWays(int[] nums, int S) { + if (nums == null || nums.length == 0) { + return count; + } + caculate(nums, 0, 0, S); + return count; + } + + private void caculate(int[] nums, int i, int sum, int S) { + if (i == nums.length) { + if (sum == S) count++; + return; + } + caculate(nums, i + 1, sum + nums[i], S); + caculate(nums, i + 1, sum - nums[i], S); + } + + /** + * 2-动态规划,0-1背包问题 + * 时间复杂度:O(n*sum),其中 N 是数组 nums 的长度。 + * 空间复杂度:O(n*sum) + * dp[i][j]: i(1 ~ len)表示遍历(不一定选)了 i 个元素,j(0 ~ sum) 表示它们的和 + *

+ * 初始化:0个元素,和为0,情况有1种(因为没有元素,所以只能不选,和为0):dp[0][0] = 1 + * 不选当前元素,即"部分和"(即j)与之前相同:dp[i][j] = dp[i - 1][j] + * 可选可不选,不选的情况是2,选当前元素的话则之前的状态应为dp[i - 1][j - num](这里的num指的是当前元素的值,即代码中的nums[i - 1]),二者相加,即:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - num] + * + * @param nums + * @param S + * @return + */ + public int findTargetSumWays2(int[] nums, int S) { + int sum = 0; + for (int num : nums) { + sum += num; + } + if (((sum + S) & 1) == 1) { // 背包容量为整数,sum + S为奇数的话不满足要求 + return 0; + } + if (S > sum) { // 目标和不可能大于总和 + return 0; + } + + int target = (sum + S) >> 1; + int len = nums.length; + int[][] dp = new int[len + 1][target + 1]; + dp[0][0] = 1; + + // 如果迭代部分 j 的初值赋 1 的话,就要先初始化 j = 0 的情况 + /* int count = 1; + for (int i = 1; i <= len; i++) { + // ±0 均可 + if (nums[i - 1] == 0) { + count *= 2; + } + dp[i][0] = count; + } */ + + // 01背包 + // i(1 ~ len)表示遍历(不一定选)了 i 个元素,j(0 ~ sum) 表示它们的和 + for (int i = 1; i <= len; i++) { + for (int j = 0; j <= target; j++) { + dp[i][j] = dp[i - 1][j]; // 装不下(不选当前元素) + if (nums[i - 1] <= j) { // 可装可不装(当前元素可选可不选) + dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]; + } + } + } + + return dp[len][target]; + } + + public int findTargetSumWays3(int[] nums, int S) { + int sum = 0; + for (int num : nums) { + sum += num; + } + if (((sum + S) & 1) == 1) { // 背包容量为整数,sum+S为奇数的话不满足要求 + return 0; + } + if (S > sum) { // 目标和不可能大于总和 + return 0; + } + + int target = (sum + S) >> 1; + int[] dp = new int[target + 1]; + dp[0] = 1; + + for (int num : nums) { + for (int i = target; i >= num; i--) { + dp[i] = dp[i] + dp[i - num]; + } + } + return dp[target]; + } + + @Test + public void test() { + int[] nums = {1, 1, 1, 1, 1}; + System.out.println(findTargetSumWays2(nums, 3)); + System.out.println(findTargetSumWays3(nums, 3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test518/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test518/Solution.java new file mode 100644 index 0000000..b9357a3 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test518/Solution.java @@ -0,0 +1,89 @@ +package com.chen.algorithm.znn.dynamic.test518; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/coin-change-2/solution/ling-qian-dui-huan-iihe-pa-lou-ti-wen-ti-dao-di-yo/ + * https://leetcode-cn.com/problems/coin-change-2/solution/dong-tai-gui-hua-wan-quan-bei-bao-wen-ti-by-liweiw/ + * 官方解答:https://leetcode-cn.com/problems/coin-change-2/solution/ling-qian-dui-huan-ii-by-leetcode/ + * 518. 零钱兑换 II + * 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 + * 示例 1: + * 输入: amount = 5, coins = [1, 2, 5] + * 输出: 4 + * 解释: 有四种方式可以凑成总金额: + * 5=5 + * 5=2+2+1 + * 5=2+1+1+1 + * 5=1+1+1+1+1 + * 示例 2: + * 输入: amount = 3, coins = [2] + * 输出: 0 + * 解释: 只用面额2的硬币不能凑成总金额3。 + * 示例 3: + * 输入: amount = 10, coins = [10] + * 输出: 1 + * + * @Auther: zhunn + * @Date: 2020/11/5 9:52 + * @Description: 零钱兑换 II:1-动态规划 + * 返回所有组合数 + * 组合问题,交换循环顺序就是排列问题(爬楼梯) + */ +public class Solution { + + /** + * 推荐 + * + * @param amount + * @param coins + * @return + */ + public int change(int amount, int[] coins) { + if (coins == null || coins.length == 0) { + if (amount == 0) { + return 1; + } + return 0; + } + + int[] dp = new int[amount + 1]; + dp[0] = 1; // amount=0时,有一种组合 0 + + for (int coin : coins) { + for (int x = coin; x <= amount; x++) { + dp[x] = dp[x] + dp[x - coin]; + } + } + return dp[amount]; + } + + //public int change2(int amount, int[] coins) { + // if (coins == null || coins.length == 0) { + // if (amount == 0) { + // return 1; + // } + // return 0; + // } + // + // int[] dp = new int[amount + 1]; + // dp[0] = 1; + // + // for (int coin : coins) { // 枚举硬币 + // for (int x = 1; x <= amount; x++) { // 枚举金额 + // if (coin > x) { + // continue; // coin不能大于金额 + // } + // dp[x] = dp[x] + dp[x - coin]; + // } + // } + // return dp[amount]; + //} + + @Test + public void test() { + int amount = 5; + int[] coins = {1, 2, 5}; + System.out.println(change(amount, coins)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test53/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test53/Solution.java new file mode 100644 index 0000000..5062535 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test53/Solution.java @@ -0,0 +1,66 @@ +package com.chen.algorithm.znn.dynamic.test53; + +import org.junit.Test; + +/** + * 53. 最大子序和 + * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + * 示例: + * 输入: [-2,1,-3,4,-1,2,1,-5,4] + * 输出: 6 + * 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 + * + * @Auther: zhunn + * @Date: 2020/11/4 18:56 + * @Description: 最大子序和:1-动态规划;2-动态规划优化空间 + */ +public class Solution { + + /** + * 1-动态规划 + * + * @param nums + * @return + */ + public int maxSubArray(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int len = nums.length; + int[] dp = new int[len]; + dp[0] = nums[0]; + int res = nums[0]; + + for (int i = 1; i < len; i++) { + dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); + res = Math.max(res, dp[i]); + } + return res; + } + + /** + * 2-动态规划优化空间 + * + * @param nums + * @return + */ + public int maxSubArray2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int pre = nums[0], res = nums[0]; + for (int i = 1; i < nums.length; i++) { + pre = Math.max(pre + nums[i], nums[i]); + res = Math.max(res, pre); + } + return res; + } + + @Test + public void test() { + int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; + System.out.println(maxSubArray2(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test62/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test62/Solution.java new file mode 100644 index 0000000..e4502aa --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test62/Solution.java @@ -0,0 +1,57 @@ +package com.chen.algorithm.znn.dynamic.test62; + +import org.junit.Test; + +/** + * 62. 不同路径 + * 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 + * 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 + * 问总共有多少条不同的路径? + * 示例 1: + * 输入: m = 3, n = 2 + * 输出: 3 + * 解释: + * 从左上角开始,总共有 3 条路径可以到达右下角。 + * 1. 向右 -> 向右 -> 向下 + * 2. 向右 -> 向下 -> 向右 + * 3. 向下 -> 向右 -> 向右 + * 示例 2: + * 输入: m = 7, n = 3 + * 输出: 28 + * + * @Auther: zhunn + * @Date: 2020/11/5 16:05 + * @Description: 不同路径:1-动态规划 + * 返回共有几条路径 + */ +public class Solution { + + public int uniquePaths(int m, int n) { + if (m == 0 || n == 0) { + return 0; + } + //if (m == 1 || n == 1) { + // return 1; + //} + + int[][] dp = new int[m][n]; //定义状态转移方程 + for (int i = 0; i < m; i++) { //边界初始化 + dp[i][0] = 1; + } + for (int j = 0; j < n; j++) { + dp[0][j] = 1; + } + + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; + } + + @Test + public void test() { + System.out.println(uniquePaths(7, 3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test64/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test64/Solution.java new file mode 100644 index 0000000..485e4d0 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test64/Solution.java @@ -0,0 +1,112 @@ +package com.chen.algorithm.znn.dynamic.test64; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/minimum-path-sum/solution/zui-xiao-lu-jing-he-dong-tai-gui-hua-gui-fan-liu-c/ + * 64. 最小路径和 + * 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + * 说明:每次只能向下或者向右移动一步。 + * 示例 1: + * 输入:grid = [[1,3,1],[1,5,1],[4,2,1]] + * 输出:7 + * 解释:因为路径 1→3→1→1→1 的总和最小。 + * 示例 2: + * 输入:grid = [[1,2,3],[4,5,6]] + * 输出:12 + * + * @Auther: zhunn + * @Date: 2020/11/5 11:33 + * @Description: 最小路径和:1-动态规划(推荐);2-动态规划-优化空间,使用原数组空间;3-动态规划-优化空间,不更改原数组(可忽略) + */ +public class Solution { + + /** + * 1-动态规划,官方解答(推荐) + * + * @param grid + * @return + */ + public int minPathSum(int[][] grid) { + if (grid == null || grid.length == 0) { + return 0; + } + + int rows = grid.length; + int cols = grid[0].length; + int[][] dp = new int[rows][cols]; // dp[i][j],i表示行,j表示列 + dp[0][0] = grid[0][0]; + + for (int i = 1; i < rows; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + for (int j = 1; j < cols; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + + for (int i = 1; i < rows; i++) { + for (int j = 1; j < cols; j++) { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; + } + } + return dp[rows - 1][cols - 1]; + } + + /** + * 2-动态规划-优化空间,使用原数组空间 + * + * @return + */ + public int minPathSum2(int[][] grid) { + if (grid == null || grid.length == 0) { + return 0; + } + + int rows = grid.length; + int cols = grid[0].length; + + //int[][] dp = new int[rows][cols]; // dp[i][j],i表示行,j表示列 + //dp[0][0] = grid[0][0]; + + for (int i = 1; i < rows; i++) { + grid[i][0] = grid[i - 1][0] + grid[i][0]; + } + for (int j = 1; j < cols; j++) { + grid[0][j] = grid[0][j - 1] + grid[0][j]; + } + + for (int i = 1; i < rows; i++) { + for (int j = 1; j < cols; j++) { + grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]; + } + } + return grid[rows - 1][cols - 1]; + } + + /** + * 3-动态规划-优化空间,不更改原数组 + * + * @param grid + * @return + */ + public int minPathSum3(int[][] grid) { + int len = grid[0].length; + int[] dp = new int[len]; + dp[0] = grid[0][0]; + for (int i = 1; i < len; i++) + dp[i] = dp[i - 1] + grid[0][i]; + for (int i = 1; i < grid.length; i++) { + dp[0] = dp[0] + grid[i][0]; + for (int j = 1; j < len; j++) + dp[j] = Math.min(dp[j - 1] + grid[i][j], dp[j] + grid[i][j]); + } + return dp[len - 1]; + } + + @Test + public void testCase() { + int[][] nums = {{1, 3, 1}, {1, 5, 1}, {4, 2, 1}}; + + System.out.println(minPathSum(nums)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test70/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test70/Solution.java new file mode 100644 index 0000000..e9b5de5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test70/Solution.java @@ -0,0 +1,113 @@ +package com.chen.algorithm.znn.dynamic.test70; + +import org.junit.Test; + +/** + * 70. 爬楼梯 + * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 + * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? + * 注意:给定 n 是一个正整数。 + * 示例 1: + * 输入: 2 + * 输出: 2 + * 解释: 有两种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + * 2. 2 阶 + * 示例 2: + * 输入: 3 + * 输出: 3 + * 解释: 有三种方法可以爬到楼顶。 + * 1. 1 阶 + 1 阶 + 1 阶 + * 2. 1 阶 + 2 阶 + * 3. 2 阶 + 1 阶 + * //////////////////////////////////////////// + * 不难发现,这个问题可以被分解为一些包含最优子结构的子问题,即它的最优解可以从其子问题的最优解来有效地构建,我们可以使用动态规划来解决这一问题。 + * 第 i 阶可以由以下两种方法得到: + * 在第(i−1) 阶后向上爬 1 阶。 + * 在第(i−2) 阶后向上爬 2 阶。 + * 所以到达第 i 阶的方法总数就是到第(i−1) 阶和第 (i−2) 阶的方法数之和。 + * 令 dp[i] 表示能到达第 i 阶的方法总数: + * dp[i]=dp[i-1]+dp[i-2] + * 在上述方法中,我们使用 dp 数组,其中 dp[i]=dp[i-1]+dp[i-2]。可以很容易通过分析得出 dp[i] 其实就是第 i 个斐波那契数。 + * Fib(n)=Fib(n-1)+Fib(n-2) + * 现在我们必须找出以 1 和 2 作为第一项和第二项的斐波那契数列中的第 n 个数,也就是说 Fib(1)=1Fib(1)=1 且 Fib(2)=2Fib(2)=2 + * + * @Auther: zhunn + * @Date: 2020/11/2 11:03 + * @Description: 爬楼梯:1-迭代/递归(斐波那契数列);2-动态规划 + */ +public class Solution { + + /** + * 1-迭代/递归(斐波那契数列) + * + * @param n + * @return + */ + public int climbStairs(int n) { + if (n <= 0) { + return 0; + } + if (n == 1) { + return 1; + } + if (n == 2) { + return 2; + } + int first = 1, second = 2, third = 0; + for (int i = 3; i <= n; i++) { + third = first + second; + first = second; + second = third; + } + return third; + } + + /** + * 2-动态规划 + * + * @param n + * @return + */ + public int climbStairs2(int n) { + if (n <= 0) { + return 0; + } + int[] dp = new int[n + 1]; + dp[1] = 1; + dp[2] = 2; + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + @Test + public void test() { + int res = climbStairs2(10); + System.out.println(res); + int res3 = climbStairs3(10); + System.out.println(res3); + } + + public int climbStairs3(int n) { + if (n <= 0) { + return 0; + } + if (n == 1) { + return 1; + } + if (n == 2) { + return 2; + } + + int[] dp = new int[n + 1]; + dp[0] = 0; + dp[1] = 1; + dp[2] = 2; + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test714/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test714/Solution.java new file mode 100644 index 0000000..8ad0106 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test714/Solution.java @@ -0,0 +1,87 @@ +package com.chen.algorithm.znn.dynamic.test714; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/solution/dong-tai-gui-hua-by-liweiwei1419-6/ + * 714. 买卖股票的最佳时机含手续费 + * 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 + * 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + * 返回获得利润的最大值。 + * 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。 + * 示例 1: + * 输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 + * 输出: 8 + * 解释: 能够达到的最大利润: + * 在此处买入 prices[0] = 1 + * 在此处卖出 prices[3] = 8 + * 在此处买入 prices[4] = 4 + * 在此处卖出 prices[5] = 9 + * 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8. + * + * @Auther: zhunn + * @Date: 2020/11/3 22:04 + * @Description: 买卖股票的最佳时机含手续费:1-动态规划,2-动态规划-优化空间 + * 注意:人为规定在买入股票的时候扣除手续费 + */ +public class Solution { + + /** + * 1-动态规划 + * + * @param prices + * @return + */ + public int maxProfit(int[] prices, int fee) { + if (prices == null || prices.length < 2) { + return 0; + } + + // dp[i][j] 表示 [0, i] 区间内,到第 i 天(从 0 开始)状态为 j 时的最大收益' + // j = 0 表示不持股,j = 1 表示持股 + // 并且规定在买入股票的时候,扣除手续费 + + int len = prices.length; + int[][] dp = new int[len][2]; // 定义状态转移方程 + // 初始化值 + dp[0][0] = 0; + dp[0][1] = -prices[0] - fee; + + for (int i = 1; i < len; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee); + } + return dp[len - 1][0]; + } + + /** + * 2-动态规划-优化空间 + * + * @param prices + * @param fee + * @return + */ + public int maxProfit2(int[] prices, int fee) { + if (prices == null || prices.length < 2) { + return 0; + } + + int len = prices.length; + int[] dp = new int[2]; + dp[0] = 0; + dp[1] = -prices[0] - fee; + for (int i = 1; i < len; i++) { + dp[0] = Math.max(dp[0], dp[1] + prices[i]); + dp[1] = Math.max(dp[1], dp[0] - prices[i] - fee); + } + return dp[0]; + } + + @Test + public void test() { + int[] prices = {1, 3, 2, 8, 4, 9}; + int fee = 2; + System.out.println(maxProfit(prices, fee)); + System.out.println(maxProfit2(prices, fee)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/dynamic/test72/Solution.java b/src/main/java/com/chen/algorithm/znn/dynamic/test72/Solution.java new file mode 100644 index 0000000..4e5af7f --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/dynamic/test72/Solution.java @@ -0,0 +1,69 @@ +package com.chen.algorithm.znn.dynamic.test72; + +import org.junit.Test; + +/** + * 官方:https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/ + * https://leetcode-cn.com/problems/edit-distance/solution/zi-di-xiang-shang-he-zi-ding-xiang-xia-by-powcai-3/ + * 72. 编辑距离 + * 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 + * 你可以对一个单词进行如下三种操作: + * 插入一个字符 + * 删除一个字符 + * 替换一个字符 + * 示例 1: + * 输入:word1 = "horse", word2 = "ros" + * 输出:3 + * 解释: + * horse -> rorse (将 'h' 替换为 'r') + * rorse -> rose (删除 'r') + * rose -> ros (删除 'e') + * 示例 2: + * 输入:word1 = "intention", word2 = "execution" + * 输出:5 + * 解释: + * intention -> inention (删除 't') + * inention -> enention (将 'i' 替换为 'e') + * enention -> exention (将 'n' 替换为 'x') + * exention -> exection (将 'n' 替换为 'c') + * exection -> execution (插入 'u') + * + * @Auther: zhunn + * @Date: 2020/11/5 15:09 + * @Description: 编辑距离:1-动态规划 + */ +public class Solution { + + public int minDistance(String word1, String word2) { + int m = word1 == null ? 0 : word1.length(); + int n = word2 == null ? 0 : word2.length(); + if (m == 0 || n == 0) { // m*n == 0,有一个字符串为空串 + return m + n; + } + + int[][] dp = new int[m + 1][n + 1]; // 多开一行一列是为了保存边界条件,即字符长度为 0 的情况,这一点在字符串的动态规划问题中比较常见 + for (int i = 0; i <= m; i++) { // 边界状态初始化:当 word 2 长度为 0 时,将 word1 的全部删除 + dp[i][0] = i; + } + for (int j = 0; j <= n; j++) { // 当 word1 长度为 0 时,就插入所有 word2 的字符 + dp[0][j] = j; + } + + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (word1.charAt(i - 1) == word2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1; + } + } + } + return dp[m][n]; + } + + @Test + public void test() { + System.out.println(minDistance("horse", "ros")); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/Test.java b/src/main/java/com/chen/algorithm/znn/frequency/Test.java new file mode 100644 index 0000000..998236c --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.frequency; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 高频常考算法 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCache.java b/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCache.java new file mode 100644 index 0000000..1a70e0f --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCache.java @@ -0,0 +1,120 @@ +package com.chen.algorithm.znn.frequency.test146; + + +import java.util.HashMap; +import java.util.Map; + +/** + * https://leetcode-cn.com/problems/lru-cache/ + * 146. LRU缓存机制 + * 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 + * 实现 LRUCache 类: + * LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 + * int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 + * void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 + * 进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作? + * 示例: + * 输入 + * ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] + * [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] + * 输出 + * [null, null, null, 1, null, -1, null, -1, 3, 4] + * 解释 + * LRUCache lRUCache = new LRUCache(2); + * lRUCache.put(1, 1); // 缓存是 {1=1} + * lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} + * lRUCache.get(1); // 返回 1 + * lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} + * lRUCache.get(2); // 返回 -1 (未找到) + * lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} + * lRUCache.get(1); // 返回 -1 (未找到) + * lRUCache.get(3); // 返回 3 + * lRUCache.get(4); // 返回 4 + * + * @Auther: zhunn + * @Date: 2020/11/08 14:47 + * @Description: LRU缓存机制:哈希表 + 双向链表(手动实现) + */ +public class LRUCache { + + class CacheNode { + private int key; + private int val; + private CacheNode pre; + private CacheNode next; + + public CacheNode() { + } + + public CacheNode(int key, int val) { + this.key = key; + this.val = val; + } + } + + private Map cache = new HashMap<>(); + private int size; + private int capacity; + private CacheNode head, tail; + + public LRUCache(int capacity) { + this.capacity = capacity; + this.size = 0; + this.head = new CacheNode(); + this.tail = new CacheNode(); + head.next = tail; + tail.pre = head; + } + + public int get(int key) { + CacheNode node = cache.get(key); + if (node == null) { + return -1; + } + moveToHead(node); + return node.val; + } + + public void put(int key, int value) { + CacheNode node = cache.get(key); + if (node != null) { + node.val = value; + moveToHead(node); + return; + } + + CacheNode newNode = new CacheNode(key, value); + cache.put(key, newNode); + addToHead(newNode); + size++; + + if (size > capacity) { + CacheNode tail = removeTail(); + cache.remove(tail.key); + size--; + } + } + + public void addToHead(CacheNode node) { + node.pre = head; + node.next = head.next; + head.next.pre = node; + head.next = node; + } + + public void removeNode(CacheNode node) { + node.pre.next = node.next; + node.next.pre = node.pre; + } + + public void moveToHead(CacheNode node) { + removeNode(node); + addToHead(node); + } + + public CacheNode removeTail() { + CacheNode res = tail.pre; + removeNode(res); + return res; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCacheMap.java b/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCacheMap.java new file mode 100644 index 0000000..a4bb03e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test146/LRUCacheMap.java @@ -0,0 +1,32 @@ +package com.chen.algorithm.znn.frequency.test146; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @Auther: zhunn + * @Date: 2020/11/08 14:47 + * @Description: LRU缓存机制:1-使用java的LinkedHashMap + */ +public class LRUCacheMap extends LinkedHashMap { + + private int capacity; + + public LRUCacheMap(int capacity) { + super(capacity, 0.75f, true); + this.capacity = capacity; + } + + public int get(int key) { + return super.getOrDefault(key, -1); + } + + public void put(int key, int value) { + super.put(key, value); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test208/Trie.java b/src/main/java/com/chen/algorithm/znn/frequency/test208/Trie.java new file mode 100644 index 0000000..a2e33b5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test208/Trie.java @@ -0,0 +1,79 @@ +package com.chen.algorithm.znn.frequency.test208; + +/** + * https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shu-ju-jie-gou-she-ji-zhi-shi-xian-trie-qian-zhui-/ + * 208. 实现 Trie (前缀树) + * 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 + * 示例: + * Trie trie = new Trie(); + * trie.insert("apple"); + * trie.search("apple"); // 返回 true + * trie.search("app"); // 返回 false + * trie.startsWith("app"); // 返回 true + * trie.insert("app"); + * trie.search("app"); // 返回 true + * + * @Auther: zhunn + * @Date: 2020/11/08 14:46 + * @Description: 实现 Trie (前缀树) + */ +public class Trie { + + public Trie root; + public Trie[] children = new Trie[26]; + public char val; + public boolean isWord; + + public Trie() { + root = new Trie(' '); + } + + public Trie(char val) { + this.val = val; + } + + /** + * Inserts a word into the trie. + */ + public void insert(String word) { + Trie ws = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (ws.children[c - 'a'] == null) { + ws.children[c - 'a'] = new Trie(c); + } + ws = ws.children[c - 'a']; + } + ws.isWord = true; + } + + /** + * Returns if the word is in the trie. + */ + public boolean search(String word) { + Trie ws = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (ws.children[c - 'a'] == null) { + return false; + } + ws = ws.children[c - 'a']; + } + return ws.isWord; + } + + /** + * Returns if there is any word in the trie that starts with the given prefix. + */ + public boolean startsWith(String prefix) { + Trie ws = root; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (ws.children[c - 'a'] == null) { + return false; + } + ws = ws.children[c - 'a']; + } + return true; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test48/Solution.java b/src/main/java/com/chen/algorithm/znn/frequency/test48/Solution.java new file mode 100644 index 0000000..34b9820 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test48/Solution.java @@ -0,0 +1,94 @@ +package com.chen.algorithm.znn.frequency.test48; + +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; + +/** + * 48. 旋转图像 + * 给定一个 n × n 的二维矩阵表示一个图像。 + * 将图像顺时针旋转 90 度。 + * 说明: + * 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。 + * 示例 1: + * 给定 matrix = + * [ + * [1,2,3], + * [4,5,6], + * [7,8,9] + * ], + * 原地旋转输入矩阵,使其变为: + * [ + * [7,4,1], + * [8,5,2], + * [9,6,3] + * ] + * 示例 2: + * 给定 matrix = + * [ + * [ 5, 1, 9,11], + * [ 2, 4, 8,10], + * [13, 3, 6, 7], + * [15,14,12,16] + * ], + * 原地旋转输入矩阵,使其变为: + * [ + * [15,13, 2, 5], + * [14, 3, 4, 1], + * [12, 6, 8, 9], + * [16, 7,10,11] + * ] + * + * @Auther: zhunn + * @Date: 2020/9/16 18:22 + * @Description: 顺时针旋转图像90° + */ +public class Solution { + + /** + * 顺时针旋转90°,先转置再反转每一行 + * 逆时针旋转90°,先转置再反转每一列 + * @param matrix + */ + public void rotate(int[][] matrix) { + + int n = matrix.length; + int m = matrix[0].length; + + //先转置 + for (int i = 0; i < n; i++) { + for (int j = i; j < m; j++) { + int temp = matrix[j][i]; + matrix[j][i] = matrix[i][j]; + matrix[i][j] = temp; + System.out.println("-----" + JSONObject.toJSONString(matrix)); + } + } + + // 反转每一行 + for (int i = 0; i < n; i++) { + for (int j = 0; j < m / 2; j++) { + int temp = matrix[i][j]; + matrix[i][j] = matrix[i][m - 1 - j]; + matrix[i][m - 1 - j] = temp; + } + } + } + + + @Test + public void testCase() { + + int[][] matrix = new int[3][3]; + + matrix[0] = new int[]{1, 2, 3}; + matrix[1] = new int[]{4, 5, 6}; + matrix[2] = new int[]{7, 8, 9}; + + rotate(matrix); + + System.out.println("last:" + JSONObject.toJSONString(matrix)); + + } + + +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test7/Solution.java b/src/main/java/com/chen/algorithm/znn/frequency/test7/Solution.java new file mode 100644 index 0000000..d1b962f --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test7/Solution.java @@ -0,0 +1,40 @@ +package com.chen.algorithm.znn.frequency.test7; + +import org.junit.Test; + +/** + * 7. 整数反转 + * 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 + * 示例 1: + * 输入: 123 + * 输出: 321 + * 示例 2: + * 输入: -123 + * 输出: -321 + * 示例 3: + * 输入: 120 + * 输出: 21 + * + * @Auther: zhunn + * @Date: 2020/9/16 18:20 + * @Description: 整数反转 + */ +public class Solution { + + public int reverse(int x) { + int res = 0; + while (x != 0) { + res = res * 10 + x % 10; + x /= 10; + } + if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) { + return 0; + } + return res; + } + + @Test + public void test() { + System.out.println(reverse(123)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/frequency/test9/Solution.java b/src/main/java/com/chen/algorithm/znn/frequency/test9/Solution.java new file mode 100644 index 0000000..1c7764b --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/frequency/test9/Solution.java @@ -0,0 +1,51 @@ +package com.chen.algorithm.znn.frequency.test9; + +import org.junit.Test; + +/** + * 9. 回文数 + * 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 + * 示例 1: + * 输入: 121 + * 输出: true + * 示例 2: + * 输入: -121 + * 输出: false + * 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 + * 示例 3: + * 输入: 10 + * 输出: false + * 解释: 从右向左读, 为 01 。因此它不是一个回文数。 + * + * @Auther: zhunn + * @Date: 2020/9/16 18:22 + * @Description: 回文数 + */ +public class Solution { + + public boolean isPalindrome(int x) { + // 特殊情况: + // 当 x < 0 时,x 不是回文数。 + // 同样地,如果数字的最后一位是 0,为了使该数字为回文, + // 则其第一位数字也应该是 0 + // 只有 0 满足这一属性 + if (x < 0 || (x != 0 && x % 10 == 0)) return false; + int res = 0; + while (x > res) { + res = res * 10 + x % 10; + x /= 10; + } + // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。 + return res == x || res / 10 == x; + } + + @Test + public void test() { + System.out.println(isPalindrome(-121)); + System.out.println(isPalindrome(0)); + System.out.println(isPalindrome(1000)); + System.out.println(isPalindrome(12321)); + System.out.println(isPalindrome(1221)); + System.out.println(isPalindrome(1231)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/greedy/Test.java b/src/main/java/com/chen/algorithm/znn/greedy/Test.java new file mode 100644 index 0000000..7309c26 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/greedy/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.greedy; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 贪心算法相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/greedy/test122/Solution.java b/src/main/java/com/chen/algorithm/znn/greedy/test122/Solution.java new file mode 100644 index 0000000..9799995 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/greedy/test122/Solution.java @@ -0,0 +1,92 @@ +package com.chen.algorithm.znn.greedy.test122; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/tan-xin-suan-fa-by-liweiwei1419-2/ + * 122. 买卖股票的最佳时机 II + * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * 示例 1: + * 输入: [7,1,5,3,6,4] + * 输出: 7 + * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + * 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + * 示例 2: + * 输入: [1,2,3,4,5] + * 输出: 4 + * 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + * 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + * 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + * 示例 3: + * 输入: [7,6,4,3,1] + * 输出: 0 + * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + * + * @Auther: zhunn + * @Date: 2020/11/2 13:50 + * @Description: 买卖股票的最佳时机 II:1-贪心算法;2-动态规划 + * 简述题意:(手里只能持有一支股票,可以买卖多次,必须在再次购买前出售掉之前的股票) + */ +public class Solution { + + /** + * 1-贪心算法(最优解) + * + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + + int max = 0; + for (int i = 1; i < prices.length; i++) { + int profit = prices[i] - prices[i - 1]; + if (profit > 0) { + max += profit; + } + } + return max; + } + + /** + * 2-动态规划 + * 1)定义状态:dp[i][j] + * 2)思考状态转移方程 + * 3)确定初始值 + * 4)确定输出值 + * + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + + // 0:持有现金 + // 1:持有股票 + // 状态转移:0 -> 1 -> 0 + int len = prices.length; + int[][] dp = new int[len][2]; + // 初始值 + dp[0][0] = 0; + dp[0][1] = -prices[0]; + for (int i = 1; i < len; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + } + // 输出值 + return dp[len - 1][0]; + } + + @Test + public void test() { + int[] prices = {7, 1, 5, 3, 6, 4}; + int max = maxProfit(prices); + System.out.println(max); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/greedy/test55/Solution.java b/src/main/java/com/chen/algorithm/znn/greedy/test55/Solution.java new file mode 100644 index 0000000..dfb39c5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/greedy/test55/Solution.java @@ -0,0 +1,79 @@ +package com.chen.algorithm.znn.greedy.test55; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/jump-game/solution/dong-tai-gui-hua-yu-tan-xin-suan-fa-jie-jue-ci-wen/ + * 55. 跳跃游戏 + * 给定一个非负整数数组,你最初位于数组的第一个位置。 + * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 + * 判断你是否能够到达最后一个位置。 + * 示例 1: + * 输入: [2,3,1,1,4] + * 输出: true + * 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 + * 示例 2: + * 输入: [3,2,1,0,4] + * 输出: false + * 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。 + * + * @Auther: zhunn + * @Date: 2020/11/2 14:51 + * @Description: 跳跃游戏:1-贪心算法;2-动态规划 + */ +public class Solution { + + /** + * 1-贪心算法 + * 倒序遍历,如果n-2下标能到达n-1下标,意味着前面只要有坐标能够到达n-2即可完成跳跃,于是末尾位置更改为n-2,这样构成一个子问题,迭代求解。 + * + * @param nums + * @return + */ + public boolean canJump(int[] nums) { + if (nums == null || nums.length == 0) { + return false; + } + + int lastPosition = nums.length - 1; + for (int i = nums.length - 1; i >= 0; i--) { + if (i + nums[i] >= lastPosition) { + lastPosition = i; + } + } + return lastPosition == 0; + } + + /** + * 2-动态规划 + * + * @param nums + * @return + */ + public boolean canJump2(int[] nums) { + + if (nums == null || nums.length == 0) { + return false; + } + boolean[] dp = new boolean[nums.length]; + dp[0] = true; + for (int i = 1; i < nums.length; i++) { + for (int j = 0; j < i; j++) { + // 如果之前的j节点可达,并且从此节点可以到跳到i + if (dp[j] && nums[j] + j >= i) { + dp[i] = true; + break; + } + } + } + return dp[nums.length - 1]; + } + + @Test + public void test() { + int[] nums = {3, 2, 1, 1, 4}; + int[] nums2 = {3, 2, 1, 0, 4}; + boolean res = canJump(nums2); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/hash/Test.java b/src/main/java/com/chen/algorithm/znn/hash/Test.java new file mode 100644 index 0000000..9c44d57 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/hash/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.hash; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: hash相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/hash/test15/Solution.java b/src/main/java/com/chen/algorithm/znn/hash/test15/Solution.java new file mode 100644 index 0000000..9b88a2c --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/hash/test15/Solution.java @@ -0,0 +1,157 @@ +package com.chen.algorithm.znn.hash.test15; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/3sum/solution/hua-jie-suan-fa-15-san-shu-zhi-he-by-guanpengchn/ + * 15. 三数之和 + * 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 + * 注意:答案中不可以包含重复的三元组。 + * 示例: + * 给定数组 nums = [-1, 0, 1, 2, -1, -4], + * 满足要求的三元组集合为: + * [ + * [-1, 0, 1], + * [-1, -1, 2] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/26 14:35 + * @Description: 三数之和:排序+双指针 + */ +public class Solution { + + /** + * 官方解法 + * + * @param nums + * @return + */ + public List> threeSum1(int[] nums) { + int n = nums.length; + Arrays.sort(nums); + List> ans = new ArrayList<>(); + // 枚举 a + for (int first = 0; first < n; ++first) { + // 需要和上一次枚举的数不相同 + if (first > 0 && nums[first] == nums[first - 1]) { + continue; + } + // c 对应的指针初始指向数组的最右端 + int third = n - 1; + int target = -nums[first]; + // 枚举 b + for (int second = first + 1; second < n; ++second) { + // 需要和上一次枚举的数不相同 + if (second > first + 1 && nums[second] == nums[second - 1]) { + continue; + } + // 需要保证 b 的指针在 c 的指针的左侧 + while (second < third && nums[second] + nums[third] > target) { + --third; + } + // 如果指针重合,随着 b 后续的增加 + // 就不会有满足 a+b+c=0 并且 b list = new ArrayList<>(); + list.add(nums[first]); + list.add(nums[second]); + list.add(nums[third]); + ans.add(list); + } + } + } + return ans; + } + + //public List> threeSum(int[] nums) { + // int n = nums.length; + // Arrays.sort(nums); + // List> ans = new ArrayList<>(); + // for (int first = 0; first < n; ++first) { + // if (first > 0 && nums[first] == nums[first - 1]) { + // continue; + // } + // int third = n - 1; + // int target = -nums[first]; + // for (int second = first + 1; second < n; ++second) { + // if (second > first + 1 && nums[second] == nums[second - 1]) { + // continue; + // } + // while (second < third && nums[second] + nums[third] > target) { + // --third; + // } + // if (second == third) { + // break; + // } + // if (nums[second] + nums[third] == target) { + // List list = new ArrayList<>(); + // list.add(nums[first]); + // list.add(nums[second]); + // list.add(nums[third]); + // ans.add(list); + // } + // } + // } + // return ans; + //} + + /** + * 推荐此方法,方便记忆 + * + * @param nums + * @return + */ + public List> threeSum2(int[] nums) { + if (nums == null || nums.length < 3) { + return null; + } + + List> ans = new ArrayList<>(); + Arrays.sort(nums); + int len = nums.length; + for (int i = 0; i < len; i++) { + if (nums[i] > 0) { // 剪枝,第一个数大于0,后面的数是递增的,不会有等于0的组合,直接返回结果 + return ans; + } + if (i > 0 && nums[i] == nums[i - 1]) { // 剪枝,去重 + continue; + } + int L = i + 1; + int R = len - 1; + while (L < R) { + int sum = nums[i] + nums[L] + nums[R]; + if (sum == 0) { + ans.add(Arrays.asList(nums[i], nums[L], nums[R])); + while (L < R && nums[L] == nums[L + 1]) { // 剪枝去重 + L++; + } + while (L < R && nums[R] == nums[R - 1]) { // 剪枝去重 + R--; + } + L++; + R--; + } else if (sum < 0) { + L++; + } else { + R--; + } + } + } + return ans; + } + + @Test + public void test() { + int[] arrayNum = {-1, 0, 1, 4, 2, -1, -4}; + List> result = threeSum2(arrayNum); + System.out.println(result); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/hash/test18/Solution.java b/src/main/java/com/chen/algorithm/znn/hash/test18/Solution.java new file mode 100644 index 0000000..24311d9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/hash/test18/Solution.java @@ -0,0 +1,79 @@ +package com.chen.algorithm.znn.hash.test18; + +import com.alibaba.fastjson.JSON; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/4sum/solution/si-shu-zhi-he-by-leetcode-solution/ + * 18. 四数之和 + * 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 + * 注意: + * 答案中不可以包含重复的四元组。 + * 示例: + * 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 + *

+ * 满足要求的四元组集合为: + * [ + * [-1, 0, 0, 1], + * [-2, -1, 1, 2], + * [-2, 0, 0, 2] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/26 15:09 + * @Description: 四数之和:类比三数之和方法 + */ +public class Solution { + + public List> fourSum(int[] nums, int target) { + if (nums == null || nums.length < 4) { + return null; + } + + List> ans = new ArrayList<>(); + Arrays.sort(nums); + + for (int i = 0; i < nums.length; i++) { + if (i > 0 && nums[i] == nums[i - 1]) { + continue; + } + for (int j = i + 1; j < nums.length; j++) { + if (j > i + 1 && nums[j] == nums[j - 1]) { + continue; + } + int L = j + 1; + int R = nums.length - 1; + while (L < R) { + int sum = nums[i] + nums[j] + nums[L] + nums[R]; + if (sum == target) { + ans.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R])); + while (L < R && nums[L] == nums[L + 1]) { + L++; + } + while (L < R && nums[R] == nums[R - 1]) { + R--; + } + L++; + R--; + } else if (sum < target) { + L++; + } else { + R--; + } + } + } + } + return ans; + } + + @Test + public void test() { + int[] nums = {1, 0, -1, 0, -2, 2}; + List> ans = fourSum(nums, 0); + System.out.println(JSON.toJSONString(ans)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/hash/test242/Solution.java b/src/main/java/com/chen/algorithm/znn/hash/test242/Solution.java new file mode 100644 index 0000000..d848147 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/hash/test242/Solution.java @@ -0,0 +1,49 @@ +package com.chen.algorithm.znn.hash.test242; + +import org.junit.Test; + +/** + * 242. 有效的字母异位词 + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + * 示例 1: + * 输入: s = "anagram", t = "nagaram" + * 输出: true + * 示例 2: + * 输入: s = "rat", t = "car" + * 输出: false + * + * @Author: zhunn + * @Date: 2020-10-22 22:12 + * @Description: 有效的字母异位词。哈希表 + */ +public class Solution { + + public boolean isAnagram(String s, String t) { + + if (s == null || t == null || s.length() != t.length()) { + return false; + } + + int[] counter = new int[26]; + for (int i = 0; i < s.length(); i++) { + counter[s.charAt(i) - 'a']++; + counter[t.charAt(i) - 'a']--; + } + + for (int i = 0; i < counter.length; i++) { + if (counter[i] != 0) { + return false; + } + } + return true; + } + + + @Test + public void testCase() { + + System.out.println(isAnagram("anagram", "nagaram")); + + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/hash/test49/Solution.java b/src/main/java/com/chen/algorithm/znn/hash/test49/Solution.java new file mode 100644 index 0000000..2f5d641 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/hash/test49/Solution.java @@ -0,0 +1,52 @@ +package com.chen.algorithm.znn.hash.test49; + +import org.junit.Test; + +import java.util.*; + +/** + * 49. 字母异位词分组 + * 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 + * 示例: + * 输入: ["eat", "tea", "tan", "ate", "nat", "bat"] + * 输出: + * [ + * ["ate","eat","tea"], + * ["nat","tan"], + * ["bat"] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/26 11:19 + * @Description: 字母异位词分组 + */ +public class Solution { + + public List> groupAnagrams(String[] strs) { + if (strs == null || strs.length == 0) { + return null; + } + + Map> resuMap = new HashMap<>(); + + for (int i = 0; i < strs.length; i++) { + + char[] chars = strs[i].toCharArray(); + Arrays.sort(chars); + String key = new String(chars); + + if (!resuMap.containsKey(key)) { + resuMap.put(key, new ArrayList<>()); + } + resuMap.get(key).add(strs[i]); + } + return new ArrayList<>(resuMap.values()); + } + + @Test + public void test() { + String[] words = {"eat", "tea", "tan", "ate", "nat", "bat"}; + List> result = groupAnagrams(words); + System.out.println(result); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/heap/Test.java b/src/main/java/com/chen/algorithm/znn/heap/Test.java new file mode 100644 index 0000000..093eff9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/heap/Test.java @@ -0,0 +1,14 @@ +package com.chen.algorithm.znn.heap; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 堆相关 + * 1、堆的基本操作 heap/Heap + * + * 2、堆化,大根堆,小根堆,堆排序 + * 3、堆的应用(大根堆-优先级队列,小根堆-topK,中位数) + * java的priorityQueue 用小根堆实现的优先级队列 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/heap/test215/Solution.java b/src/main/java/com/chen/algorithm/znn/heap/test215/Solution.java new file mode 100644 index 0000000..9d3125f --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/heap/test215/Solution.java @@ -0,0 +1,61 @@ +package com.chen.algorithm.znn.heap.test215; + +import org.junit.Test; + +import java.util.PriorityQueue; + +/** + * 215. 数组中的第K个最大元素 + * 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 + * 示例 1: + * 输入: [3,2,1,5,6,4] 和 k = 2 + * 输出: 5 + * 示例 2: + * 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 + * 输出: 4 + * + * @Auther: zhunn + * @Date: 2020/10/26 16:55 + * @Description: 求数组中的第K个最大元素:1-暴力;2-优先级队列 PriorityQueue-小根堆 + */ +public class Solution { + + public int findKthLargest1(int[] nums, int k) { + //if (nums == null || k > nums.length) { + // return -1; + //} + + for (int i = 0; i < nums.length - 1; i++) { + for (int j = i + 1; j < nums.length; j++) { + if (nums[j] > nums[i]) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + } + } + + return nums[k - 1]; + } + + public int findKthLargest(int[] nums, int k) { + PriorityQueue queue = new PriorityQueue<>(k); //小根堆 + for (int i = 0; i < nums.length; i++) { + if (queue.size() < k) { + queue.add(nums[i]); + } else if (nums[i] > queue.peek()) { + queue.poll(); + queue.add(nums[i]); + } + } + return queue.peek(); + } + + + @Test + public void testCase() { + int[] n = {3, 2, 3, 1, 2, 4, 5, 5, 6}; + System.out.println(findKthLargest(n, 2)); + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/heap/test347/Solution.java b/src/main/java/com/chen/algorithm/znn/heap/test347/Solution.java new file mode 100644 index 0000000..b3e0910 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/heap/test347/Solution.java @@ -0,0 +1,66 @@ +package com.chen.algorithm.znn.heap.test347; + +import org.junit.Test; + +import java.util.*; + +/** + * 347. 前 K 个高频元素 + * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 + * 示例 1: + * 输入: nums = [1,1,1,2,2,3], k = 2 + * 输出: [1,2] + * 示例 2: + * 输入: nums = [1], k = 1 + * 输出: [1] + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 前 K 个高频元素:PriorityQueue-小根堆 + */ +public class Solution { + + public int[] topKFrequent(int[] nums, int k) { + // 计算频次 + Map map = new HashMap<>(); + for (int num : nums) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + + //PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(key -> map.get(key))); + PriorityQueue queue = new PriorityQueue<>(new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return map.get(o1) - map.get(o2); + } + }); + + for (Integer key : map.keySet()) { + if (queue.size() < k) { + queue.add(key); + } else if (map.get(queue.peek()) < map.get(key)) { + queue.poll(); + queue.add(key); + } + } + + int[] res = new int[k]; + int i = 0; + while (!queue.isEmpty()) { + res[i] = queue.poll(); + i++; + } + return res; + } + + @Test + public void test() { + int[] nums = {5, 2, 5, 3, 5, 3, 1, 1, 3}; + int[] res = topKFrequent(nums, 2); + System.out.println(Arrays.toString(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/heap/test703/KthLargest.java b/src/main/java/com/chen/algorithm/znn/heap/test703/KthLargest.java new file mode 100644 index 0000000..7fea049 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/heap/test703/KthLargest.java @@ -0,0 +1,59 @@ +package com.chen.algorithm.znn.heap.test703; + +import java.util.PriorityQueue; + +/** + * 703. 数据流中的第 K 大元素 + * 设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。 + * 请实现 KthLargest 类: + * KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 + * int add(int val) 返回当前数据流中第 k 大的元素。 + * 示例: + * 输入: + * ["KthLargest", "add", "add", "add", "add", "add"] + * [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]] + * 输出: + * [null, 4, 5, 5, 8, 8] + * 解释: + * KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]); + * kthLargest.add(3); // return 4 + * kthLargest.add(5); // return 5 + * kthLargest.add(10); // return 5 + * kthLargest.add(9); // return 8 + * kthLargest.add(4); // return 8 + * + * @Auther: zhunn + * @Date: 2020/10/26 16:55 + * @Description: 求数组中的第K个最大元素:1-暴力;2-优先级队列 + */ +public class KthLargest { + + private PriorityQueue queue; + private int limit; + + public KthLargest(int size, int[] nums) { + this.limit = size; + queue = new PriorityQueue<>(limit); + for (int num : nums) { + add(num); + } + } + + public int add(int num) { + + if (queue.size() < limit) { + queue.add(num); + } else if (queue.peek() < num) { + queue.poll(); + queue.add(num); + } + + return queue.peek(); + } + + public static void main(String[] args) { + int[] n = {3, 2, 3, 1, 2, 4, 5, 5, 6}; + KthLargest kthLargest = new KthLargest(3, n); + System.out.println(kthLargest.add(3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/ListNode.java b/src/main/java/com/chen/algorithm/znn/linkedlist/ListNode.java new file mode 100644 index 0000000..01ab163 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/ListNode.java @@ -0,0 +1,26 @@ +package com.chen.algorithm.znn.linkedlist; + +/** + * @Auther: zhunn + * @Date: 2020/10/9 15:31 + * @Description: + */ +public class ListNode { + + public int val; + + public ListNode next; + + public ListNode() { + } + + public ListNode(int val) { + this.val = val; + } + + public ListNode(int val, ListNode next) { + this.val = val; + this.next = next; + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/Test.java b/src/main/java/com/chen/algorithm/znn/linkedlist/Test.java new file mode 100644 index 0000000..a4367d8 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.linkedlist; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 链表相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test141/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test141/Solution.java new file mode 100644 index 0000000..04a06e1 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test141/Solution.java @@ -0,0 +1,119 @@ +package com.chen.algorithm.znn.linkedlist.test141; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +/** + * 141. 环形链表 + * 给定一个链表,判断链表中是否有环。 + * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 + * 如果链表中存在环,则返回 true 。 否则,返回 false 。 + * 进阶: + * 你能用 O(1)(即,常量)内存解决此问题吗? + * 示例 1: + * 输入:head = [3,2,0,-4], pos = 1 + * 输出:true + * 解释:链表中有一个环,其尾部连接到第二个节点。 + * 示例 2: + * 输入:head = [1,2], pos = 0 + * 输出:true + * 解释:链表中有一个环,其尾部连接到第一个节点。 + * 示例 3: + * 输入:head = [1], pos = -1 + * 输出:false + * 解释:链表中没有环。 + * + * @Auther: zhunn + * @Date: 2020/10/23 16:26 + * @Description: 环形链表一:判断链表是否有环 + * 方法:1-哈希表,2-快慢指针 + */ +public class Solution { + + /** + * 2-快慢指针 + * + * @param head + * @return + */ + public boolean hasCycle(ListNode head) { + if (head == null || head.next == null) { + return false; + } + + ListNode slow = head; + ListNode fast = head; + + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + + if (slow == fast) { + return true; + } + } + return false; + } + + /** + * 1-哈希表 + * + * @param head + * @return + */ + public boolean hasCycle2(ListNode head) { + if (head == null || head.next == null) { + return false; + } + + ListNode dummy = head; + Set visited = new HashSet<>(); + while (dummy != null) { + if (visited.contains(dummy)) { + return true; + } else { + visited.add(dummy); + } + dummy = dummy.next; + } + return false; + } + + /** + * 1-哈希表简易版 + * + * @param head + * @return + */ + public boolean hasCycle3(ListNode head) { + Set seen = new HashSet<>(); + while (head != null) { + if (!seen.add(head)) { + return true; + } + head = head.next; + } + return false; + } + + @Test + public void test() { + ListNode l1_1 = new ListNode(6); + ListNode l1_2 = new ListNode(1); + ListNode l1_3 = new ListNode(8); + ListNode l1_4 = new ListNode(7); + ListNode l1_5 = new ListNode(5); + + l1_1.next = l1_2; + l1_2.next = l1_3; + l1_3.next = l1_4; + l1_4.next = l1_5; + l1_5.next = l1_3; + + boolean result = hasCycle(l1_1); + System.out.println(result); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test142/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test142/Solution.java new file mode 100644 index 0000000..7a1c52e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test142/Solution.java @@ -0,0 +1,106 @@ +package com.chen.algorithm.znn.linkedlist.test142; + + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +/** + * 142. 环形链表 II + * 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 + * 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。 + * 说明:不允许修改给定的链表。 + * 进阶: + * 你是否可以使用 O(1) 空间解决此题? + * 示例 1: + * 输入:head = [3,2,0,-4], pos = 1 + * 输出:返回索引为 1 的链表节点 + * 解释:链表中有一个环,其尾部连接到第二个节点。 + * 示例 2: + * 输入:head = [1,2], pos = 0 + * 输出:返回索引为 0 的链表节点 + * 解释:链表中有一个环,其尾部连接到第一个节点。 + * 示例 3: + * 输入:head = [1], pos = -1 + * 输出:返回 null + * 解释:链表中没有环。 + * + * @Auther: zhunn + * @Date: 2020/10/23 16:10 + * @Description: 环形链表二,找出入环点 + * 方法:1-哈希表,2-快慢指针(推荐) + */ +public class Solution { + + /** + * 1-哈希表 + * + * @param head + * @return + */ + public ListNode detectCycle(ListNode head) { + if (head == null || head.next == null) { + return null; + } + ListNode dummy = head; + Set visited = new HashSet<>(); + while (dummy != null) { + if (visited.contains(dummy)) { + return dummy; + } else { + visited.add(dummy); + } + dummy = dummy.next; + } + return null; + } + + /** + * 2-快慢指针 + * + * @param head + * @return + */ + public ListNode detectCycle2(ListNode head) { + if (head == null || head.next == null) { + return null; + } + + ListNode slow = head; + ListNode fast = head; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + if (slow == fast) { + break; + } + } + + ListNode ptr = head; + while (slow != ptr) { + slow = slow.next; + ptr = ptr.next; + } + return slow; + } + + @Test + public void test() { + ListNode l1_1 = new ListNode(6); + ListNode l1_2 = new ListNode(2); + ListNode l1_3 = new ListNode(8); + ListNode l1_4 = new ListNode(7); + ListNode l1_5 = new ListNode(5); + + l1_1.next = l1_2; + l1_2.next = l1_3; + l1_3.next = l1_4; + l1_4.next = l1_5; + l1_5.next = l1_3; + + ListNode result = detectCycle2(l1_1); + System.out.println(result.val); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test160/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test160/Solution.java new file mode 100644 index 0000000..de27516 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test160/Solution.java @@ -0,0 +1,77 @@ +package com.chen.algorithm.znn.linkedlist.test160; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/intersection-of-two-linked-lists/ + * https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/tu-jie-xiang-jiao-lian-biao-by-user7208t/ + * 160. 相交链表 + * 编写一个程序,找到两个单链表相交的起始节点。 + * 如下面的两个链表: + * 在节点 c1 开始相交。 + * 示例 1: + * 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 + * 输出:Reference of the node with value = 8 + * 输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 + * 示例 2: + * 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 + * 输出:Reference of the node with value = 2 + * 输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 + * 示例 3: + * 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 + * 输出:null + * 输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 + * 解释:这两个链表不相交,因此返回 null。 + * + * @Author: zhunn + * @Date: 2020-10-22 17:40 + * @Description: 相交链表:双指针法 + */ +public class Solution { + + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + + if (headA == null || headB == null) { + return null; + } + ListNode a = headA; + ListNode b = headB; + + while (a != b) { + a = a == null ? headB : a.next; + b = b == null ? headA : b.next; + } + return a; + } + + @Test + public void test() { + ListNode l1_1 = new ListNode(6); + ListNode l1_2 = new ListNode(1); + ListNode l1_3 = new ListNode(8); + ListNode l1_4 = new ListNode(7); + ListNode l1_5 = new ListNode(5); + + ListNode l2_1 = new ListNode(5); + ListNode l2_2 = new ListNode(0); + ListNode l2_3 = new ListNode(1); + + l1_1.next = l1_2; + l1_2.next = l1_3; + l1_3.next = l1_4; + l1_4.next = l1_5; + + l2_1.next = l2_2; + l2_2.next = l2_3; + l2_3.next = l1_3; + + ListNode result = getIntersectionNode(l1_1, l2_1); + + while (result != null) { + System.out.println(result.val); + result = result.next; + } + } + +} diff --git a/src/main/java/com/chen/algorithm/study/test19/Solution4.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test19/Solution.java similarity index 83% rename from src/main/java/com/chen/algorithm/study/test19/Solution4.java rename to src/main/java/com/chen/algorithm/znn/linkedlist/test19/Solution.java index 0f7b416..fa7bd2b 100644 --- a/src/main/java/com/chen/algorithm/study/test19/Solution4.java +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test19/Solution.java @@ -1,33 +1,23 @@ -package com.chen.algorithm.study.test19; +package com.chen.algorithm.znn.linkedlist.test19; +import com.chen.algorithm.znn.linkedlist.ListNode; import org.junit.Test; import java.util.Deque; import java.util.LinkedList; /** + * 19. 删除链表的倒数第N个节点 + * 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 + * 示例: + * 给定一个链表: 1->2->3->4->5, 和 n = 2. + * 当删除了倒数第二个节点后,链表变为 1->2->3->5. + * * @Auther: zhunn * @Date: 2020/10/22 14:51 - * @Description: 1、遍历length-n+1;2、栈、3、双指针法 + * @Description: 删除链表的倒数第N个节点 1、遍历length-n+1;2、栈、3、双指针法(推荐) */ -public class Solution4 { - - class ListNode { - int val; - ListNode next; - - public ListNode() { - } - - public ListNode(int val) { - this.val = val; - } - - public ListNode(int val, ListNode next) { - this.val = val; - this.next = next; - } - } +public class Solution { /** * 计算链表长度并遍历 @@ -80,7 +70,7 @@ public ListNode removeNthFromEnd2(ListNode head, int n) { } /** - * 双指针法 + * 双指针法 (推荐) * * @param head 头结点 * @param n 删除倒数第几个 diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test2/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test2/Solution.java new file mode 100644 index 0000000..ba5cae8 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test2/Solution.java @@ -0,0 +1,78 @@ +package com.chen.algorithm.znn.linkedlist.test2; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * 2. 两数相加 + * 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 + * 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 + * 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 + * 示例: + * 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) + * 输出:7 -> 0 -> 8 + * 原因:342 + 465 = 807 + * + * @Author: zhunn + * @Date: 2020-10-20 17:43 + * @Description: 两数相加 + */ +public class Solution { + + + public ListNode addTwo(ListNode a, ListNode b) { + + ListNode result = new ListNode(-1); + ListNode curr = result; + + + int carry = 0; + + while (a != null || b != null || carry != 0) { + + if (a != null) { + carry += a.val; + a = a.next; + } + + if (b != null) { + carry += b.val; + b = b.next; + } + curr.next = new ListNode(carry % 10); + carry = carry / 10; + curr = curr.next; + } + return result.next; + } + + @Test + public void testCase() { + + ListNode l1_1 = new ListNode(2); + ListNode l1_2 = new ListNode(4); + ListNode l1_3 = new ListNode(3); + + l1_1.next = l1_2; + l1_2.next = l1_3; + + + ListNode l2_1 = new ListNode(5); + ListNode l2_2 = new ListNode(6); + ListNode l2_3 = new ListNode(4); + + l2_1.next = l2_2; + l2_2.next = l2_3; + + + ListNode result = addTwo(l1_1, l2_1); + + System.out.println(result.val); + System.out.println(result.next.val); + System.out.println(result.next.next.val); + + + } + + +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test206/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test206/Solution.java new file mode 100644 index 0000000..014b771 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test206/Solution.java @@ -0,0 +1,62 @@ +package com.chen.algorithm.znn.linkedlist.test206; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * 206. 反转链表 + * 反转一个单链表。 + * 示例: + * 输入: 1->2->3->4->5->NULL + * 输出: 5->4->3->2->1->NULL + * + * @Auther: zhunn + * @Date: 2020/10/22 17:23 + * @Description: 反转链表:1-双指针法(推荐),2-递归 + */ +public class Solution { + + public ListNode reverseList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + + ListNode pre = null; + ListNode curr = head; + while (curr != null) { + ListNode nextTemp = curr.next; + curr.next = pre; // 当前指针的next指向前一个,链表指针反转 + pre = curr; // pre和curr指针向后推进 + curr = nextTemp; + } + + return pre; + } + + public ListNode reverseList2(ListNode head) { + if (head == null || head.next == null) { + return head; + } + ListNode p = reverseList2(head.next); + head.next.next = head; + head.next = null; + return p; + } + + @Test + public void test() { + ListNode l1_4 = new ListNode(18); + ListNode l1_3 = new ListNode(9, l1_4); + ListNode l1_2 = new ListNode(6, l1_3); + ListNode l1_1 = new ListNode(7, l1_2); + + ListNode result = reverseList(l1_1); + ListNode a = result; + while (a != null) { + System.out.println(a.val); + a = a.next; + } + } + + +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test21/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test21/Solution.java new file mode 100644 index 0000000..f4701d3 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test21/Solution.java @@ -0,0 +1,69 @@ +package com.chen.algorithm.znn.linkedlist.test21; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * 21. 合并两个有序链表 + * 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 + * 示例: + * 输入:1->2->4, 1->3->4 + * 输出:1->1->2->3->4->4 + * + * @Author: zhunn + * @Date: 2020-09-06 01:43 + * @Description: 合并两个有序链表 + */ +public class Solution { + + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + ListNode res = new ListNode(0); + ListNode curr = res; + + while (l1 != null && l2 != null) { + + if (l1.val <= l2.val) { + curr.next = l1; + l1 = l1.next; + } else { + curr.next = l2; + l2 = l2.next; + } + curr = curr.next; + } + curr.next = l1 == null ? l2 : l1; + + return res.next; + } + + @Test + public void testCase() { + + ListNode l1_1 = new ListNode(3); + ListNode l1_2 = new ListNode(6); + ListNode l1_3 = new ListNode(9); + + l1_1.next = l1_2; + l1_2.next = l1_3; + + + ListNode l2_1 = new ListNode(2); + ListNode l2_2 = new ListNode(6); + ListNode l2_3 = new ListNode(8); + + l2_1.next = l2_2; + l2_2.next = l2_3; + + + ListNode result = mergeTwoLists(l1_1, l2_1); + + System.out.println(result.val); + System.out.println(result.next.val); + System.out.println(result.next.next.val); + System.out.println(result.next.next.next.val); + System.out.println(result.next.next.next.next.val); + System.out.println(result.next.next.next.next.next.val); + + } + +} diff --git a/src/main/java/com/chen/algorithm/study/test24/Solution3.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test24/Solution.java similarity index 66% rename from src/main/java/com/chen/algorithm/study/test24/Solution3.java rename to src/main/java/com/chen/algorithm/znn/linkedlist/test24/Solution.java index 46f6bbc..fdc1fe7 100644 --- a/src/main/java/com/chen/algorithm/study/test24/Solution3.java +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test24/Solution.java @@ -1,17 +1,32 @@ -package com.chen.algorithm.study.test24; +package com.chen.algorithm.znn.linkedlist.test24; +import com.chen.algorithm.znn.linkedlist.ListNode; import org.junit.Test; /** + * 24. 两两交换链表中的节点 + * 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 + * 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 + * 示例 1: + * 输入:head = [1,2,3,4] + * 输出:[2,1,4,3] + * 示例 2: + * 输入:head = [] + * 输出:[] + * 示例 3: + * 输入:head = [1] + * 输出:[1] + * * @Auther: zhunn * @Date: 2020/10/23 10:48 - * @Description: 两两交换链表中结点:1-递归,2-迭代 + * @Description: 两两交换链表中结点:1-递归,2-迭代(推荐) */ -public class Solution3 { +public class Solution { /** * 1-递归 + * * @param head * @return */ @@ -26,7 +41,8 @@ public ListNode swapPairs1(ListNode head) { } /** - * 2-迭代 + * 2-迭代(推荐) + * * @param head * @return */ @@ -43,17 +59,18 @@ public ListNode swapPairs2(ListNode head) { ListNode node1 = temp.next; ListNode node2 = temp.next.next; - temp.next = node2; node1.next = node2.next; - node2.next = node1; + node2.next = temp.next; //等价于 node2.next = node1; + temp.next = node2; //两两交换 画图演示,想着图写代码 - temp = node1; + temp = node1; // 临时结点往前推进 } return dummyHead.next; } /** * 2-迭代(操作head) + * * @param head * @return */ @@ -70,9 +87,9 @@ public ListNode swapPairs3(ListNode head) { ListNode node1 = head; ListNode node2 = head.next; - temp.next = node2; node1.next = node2.next; - node2.next = node1; + node2.next = temp.next; + temp.next = node2; temp = node1; head = node1.next; @@ -93,12 +110,12 @@ public void test() { l1_3.next = l1_4; l1_4.next = l1_5; - ListNode result = swapPairs3(l1_1); + ListNode result = swapPairs2(l1_1); - System.out.println(result.val); - System.out.println(result.next.val); - System.out.println(result.next.next.val); - System.out.println(result.next.next.next.val); - System.out.println(result.next.next.next.next.val); + ListNode a = result; + while (a != null) { + System.out.println(a.val); + a = a.next; + } } } diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test25/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test25/Solution.java new file mode 100644 index 0000000..42bc0dc --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test25/Solution.java @@ -0,0 +1,142 @@ +package com.chen.algorithm.znn.linkedlist.test25; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/ + * 25. K 个一组翻转链表 + * 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。 + * k 是一个正整数,它的值小于或等于链表的长度。 + * 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 + * 示例: + * 给你这个链表:1->2->3->4->5 + * 当 k = 2 时,应当返回: 2->1->4->3->5 + * 当 k = 3 时,应当返回: 3->2->1->4->5 + * + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: K个一组翻转链表:1-官方解法;2-(推荐)链表分区(已翻转,待翻转,未翻转),pre,end,start=pre.next,nextTemp=end.next指针,pre和end移动指针到翻转后的start位置 + */ +public class Solution { + + /** + * 官网解法 + * + * @param head + * @param k + * @return + */ + public ListNode reverseKGroup(ListNode head, int k) { + ListNode hair = new ListNode(0); + hair.next = head; + ListNode pre = hair; + + while (head != null) { + ListNode tail = pre; + // 查看剩余部分长度是否大于等于 k + for (int i = 0; i < k; ++i) { + tail = tail.next; + if (tail == null) { + return hair.next; + } + } + ListNode nextTemp = tail.next; + ListNode[] reverse = myReverse(head, tail); + head = reverse[0]; + tail = reverse[1]; + // 把子链表重新接回原链表 + pre.next = head; + tail.next = nextTemp; + pre = tail; + head = tail.next; + } + + return hair.next; + } + + private ListNode[] myReverse(ListNode head, ListNode tail) { + ListNode prev = tail.next; + ListNode p = head; + while (prev != tail) { + ListNode nex = p.next; + p.next = prev; + prev = p; + p = nex; + } + return new ListNode[]{tail, head}; + } + + + /** + * 链表分区,pre,end,start,nextTemp指针(推荐) + * @param head + * @param k + * @return + */ + public ListNode reverseKGroup1(ListNode head, int k) { + if (head == null || head.next == null) { + return head; + } + + ListNode dummy = new ListNode(0, head); + + ListNode pre = dummy; + ListNode end = dummy; + + while (end.next != null) { + for (int i = 0; i < k && end != null; i++) { + end = end.next; + } + if (end == null) { + break; + } + + ListNode start = pre.next; + ListNode nextTemp = end.next; + end.next = null; + pre.next = reverse(start); + start.next = nextTemp; + + pre = start; + end = pre; + } + return dummy.next; + } + + private ListNode reverse(ListNode head) { + ListNode pre = null; + ListNode curr = head; + while (curr != null) { + ListNode nextTemp = curr.next; + curr.next = pre; + + pre = curr; + curr = nextTemp; + } + return pre; + } + + @Test + public void test() { + ListNode l1_1 = new ListNode(1); + ListNode l1_2 = new ListNode(2); + ListNode l1_3 = new ListNode(3); + ListNode l1_4 = new ListNode(4); + ListNode l1_5 = new ListNode(5); + + l1_1.next = l1_2; + l1_2.next = l1_3; + l1_3.next = l1_4; + l1_4.next = l1_5; + + ListNode result = reverseKGroup1(l1_1, 3); + + ListNode a = result; + while (a != null) { + System.out.println(a.val); + a = a.next; + } + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/linkedlist/test83/Solution.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test83/Solution.java new file mode 100644 index 0000000..d499091 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test83/Solution.java @@ -0,0 +1,66 @@ +package com.chen.algorithm.znn.linkedlist.test83; + +import com.chen.algorithm.znn.linkedlist.ListNode; +import org.junit.Test; + +/** + * 83. 删除排序链表中的重复元素 + * 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 + * 示例 1: + * 输入: 1->1->2 + * 输出: 1->2 + * 示例 2: + * 输入: 1->1->2->3->3 + * 输出: 1->2->3 + * + * @Author: zhunn + * @Date: 2020-10-08 02:19 + * @Description: 删除排序链表中的重复元素:同删除排序数组中的重复元素 + */ +public class Solution { + + + /** + * 需要注意的点, + * 1、应该是后面节点和前面节点比较; + * 2、如果后面节点和前面节点的值一致,则只是修改前节点的指针,遍历指针不前进 + * + * @param head + * @return + */ + public ListNode deleteDuplicates(ListNode head) { + + if (head == null || head.next == null) { + return head; + } + + ListNode temp = head; + while (temp.next != null) { + if (temp.next.val == temp.val) { + temp.next = temp.next.next; + } else { + temp = temp.next; + } + } + return head; + } + + @Test + public void test() { + ListNode five = new ListNode(3); + ListNode four = new ListNode(3, five); + ListNode three = new ListNode(2, four); + ListNode two = new ListNode(1, three); + ListNode head = new ListNode(1, two); + + ListNode result = deleteDuplicates(head); + + ListNode a = result; + while (a != null) { + System.out.println(a.val); + a = a.next; + } + //System.out.println(result); + } + +} diff --git a/src/main/java/com/chen/algorithm/study/test92/Solution3.java b/src/main/java/com/chen/algorithm/znn/linkedlist/test92/Solution.java similarity index 55% rename from src/main/java/com/chen/algorithm/study/test92/Solution3.java rename to src/main/java/com/chen/algorithm/znn/linkedlist/test92/Solution.java index de47efd..7e81c02 100644 --- a/src/main/java/com/chen/algorithm/study/test92/Solution3.java +++ b/src/main/java/com/chen/algorithm/znn/linkedlist/test92/Solution.java @@ -1,22 +1,26 @@ -package com.chen.algorithm.study.test92; +package com.chen.algorithm.znn.linkedlist.test92; +import com.chen.algorithm.znn.linkedlist.ListNode; +import com.chen.api.util.thread.study.chapter2.t6.CommonUtils; import org.junit.Test; +import javax.sound.midi.Soundbank; + /** + * https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/java-shuang-zhi-zhen-tou-cha-fa-by-mu-yi-cheng-zho/ + * 92. 反转链表 II + * 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 + * 说明: + * 1 ≤ m ≤ n ≤ 链表长度。 + * 示例: + * 输入: 1->2->3->4->5->NULL, m = 2, n = 4 + * 输出: 1->4->3->2->5->NULL + * * @Auther: zhunn * @Date: 2020/10/24 17:23 - * @Description: 反转链表二:1-双指针,2-删除结点递推 + * @Description: 反转链表二:1-双指针,2-双指针头插法-删除结点递推(推荐) */ -public class Solution3 { - - class ListNode { - int val; - ListNode next; - - ListNode(int x) { - val = x; - } - } +public class Solution { // 翻转n个节点,返回新链表的头部 private ListNode reverseN(ListNode head, int n) { @@ -33,6 +37,7 @@ private ListNode reverseN(ListNode head, int n) { /** * 1-双指针 + * * @param head * @param m * @param n @@ -62,30 +67,34 @@ public ListNode reverseBetween1(ListNode head, int m, int n) { } /** - * 2-删除结点递推 + * 2-删除结点递推 (推荐) + * curr后面的元素删除,然后添加到pre的后面。也即头插法 + * * @param head * @param m * @param n * @return */ - public ListNode reverseBetween2(ListNode head, int m, int n){ - if(head == null || head.next == null){ + public ListNode reverseBetween(ListNode head, int m, int n) { + if (head == null || head.next == null) { return head; } - ListNode dummy = new ListNode(-1); - dummy.next = head; + ListNode dummy = new ListNode(-1, head); ListNode pre = dummy; - for(int i =0;i,start = Math.max(start, map.get(c));map.put(c, end+1);res = Math.max(res, end-start+1); +5. 最长回文子串 动态规划 dp[i][j]表示字符串 s 的第 i 到 j 个字母组成的串是否是回文串(x 表示增量,子串长度,) +14. 最长公共前缀 *** +415. 字符串相加 + +hash +15. 三数之和 *** 排序+双指针 +18. 四数之和 ??? 排序+双指针 +49. 字母异位词分组 *** 排序+map +242. 有效的字母异位词 *** 迭代,int[] counter = new int[26];counter[s.charAt(i) - 'a']++;counter[t.charAt(i) - 'a']--;if(counter[i] != 0){return false;} + +array +剑指 Offer 29. 顺时针打印矩阵 left right top bottom +剑指 Offer 57 - II. 和为s的连续正数序列 滑动窗口法 +1. 两数之和 +11. 盛最多水的容器 双指针 +26. 删除排序数组中的重复项 双指针,return i+1; +27. 移除元素 双指针,return i; +283. 移动零 双指针(方法同27题移除元素) +34. 在排序数组中查找元素的第一个和最后一个位置 二分查找 +56. 合并区间 排序 +69. x 的平方根 *** 二分法与50题Pow(x, n)相对应 +88. 合并两个有序数组 *** p1,p2,p3=p1+p2 +238. 除自身以外数组的乘积 先求出左右两边的乘积再相乘 +240. 搜索二维矩阵 II 从左下角开始 +287. 寻找重复数 排序迭代/快慢指针 +448. 找到所有数组中消失的数字 迭代并赋值为-1,最后大于0的既满足(数组中数都大于0) +581. 最短无序连续子数组 排序迭代对比原数组 +674. 最长连续递增序列 *** 滑动窗口 +704. 二分查找 *** + +heap/Heap +堆的基本操作:堆化,大根堆,小根堆,堆排序 +215. 数组中的第K个最大元素 *** 优先级队列 PriorityQueue-小根堆,比较元素默认int的自然升序排序 +347. 前K个高频元素 *** PriorityQueue-小根堆,自定义比较器比较频次 +703. 数据流中的第K大元素 *** 优先级队列 PriorityQueue-小根堆,创建类和两个属性 limit 和 queue,构造函数构造好初始数据 + +stack/queue +20. 有效的括号 *** 栈+HashMap +155. 最小栈 *** 维护两个栈,增删-合理操作两个栈,获取操作相应栈 +225. 用队列实现栈 *** q2入栈,q1出栈(注意入栈逻辑) +232. 用栈实现队列 *** s1入队列,s2出队列(注意出队列逻辑) +227. 基本计算器 II *** 加减直接入栈正数或负数,乘除先取出栈顶元素乘以或除以新元素,将结果入栈 +239. 滑动窗口最大值 *** ??? LinkedList双向链表队列,构建递减队列,添加,超过长度删除并写入最大值 +496. 下一个更大元素 I *** 单调栈,栈和HashMap配合实现 + +linkedlist +2. 两数相加 *** 与 415. 数字型字符串相加 有异曲同工之妙 +21. 合并两个有序链表 *** while循环比对,并next +19. 删除链表的倒数第N个节点 *** 双指针法、栈、迭代len-n+1,推荐使用快慢双指针法 +83. 删除排序链表中的重复元素 *** 同26.删除排序数组中的重复元素 +24. 两两交换链表中的节点 *** 画图演示,看图写代码,声明两链表,while next和next.next都不为空 +206. 反转链表 ******* 声明双指针pre=null, curr = head;curr.next = pre(链表指针反转),最后返回pre +92. 反转链表 II 指定长度反转 双指针头插法-删除结点递推,画图演示,想图写代码 +25 K 个一组翻转链表 ??? (推荐)链表分区(已翻转,待翻转,未翻转),pre,end,start=pre.next,nextTemp=end.next指针,pre和end移动指针到翻转后的start位置 +160 相交链表 *** 双指针法(a+c+b = b+c+a)a=链表1未相交部分,b=链表2未相交部分,c=两链表相交部分 +141 环形链表(判断链表是否有环) 1-哈希表;(推荐)2-双指针法-快慢指针 while(fast != null && fast.next != null) +142 环形链表 II ***(找出入环点) 1-哈希表;(推荐)2-快慢指针 +//148. 排序链表 + +tree +94. 二叉树的中序遍历 递归和迭代都要会,推荐迭代(一个stack实现)Stack stack = new Stack<>();(两层while循环,内层放左子树) +144. 二叉树的前序遍历 ***** 递归和迭代都要会,推荐迭代(一个stack实现)while(!stack.isEmpty()) +145. 后序遍历二叉树 递归和迭代都要会,推荐迭代(两个stack实现) +102. 二叉树的层序遍历 ***** BFS(一个queue实现)Queue queue = new LinkedList<>(); 写法同107 +107. 二叉树的层次遍历 II (自底向上的层次遍历) BFS(一个queue实现)1-使用队列存储每层元素,用栈存储每层的结果集;2-(推荐)使用java的LinkedList的性质,从上到下遍历,将新遍历的层结果集放入linkedlist的头部 +103. 二叉树的锯齿形层次遍历 ***** 使用两个stack交替迭代放值 +104. 二叉树的最大深度 *** 1-递归;2-(推荐)层次遍历+累加深度(广度优先搜索) +111. 二叉树的最小深度 *** 1-深度优先搜索,即递归;2-层次遍历,相比104题,多加一个判断,判断是否已经到叶子结点,到了直接return depth(if (node.left == null && node.right == null) ) +199. 二叉树的右视图 ***** 1-广度优先搜索,即层次遍历(推荐)将每一层的最后一个节点放入结果列表 +543. 二叉树的直径 递归,左子树的深度+右子树的深度 Math.max(ans, leftDepth + rightDepth);return Math.max(leftDepth, rightDepth) + 1; +101. 对称二叉树 ***** 迭代:队列,放左右结点和右左结点 +226. 翻转二叉树 ***** 迭代:用queue层次遍历交换处理,循环体内,每次取出一个node +617. 合并二叉树 递归 +96. 不同的二叉搜索树 动态规划,i为根,G(i-1)*G(n-i) 求和(1<= i <=n) +98. 验证二叉搜索树??? 中序遍历:遍历后是升序 +235. 二叉搜索树的最近公共祖先 ***** while循环判断根的值和另两个参数的大小。while (root != null) { +236. 二叉树的最近公共祖先 *** 递归 +538. 把二叉搜索树转换为累加树 递归-反序中序遍历 +112. 路径总和 *** 判断是否存在 层次遍历(BFS推荐) +113. 路径之和 II *** 返回所有路径 同112,用map把结点父节点记录下来,最后求路径反转 +437. 路径之和 III *** 不需要到叶子结点返回所有路径的数量 两层递归 + +recursion +汉诺塔问题 HanNuoTa 递归,理解记忆 +172. 阶乘后的零 *** (打印阶乘 Recursion) 递归或迭代获取阶乘后结果,再取0的个数 + +bfs + +dfs +22. 括号生成 *** 递归dfs,左右括号剩余数量,left>right剪枝, 左右括号数递减,字符串追加 + +divide +50. Pow(x, n) *** 迭代,初始条件分类讨论判断,定义res和中间x,while(n>0) +169. 多数元素 *** 1-排序;2-哈希表(推荐)放入hashmap计数,判断数量是否大于len/2 + +greedy +122. 买卖股票的最佳时机 II *** 贪心-迭代计算只要有收益就累加到max,返回max; 动态规划 +55. 跳跃游戏 贪心-倒序遍历,如果n-2下标能到达n-1下标,意味着前面只要有坐标能够到达n-2即可完成跳跃,于是末尾位置更改为n-2,这样构成一个子问题,迭代求解。 + + +backtrack +36. 有效的数独(9X9) 1-哈希数组(占用空间)2-二维数组(推荐),第一维表示位置,第二维表示1-9数字,出现过对应二维数组值置为1标识起来,下次循环等于1直接return false +39. 组合总和 *** 元素无重复可以取多次(结果都不可重复) 回溯法-先排序,递归然后循环并剪枝,最后回溯dfs(start=i) +40. 组合总和 II ***元素有重复只能取一次(结果都不可重复) 回溯法-先排序,递归然后循环并剪枝,最后回溯dfs(start=i+1);if (i > start && nums[i - 1] == nums[i])(剪枝) +46. 全排列 *** 给定数组无重复元素(结果都不可重复) 回溯法-depth、boolen[] +47. 全排列II ***给定数组有重复元素(结果都不可重复) 回溯法-先排序depth、boolen[];i > 0 && nums[i] == nums[i - 1] && !used[i - 1](剪枝) +78. 子集 *** 给定数组无重复元素(结果都不可重复) 回溯法-start,dfs(start=i + 1),递归方法先res.add(new ArrayList<>(curList)); +90. 子集II***给定数组有重复元素(结果都不可重复) 回溯法-先排序,剪枝 +//51. N皇后 *** ?? +79. 单词搜索 ???? 回溯法-定义boolean[][] visited;dfs(int index, char[][] board, String word, int x, int y),判断上|下|左|右 +0-1背包问题 /backtrack/ZeroOneBag + +dynamic +0-1背包问题 /dynamicprogramming/ZeroOndBag3======动态规划优化空间(滚动数组,从后往前) +416. 分割等和子集(0-1背包)数组是否能分割成两个元素和相等的子集 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; +474. 一和零(0-1背包)(String[] strs, int m, int n) 返回 strs 的最大子集的长度,dp[i][j][k]=Math.max(dp[i-1][j][k], dp[i-1][j-cnt[0]][k-cnt[1]] + 1); +494. 目标和(0-1背包)(int[] nums, int S)可加减 (同416)dp[i][j] = dp[i-1][j]+dp[i - 1][j-nums[i-1]]; +322. 零钱兑换(完全背包)返回所需的最少的硬币个数 Math.min(dp[i], dp[i - coin] + 1);return dp[amount] > amount ? -1 : dp[amount]; +518.零钱兑换 II (完全背包)返回所有组合数 dp[x] = dp[x] + dp[x - coin];return dp[amount]; +(股票买卖系列 start)===人为规定买入股票算一笔交易,dp[i][j][k]: i 表示第几天(0-n),j表示交易了几次(0,1,2),k表示是否持股(0-不持股,1-持股) + 由于当前天只参考了昨天的状态值,因此可以考虑使用「滚动数组」。 +121、最多买卖(交易)一次 双指针法,记录最小值和利益最大值 +122、最多买卖(交易)多次,不限次 贪心-迭代计算只要有收益就累加到max,返回max; +123、最多买卖(交易)两次 初始化:dp[0][1][1] = -prices[0];dp[0][2][1] = Integer.MIN_VALUE; +188、最多买卖(交易)K次 特判k>=len/2,转122,初始化:第i天交易k次持有股票dp[i][j][1] = Integer.MIN_VALUE;(还没发生的交易)i和j都有1个位置的偏移 +309、可以买卖(交易)多次,有冷冻期 dp[i][0]: 手上不持有股票不冷冻,dp[i][1]: 手上持有股票时,dp[i][2]: 手上不持有股票冷冻 + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2]);dp[i][2] = dp[i - 1][1] + prices[i]; +714、 可以买卖(交易)多次,一次买卖含一次手续费 +(股票买卖系列 end) +70. 爬楼梯 *** 1-迭代,2-动态规划,dp[i] = dp[i - 1] + dp[i - 2]; +120. 三角形最小路径和(自底向上) for(int i = n - 1; i >= 0; i--);for(int j = 0; j <= i; j++);dp[j]=Math.min(dp[j],dp[j+1]) + triangle.get(i).get(j); +64. 最小路径和(方格子自顶向下) 初始化dp[0][0]=grid[0][0];边界初始化第0行和第0列,for(int j=1;j= 0; j++){dp[i] = Math.min(dp[i], dp[i - j * j] + 1);} +343. 整数拆分-返回最大乘积 int[] dp=new int[n+1]; dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积 + for (int i = 2; i <= n; i++);for(int j = 1; j < i; j++){dp[i]=Math.max(dp[i], Math.max(dp[i-j]*j, (i-j) * j));} +72. 编辑距离 *** 边界初始化,if (word1.charAt(i - 1) == word2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1];}else{ + dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1;} +198. 打家劫舍 *** dp[i] 表示前 i 间房屋能偷窃到的最高总金额。dp[i] = Math.max(dp[i - 2]+nums[i], dp[i-1]);第i间房偷dp[i-2]+nums[i];第i间房不偷dp[i-1] +213. 打家劫舍II (环形房屋在198题的基础上,选择不偷第一家或不偷最后一家)Math.max(myRob2(Arrays.copyOfRange(nums, 0, len-1)),myRob2(Arrays.copyOfRange(nums,1,len))); +337. 打家劫舍III (二叉树房屋) 1-树的后序遍历; +(dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷; dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷) +int[] left = dfs(root.left); int[] right = dfs(root.right);int[] res = new int[2]; +res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);res[1] = root.val + left[0] + right[0]; diff --git a/src/main/java/com/chen/algorithm/znn/recursion/HanNuoTa/HanNuoTa.java b/src/main/java/com/chen/algorithm/znn/recursion/HanNuoTa/HanNuoTa.java new file mode 100644 index 0000000..73bc576 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/recursion/HanNuoTa/HanNuoTa.java @@ -0,0 +1,48 @@ +package com.chen.algorithm.znn.recursion.HanNuoTa; + +import org.junit.Test; + +import java.util.LinkedList; +import java.util.List; + +/** + * @Auther: zhunn + * @Date: 2020/11/2 10:12 + * @Description: 汉诺塔问题 + */ +public class HanNuoTa { + /** + * 将 A 上的所有盘子,借助 B,移动到C 上 + * + * @param A 原柱子 + * @param B 辅助柱子 + * @param C 目标柱子 + */ + public void hanota(List A, List B, List C) { + movePlate(A.size(), A, B, C); + } + + private void movePlate(int num, List original, List auxiliary, List target) { + if (num == 1) { // 只剩一个盘子时,直接移动即可 + target.add(original.remove(original.size() - 1)); + return; + } + + movePlate(num - 1, original, target, auxiliary); // 将 size-1 个盘子,从 original 移动到 auxiliary + target.add(original.remove(original.size() - 1)); // 将 第size个盘子,从 original 移动到 target + movePlate(num - 1, auxiliary, original, target); // 将 size-1 个盘子,从 auxiliary 移动到 target + } + + @Test + public void test() { + List listA = new LinkedList<>(); + listA.add(4); + listA.add(3); + listA.add(2); + listA.add(1); + List listB = new LinkedList<>(); + List listC = new LinkedList<>(); + hanota(listA, listB, listC); + System.out.println(listC); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/recursion/Test.java b/src/main/java/com/chen/algorithm/znn/recursion/Test.java new file mode 100644 index 0000000..3a8411a --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/recursion/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.recursion; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 递归相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/recursion/test172/Solution.java b/src/main/java/com/chen/algorithm/znn/recursion/test172/Solution.java new file mode 100644 index 0000000..0562445 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/recursion/test172/Solution.java @@ -0,0 +1,75 @@ +package com.chen.algorithm.znn.recursion.test172; + +import org.junit.Test; + +/** + * 172. 阶乘后的零 + * 给定一个整数 n,返回 n! 结果尾数中零的数量。 + * 示例 1: + * 输入: 3 + * 输出: 0 + * 解释: 3! = 6, 尾数中没有零。 + * 示例 2: + * 输入: 5 + * 输出: 1 + * 解释: 5! = 120, 尾数中有 1 个零. + * + * @Auther: zhunn + * @Date: 2020/11/2 10:43 + * @Description: 阶乘后的零的个数:1-递归;2-迭代 + */ +public class Solution { + + /** + * 1-递归 求阶乘 + * + * @param n + * @return + */ + private int getFactorial(int n) { + if (n < 0) { + return -1; + } + if (n == 0) { + return 1; + } + return n * getFactorial(n - 1); + } + + /** + * 2-迭代 求阶乘 + * + * @param n + * @return + */ + private int getFactorial2(int n) { + if (n < 0) { + return -1; + } + if (n == 0) { + return 1; + } + + int sum = 1; + for (int i = 1; i <= n; i++) { + sum *= i; + } + return sum; + } + + public int getZeroCount(int n) { + int sum = getFactorial2(n); + int count = 0; + while (sum % 10 == 0) { + count++; + sum /= 10; + } + return count; + } + + @Test + public void test() { + int res = getZeroCount(10); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/BubbleSort.java b/src/main/java/com/chen/algorithm/znn/sort/BubbleSort.java new file mode 100644 index 0000000..91e7e5b --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/BubbleSort.java @@ -0,0 +1,42 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * @Auther: zhunn + * @Date: 2020/11/9 18:40 + * @Description: 冒泡排序:O(n^2)稳定 + */ +public class BubbleSort { + + /** + * 冒泡排序: + * 初始关键字:[36 28 45 13 67 36 18 56] + * 第一趟:13 [36 28 45 18 67 36 56] + * 第二趟:13 18 [36 28 45 36 67 56] + * 第三趟:13 18 28 [36 36 45 56 67] + * 第四趟:13 18 28 36 [36 45 56 67] + */ + public int[] bubbleSort(int[] nums) { + + for (int i = 0; i < nums.length; i++) { + for (int j = nums.length - 1; j > i; j--) { + if (nums[j] < nums[j - 1]) { + int temp = nums[j]; + nums[j] = nums[j - 1]; + nums[j - 1] = temp; + } + } + } + return nums; + } + + @Test + public void test() { + int[] nums = {36, 28, 45, 13, 67, 36, 18, 56}; + int[] res = bubbleSort(nums); + System.out.println(Arrays.toString(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/ChoiceSort.java b/src/main/java/com/chen/algorithm/znn/sort/ChoiceSort.java new file mode 100644 index 0000000..aa928c5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/ChoiceSort.java @@ -0,0 +1,50 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * @Auther: zhunn + * @Date: 2020/11/9 18:41 + * @Description: 选择排序:O(n^2) 不稳定 + */ +public class ChoiceSort { + + /** + * 每一轮选择最小元素交换到未排定部分的开头 + * + * @param nums + * @return + */ + public int[] choiceSort(int[] nums) { + int len = nums.length; + + for (int i = 0; i < len - 1; i++) { // 循环不变量:[0, i) 有序,且该区间里所有元素就是最终排定的样子 + int minIndex = i; // 选择无序区间 [i, len - 1] 里最小的元素的索引,交换到下标 i,即未排序部分的开头 + for (int j = i + 1; j < len; j++) { + if (nums[j] < nums[minIndex]) { + minIndex = j; + } + } + + if (i == minIndex) { // 索引相同,不交换 + continue; + } + + // 交换 + int temp = nums[i]; + nums[i] = nums[minIndex]; + nums[minIndex] = temp; + } + return nums; + } + + @Test + public void test() { + int[] nums = {36, 28, 45, 13, 67, 36, 18, 56}; + int[] res = choiceSort(nums); + System.out.println(Arrays.toString(res)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/HeapSort.java b/src/main/java/com/chen/algorithm/znn/sort/HeapSort.java new file mode 100644 index 0000000..23df845 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/HeapSort.java @@ -0,0 +1,78 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * (了解) + * + * @Auther: zhunn + * @Date: 2020/11/10 16:32 + * @Description: 堆排序:O(nlogn) 不稳定 + */ +public class HeapSort { + + public int[] sortArray(int[] nums) { + int len = nums.length; + // 将数组整理成堆 + heapify(nums); + + // 循环不变量:区间 [0, i] 堆有序 + for (int i = len - 1; i >= 1; ) { + // 把堆顶元素(当前最大)交换到数组末尾 + swap(nums, 0, i); + // 逐步减少堆有序的部分 + i--; + // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序 + siftDown(nums, 0, i); + } + return nums; + } + + /** + * 将数组整理成堆(堆有序) + * + * @param nums + */ + private void heapify(int[] nums) { + int len = nums.length; + // 只需要从 i = (len - 1) / 2 这个位置开始逐层下移 + for (int i = (len - 1) / 2; i >= 0; i--) { + siftDown(nums, i, len - 1); + } + } + + /** + * @param nums + * @param k 当前下沉元素的下标 + * @param end [0, end] 是 nums 的有效部分 + */ + private void siftDown(int[] nums, int k, int end) { + while (2 * k + 1 <= end) { + int j = 2 * k + 1; + if (j + 1 <= end && nums[j + 1] > nums[j]) { + j++; + } + if (nums[j] > nums[k]) { + swap(nums, j, k); + } else { + break; + } + k = j; + } + } + + private void swap(int[] nums, int index1, int index2) { + int temp = nums[index1]; + nums[index1] = nums[index2]; + nums[index2] = temp; + } + + @Test + public void test() { + int[] nums = {6, 8, 2, 1, 2, 7}; + int[] res = sortArray(nums); + System.out.println(Arrays.toString(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/InsertSort.java b/src/main/java/com/chen/algorithm/znn/sort/InsertSort.java new file mode 100644 index 0000000..3943d43 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/InsertSort.java @@ -0,0 +1,36 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * @Auther: zhunn + * @Date: 2020/11/9 18:41 + * @Description: 插入排序:O(n^2) 稳定 + * 稳定排序,在接近有序的情况下,表现优异 + */ +public class InsertSort { + + public int[] insertSort(int[] nums) { + int len = nums.length; + for (int i = 1; i < len; i++) { // 循环不变量:将 nums[i] 插入到区间 [0, i) 使之成为有序数组 + int temp = nums[i]; // 先暂存这个元素,然后之前元素逐个后移,留出空位 + int left = i - 1; + // 注意边界 left >= 0 + while (left >= 0 && nums[left] > temp) { + nums[left + 1] = nums[left]; + left--; + } + nums[left + 1] = temp; + } + return nums; + } + + @Test + public void test() { + int[] nums = {6, 8, 2, 1, 2, 7}; + int[] res = insertSort(nums); + System.out.println(Arrays.toString(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/MergeSort.java b/src/main/java/com/chen/algorithm/znn/sort/MergeSort.java new file mode 100644 index 0000000..a38e5d8 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/MergeSort.java @@ -0,0 +1,64 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * @Auther: zhunn + * @Date: 2020/11/9 18:41 + * @Description: 归并排序:O(nlogn) 稳定,非原地排序 + */ +public class MergeSort { + + public int[] sortArray(int[] nums) { + int len = nums.length; + mergeSort(nums, 0, len - 1); + return nums; + } + + private void mergeSort(int[] nums, int low, int hight) { + if (low >= hight) { + return; + } + int mid = low + (hight - low) / 2; + mergeSort(nums, low, mid); + mergeSort(nums, mid + 1, hight); + merge(nums, low, mid, hight); + } + + public void merge(int[] nums, int low, int mid, int hight) { + int i = low; + int j = mid + 1; + int k = 0; + + int[] temp = new int[hight - low + 1]; + + while (i <= mid && j <= hight) { + if (nums[i] <= nums[j]) { + temp[k++] = nums[i++]; + } else { + temp[k++] = nums[j++]; + } + } + + while (i <= mid) { + temp[k++] = nums[i++]; + } + while (j <= hight) { + temp[k++] = nums[j++]; + } + + for (int m = 0; m < temp.length; m++) { + nums[low + m] = temp[m]; + } + } + + @Test + public void test() { + int[] nums = {6, 8, 2, 1, 2, 7}; + int[] res = sortArray(nums); + System.out.println(Arrays.toString(res)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/QuickSort.java b/src/main/java/com/chen/algorithm/znn/sort/QuickSort.java new file mode 100644 index 0000000..31caa1d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/QuickSort.java @@ -0,0 +1,58 @@ +package com.chen.algorithm.znn.sort; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * 重点 + * + * @Auther: zhunn + * @Date: 2020/11/9 18:41 + * @Description: 快速排序:O(nlogn) 不稳定 + */ +public class QuickSort { + + public int[] sortArray(int[] nums) { + if (nums == null || nums.length < 2) { + return nums; + } + quickSort(nums, 0, nums.length - 1); + return nums; + } + + private void quickSort(int[] nums, int low, int hight) { + if (low >= hight) { + return; + } + // 枢轴(基准值) + int pivot = partition(nums, low, hight); + quickSort(nums, low, pivot - 1); + quickSort(nums, pivot + 1, hight); + } + + private int partition(int[] nums, int low, int hight) { + int pivotValue = nums[low]; + while (low < hight) { + while (low < hight && pivotValue <= nums[hight]) { + hight--; + } + nums[low] = nums[hight]; + while (low < hight && nums[low] <= pivotValue) { + low++; + } + nums[hight] = nums[low]; + } + //System.out.println(low==hight); + nums[low] = pivotValue; + return low; + } + + @Test + public void test() { + int[] nums = {3, 7, 2, 7, 10, -1, 6}; + int[] res = sortArray(nums); + System.out.println(Arrays.toString(res)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/sort/Test.java b/src/main/java/com/chen/algorithm/znn/sort/Test.java new file mode 100644 index 0000000..fbdab5e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/sort/Test.java @@ -0,0 +1,70 @@ +package com.chen.algorithm.znn.sort; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 排序相关 + */ +public class Test { + + /** + * 冒泡排序 sort/BubbleSort (n2 稳定) *** (了解) + * 快速排序 sort/QuickSort (nlogn 不稳定)**(重点) + * 归并排序 sort/MergeSort (nlogn 稳定,非原地排序)(重点) + * 选择排序 sort/ChoiceSort (n2 稳定) *** (了解) + * 插入排序 sort/InsertSort (n2 不稳定) ***(熟悉) + * + * 堆排序 (根据需要熟悉) + */ + + /* + 一、内部排序: + 1、插入排序:1)直接插入排序 + 2)折半插入排序 + 3)希尔排序 + 2、交换排序:1)冒泡排序 + 2)快速排序 + 3、选择排序:1)简单选择排序 + 2)堆排序 + 4、归并排序 + 5、基数排序 + 二、外部排序:多路归并排序 + */ + + /** + * https://leetcode-cn.com/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/ + * 十大经典排序算法:https://www.cnblogs.com/onepixel/articles/7674659.html + * https://www.cnblogs.com/mcgrady/category/396002.html + * 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。 + * 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序 + */ + + /** + * 排序分为以下四类共七种排序方法: + * 交换排序: + * 1) 冒泡排序 + * 2) 快速排序 + * 选择排序: + * 3) 直接选择排序 + * 4) 堆排序 + * 插入排序: + * 5) 直接插入排序 + * 6) 希尔排序 + * 合并排序: + * 7) 归并排序 + * + * 每个排序方法的时间复杂度详细请看链接:https://www.cnblogs.com/onepixel/articles/7674659.html + * + * 排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性 复杂性 备注 + * 直接插入排序 O(n2) O(n2) O(n) O(1) 稳定 简单 大部分已排序时较好 + * 希尔排序 O(nlog2n) O(n2) O(n) O(1) 不稳定 较复杂 + * 直接选择排序 O(n2) O(n2) O(n2) O(1) 不稳定 简单 n小时较好 + * 堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定 较复杂 n大时较好 + * 冒泡排序 O(n2) O(n2) O(n) O(1) 稳定 简单 n小时较好 + * 快速排序 O(nlog2n) O(n2) O(nlog2n) O(nlog2n) 不稳定 较复杂 n大时较好 + * 归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定 较复杂 n大时较好 + * 基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定 较复杂 + * 计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定 + * 桶排序 O(n+k) O(n2) O(n) O(n+k) 稳定 + */ +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/Test.java b/src/main/java/com/chen/algorithm/znn/stack/Test.java new file mode 100644 index 0000000..b16cb67 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.stack; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 栈和队列相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test155/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test155/Solution.java new file mode 100644 index 0000000..925efd5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test155/Solution.java @@ -0,0 +1,76 @@ +package com.chen.algorithm.znn.stack.test155; + +import org.junit.Test; + +import java.util.Stack; + +/** + * 155. 最小栈 + * 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 + * push(x) —— 将元素 x 推入栈中。 + * pop() —— 删除栈顶的元素。 + * top() —— 获取栈顶元素。 + * getMin() —— 检索栈中的最小元素。 + * 示例: + * 输入: + * ["MinStack","push","push","push","getMin","pop","top","getMin"] + * [[],[-2],[0],[-3],[],[],[],[]] + * 输出: + * [null,null,null,null,-3,null,0,-2] + * 解释: + * MinStack minStack = new MinStack(); + * minStack.push(-2); + * minStack.push(0); + * minStack.push(-3); + * minStack.getMin(); --> 返回 -3. + * minStack.pop(); + * minStack.top(); --> 返回 0. + * minStack.getMin(); --> 返回 -2. + * + * @Auther: zhunn + * @Date: 2020/10/26 18:20 + * @Description: 最小栈 + */ +public class Solution { + + public Stack stack = new Stack<>(); + public Stack minStack = new Stack<>(); + + public void push(Integer value) { + stack.push(value); // 元素存入基础栈 + + if (minStack.isEmpty() || value < minStack.peek()) { // 元素满足条件就存入最小栈,比最小栈栈顶元素小也存入 + minStack.push(value); + } + } + + public void pop() { + + if (stack.pop().equals(minStack.peek())) { // 删除基础栈 栈顶元素,如果最小栈栈顶元素等于被删除的元素,一并删除。 + minStack.pop(); + } + } + + public int top() { + return stack.peek(); // 取出的是基础栈的栈顶元素 + } + + public int getMin() { + return minStack.peek(); // 获取的是最小栈的栈顶元素 + } + + @Test + public void test() { + Solution minStack = new Solution(); + minStack.push(-2); + minStack.push(0); + minStack.push(-3); + int minRes = minStack.getMin(); + System.out.println(minRes); + minStack.pop(); + int topRes = minStack.top(); + System.out.println(topRes); + int minRes2 = minStack.getMin(); + System.out.println(minRes2); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test20/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test20/Solution.java new file mode 100644 index 0000000..dcec18d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test20/Solution.java @@ -0,0 +1,72 @@ +package com.chen.algorithm.znn.stack.test20; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * 20. 有效的括号 + * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 + * 有效字符串需满足: + * 左括号必须用相同类型的右括号闭合。 + * 左括号必须以正确的顺序闭合。 + * 注意空字符串可被认为是有效字符串。 + * 示例 1: + * 输入: "()" + * 输出: true + * 示例 2: + * 输入: "()[]{}" + * 输出: true + * 示例 3: + * 输入: "(]" + * 输出: false + * 示例 4: + * 输入: "([)]" + * 输出: false + * 示例 5: + * 输入: "{[]}" + * 输出: true + * + * @Auther: zhunn + * @Date: 2020/10/27 14:18 + * @Description: 有效的括号 + */ +public class Solution { + + public boolean isValid(String s) { + if (s == null) { + return false; + } + Map signMap = new HashMap<>(3); + signMap.put('(', ')'); + signMap.put('[', ']'); + signMap.put('{', '}'); + + Stack stack = new Stack<>(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (signMap.containsKey(c)) { + stack.push(c); + } else { + if (stack.isEmpty() || signMap.get(stack.pop()) != c) { + return false; + } + } + } + return stack.isEmpty(); + } + + @Test + public void test() { + System.out.println(isValid("")); + System.out.println(isValid(" ")); + System.out.println(isValid("( ")); + System.out.println(isValid("()")); + System.out.println(isValid("(]")); + System.out.println(isValid("()[]{}")); + System.out.println(isValid("([)]")); + System.out.println(isValid("{[]}")); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test225/MyStack.java b/src/main/java/com/chen/algorithm/znn/stack/test225/MyStack.java new file mode 100644 index 0000000..ee6536b --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test225/MyStack.java @@ -0,0 +1,54 @@ +package com.chen.algorithm.znn.stack.test225; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 225. 用队列实现栈 + * 使用队列实现栈的下列操作: + * push(x) -- 元素 x 入栈 + * pop() -- 移除栈顶元素 + * top() -- 获取栈顶元素 + * empty() -- 返回栈是否为空 + * 注意: + * 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。 + * 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。 + * 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。 + * + * @Auther: zhunn + * @Date: 2020/10/26 22:20 + * @Description: 用两个队列实现一个栈, 官方解法:q2入栈,q1出栈(注意入栈逻辑) + */ +public class MyStack { + + private Queue q1; + private Queue q2; + + public MyStack() { + q1 = new LinkedList<>(); + q2 = new LinkedList<>(); + } + + public void push(int value) { + q2.offer(value); + while (!q1.isEmpty()) { + q2.offer(q1.poll()); + } + + Queue temp = q1; + q1 = q2; + q2 = temp; + } + + public int pop() { + return q1.poll(); + } + + public int top() { + return q1.peek(); + } + + public boolean empty() { + return q1.isEmpty(); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test225/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test225/Solution.java new file mode 100644 index 0000000..becb090 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test225/Solution.java @@ -0,0 +1,41 @@ +package com.chen.algorithm.znn.stack.test225; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * @Auther: zhunn + * @Date: 2020/10/26 21:20 + * @Description: 用两个队列实现一个栈 + */ +public class Solution { + + private Queue q1 = new LinkedList<>(); + private Queue q2 = new LinkedList<>(); + private int top; + + public void push(Integer value) { + q1.add(value); + top = value; + } + + public int pop() { + while (q1.size() > 1) { + q2.add(q1.peek()); + top = q1.remove(); + } + int result = q1.remove(); + Queue temp = q1; + q1 = q2; + q2 = temp; + return result; + } + + public int top() { + return top; + } + + public boolean empty() { + return q1.isEmpty() && q2.isEmpty(); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test227/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test227/Solution.java new file mode 100644 index 0000000..06ac6c3 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test227/Solution.java @@ -0,0 +1,82 @@ +package com.chen.algorithm.znn.stack.test227; + +import org.junit.Test; + +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/basic-calculator-ii/solution/chai-jie-fu-za-wen-ti-shi-xian-yi-ge-wan-zheng-ji-/ + * 227. 基本计算器 II + * 实现一个基本的计算器来计算一个简单的字符串表达式的值。 + * 字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。 + * 示例 1: + * 输入: "3+2*2" + * 输出: 7 + * 示例 2: + * 输入: " 3/2 " + * 输出: 1 + * 示例 3: + * 输入: " 3+5 / 2 " + * 输出: 5 + * + * @Auther: zhunn + * @Date: 2020/10/27 11:11 + * @Description: 基本计算II + */ +public class Solution { + + public void vert() { + String a = "796"; + int num = 0; + for (char c : a.toCharArray()) { + num = num * 10 + (c - '0'); + } + System.out.println(num); + } + + public int calculate(String s) { + if (s == null || s.trim().equals("")) { + return 0; + } + + Stack stack = new Stack<>(); + char sign = '+'; // 记录 num 前的符号,初始化为 + + int num = 0; // 记录算式中的数字 + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + (c - '0'); + } + + if ((!Character.isDigit(c) && c != ' ') || i == s.length() - 1) { //不只是遇到新的符号会触发入栈,当i走到了算式的尽头(i == s.size() - 1),也应该将前面的数字入栈,方便后续计算最终结果。 + int pre; + if (sign == '+') { + stack.push(num); + } else if (sign == '-') { + stack.push(-num); + } else if (sign == '*') { + pre = stack.pop(); + stack.push(pre * num); + } else if (sign == '/') { + pre = stack.pop(); + stack.push(pre / num); + } + sign = c; + num = 0; + } + } + int res = 0; + while (!stack.isEmpty()) { + res += stack.pop(); + } + return res; + } + + @Test + public void test() { + vert(); + System.out.println(calculate("60+20-30+5/2 ")); + } + + +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test232/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test232/Solution.java new file mode 100644 index 0000000..90f92cc --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test232/Solution.java @@ -0,0 +1,74 @@ +package com.chen.algorithm.znn.stack.test232; + +import java.util.Stack; + +/** + * 232. 用栈实现队列 + * 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列的支持的所有操作(push、pop、peek、empty): + * 实现 MyQueue 类: + * void push(int x) 将元素 x 推到队列的末尾 + * int pop() 从队列的开头移除并返回元素 + * int peek() 返回队列开头的元素 + * boolean empty() 如果队列为空,返回 true ;否则,返回 false + * 说明: + * 你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 + * 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 + * 进阶: + * 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。 + * 示例: + * 输入: + * ["MyQueue", "push", "push", "peek", "pop", "empty"] + * [[], [1], [2], [], [], []] + * 输出: + * [null, null, null, 1, 1, false] + * 解释: + * MyQueue myQueue = new MyQueue(); + * myQueue.push(1); // queue is: [1] + * myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) + * myQueue.peek(); // return 1 + * myQueue.pop(); // return 1, queue is [2] + * myQueue.empty(); // return false + * + * @Auther: zhunn + * @Date: 2020/10/26 17:36 + * @Description: 用栈实现队列: s1入队列,s2出队列(注意出队列逻辑) + */ +public class Solution { + + private Stack stack1 = new Stack<>(); + private Stack stack2 = new Stack<>(); + private int front; + + public void push(Integer value) { + if (stack1.isEmpty()) { + front = value; + } + stack1.push(value); + } + + public Integer pop() { + if (stack1.isEmpty() && stack2.isEmpty()) { + return null; + } + + if (stack2.isEmpty()) { + while (!stack1.isEmpty()) { + stack2.push(stack1.pop()); + } + } + return stack2.pop(); + } + + public Integer peek() { + if (!stack2.isEmpty()) { + return stack2.peek(); + } + + return front; + } + + public boolean empty() { + return stack1.isEmpty() && stack2.isEmpty(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test239/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test239/Solution.java new file mode 100644 index 0000000..55b5a68 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test239/Solution.java @@ -0,0 +1,157 @@ +package com.chen.algorithm.znn.stack.test239; + +import org.junit.Test; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.LinkedList; + +/** + * https://leetcode-cn.com/problems/sliding-window-maximum/solution/shuang-xiang-dui-lie-jie-jue-hua-dong-chuang-kou-2/ + * 239. 滑动窗口最大值 + * 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 + * 返回滑动窗口中的最大值。 + * 进阶: + * 你能在线性时间复杂度内解决此题吗? + * 示例: + * 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 + * 输出: [3,3,5,5,6,7] + * 解释: + * 滑动窗口的位置 最大值 + * --------------- ----- + * [1 3 -1] -3 5 3 6 7 3 + * 1 [3 -1 -3] 5 3 6 7 3 + * 1 3 [-1 -3 5] 3 6 7 5 + * 1 3 -1 [-3 5 3] 6 7 5 + * 1 3 -1 -3 [5 3 6] 7 6 + * 1 3 -1 -3 5 [3 6 7] 7 + * + * @Auther: zhunn + * @Date: 2020/10/27 14:32 + * @Description: 滑动窗口最大值 + */ +public class Solution { + + + /** + * 采用双端队列存储数组值 + * + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow(int[] nums, int k) { + if (nums == null || nums.length < 2) return nums; + + int[] res = new int[nums.length - k + 1]; + // 递减队列 + LinkedList queue = new LinkedList<>(); + + for (int i = 0; i < nums.length; i++) { + + // 添加滑入的数 nums[i] ,构造递减队列 + while (!queue.isEmpty() && queue.peekLast() < nums[i]) { + queue.pollLast(); + } + queue.addLast(nums[i]); + + // 删除滑出的数 nums[i - k],如果删除的数等于队头,删除队头 + if (i >= k && nums[i - k] == queue.peekFirst()) { + queue.pollFirst(); + } + + // 写入当前最大值 + if (i + 1 >= k) { + res[i + 1 - k] = queue.peekFirst(); + } + } + return res; + } + + ///** + // * 采用双端队列存储数组下标 (推荐) + // * https://leetcode-cn.com/problems/sliding-window-maximum/solution/shuang-xiang-dui-lie-jie-jue-hua-dong-chuang-kou-2/ + // * + // * @param nums + // * @param k + // * @return + // */ + //public int[] maxSlidingWindow1(int[] nums, int k) { + // if (nums == null || nums.length < 2) return nums; + // // 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序 + // LinkedList queue = new LinkedList(); + // // 结果数组 + // int[] result = new int[nums.length - k + 1]; + // // 遍历nums数组 + // for (int i = 0; i < nums.length; i++) { + // // 保证从大到小 如果前面数小则需要依次弹出,直至满足要求 + // while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) { + // queue.pollLast(); + // } + // // 添加当前值对应的数组下标 + // queue.addLast(i); + // // 判断当前队列中队首的值是否有效 + // if (queue.peek() <= i - k) { + // queue.poll(); + // } + // // 当窗口长度为k时 保存当前窗口中最大值 + // if (i + 1 >= k) { + // result[i + 1 - k] = nums[queue.peek()]; + // } + // } + // return result; + //} + // + //public int[] maxSlidingWindow2(int[] nums, int k) { + // if (nums == null || nums.length < 2) { + // return nums; + // } + // + // LinkedList queue = new LinkedList<>(); + // int[] res = new int[nums.length - k + 1]; + // for (int i = 0; i < nums.length; i++) { + // while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) { + // queue.pollLast(); + // } + // queue.addLast(i); + // if (queue.peekFirst() <= i - k) { + // queue.pollFirst(); + // } + // + // if (i + 1 >= k) { + // res[i + 1 - k] = nums[queue.peekFirst()]; + // } + // } + // return res; + //} + + @Test + public void testCase() { + + int[] nums = {1, 3, -1, -3, 5, 3, 2, 6, 7}; + int k = 3; + System.out.println(Arrays.toString(maxSlidingWindow(nums, k))); + } + + public int[] maxSlidingWindow2(int[] nums, int k) { + if (nums == null || nums.length < 2) { + return nums; + } + + int[] res = new int[nums.length - k + 1]; + LinkedList queue = new LinkedList<>(); + for (int i = 0; i < nums.length; i++) { + while (!queue.isEmpty() && nums[i] > queue.peekLast()) { + queue.pollLast(); + } + queue.addLast(nums[i]); + if (i >= k && nums[i - k] == queue.peekFirst()) { + queue.pollFirst(); + } + if (i + 1 >= k) { + res[i + 1 - k] = queue.peekFirst(); + } + } + return res; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/stack/test496/Solution.java b/src/main/java/com/chen/algorithm/znn/stack/test496/Solution.java new file mode 100644 index 0000000..4db5002 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/stack/test496/Solution.java @@ -0,0 +1,87 @@ +package com.chen.algorithm.znn.stack.test496; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/next-greater-element-i/solution/xia-yi-ge-geng-da-yuan-su-i-by-leetcode/ + * 496. 下一个更大元素 I + * 给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。 + * nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。 + * 示例 1: + * 输入: nums1 = [4,1,2], nums2 = [1,3,4,2]. + * 输出: [-1,3,-1] + * 解释: + * 对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。 + * 对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。 + * 对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。 + * 示例 2: + * 输入: nums1 = [2,4], nums2 = [1,2,3,4]. + * 输出: [3,-1] + * 解释: + * 对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。 + * 对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 下一个更大元素:单调栈 + */ +public class Solution { + + public int[] nextGreaterElement(int[] nums1, int[] nums2) { + Map map = new HashMap<>(); + Stack stack = new Stack<>(); + + for (int i = 0; i < nums2.length; i++) { + int num = nums2[i]; + while (!stack.isEmpty() && num > stack.peek()) { + map.put(stack.pop(), num); + } + stack.push(num); + } + + while (!stack.isEmpty()) { + map.put(stack.pop(), -1); + } + + int[] res = new int[nums1.length]; + for (int i = 0; i < nums1.length; i++) { + res[i] = map.get(nums1[i]); + } + return res; + } + + @Test + public void test() { + int[] nums1 = {4, 1, 2}; + int[] nums2 = {1, 3, 4, 2}; + int[] res = nextGreaterElement2(nums1, nums2); + System.out.println(Arrays.toString(res)); + } + + public int[] nextGreaterElement2(int[] nums1, int[] nums2) { + Map map = new HashMap<>(); + Stack stack = new Stack<>(); + + for (int i = 0; i < nums2.length; i++) { + while (!stack.isEmpty() && nums2[i] > stack.peek()) { + map.put(stack.pop(), nums2[i]); + } + stack.push(nums2[i]); + } + + while (!stack.isEmpty()) { + map.put(stack.pop(), -1); + } + + int[] res = new int[nums1.length]; + for (int i = 0; i < nums1.length; i++) { + res[i] = map.get(nums1[i]); + } + return res; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/string/Test.java b/src/main/java/com/chen/algorithm/znn/string/Test.java new file mode 100644 index 0000000..dcc8bae --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/string/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.string; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 字符串相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/string/test14/Solution.java b/src/main/java/com/chen/algorithm/znn/string/test14/Solution.java new file mode 100644 index 0000000..7c5b938 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/string/test14/Solution.java @@ -0,0 +1,48 @@ +package com.chen.algorithm.znn.string.test14; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/longest-common-prefix/solution/hua-jie-suan-fa-14-zui-chang-gong-gong-qian-zhui-b/ + * 14. 最长公共前缀 + * 编写一个函数来查找字符串数组中的最长公共前缀。 + * 如果不存在公共前缀,返回空字符串 ""。 + * 示例 1: + * 输入: ["flower","flow","flight"] + * 输出: "fl" + * 示例 2: + * 输入: ["dog","racecar","car"] + * 输出: "" + * 解释: 输入不存在公共前缀。 + * + * @Auther: zhunn + * @Date: 2020/10/27 15:51 + * @Description: 最长公共前缀 + */ +public class Solution { + + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + + String prefix = strs[0]; + + for (int i = 1; i < strs.length; i++) { + while (!strs[i].startsWith(prefix)) { // 如果不包含此前缀,缩小前缀继续比对 + if (prefix.length() == 0) { // 如果前缀长度已缩小为0,仍没有符合的,直接返回空串 + return ""; + } + prefix = prefix.substring(0, prefix.length() - 1); // 缩小前缀 + } + } + return prefix; + } + + @Test + public void test() { + String[] strs = {"flower", "flow", "flight"}; + String prefix = longestCommonPrefix(strs); + System.out.println(prefix); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/string/test3/Solution.java b/src/main/java/com/chen/algorithm/znn/string/test3/Solution.java new file mode 100644 index 0000000..94d9da1 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/string/test3/Solution.java @@ -0,0 +1,92 @@ +package com.chen.algorithm.znn.string.test3; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-jie-suan-fa-3-wu-zhong-fu-zi-fu-de-zui-chang-z/ + * 3. 无重复字符的最长子串 + * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 + * 示例 1: + * 输入: "abcabcbb" + * 输出: 3 + * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 + * 示例 2: + * 输入: "bbbbb" + * 输出: 1 + * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 + * 示例 3: + * 输入: "pwwkew" + * 输出: 3 + * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 + * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 + * + * @Auther: zhunn + * @Date: 2020/10/27 16:02 + * @Description: 无重复字符的最长子串:滑动窗口 + */ +public class Solution { + + /** + * 返回子串长度 + * @param s + * @return + */ + public int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) { + return 0; + } + + Map map = new HashMap<>(); + int ans = 0; + int n = s.length(); + int start = 0; + for (int end = 0; end < n; end++) { + char c = s.charAt(end); + if (map.containsKey(c)) { + start = Math.max(start, map.get(c)); + } + + ans = Math.max(ans, end - start + 1); // [start,end]区间内不存在重复字符 + map.put(c, end + 1); // value值为字符位置+1,加1标识从字符位置后一个才开始不重复 + } + return ans; + } + + /** + * 返回子串 + * @param s + * @return + */ + public String getSubString(String s) { + if (s == null || s.length() == 0) { + return s; + } + Map map = new HashMap<>(); + int start = 0; + String sub = ""; + for (int end = 0; end < s.length(); end++) { + char c = s.charAt(end); + if (map.containsKey(c)) { + start = Math.max(start, map.get(c)); + } + + if (sub.length() < end - start + 1) { + sub = s.substring(start, end + 1); + } + map.put(c, end + 1); + } + return sub; + } + + @Test + public void test() { + System.out.println(lengthOfLongestSubstring("abcabcbb")); + System.out.println(lengthOfLongestSubstring("bbbbb")); + System.out.println(lengthOfLongestSubstring("pwwkew")); + System.out.println(getSubString("pwwkew")); + + } +} diff --git a/src/main/java/com/chen/algorithm/znn/string/test415/Solution.java b/src/main/java/com/chen/algorithm/znn/string/test415/Solution.java new file mode 100644 index 0000000..b24882d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/string/test415/Solution.java @@ -0,0 +1,44 @@ +package com.chen.algorithm.znn.string.test415; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/add-strings/solution/zi-fu-chuan-xiang-jia-by-leetcode-solution/ + * 415. 字符串相加 + * 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。 + * + * @Auther: zhunn + * @Date: 2020/10/27 16:03 + * @Description: 字符串相加 + */ +public class Solution { + + public String addStrings(String num1, String num2) { + if (num1 == null || num1.length() == 0) { + return num2; + } + if (num2 == null || num2.length() == 0) { + return num1; + } + + int i = num1.length() - 1, j = num2.length() - 1, carry = 0; + StringBuilder ans = new StringBuilder(); + + while (i >= 0 || j >= 0 || carry != 0) { + int n1 = i >= 0 ? num1.charAt(i) - '0' : 0; + int n2 = j >= 0 ? num2.charAt(j) - '0' : 0; + int temp = n1 + n2 + carry; + ans.append(temp % 10); + carry = temp / 10; + i--; + j--; + } + + return ans.reverse().toString(); + } + + @Test + public void test() { + System.out.println(addStrings("623", "401")); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/string/test5/Solution.java b/src/main/java/com/chen/algorithm/znn/string/test5/Solution.java new file mode 100644 index 0000000..31e742d --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/string/test5/Solution.java @@ -0,0 +1,132 @@ +package com.chen.algorithm.znn.string.test5; + +import org.junit.Test; + +/** + * 官方解法:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/ + * https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/ + * 5. 最长回文子串 + * 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 + * 示例 1: + * 输入: "babad" + * 输出: "bab" + * 注意: "aba" 也是一个有效答案。 + * 示例 2: + * 输入: "cbbd" + * 输出: "bb" + * + * @Auther: zhunn + * @Date: 2020/10/27 16:02 + * @Description: 最长回文子串: + * 1-动态规划;时间复杂度:O(n2),空间复杂度:O(n2) + * 2-中心扩展算法;时间复杂度:O(n2),空间复杂度:O(1) + * 3-判断字符串是否是回文串;时间复杂度:O(n),空间复杂度:O(1) + */ +public class Solution { + + /** + * 1-动态规划 + * + * @param s + * @return x 表示增量,子串长度, + * 状态转移方程:dp[i][j]表示字符串 s 的第 i 到 j 个字母组成的串是否是回文串 + */ + public String longestPalindrome(String s) { + if (s == null || s.length() < 2) { + return s; + } + + int n = s.length(); + String ans = ""; + boolean[][] dp = new boolean[n][n]; + for (int x = 0; x < n; x++) { + for (int i = 0; i + x < n; i++) { //x 表示增量,子串长度, + int j = i + x; + if (x == 0) { //增量是0 + dp[i][j] = true; + } else if (x == 1) { //增量是1,只有两个字符 + dp[i][j] = (s.charAt(i) == s.charAt(j)); + } else { + dp[i][j] = (dp[i + 1][j - 1] && s.charAt(i) == s.charAt(j)); // 判断c aba c是否是回文串,需判断aba是否是回文串以及左右两边的字符是否相等 + } + + if (dp[i][j] && j - i + 1 > ans.length()) { // i到j组成的串是回文串,并且回文串的长度是否大于之前的回文串长度 + ans = s.substring(i, j + 1); + } + + //if (dp[i][j] && x + 1 > ans.length()) { // 可以将j-i+1替换成x+1,i+x+1替换成j+1,都表示回文串的长度 + // ans = s.substring(i, i + x + 1); + //} + } + } + return ans; + } + + /** + * 2-中心扩展算法 + * + * @param s + * @return + */ + public String longestPalindrome2(String s) { + if (s == null || s.length() < 1) { + return ""; + } + int start = 0, end = 0; + for (int i = 0; i < s.length(); i++) { + int len1 = expandAroundCenter(s, i, i); + int len2 = expandAroundCenter(s, i, i + 1); + int len = Math.max(len1, len2); + if (len > end - start) { + start = i - (len - 1) / 2; + end = i + len / 2; + } + } + return s.substring(start, end + 1); + } + + private int expandAroundCenter(String s, int left, int right) { + while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { + --left; + ++right; + } + return right - left - 1; + } + + /** + * 3-判断字符串是否是回文串 + * + * @param s + * @return + */ + public boolean isPalindrome(String s) { + if (s == null || s.length() == 0) { + return false; + } + + int len = s.length(); + int j = len - 1; + for (int i = 0; i < len / 2; i++) { + char head = s.charAt(i); + char tail = s.charAt(j); + if (head != tail) { + return false; + } + j--; + } + return true; + } + + @Test + public void test() { + System.out.println("123456".substring(0, 3)); + System.out.println(longestPalindrome("dcacdefd")); + System.out.println(longestPalindrome("babad")); + System.out.println(longestPalindrome("cbbd")); + System.out.println("---------------"); + System.out.println(isPalindrome("12345")); + System.out.println(isPalindrome("12321")); + System.out.println(isPalindrome("1221")); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/thread/ThreadTest.java b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest.java new file mode 100644 index 0000000..5282daa --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest.java @@ -0,0 +1,43 @@ +package com.chen.algorithm.znn.thread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @Auther: zhunn + * @Date: 2021/1/26 13:47 + * @Description: 仅使用volatile关键字 + */ +public class ThreadTest { + + private volatile static int count = 0; + + private volatile static boolean flag = true; + + + public static void main(String[] args) { + + // 启用线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + // 先从0,偶数开始 + executorService.execute(() -> { + while (count <= 100) { + if (flag) { + System.out.println("偶数:" + count++); + flag = false; + } + } + }); + executorService.execute(() -> { + while (count <= 100) { + if (!flag) { + System.out.println("奇数:" + count++); + flag = true; + } + } + }); + executorService.shutdown(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/thread/ThreadTest2.java b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest2.java new file mode 100644 index 0000000..a0073c4 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest2.java @@ -0,0 +1,42 @@ +package com.chen.algorithm.znn.thread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Auther: zhunn + * @Date: 2021/1/26 13:54 + * @Description: 使用AtomicInteger + */ +public class ThreadTest2 { + private static AtomicInteger count = new AtomicInteger(0); + + private volatile static boolean flag = true; + + public static void main(String[] args) { + + // 启用线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + // 先从0,偶数开始 + executorService.execute(() -> { + while (count.get() <= 100) { + if (flag) { + System.out.println("偶数:" + count.getAndIncrement()); + flag = false; + } + } + }); + executorService.execute(() -> { + while (count.get() <= 100) { + if (!flag) { + System.out.println("奇数:" + count.getAndIncrement()); + flag = true; + } + } + }); + executorService.shutdown(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/thread/ThreadTest3.java b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest3.java new file mode 100644 index 0000000..9b4c79a --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest3.java @@ -0,0 +1,70 @@ +package com.chen.algorithm.znn.thread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @Auther: zhunn + * @Date: 2021/1/26 13:55 + * @Description: 使用synchronized + */ +public class ThreadTest3 { + // 需要打印的资源: 0~100 + private static int count = 0; + + private static Object lock = new Object(); + + public static void main(String[] args) { + + // 启用线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + // 先从0,偶数开始 + executorService.execute(() -> { + while (count <= 100) { + synchronized (lock) { + try { + System.out.println("偶: " + count); + count++; + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 释放奇数线程 + lock.notifyAll(); + if (count <= 100) { + try { + // 防止偶数线程继续执行 + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + }); + executorService.execute(() -> { + while (count <= 100) { + synchronized (lock) { + try { + System.out.println("奇: " + count); + count++; + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.notifyAll(); + if (count <= 100) { + try { + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + }); + executorService.shutdown(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/thread/ThreadTest4.java b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest4.java new file mode 100644 index 0000000..e029e83 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest4.java @@ -0,0 +1,65 @@ +package com.chen.algorithm.znn.thread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @Auther: zhunn + * @Date: 2021/1/26 13:55 + * @Description: 使用ReentrantLock与Condition + */ +public class ThreadTest4 { + // 需要打印的资源: 0~100 + private static int count = 0; + + // 通过condition控制线程竞争 + private static final ReentrantLock lock = new ReentrantLock(); + private static final Condition condition1 = lock.newCondition(); + private static final Condition condition2 = lock.newCondition(); + + public static void main(String[] args) { + + // 启用线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + // 先从0,偶数开始 + executorService.execute(() -> { + while (count <= 100) { + try { + lock.lock(); + System.out.println("偶: " + count); + count++; + // 把偶数线程阻塞 + condition1.await(); + // 把奇数线程唤醒 + condition2.signal(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + }); + executorService.execute(() -> { + while (count <= 100) { + try { + lock.lock(); + System.out.println("奇: " + count); + count++; + // 把偶数线程唤醒 + condition1.signal(); + // 把奇数线程阻塞 + condition2.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + }); + executorService.shutdown(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/thread/ThreadTest5.java b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest5.java new file mode 100644 index 0000000..6a20a02 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/thread/ThreadTest5.java @@ -0,0 +1,75 @@ +package com.chen.algorithm.znn.thread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @Auther: zhunn + * @Date: 2021/1/26 13:56 + * @Description: 仅使用ReentrantLock + */ +public class ThreadTest5 { + // 需要打印的资源: 0~100 + private static int count = 0; + + // 是否执行打印的标志 + private static volatile boolean flag = false; + + // 通过加锁控制线程竞争 + private static final ReentrantLock lock = new ReentrantLock(); + + public static void main(String[] args) { + + // 启用线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + // 先从0,偶数开始 + executorService.execute(() -> { + while (count <= 100) { + if (!flag) { + try { + lock.lock(); + System.out.println("偶: " + count); + count++; + // 只有flag变为true了,此线程才不会接着执行,将争夺权给奇数线程 + flag = true; + } finally { + lock.unlock(); + } + } else { + // 防止线程空转 + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + executorService.execute(() -> { + while (count <= 100) { + // 当偶数线程执行后flag变为true,此线程可以执行了 + if (flag) { + try { + lock.lock(); + System.out.println("奇: " + count); + count++; + // 让出CPU + flag = false; + } finally { + lock.unlock(); + } + } else { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + executorService.shutdown(); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/Test.java b/src/main/java/com/chen/algorithm/znn/tree/Test.java new file mode 100644 index 0000000..42ecc17 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/Test.java @@ -0,0 +1,9 @@ +package com.chen.algorithm.znn.tree; + +/** + * @Auther: zhunn + * @Date: 2020/10/24 17:23 + * @Description: 二叉树相关 + */ +public class Test { +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/TreeNode.java b/src/main/java/com/chen/algorithm/znn/tree/TreeNode.java new file mode 100644 index 0000000..0ea49dd --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/TreeNode.java @@ -0,0 +1,25 @@ +package com.chen.algorithm.znn.tree; + +/** + * @Auther: zhunn + * @Date: 2020/10/28 10:25 + * @Description: 二叉树节点 + */ +public class TreeNode { + + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(){} + + public TreeNode(int val){ + this.val = val; + } + + public TreeNode(int val, TreeNode left, TreeNode right){ + this.val = val; + this.left = left; + this.right = right; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test101/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test101/Solution.java new file mode 100644 index 0000000..3bf4340 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test101/Solution.java @@ -0,0 +1,125 @@ +package com.chen.algorithm.znn.tree.test101; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 101. 对称二叉树 + * 给定一个二叉树,检查它是否是镜像对称的。 + * 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 + * 1 + * / \ + * 2 2 + * / \ / \ + * 3 4 4 3 + * 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: + * 1 + * / \ + * 2 2 + * \ \ + * 3 3 + * + * @Auther: zhunn + * @Date: 2020/10/29 11:03 + * @Description: 对称二叉树:1-递归,2-迭代(推荐) + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public boolean isSymmetric1(TreeNode root) { + return isMirror(root, root); + } + + private boolean isMirror(TreeNode root1, TreeNode root2) { + if (root1 == null && root2 == null) { + return true; + } + if (root1 == null || root2 == null) { + return false; + } + return (root1.val == root2.val) && isMirror(root1.left, root2.right) && isMirror(root1.right, root2.left); + } + + /** + * 2-迭代(推荐) + * + * @param root + * @return + */ + public boolean isSymmetric2(TreeNode root) { + return check(root, root); + } + + private boolean check(TreeNode u, TreeNode v) { + Queue queue = new LinkedList<>(); + queue.add(u); + queue.add(v); + while (!queue.isEmpty()) { + u = queue.poll(); + v = queue.poll(); + if (u == null && v == null) { + continue; + } + if ((u == null || v == null) || (u.val != v.val)) { + return false; + } + + queue.add(u.left); + queue.add(v.right); + + queue.add(u.right); + queue.add(v.left); + } + return true; + } + + @Test + public void test() { + TreeNode left3 = new TreeNode(3); + TreeNode right4 = new TreeNode(4); + TreeNode left3_3 = new TreeNode(3); + TreeNode right4_4 = new TreeNode(4); + TreeNode left2 = new TreeNode(2, left3, right4); + TreeNode right2 = new TreeNode(2, right4_4, left3_3); + TreeNode root = new TreeNode(1, left2, right2); + + System.out.println(isSymmetric3(root)); + + } + + public boolean isSymmetric3(TreeNode root){ + return check(root,root); + } + private boolean check2(TreeNode u,TreeNode v){ + Queue queue = new LinkedList<>(); + + queue.add(u); + queue.add(v); + + while(!queue.isEmpty()){ + u = queue.poll(); + v = queue.poll(); + if(u == null && v == null){ + continue; + } + if(u == null || v == null || u.val != v.val){ + return false; + } + + queue.add(u.left); + queue.add(v.right); + + queue.add(u.right); + queue.add(v.left); + } + return true; + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test102/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test102/Solution.java new file mode 100644 index 0000000..6de2aa9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test102/Solution.java @@ -0,0 +1,78 @@ +package com.chen.algorithm.znn.tree.test102; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 102. 二叉树的层序遍历 + * 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 + * 示例: + * 二叉树:[3,9,20,null,null,15,7], + * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回其层次遍历结果: + * [ + * [3], + * [9,20], + * [15,7] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/28 13:52 + * @Description: 二叉树的层序遍历:1-BFS实现 + */ +public class Solution { + + /** + * 1-BFS + * + * @param root + * @return + */ + public List> levelOrder(TreeNode root) { + if (root == null) { + return null; + } + + List> res = new LinkedList<>(); + Queue queue = new LinkedList<>(); + queue.add(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List levelList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + levelList.add(node.val); + + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + } + res.add(levelList); + } + return res; + } + + @Test + public void test() { + TreeNode left = new TreeNode(9); + TreeNode right = new TreeNode(20, new TreeNode(15), new TreeNode(7)); + TreeNode root = new TreeNode(3, left, right); + + List> res = levelOrder(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test103/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test103/Solution.java new file mode 100644 index 0000000..5d252f9 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test103/Solution.java @@ -0,0 +1,143 @@ +package com.chen.algorithm.znn.tree.test103; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/solution/jiao-ti-shi-yong-zhan-jian-dan-shi-xian-ju-chi-xin/ + * 103. 二叉树的锯齿形层次遍历 + * 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 + * 例如: + * 给定二叉树 [3,9,20,null,null,15,7], + * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回锯齿形层次遍历如下: + * [ + * [3], + * [20,9], + * [15,7] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/28 15:03 + * @Description: 二叉树的锯齿形层次遍历:交替使用栈 + */ +public class Solution { + + /** + * 交替使用栈 + * + * @param root + * @return + */ + public List> zigzagLevelOrder1(TreeNode root) { + if (root == null) { + return null; + } + + List> res = new ArrayList<>(); + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + + stack1.push(root); + + while (!stack1.isEmpty() || !stack2.isEmpty()) { + List subList = new ArrayList<>(); + + if (!stack1.isEmpty()) { + while (!stack1.isEmpty()) { + TreeNode node = stack1.pop(); + subList.add(node.val); + if (node.left != null) { + stack2.push(node.left); + } + if (node.right != null) { + stack2.push(node.right); + } + } + res.add(subList); + } else { + while (!stack2.isEmpty()) { + TreeNode node = stack2.pop(); + subList.add(node.val); + + if (node.right != null) { + stack1.push(node.right); + } + if (node.left != null) { + stack1.push(node.left); + } + } + res.add(subList); + } + } + return res; + } + + //public List> zigzagLevelOrder(TreeNode root) { + // List> list = new ArrayList<>(); + // if (root == null) { + // return list; + // } + // //栈1来存储右节点到左节点的顺序 + // Stack stack1 = new Stack<>(); + // //栈2来存储左节点到右节点的顺序 + // Stack stack2 = new Stack<>(); + // + // //根节点入栈 + // stack1.push(root); + // + // //每次循环中,都是一个栈为空,一个栈不为空,结束的条件两个都为空 + // while (!stack1.isEmpty() || !stack2.isEmpty()) { + // List subList = new ArrayList<>(); // 存储这一个层的数据 + // TreeNode cur; + // + // if (!stack1.isEmpty()) { //栈1不为空,则栈2此时为空,需要用栈2来存储从下一层从左到右的顺序 + // while (!stack1.isEmpty()) { //遍历栈1中所有元素,即当前层的所有元素 + // cur = stack1.pop(); + // subList.add(cur.val); //存储当前层所有元素 + // + // if (cur.left != null) { //左节点不为空加入下一层 + // stack2.push(cur.left); + // } + // if (cur.right != null) { //右节点不为空加入下一层 + // stack2.push(cur.right); + // } + // } + // list.add(subList); + // } else {//栈2不为空,则栈1此时为空,需要用栈1来存储从下一层从右到左的顺序 + // while (!stack2.isEmpty()) { + // cur = stack2.pop(); + // subList.add(cur.val); + // + // if (cur.right != null) {//右节点不为空加入下一层 + // stack1.push(cur.right); + // } + // if (cur.left != null) { //左节点不为空加入下一层 + // stack1.push(cur.left); + // } + // } + // list.add(subList); + // } + // } + // return list; + //} + + @Test + public void test() { + TreeNode left = new TreeNode(9); + TreeNode right = new TreeNode(20, new TreeNode(15), new TreeNode(7)); + TreeNode root = new TreeNode(3, left, right); + + List> res = zigzagLevelOrder1(root); + System.out.println(JSON.toJSON(res)); + } +} \ No newline at end of file diff --git a/src/main/java/com/chen/algorithm/znn/tree/test104/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test104/Solution.java new file mode 100644 index 0000000..63c0409 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test104/Solution.java @@ -0,0 +1,83 @@ +package com.chen.algorithm.znn.tree.test104; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 104. 二叉树的最大深度 + * 给定一个二叉树,找出其最大深度。 + * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 + * 说明: 叶子节点是指没有子节点的节点。 + * 示例: + * 给定二叉树 [3,9,20,null,null,15,7], + * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回它的最大深度 3 。 + * + * @Auther: zhunn + * @Date: 2020/10/29 17:57 + * @Description: 二叉树的最大深度:1-递归;2-广度优先搜索(BFS-层次遍历)(推荐) + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public int maxDepth(TreeNode root) { + return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; + } + + /** + * 2-广度优先搜索(层次遍历)(推荐) + * + * @param root + * @return + */ + public int maxDepth2(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + + int maxDepth = 0; + while (!queue.isEmpty()) { + maxDepth++; + + int len = queue.size(); + for (int i = 0; i < len; i++) { + TreeNode node = queue.poll(); + + if (node.left != null) { + queue.add(node.left); + } + + if (node.right != null) { + queue.add(node.right); + } + } + } + return maxDepth; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + int res = maxDepth2(root); + System.out.println(res); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test107/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test107/Solution.java new file mode 100644 index 0000000..90d098e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test107/Solution.java @@ -0,0 +1,117 @@ +package com.chen.algorithm.znn.tree.test107; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.*; + +/** + * 107. 二叉树的层次遍历 II + * 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) + * 例如: + * 给定二叉树 [3,9,20,null,null,15,7], + * 3 + * / \ + * 9 20 + * / \ + * 15 7 + * 返回其自底向上的层次遍历为: + * [ + * [15,7], + * [9,20], + * [3] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/28 15:50 + * @Description: 二叉树的层次遍历II + */ +public class Solution { + + /** + * 使用队列存储每层元素,用栈存储每层的结果集 + * @param root + * @return + */ + public List> levelOrderBottom(TreeNode root) { + if (root == null) { + return null; + } + + Stack> stack = new Stack<>(); + Queue queue = new LinkedList<>(); + queue.add(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List subList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + subList.add(node.val); + + if (node.left != null) { + queue.add(node.left); + } + + if (node.right != null) { + queue.add(node.right); + } + } + + stack.push(subList); + } + + List> res = new ArrayList<>(); + while (!stack.isEmpty()) { + res.add(stack.pop()); + } + return res; + } + + /** + * (推荐)使用java的LinkedList的性质,从上到下遍历,将新遍历的层结果集放入linkedlist的头部 + * @param root + * @return + */ + public List> levelOrderBottom1(TreeNode root) { + if (root == null) { + return null; + } + + List> res = new LinkedList<>(); + Queue queue = new LinkedList<>(); + queue.add(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List subList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + subList.add(node.val); + + if (node.left != null) { + queue.add(node.left); + } + + if (node.right != null) { + queue.add(node.right); + } + } + + res.add(0, subList); + } + + return res; + } + + @Test + public void test() { + TreeNode left = new TreeNode(9); + TreeNode right = new TreeNode(20, new TreeNode(15), new TreeNode(7)); + TreeNode root = new TreeNode(3, left, right); + + List> res = levelOrderBottom1(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test111/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test111/Solution.java new file mode 100644 index 0000000..672a5ac --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test111/Solution.java @@ -0,0 +1,107 @@ +package com.chen.algorithm.znn.tree.test111; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 111. 二叉树的最小深度 (需要看leetcode题上面的图) + * 给定一个二叉树,找出其最小深度。 + * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 + * 说明:叶子节点是指没有子节点的节点。 + * 示例 1: + * 输入:root = [3,9,20,null,null,15,7] + * 输出:2 + * 示例 2: + * 输入:root = [2,null,3,null,4,null,5,null,6] + * 输出:5 + * + * @Auther: zhunn + * @Date: 2020/10/29 18:40 + * @Description: 二叉树的最小深度:1-深度优先搜索(DFS),即递归;2-广度优先搜索(BFS)(推荐) + */ +public class Solution { + + /** + * 1-深度优先搜索(DFS),即递归 + * 叶子结点都为空,需要走到叶子结点,当一个结点为空一个结点不为空时,说明叶子结点在不为空的那个节点,返回不为空 + * + * @param root + * @return + */ + public int minDepth(TreeNode root) { + + if (root == null) { + return 0; + } + int left = minDepth(root.left); + int right = minDepth(root.right); + + return (left == 0 || right == 0) ? left + right + 1 : Math.min(left, right) + 1; + } + + public int minDepth1(TreeNode root) { + if (root == null) { + return 0; + } + + //if(root.left == null && root.right == null){ + // return 1; + //} + + int leftDepth = minDepth1(root.left); + int rightDepth = minDepth1(root.right); + + // 叶子结点,是左右子节点都为空的节点,当某一个子节点为空时,则返回另一个结点的高度。 + // 当一个结点为空一个结点不为空时,说明**叶子结点**在不为空的那个节点。 + return root.left == null || root.right == null ? leftDepth + rightDepth + 1 : Math.min(leftDepth, rightDepth) + 1; + } + + /** + * 2-广度优先搜索(BFS)(推荐) + * + * @param root + * @return + */ + public int minDepth2(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + + int minDepth = 0; + while (!queue.isEmpty()) { + minDepth++; + + int length = queue.size(); + for (int i = 0; i < length; i++) { + TreeNode node = queue.poll(); + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + + if (node.left == null && node.right == null) { + return minDepth; + } + } + } + return minDepth; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + int res = minDepth2(root); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test112/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test112/Solution.java new file mode 100644 index 0000000..709e6ac --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test112/Solution.java @@ -0,0 +1,97 @@ +package com.chen.algorithm.znn.tree.test112; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 112. 路径总和 + * 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 + * 说明: 叶子节点是指没有子节点的节点。 + * 示例: + * 给定如下二叉树,以及目标和 sum = 22, + *

+ * 5 + * / \ + * 4 8 + * / / \ + * 11 13 4 + * / \ \ + * 7 2 1 + * 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 路径总和:1-递归;2-广度优先搜索(推荐) + */ +public class Solution { + + /** + * 1-深度优先搜索 + * + * @param root + * @param sum + * @return + */ + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) { + return false; + } + if (root.left == null && root.right == null) { + return sum == root.val; + } + return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); + } + + /** + * 2-广度优先搜索(推荐) + * + * @param root + * @param sum + * @return + */ + public boolean hasPathSum2(TreeNode root, int sum) { + if (root == null) { + return false; + } + Queue queueNode = new LinkedList<>(); + queueNode.add(root); + Queue queueSum = new LinkedList<>(); + queueSum.add(0); + + while (!queueNode.isEmpty()) { + TreeNode node = queueNode.poll(); + int rec = queueSum.poll() + node.val; + + if (node.left == null && node.right == null) { + if (rec == sum) { + return true; + } + continue; + } + if (node.left != null) { + queueNode.add(node.left); + queueSum.add(rec); + } + if (node.right != null) { + queueNode.add(node.right); + queueSum.add(rec); + } + + } + return false; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1, new TreeNode(3), null); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + System.out.println(hasPathSum(root, 7)); + System.out.println(hasPathSum2(root, 7)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test113/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test113/Solution.java new file mode 100644 index 0000000..79cee83 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test113/Solution.java @@ -0,0 +1,131 @@ +package com.chen.algorithm.znn.tree.test113; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.*; + +/** + * 113. 路径总和 II + * 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 + * 说明: 叶子节点是指没有子节点的节点。 + * 示例: + * 给定如下二叉树,以及目标和 sum = 22, + *

+ * 5 + * / \ + * 4 8 + * / / \ + * 11 13 4 + * / \ / \ + * 7 2 5 1 + * 返回: + * [ + * [5,4,11,2], + * [5,8,4,5] + * ] + * + * @Auther: zhunn + * @Date: 2020/10/28 10:11 + * @Description: 路径之和 II,二叉树中和为某一值的路径:1-深度优先搜索;2-广度优先搜索(推荐) + * 同剑指 Offer 34 + */ +public class Solution { + + List> res = new LinkedList<>(); + Deque path = new LinkedList<>(); + + /** + * 1-深度优先搜索 + * + * @param root + * @param sum + * @return + */ + public List> pathSum(TreeNode root, int sum) { + if (root == null) { + return res; + } + dfs(root, sum); + return res; + } + + private void dfs(TreeNode root, int sum) { + if (root == null) { + return; + } + path.add(root.val); + sum -= root.val; + if (root.left == null && root.right == null && sum == 0) { + res.add(new LinkedList<>(path)); + } + + dfs(root.left, sum); + dfs(root.right, sum); + path.pollLast(); + } + + List> res2 = new LinkedList<>(); + Map map = new HashMap<>(); + + /** + * 2-广度优先搜索 + * + * @param root + * @param sum + * @return + */ + public List> pathSum2(TreeNode root, int sum) { + if (root == null) { + return res2; + } + Queue queueNode = new LinkedList<>(); + queueNode.add(root); + Queue queueSum = new LinkedList<>(); + queueSum.add(0); + + while (!queueNode.isEmpty()) { + TreeNode node = queueNode.poll(); + int rec = queueSum.poll() + node.val; + + if (node.left == null && node.right == null) { + if (rec == sum) { + getPath(node); + } + continue; + } + if (node.left != null) { + map.put(node.left, node); + queueNode.add(node.left); + queueSum.add(rec); + } + if (node.right != null) { + map.put(node.right, node); + queueNode.add(node.right); + queueSum.add(rec); + } + + } + return res2; + } + + private void getPath(TreeNode node) { + List temp = new LinkedList<>(); + while (node != null) { + temp.add(node.val); + node = map.get(node); + } + Collections.reverse(temp); + res2.add(temp); + } + + @Test + public void test() { + TreeNode left = new TreeNode(1, new TreeNode(3), null); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + List> res = pathSum2(root, 7); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test144/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test144/Solution.java new file mode 100644 index 0000000..2f645c5 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test144/Solution.java @@ -0,0 +1,85 @@ +package com.chen.algorithm.znn.tree.test144; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * 144. 二叉树的前序遍历 + * 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 + * + * @Auther: zhunn + * @Date: 2020/10/28 16:25 + * @Description: 二叉树的前序遍历:1-递归;2-迭代 (推荐) 两种方式都要会 + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public List preorderTraversal1(TreeNode root) { + if (root == null) { + return null; + } + List res = new ArrayList<>(); + preOrder(root, res); + return res; + } + + private void preOrder(TreeNode root, List res) { + if (root == null) { + return; + } + + res.add(root.val); + preOrder(root.left, res); + preOrder(root.right, res); + } + + /** + * 2-迭代 (推荐) + * + * @param root + * @return + */ + public List preorderTraversal2(TreeNode root) { + if (root == null) { + return null; + } + + List res = new ArrayList<>(); + Stack stack = new Stack<>(); + + stack.push(root); + while (!stack.isEmpty()) { + TreeNode node = stack.pop(); + res.add(node.val); + + if (node.right != null) { + stack.push(node.right); + } + + if (node.left != null) { + stack.push(node.left); + } + } + + return res; + } + + @Test + public void test() { + TreeNode right = new TreeNode(2, new TreeNode(3), null); + TreeNode root = new TreeNode(1, null, right); + + List res = preorderTraversal2(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test145/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test145/Solution.java new file mode 100644 index 0000000..737a893 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test145/Solution.java @@ -0,0 +1,116 @@ +package com.chen.algorithm.znn.tree.test145; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.*; + +/** + * 145. 二叉树的后序遍历 + * 给定一个二叉树,返回它的 后序 遍历。 + * + * @Auther: zhunn + * @Date: 2020/10/28 10:28 + * @Description: 二叉树的后序遍历:1-递归;2-(推荐)迭代(时间复杂度和空间复杂度都是O(n)) 两种方式都要会 + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public List postorderTraversal1(TreeNode root) { + if (root == null) { + return null; + } + List res = new ArrayList<>(); + postorder(root, res); + return res; + } + + private void postorder(TreeNode root, List res) { + if (root == null) return; + + postorder(root.left, res); + postorder(root.right, res); + res.add(root.val); + } + + /** + * 2-迭代法,两个栈实现(推荐) + * + * @param root + * @return + */ + public List postorderTraversal2(TreeNode root) { + if (root == null) return null; + + List res = new ArrayList<>(); + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + + stack1.push(root); // 先放根 + while (!stack1.isEmpty()) { + TreeNode node = stack1.pop(); // 出栈压入stack2的栈底 + stack2.push(node); + + if (node.left != null) { // 先入左子树,再出栈入stack2,迭代出栈就会先出 + stack1.push(node.left); + } + + if (node.right != null) { + stack1.push(node.right); + } + } + + while (!stack2.isEmpty()) { + res.add(stack2.pop().val); + } + + return res; + } + + /** + * 2-迭代法 官方题解,双端队列实现 + * + * @param root + * @return + */ + public List postorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) { + return res; + } + + Deque stack = new LinkedList<>(); + TreeNode prev = null; + while (root != null || !stack.isEmpty()) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + if (root.right == null || root.right == prev) { + res.add(root.val); + prev = root; + root = null; + } else { + stack.push(root); + root = root.right; + } + } + return res; + } + + @Test + public void test() { + TreeNode right = new TreeNode(2, new TreeNode(3), null); + TreeNode root = new TreeNode(1, null, right); + + List res = postorderTraversal2(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test199/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test199/Solution.java new file mode 100644 index 0000000..d31a72e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test199/Solution.java @@ -0,0 +1,99 @@ +package com.chen.algorithm.znn.tree.test199; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 199. 二叉树的右视图 + * 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 + * 示例: + * 输入: [1,2,3,null,5,null,4] + * 输出: [1, 3, 4] + * 解释: + * 1 <--- + * / \ + * 2 3 <--- + * \ \ + * 5 4 <--- + * + * @Auther: zhunn + * @Date: 2020/10/28 17:41 + * @Description: 二叉树的右视图:1-广度优先搜索,即层次遍历(推荐);2-深度优先搜索 + */ +public class Solution { + + /** + * 1-广度优先搜索(推荐) + * + * @param root + * @return + */ + public List rightSideView(TreeNode root) { + if (root == null) { + return null; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + List res = new ArrayList<>(); + + while (!queue.isEmpty()) { + int size = queue.size(); + + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + // 将当前层的最后一个节点放入结果列表 + if (i == size - 1) { + res.add(node.val); + } + } + } + return res; + } + + List res = new ArrayList<>(); + /** + * 2-深度优先搜索 + * @param root + * @return + */ + public List rightSideView2(TreeNode root) { + dfs(root, 0); // 从根节点开始访问,根节点深度是0 + return res; + } + + private void dfs(TreeNode root, int depth) { + if (root == null) { + return; + } + // 先访问 当前节点,再递归地访问 右子树 和 左子树。 + if (depth == res.size()) { // 如果当前节点所在深度还没有出现在res里,说明在该深度下当前节点是第一个被访问的节点,因此将当前节点加入res中。 + res.add(root.val); + } + depth++; + dfs(root.right, depth); + dfs(root.left, depth); + } + + @Test + public void test() { + TreeNode left = new TreeNode(9); + TreeNode right = new TreeNode(20, new TreeNode(15), new TreeNode(7)); + TreeNode root = new TreeNode(3, left, right); + + List res = rightSideView(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test226/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test226/Solution.java new file mode 100644 index 0000000..003d547 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test226/Solution.java @@ -0,0 +1,120 @@ +package com.chen.algorithm.znn.tree.test226; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.apache.zookeeper.server.quorum.flexible.QuorumHierarchical; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * https://leetcode-cn.com/problems/invert-binary-tree/solution/dong-hua-yan-shi-liang-chong-shi-xian-226-fan-zhua/ + * 226. 翻转二叉树 + * 翻转一棵二叉树。 + * 示例: + * 输入: + * 4 + * / \ + * 2 7 + * / \ / \ + * 1 3 6 9 + * 输出: + * 4 + * / \ + * 7 2 + * / \ / \ + * 9 6 3 1 + * + * @Auther: zhunn + * @Date: 2020/10/28 17:58 + * @Description: 翻转二叉树:1-递归;2-迭代(推荐) + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public TreeNode invertTree1(TreeNode root) { + if (root == null) { + return root; + } + TreeNode temp = root.right; + root.right = root.left; + root.left = temp; + + invertTree1(root.left); + invertTree1(root.right); + return root; + } + + /** + * 2-迭代 用queue层次遍历交换处理(推荐) + * + * @param root + * @return + */ + public TreeNode invertTree2(TreeNode root) { + if (root == null) { + return root; + } + + Queue queue = new LinkedList<>(); //将二叉树中的节点逐层放入队列中,再迭代处理队列中的元素 + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // //每次都从队列中拿一个节点,并交换这个节点的左右子树 + + TreeNode temp = node.right; + node.right = node.left; + node.left = temp; + + if (node.left != null) { //如果当前节点的左子树不为空,则放入队列等待后续处理 + queue.add(node.left); + } + if (node.right != null) { //如果当前节点的右子树不为空,则放入队列等待后续处理 + queue.add(node.right); + } + } + return root; + } + + //public TreeNode invertTree3(TreeNode root) { + // if (root == null) { + // return null; + // } + // //将二叉树中的节点逐层放入队列中,再迭代处理队列中的元素 + // LinkedList queue = new LinkedList<>(); + // queue.add(root); + // while (!queue.isEmpty()) { + // //每次都从队列中拿一个节点,并交换这个节点的左右子树 + // TreeNode tmp = queue.poll(); + // TreeNode left = tmp.left; + // tmp.left = tmp.right; + // tmp.right = left; + // //如果当前节点的左子树不为空,则放入队列等待后续处理 + // if (tmp.left != null) { + // queue.add(tmp.left); + // } + // //如果当前节点的右子树不为空,则放入队列等待后续处理 + // if (tmp.right != null) { + // queue.add(tmp.right); + // } + // + // } + // //返回处理完的根节点 + // return root; + //} + + @Test + public void test() { + TreeNode left = new TreeNode(9); + TreeNode right = new TreeNode(20, new TreeNode(15), new TreeNode(7)); + TreeNode root = new TreeNode(3, left, right); + + TreeNode res = invertTree2(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test235/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test235/Solution.java new file mode 100644 index 0000000..56770c0 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test235/Solution.java @@ -0,0 +1,48 @@ +package com.chen.algorithm.znn.tree.test235; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * 235. 二叉搜索树的最近公共祖先 (需要看leetcode题上面的图) + * 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 + * 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” + * 例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5] + * 示例 1: + * 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 + * 输出: 6 + * 解释: 节点 2 和节点 8 的最近公共祖先是 6。 + * 示例 2: + * 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 + * 输出: 2 + * 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 + * + * @Auther: zhunn + * @Date: 2020/10/29 15:02 + * @Description: 二叉搜索树的最近公共祖先 + */ +public class Solution { + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + while (root != null) { + if (p.val < root.val && q.val < root.val) { + root = root.left; + } else if (p.val > root.val && q.val > root.val) { + root = root.right; + } else { + return root; + } + } + return root; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(6), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + TreeNode res = lowestCommonAncestor(root, left, right); + System.out.println(res.val); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test236/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test236/Solution.java new file mode 100644 index 0000000..a516221 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test236/Solution.java @@ -0,0 +1,108 @@ +package com.chen.algorithm.znn.tree.test236; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetc-2/ + * 236. 二叉树的最近公共祖先 (需要看leetcode题上面的图) + * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + * 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” + * 例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4] + * 示例 1: + * 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 + * 输出: 3 + * 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 + * 示例 2: + * 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 + * 输出: 5 + * 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。 + * + * @Auther: zhunn + * @Date: 2020/10/29 15:29 + * @Description: 二叉树的最近公共祖先:1-递归(推荐);2-存储父节点 + */ +public class Solution { + + /** + * 1-递归(推荐) + * + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || root == p || root == q) { + return root; + } + TreeNode left = lowestCommonAncestor1(root.left, p, q); + TreeNode right = lowestCommonAncestor1(root.right, p, q); + + if (left == null) { + return right; + } + if (right == null) { + return left; + } + + return root; + } + + /** + * 2-存储父节点 + * + * @param root + * @param p + * @param q + * @return + */ + Map parent = new HashMap<>(); + Set visited = new HashSet<>(); + + public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) { + return root; + } + + dfs(root); + while (p != null) { + visited.add(p.val); + p = parent.get(p.val); + } + while (q != null) { + if (visited.contains(q.val)) { + return q; + } + q = parent.get(q.val); + } + return null; + } + + private void dfs(TreeNode root) { + if (root.left != null) { + parent.put(root.left.val, root); + dfs(root.left); + } + if (root.right != null) { + parent.put(root.right.val, root); + dfs(root.right); + } + } + + @Test + public void test() { + TreeNode left = new TreeNode(7, new TreeNode(5), new TreeNode(9)); + TreeNode right = new TreeNode(2); + TreeNode root = new TreeNode(8, left, right); + + TreeNode res = lowestCommonAncestor1(root, left, right); + System.out.println(res.val); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test437/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test437/Solution.java new file mode 100644 index 0000000..3a11857 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test437/Solution.java @@ -0,0 +1,76 @@ +package com.chen.algorithm.znn.tree.test437; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/path-sum-iii/solution/437lu-jing-zong-he-iii-di-gui-fang-shi-by-ming-zhi/ + * 437. 路径总和 III + * 给定一个二叉树,它的每个结点都存放着一个整数值。 + * 找出路径和等于给定数值的路径总数。 + * 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 + * 二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。 + * 示例: + * root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 + * 10 + * / \ + * 5 -3 + * / \ \ + * 3 2 11 + * / \ \ + * 3 -2 1 + * 返回 3。和等于 8 的路径有: + * 1. 5 -> 3 + * 2. 5 -> 2 -> 1 + * 3. -3 -> 11 + * + * @Auther: zhunn + * @Date: 2020/11/08 15:25 + * @Description: 路径总和 III + * 以当前节点作为头结点的路径数量 + 以当前节点的左孩子作为头结点的路径数量 + 以当前节点的右孩子作为头结点啊路径数量 + */ +public class Solution { + + public int pathSum(TreeNode root, int sum) { + if (root == null) return 0; + return countPath(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); // 根结点开始或左右子树开始的所有路径之和 + } + + private int countPath(TreeNode root, int sum) { + if (root == null) { + return 0; + } + sum = sum - root.val; + int result = sum == 0 ? 1 : 0; + return result + countPath(root.left, sum) + countPath(root.right, sum); + } + + private int find(TreeNode root, int sum) { + return (root.val == sum ? 1 : 0) + + (root.left == null ? 0 : find(root.left, sum - root.val)) + + (root.right == null ? 0 : find(root.right, sum - root.val)); + } + + private int paths(TreeNode root, int sum) { + if (root == null) { + return 0; + } + int res = 0; + if (root.val == sum) { + res += 1; + } + res += paths(root.left, sum - root.val); + res += paths(root.right, sum - root.val); + return res; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1, new TreeNode(3), null); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + int res = pathSum(root, 7); + System.out.println(res); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test538/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test538/Solution.java new file mode 100644 index 0000000..48e69b7 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test538/Solution.java @@ -0,0 +1,59 @@ +package com.chen.algorithm.znn.tree.test538; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/convert-bst-to-greater-tree/solution/ba-er-cha-sou-suo-shu-zhuan-huan-wei-lei-jia-sh-14/ + * 538. 把二叉搜索树转换为累加树(需要看leetcode题上面的图) + * 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 + * 提醒一下,二叉搜索树满足下列约束条件: + * 节点的左子树仅包含键 小于 节点键的节点。 + * 节点的右子树仅包含键 大于 节点键的节点。 + * 左右子树也必须是二叉搜索树。 + * 注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同 + * 示例 1: + * 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] + * 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] + * 示例 2: + * 输入:root = [0,null,1] + * 输出:[1,null,1] + * 示例 3: + * 输入:root = [1,0,2] + * 输出:[3,3,2] + * 示例 4: + * 输入:root = [3,2,4,1] + * 输出:[7,9,4,10] + * + * @Auther: zhunn + * @Date: 2020/10/29 11:39 + * @Description: 把二叉搜索树转换成累加树:递归-反序中序遍历 + */ +public class Solution { + + int sum = 0; + + public TreeNode convertBST(TreeNode root) { + if (root == null) { + return root; + } + + convertBST(root.right); + sum += root.val; + root.val = sum; + convertBST(root.left); + + return root; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(6), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + TreeNode res = convertBST(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test543/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test543/Solution.java new file mode 100644 index 0000000..569739e --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test543/Solution.java @@ -0,0 +1,70 @@ +package com.chen.algorithm.znn.tree.test543; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * 543. 二叉树的直径 + * 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 + * 示例 : + * 给定二叉树 + * + * 1 + * / \ + * 2 3 + * / \ + * 4 5 + * 返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 + * + * @Auther: zhunn + * @Date: 2020/10/29 14:26 + * @Description: 二叉树的直径: = 左子树的深度+右子树的深度 + * = 经过的左子树的节点数+右子树的节点数-1 = 左子树的深度+右子树的深度 + 1 - 1 + */ +public class Solution { + + int ans; + + public int diameterOfBinaryTree(TreeNode root) { + depth(root); + return ans; + } + + private int depth(TreeNode root) { + if (root == null) { + return 0; + } + + int leftDepth = depth(root.left); + int rightDepth = depth(root.right); + ans = Math.max(ans, leftDepth + rightDepth); + return Math.max(leftDepth, rightDepth) + 1; + } + + + //public int diameterOfBinaryTree2(TreeNode root) { + // depth2(root); + // return ans - 1; + //} + // + //private int depth2(TreeNode node) { + // if (node == null) { + // return 0; // 访问到空节点了,返回0 + // } + // int L = depth(node.left); // 左儿子为根的子树的深度 + // int R = depth(node.right); // 右儿子为根的子树的深度 + // ans = Math.max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans + // return Math.max(L, R) + 1; // 返回该节点为根的子树的深度 + //} + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(15), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + int res = diameterOfBinaryTree(root); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test617/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test617/Solution.java new file mode 100644 index 0000000..c727046 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test617/Solution.java @@ -0,0 +1,60 @@ +package com.chen.algorithm.znn.tree.test617; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +/** + * 617. 合并二叉树 + * 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 + * 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 + * 示例 1: + * 输入: + * Tree 1 Tree 2 + * 1 2 + * / \ / \ + * 3 2 1 3 + * / \ \ + * 5 4 7 + * 输出: + * 合并后的树: + * 3 + * / \ + * 4 5 + * / \ \ + * 5 4 7 + * 注意: 合并必须从两个树的根节点开始。 + * + * @Auther: zhunn + * @Date: 2020/10/29 14:50 + * @Description: 合并二叉树 + */ +public class Solution { + + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) { + return t2; + } + if (t2 == null) { + return t1; + } + TreeNode mergeTree = new TreeNode(t1.val + t2.val); + mergeTree.left = mergeTrees(t1.left, t2.left); + mergeTree.right = mergeTrees(t1.right, t2.right); + return mergeTree; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(5), new TreeNode(6)); + TreeNode root = new TreeNode(3, left, right); + + TreeNode right2 = new TreeNode(8); + TreeNode left2 = new TreeNode(7, new TreeNode(1), new TreeNode(2)); + TreeNode root2 = new TreeNode(3, left2, right2); + + TreeNode res = mergeTrees(root, root2); + System.out.println(JSON.toJSON(res)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test94/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test94/Solution.java new file mode 100644 index 0000000..ac8e348 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test94/Solution.java @@ -0,0 +1,99 @@ +package com.chen.algorithm.znn.tree.test94; + +import com.alibaba.fastjson.JSON; +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/94er-cha-shu-de-zhong-xu-bian-li-by-wulin-v/ + * 94. 二叉树的中序遍历 + * 给定一个二叉树的根节点 root ,返回它的 中序 遍历。 + * + * @Auther: zhunn + * @Date: 2020/10/28 16:25 + * @Description: 二叉树的中序遍历:1-递归;2-迭代 + */ +public class Solution { + + /** + * 1-递归 + * + * @param root + * @return + */ + public List inorderTraversal1(TreeNode root) { + if (root == null) { + return null; + } + List res = new ArrayList<>(); + inOrder(root, res); + return res; + } + + private void inOrder(TreeNode root, List res) { + if (root == null) { + return; + } + inOrder(root.left, res); + res.add(root.val); + inOrder(root.right, res); + } + + /** + * 2-迭代,栈(推荐) + * + * @param root + * @return + */ + public List inorderTraversal2(TreeNode root) { + if (root == null) { + return null; + } + + List res = new ArrayList<>(); + Stack stack = new Stack<>(); + TreeNode curr = root; + + while (!stack.isEmpty() || curr != null) { + while (curr != null) { // 当前节点不为空,一直将左子树入栈 + stack.push(curr); + curr = curr.left; + } + + TreeNode temp = stack.pop(); // 出栈 + res.add(temp.val); // 操作数据 + curr = temp.right; // 对出栈的节点检查是有否还有左节点,有的话继续入栈,没有的话就操作叶子节点的上一个节点 + } + return res; + } + + + //public List inorderTraversal3(TreeNode root) { + // List list = new ArrayList<>(); + // Stack s = new Stack<>(); + // while (root != null || !s.isEmpty()) { + // while (root != null) { //当前节点不为空,一直将左子树入栈 + // s.push(root); + // root = root.left; + // } + // TreeNode tem = s.pop(); //出栈 + // list.add(tem.val); // 操作数据 + // root = tem.right; //对出栈的节点检查是有否还有左节点,有的话继续入栈,没有的话就操作叶子节点的上一个节点 + // } + // return list; + //} + + @Test + public void test() { + TreeNode right = new TreeNode(2, new TreeNode(3), null); + TreeNode root = new TreeNode(1, null, right); + + List res = inorderTraversal2(root); + System.out.println(JSON.toJSON(res)); + } + +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test96/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test96/Solution.java new file mode 100644 index 0000000..f1b6b45 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test96/Solution.java @@ -0,0 +1,56 @@ +package com.chen.algorithm.znn.tree.test96; + +import org.junit.Test; + +/** + * https://leetcode-cn.com/problems/unique-binary-search-trees/solution/bu-tong-de-er-cha-sou-suo-shu-by-leetcode-solution/ + * 思路 + * 给定一个有序序列 1⋯n,为了构建出一棵二叉搜索树,我们可以遍历每个数字 i,将该数字作为树根, + * 将 1⋯(i−1) 序列作为左子树,将 (i+1)⋯n 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。 + *

+ * 96. 不同的二叉搜索树 + * 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? + * 示例: + * 输入: 3 + * 输出: 5 + * 解释: + * 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: + *

+ * 1 3 3 2 1 + * \ / / / \ \ + * 3 2 1 1 3 2 + * / / \ \ + * 2 1 2 3 + * + * @Auther: zhunn + * @Date: 2020/10/29 17:49 + * @Description: 不同的二叉搜索树:1-动态规划 + */ +public class Solution { + + /** + * 1-动态规划 求G(n) + * G(0) = 1,G(1) = 1 + * G(n) = G(i-1)*G(n-i) 求和(1<= i <=n) + * + * @param n + * @return + */ + public int numTrees(int n) { + int[] dp = new int[n + 1]; + dp[0] = 1; + dp[1] = 1; + + for (int i = 2; i <= n; i++) { + for (int j = 1; j <= i; j++) { + dp[i] += dp[j - 1] * dp[i - j]; + } + } + return dp[n]; + } + + @Test + public void test() { + System.out.println(numTrees(3)); + } +} diff --git a/src/main/java/com/chen/algorithm/znn/tree/test98/Solution.java b/src/main/java/com/chen/algorithm/znn/tree/test98/Solution.java new file mode 100644 index 0000000..cb3db35 --- /dev/null +++ b/src/main/java/com/chen/algorithm/znn/tree/test98/Solution.java @@ -0,0 +1,101 @@ +package com.chen.algorithm.znn.tree.test98; + +import com.chen.algorithm.znn.tree.TreeNode; +import org.junit.Test; + +import java.util.Stack; + +/** + * 98. 验证二叉搜索树 + * 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 + * 假设一个二叉搜索树具有如下特征: + * 节点的左子树只包含小于当前节点的数。 + * 节点的右子树只包含大于当前节点的数。 + * 所有左子树和右子树自身必须也是二叉搜索树。 + * 示例 1: + * 输入: + * 2 + * / \ + * 1 3 + * 输出: true + * 示例 2: + * 输入: + * 5 + * / \ + * 1 4 + * / \ + * 3 6 + * 输出: false + * 解释: 输入为: [5,1,4,null,null,3,6]。 + * 根节点的值为 5 ,但是其右子节点值为 4 。 + * + * @Auther: zhunn + * @Date: 2020/10/29 16:35 + * @Description: 验证二叉搜索树:1-递归,2-中序遍历(推荐) + */ +public class Solution { + + /** + * 1-递归:左子树里所有节点的值均小于根结点的值,右子树里所有节点的值均大于根结点的值 + * + * @param root + * @return + */ + public boolean isValidBST1(TreeNode root) { + return isValid(root, null, null); + } + + private boolean isValid(TreeNode root, Integer min, Integer max) { + + if (root == null) { + return true; + } + if (min != null && root.val <= min) { + return false; + } + if (max != null && root.val >= max) { + return false; + } + + return isValid(root.left, min, root.val) && isValid(root.right, root.val, max); + } + + /** + * 2-中序遍历:遍历后是升序,后一个值不得有小于前一个值(推荐) + * + * @param root + * @return + */ + public boolean isValidBST2(TreeNode root) { + if (root == null) { + return true; + } + + Stack stack = new Stack<>(); + double inorder = -Double.MAX_VALUE; + while (!stack.isEmpty() || root != null) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + if (root.val <= inorder) { + return false; + } + inorder = root.val; + root = root.right; + } + return true; + } + + @Test + public void test() { + TreeNode left = new TreeNode(1); + TreeNode right = new TreeNode(7, new TreeNode(6), new TreeNode(20)); + TreeNode root = new TreeNode(3, left, right); + + boolean res = isValidBST2(root); + System.out.println(res); + } + +} diff --git "a/src/main/java/com/chen/algorithm/znn/\345\220\204\345\205\254\345\217\270\351\235\242\350\257\225\351\242\230.md" "b/src/main/java/com/chen/algorithm/znn/\345\220\204\345\205\254\345\217\270\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 0000000..89a8dae --- /dev/null +++ "b/src/main/java/com/chen/algorithm/znn/\345\220\204\345\205\254\345\217\270\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,118 @@ +伟东云教育: + +说一下负责的项目最具难度,最有亮点一个。包括什么业务模块,用到了什么技术等 + +1、spring事物的传播属性 + +2、mysql的innodb存储引擎用的什么数据结构,为什么用B+树 + +3、mongodb用的什么数据结构,为什么用B树 + +4、kafka如何进行主从同步的 + +鲸算科技: +1、事务相关,spring如何实现事务的 +2、spring bean的生命周期 +2、mongodb和mysql的区别,谁的查询更快(他们的索引区别) +3、B树和B+树的区别,mysql索引高度一般多少 +4、一颗B+树可以存储多少数据 +5、有做过什么mysql优化,联合索引使用注意事项 +6、有哪些业务用到redis,数据量比较大,redis选型(目前的redis服务端用的什么模式,机器配置都是什么) +7、redis的过期策略 +8、(并发问题,如果批量处理很多任务,多线程处理,如何知道他处理完了,CountDownLatch)知道哪些JUC下面的工具类 +9、知道或者重构代码,写业务用到过什么设计模式 +10、机器负载是指什么 +11、知道哪些排序算法以及查找算法,快排的思想,二叉查找树,二叉平衡树的特点 +12、Dubbo使用超时时间和重试次数配置方面的问题 +13、几分钟说一下工作中解决过比较难的问题 + +水滴: +1、熟悉项目介绍 +2、vertx的运行机制 +3、常用rpc服务的注册与发现如何实现的(注册了服务什么内容) +4、java线程池业务中哪里有用得到,线程池的运行机制。 +5、java同步方式:synchronized和lock的适用场景,synchronized为什么比较消耗性能 +6、mysql的事务隔离级别 +7、mysql是如何实现这些隔离级别的 +8、MySQL的间隙锁如何使用的 + +完美世界: +1、redis分布式锁详细实现过程,redission +2、关于中间件,项目中部署的客户端以及版本清晰了解 +3、1.8特性流式处理 +4、spring如何实现依赖注入和如何启动的、springmvc和springboot的区别 +5、集合源码 +6、项目中es模糊搜索以及高亮展示 +7、锁-synchronized和lock的区别以及底层实现,静态方法和非静态方法 +8、线程池有几大类,内部运转机制。 +9、wait和sleep的区别 +10、mysql的having关键字和where条件有什么区别 +11、mysql事务隔离级别,默认是什么,不可能重复读现象 + + + +天眼查:二面 + +1、频繁youngGC如何解决,频繁FullGC如何排查?G1垃圾收集器如何实现可预测的暂停时间的,使用G1在内存上有什么要求吗?2G内存适合用G1吗 + +2、怎么解决mysql主从同步延迟问题:半同步机制,同步一个事务一刷盘 + +3、生产者端如何保证kafka高吞吐的:批量发送消息,消息压缩,底层如何实现的,是基于什么发送的(partition) + +4、spring循环依赖,三级缓存为什么是三级,两级不行吗,懒加载什么时候加载 + +5、相似匹配服务的高质量如何保证的?对于服务的监控,关注哪些监控指标? + + + + + +美团面试题:https://www.bilibili.com/read/cv8288767/ + +解决kafka数据存储于顺序一致性保证:https://cloud.tencent.com/developer/article/1682566 + +一次kafka消息堆积问题排查:https://cloud.tencent.com/developer/article/1583098 + +kafka文件存储机制那些事儿:https://tech.meituan.com/2015/01/13/kafka-fs-design-theory.html + +对于对应问题能否答对点儿上,知识点的总结不到位,需要梳理一遍。 + + + +腾讯面试题: + +1、阻塞队列,怎么实现阻塞的,条件变量怎么用(condition) + +jvm如何判断对象可回收,哪些对象可以作为GCroot对象 + +3、一个大文件,一行一个键值对,如何快速写入到redis里 + +4、操作系统读取一个文件都有哪些系统调用,过程是什么 + +5、http和https的区别,如何加密的,https的工作原理 + +6、rpc框架,如何设计的,tcp和http协议 + +6、算法:求平方根、求最大回文子串 + +阿里面试题: + +场景题:预发、灰度、线上 多个环境如何实现隔离(互不影响)设计表,以及他们增改操作如何操作,如果预发没有的话取线上数据,灰度没有的话取线上数据 + + + + + + + + + +**有道简历注意事项** + +http://note.youdao.com/s/IcP8rLel + + + +**项目经验介绍** + +http://note.youdao.com/s/2ccmlmVZ \ No newline at end of file diff --git "a/src/main/java/com/chen/algorithm/znn/\351\235\242\350\257\225\346\263\250\346\204\217\344\272\213\351\241\271.md" "b/src/main/java/com/chen/algorithm/znn/\351\235\242\350\257\225\346\263\250\346\204\217\344\272\213\351\241\271.md" new file mode 100644 index 0000000..b8d26a1 --- /dev/null +++ "b/src/main/java/com/chen/algorithm/znn/\351\235\242\350\257\225\346\263\250\346\204\217\344\272\213\351\241\271.md" @@ -0,0 +1,322 @@ +**自我介绍** + +​ 2018年5月加入凤凰网,在平台研发中心自媒体组主要负责自媒体发文平台、内容生产系统以及发抄袭服务的开发。 + +自媒体发文平台主要包括功能模块有自媒体作者注册入驻、内容发布(手动发布、(其中内容包括文章、视频、图集)绑定抓取源抓取同步内容)、内容列表管理、统计数据查看模块、评论管理、收益提现模块、以及积分和增值功能模块。 + +内容生产系统主要针对作者端发文或者抓取源抓取的内容做清洗、本地化、转码、送审、上线、分发等流程上的处理。同时还为新闻客户端提供一些内容查询的接口,包括内容详情和内容列表的查询。 + +反抄袭服务主要是针对内容做相似匹配服务。 + + 在工作中主要参与的工作是需求分析、前期数据准备和准确性评估、编码、测试、上线,以及后期的重构优化及维护; + +​ 再往前一家是2016年10月到2018年5月在久亿恒远任职,主要参与的是信贷审核系统以及能卡APP的后端服务的开发。 + +​ 主要使用的是java技术栈,包括vertx框架和常用的springboot/ mybatis框架,数据库主要用的mysql,mongo,常用的kafka以及redis中间件,用到过spring cloud以及dubbo这样的rpc框架。 + + + +**1. 如何使用aop实现读写分离** + +\1. 使用annotation定义标识数据源的注解; write/read/other + +2.定义切面,切入点在service实现层的、前置增强在对进入方法前根据对应的注解设置放到ThreadLocal中、后置处理从ThreadLocal清除对应的注解;我们使用threadLocal以及linkedList持有线程的数据源副本; + +\3. 继承spring的 AbstractRouingDataSource,**实现动态数据源**。**determineCurrentLookupKey**方法根据注解从linkedList获取对应的数据源 + +**2. 最熟悉的项目介绍下吧** + + + +**服务介绍:** + +课程和题库服务、他是尚德为学员查看课程和练习做题的平台, 主要分为多个场景,课上的随堂考,课后作业练习、平时按照章节练习、收藏练习、错题练习、智能练习(根据你之前的错题和收藏采用一定的算法给你推题)、群作业;主要功能接口包括知识点列表、科目列表、试卷内容、试题列表、交卷接口、结果页、解析页、排行榜接口; + +主app的活跃用户大概是25万, + +每天大概300节直播课,大概直播和重播15万左右的观看记录。 + +大概800到3000万的做题需求, + +整个服务的qps可以达到6000到8000左右; + +服务节点是12个; + +**服务架构** + +主题框架是 java的 springMVC+mybatis 服务节点是12个; + +数据库采用的是一主4从,3T的ssd机器 + +请求服务,为了缓解主库的压力,使用springAop实现了读写分离; + +使用sharding jdbc实现了横向分表 + +其次使用redis,实现了热点数据缓存,使用gzip压缩了json串,释放了大量的内存空间; + +使用thrift调用其它服务的比如订单、学期、课程数据 + +使用kafka完成了给数据中心同步数据,异步操作,缓解了交卷压力; + +使用es 完成了日志数据的存储; + +用nginx做反向代理 + +**3. 用户缓存项目** + + 该项目主要是为了解决接口中用户缓存信息更新不及时的问题,后续为了解决表中数据发生变化通知业务方进行业务处理的功能。 + + 亮点在于,可以根据业务的增删改查进行响应的业务处理,监控的表以及字段都可以动态调整;发送的topic可以存储到库里,然后动态的新增; + +发送消息队列可以做好多用处,比如最初是更新用户缓存,后比如我们的科目信息发生变化后要通知订单中心,就是采用的这种策略; + +**4. 单点登录系统** + +该系主要是采用cas实现的sso,一处登陆,处处可用的效果; + +主要用扫码登录、短信验证码登录、密码登录; + +**原理** + +**第一次** + +\1. 浏览器获取 A系统的资源,服务cookie中没有sessionId也没有携带ticket,那就重定向到casServer http://sso.abc.com?service=http://a.abc.com + +\2. casServer端 发现请求的cookie中并没有TGT返给你登录页面, 来登录吧 + +\3. 浏览器输入账号密码,返回到casServer进行验证; + +\4. 验证通过后,casServer发放该请求的ticket,同时在服务端设置TGT,然后 Redirect到 http://pr.abc.com/?ticket=ST-14041-VbqemTxMkTGbDtzyI3vk-cas01.example.org; + +\5. A系统发现 Client1又来访问我了,这次带了ticket去CAS Server验证一下 + +\6. server端验证,是我发放的ticket,给他资源吧 + +\7. A系统设置和client1的cookie;返回Client1请求的资源 + +**第二次** + +1 访问Client2 http://B.abc.com, B系统发现Cookie中没有sessionid,也没有携带ticket,给你重定向到CAS Server登录吧 [http://sso.abc.com?service=http://b.abc.com](http://sso.abc.com?service=http://a.abc.com) + +\2. 有请求来访问我了,发现请求的cookie中有我签发的TGT,已经登陆了,给你个ST,重定向到client2吧 + +http://ehr.abc.com?ticket=ST-12061-VdfHfgTxMkTGbDtzyI3vk-cas01.example.org + +\3. Redirect:Client2 + +http://ehr.abc.com?ticket=ST-12061-VdfHfgTxMkTGbDtzyI3vk-cas01.example.org + +\4. 这次带了ticket,去CAS Server验证一下 + +\5. server端验证,是我发放的ticket,给他资源吧 + +\6. A系统设置和client2的cookie;返回Client2请求的资源 + + + +# 面试注意事项 + +- 千万不要迟到 +- 电话面试要注意周围环境 +- 了解下要面试的公司 + +# 简历注意事项 + +- 简历要对每个公司单独修改 +- star法则: 场景、任务、行为、结果 +- 要展现自己用了什么技术、展现自己的核心竞争力 +- 简历不要太长、不要超过2页 +- 不会的东西不要往简历中写 + +# 面试中遇到的问题 + +- 自我介绍要简短 1-2分钟 +- 不会的问题不要硬撑 +- 会的技术要详细的讲解,结合自己的使用 +- 问下自己的组是干什么的,用到什么技术,组的人员架构,我到那是什么角色 + +# 为什么离职 + +1. 岗位不合适,本来说的我是干这个,进来后干另一个。干了4个月 +2. 干了两年多了。从业务开始、到发展、到现在比较稳定,后期主要是维护的工作,没有太多的技术成长空间。 +3. + +# 自己的缺点 + +- 自己对自己的小组员代码要求比较严格; +- 性格比较急,比如有任务的事时候比较急,容易有压力; + +# 自己的优点 + +- 自己责任心比较强,相对比较细,这样会导致进度相对慢点; + +# 自己的职业规划 + +- 做些有挑战性的工作; +- 做点技术相关的工作; +- + +# 常见的问题 + +1. 为什么想加入我们公司? + +- 公司平台大,我确实比较喜欢你们的公司。而且在大公司希望可以遇到一些优秀的人,技术大牛,和他们相处,可以相互讨论、学习,能促进自己的进步。 +- 我做过一些相关的高并发的业务,能胜任工作 +- + +# 工资的组成 + +1. 月薪+绩效 +年底的双薪 + +# 项目介绍 + +对项目整体感受、 + +负责什么、做了什么、担任的角色 + +学到了什么 + +使用什么技术、解决了什么问题 + +# 最大贡献是什么 + + + +#出现 OOME 时生成堆 dump: + +-XX:+HeapDumpOnOutOfMemoryError + +#生成堆文件地址: + +-XX:HeapDumpPath=/home/liuke/jvmlogs/ + +使用jvisualvm.exe分析dump文件 + +1、一次JVM堆内存溢出的问题排查:java.lang.OutOfMemoryError:Java heap space,java.lang.OutOfMemoryError: PermGen space,StackOverFlowError +收到监控报警,服务有一个实例重启,为什么重启(到公司确认下) +首先看下promethues监控,观察到有频繁FullGC,老年代内存有明显上升的现象。 +查看日志,搜索OutOfMemoryError关键字,找到对应的异常查看堆栈信息,定位到业务代码位置。是因为加载大文件导致的,图片的像素点较多,byte[] +根据日志打印的图片地址参数,想着本地启动项目复现下这个问题,启动时加上启动参数-XX:+HeapDumpOnOutOfMemoryError 生成堆 dump文件,-XX:HeapDumpPath=/home/liuke/jvmlogs/生成dump文件地址。然后jvm自带工具jvisualvm.exe分析dump文件,定位到确实是和日志打印信息一致。然后再次观察promethus监控,可以看到进行FullGC暂停时长是多少,达到 +找了一个压缩图片的包引入,把图片进行压缩后再计算。 + +(3分钟内,G1 Humongous Allocation频繁,导致堆内存溢出。 +分析:大对象直接进入老年代,再次请求申请老年代空间不足时,触发fullGC,如果之前的大对象不再用可以回收,短时间内还在用回收不掉,(间隔一分钟两次分配大对象)再次分配大对象空间不足,溢出。) + +2、Kafka队列阻塞问题: + +3、redis持久化导致eventLoop线程阻塞问题 + +项目中用到的设计模式: + +单例模式,连接milvus服务端的配置类、 + +模板方法模式,判断文章或图集是否相似,通用父类,传参一样,string类型hash值,内容id,通过不同方式比对相似在子类中实现。 + +策略模式,计算文本hash值,有minhash和simhash两种方式,通过抽象策略类或接口定义获取hash值方法,通过两个实现类定义计算hash值的不同策略的具体实现。 + +适配器模式:新加接口的时候,原有的逻辑差不多,只是入参不同,加部分逻辑。 新建一个接口,在实际工作中,适配器模式大多用在代码后期维护,和复用代码以及改写工具类。 + +工厂模式:静态(静态)工厂模式,工厂方法模式,抽象工厂模式 + +spring用到了哪些设计模式: + +- [BeanFactory](https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java)和[ApplicationContext](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/ApplicationContext.java)应用了工厂模式。 +- 在 Bean 的创建中,Spring 也为不同 scope 定义的对象,提供了单例和原型等模式实现。 +- AOP 领域则是使用了代理模式、装饰器模式、适配器模式等。 +- 各种事件监听器,是观察者模式的典型应用。 +- 类似 JdbcTemplate 等则是应用了模板模式。 + + + +离职原因: + +第一家公司,进入的是一家创业型公司,刚入职三个月没发工资,刚毕业维持不了生活。 + +第二家公司,是一家互联网金融公司,刚开始公司发展挺好的,待了一年半,涨过三次薪资,每两个月有一次绩效奖,年底有年终奖。不过,2018年年初公司受国家政策和和催收事情的其它原因,出现一些变故,领导给大家开了个会,那意思是说公司很有可能就解散了,当时大部分同事也是都离职了,所以就决定离职走了。 + +第三家公司,凤凰网,一是公司的晋升体系不太好,职级晋升但是不跟工资挂钩,我入职后晋升了两次,没涨过工资,涨薪依据没看出来是啥,可能是我们做的这块业务不受重视,不重要。二是业务发展这块,自媒体业务中间经过几次变动,后来就处于停滞的状态,感觉以后很有可能关掉自媒体业务,所以对于个人发展包括技术发展,以后的涨薪晋升都不友好,决定出来看看有什么合适的机会。 + +期望去的公司:一是公司具有完善的晋升体系,二是团队优秀,能跟着大佬学习提升自我,三是对于我这边负责的模块来说,能在技术方面或者业务方面对我能有不错的提升空间,能从中成长,有不少收获。 + + + + + +各公司二面情况总结: + +鲸算科技二面:交叉面试跟一面问的问题相当,深度及广度差不多,重视基础。 + +一起作业网二面:关注组件的设计及实现方面,最好懂其中的原理越细致越好,框架相关的知识 + +58二面:问项目,项目的设计,常见的组件相关的经典问题。(交叉面) + +火花思维二面:问项目系统设计细节,以及实现细节,以及项目的技术选型。是否注重系统高可用,稳定性、易扩展 + +跟谁学二面:涉及到技术知识的深度,设计场景题,详细说出自己的实现。 + +京东二面:会问一些比较实际的问题,项目中或工作中经常使用到的技术或踩的坑,比如threadlocal。比如遇到缓存击穿、雪崩如何避免如何解决。线上问题如何排查,如何快速解决。 + +小米二面:问到技术知识的深度,问到jdk源码层面,算法题,实现阻塞队列 + +自如二面:问到非业务实现的题,而是系统设计,系统稳定性保障,技术选型,场景设计与实现题。 + +贝壳二面:框架相关的题,在工作上涉及到框架以及组件的使用上的题,具体如何使用,如何避免踩坑,具体想要了解在实际项目中有没有用到。 + +百度二面:系统设计,场景设计题,紧急线上问题如何解决,开发时,是否考虑系统或接口高可用,效率方面问题,有没有持续学习的习惯,看过哪些书,是否有自我驱动去学习。 + +滴滴二面:组件以及框架上为什么如何设计的问题,jdk为什么这样实现的问题,抛出一个算法题,说下思路,考察你考虑问题是否全面严谨。 + +快手二面:项目的详细设计,项目中涉及到难点在哪,自己负责的项目崩了会出现什么问题,如何避免,如果真产生了如何快速修复问题带来的损失。spring框架相关的问题,通过问相关问题了解到你在项目中具体的使用情况,期望懂框架基本运行机制的同时,能熟练使用框架,甚至说实际真正用过各种场景或框架的组件更佳。 + + + +二面自我总结: + +一、在基础知识扎实的基础上,再进一步深入了解底层,做到给面试官的感觉是,基础知识扎实,同时又有一定的深度。 + +(平时注意:1-多积累,2-多思考,3-同时常用到的必备基础知识需要系统的学习。 + +1->积累什么?积累工作中遇到的问题,踩过的坑,以及知识的亮点运用。 + +2->思考什么?skiplist,对于自己的工作以及未来规划多梳理多总结并及时调整变动。梳理近阶段工作,总结学习到了什么,收获了什么,有哪些方面成长,又有哪些方面的不足。 + +3->如何系统的学习,看相关的书籍同时看源码,动手操作写demo尽可能应用到实际项目中。) + +二、1、项目设计细节,项目难点有哪些?项目目前还需要优化的点有哪些? + +2、框架相关的题。注重实际项目中使用情况,关注你在实际项目中是否很好的灵活的使用了。为什么这样使用,是否关注了框架这样设计的好处,你是否用对了。 + +3、问组件的原理及设计,问什么这样设计,这样设计的好处以及为了解决什么问题,进而关联到系统设计及场景设计题。通过这个分析你实际项目经验是否足够丰富,同时考察逻辑思维以及表达能力 + +4、实际开发中,除了功能实现,是否会考虑服务或接口的可用性,稳定性,考虑到最坏情况,(考虑到服务的要求是什么,需要高并发、高性能、高可用)服务宕机如何避免,如果宕机了如何快速恢复并修复期间丢失的数据等。 + +其实就是需要对开发这个服务了如指掌,了解到它的方方面面。针对服务相对应的指标去衡量它。 + +5、线上紧急问题相关,解决过哪些大事故或线上问题,如何解决的?给出一个线上问题的场景题,问如何定位和排查,而后如何解决。 + +6、平时都怎么去学习提升自己技术的?最近看了哪些书,举例说明(举mysql的掘金小册,dubbo相关的官方文档)最近在梳理知识点以及在公司做的项目,做一些总结与思考,准备去面试,最近系统看的书比较少。会问通过这些书或技术文档收获了什么,来以此验证你是否真正看了这些书。 + +7、优缺点:会问一些性格方面的问题,优缺点:优点:对工作比较负责,做事情比较专注,细致认真,为人处世都比较靠谱。缺点:说话比较直,话比较少,比较慢热,说话锻炼表达能力以及语言组织能力,这方面没有达到我的预期。2.如何处理同事之间矛盾,如何处理与产品或上级领导之间的需求冲突等。3.有哪些兴趣爱好,平时业余时间都做些什么? + +8、未来的职业规划,近3年(稳固现有常见的技术,关注新技术,多做事情,多思考,关注跟人沟通打交道这方面的技巧为做管理层储备。),近5年的规划(往管理方向去走,做业务leader)。(对于所面的岗位的业务是否有了解过,通过这个问题来了解你对公司此岗位的意向是否大。) + + + +各公司三面情况总结: + +小米三面:会问算法题,以及项目设计相关的题,对没有标准答案的题,尽量思考清楚之后,清晰表达出来自己的想法,尽量多说一些有价值有技术含量的信息。面试官想要的可能是你的清晰的思路,从此看出来你解决问题的能力,同时能落地,能写出来。 + +58三面:项目挂了会触发什么问题,如何保证项目的稳定性,如何定位及紧急修复问题,以及后续数据恢复处理。对面的岗位的业务是否有了解,通过这个问题来了解你对公司此岗位的意向是否大。 + +贝壳三面(四面):平时都怎么去学习提升自己技术的?最近看了哪些书,举例说明(举mysql的掘金小册,dubbo相关的官方文档)最近在梳理知识点以及在公司做的项目,准备去面试,系统看的书比较少。会问通过这些书或技术文档收获了什么,来以此验证你是否真正看了这些书。 + +鲸算科技三面:1.会问一些性格方面的问题,优缺点:优点:对工作比较负责,做事情比较专注,细致认真,为人处世都比较靠谱。缺点:。2.如何处理同事之间矛盾,如何处理与产品或上级领导之间的需求冲突等。3.有哪些兴趣爱好,平时业余时间都做些什么? + +火花思维三面:交叉面的二面,1.问一些技术,以及设计方面的题;2.未来期望做什么样的业务,会有怎样的规划。 + +跟谁学三面:1.大的方向的场景设计题,注重你的思路是否清晰,思考问题的思维方式是否恰当,关注你技术的广度。2.平时怎么进行技术提升的,需要举出具体的例子,最近看了哪些书?可以举例说出来,不只是技术方面的书。3.未来的职业规划,近3年,近5年的规划。4.有可能会问到一些框架或者组件的设计,他们为何如此设计,这样设计的好处以及为了解决什么问题。 + +三面自我总结:同二面自我总结 + + +