diff --git a/Week_01/id_14/LeetCode_20_14.java b/Week_01/id_14/LeetCode_20_14.java new file mode 100644 index 00000000..8ea9d07f --- /dev/null +++ b/Week_01/id_14/LeetCode_20_14.java @@ -0,0 +1,76 @@ +import java.util.Stack; + +/** +* https://leetcode-cn.com/problems/valid-parentheses/ +*

栈 stack +*

简单 +*/ +public class LeetCode_20_14 { + + public static void main(String[] args) { + Solution solution = new Solution(); + + String[] arrs = {"()" + , "()[]{}" + , "(]" + , "([)]" + , "{[]}"}; + boolean[] results = {true, true, false, false, true}; + for (int i = 0; i < arrs.length; i++) { + System.out.println(arrs[i] + ":" + (results[i] == solution.isValid(arrs[i]))); + } + } + + static class Solution { + private final static char[] A = {'(', '{', '[', ']', '}', ')'}; + + /** + * 利用java的Stack,遇到左括号,就入栈(push),遇到右括号就出栈(pop) + *

+         *     1. 如果需要出栈时,栈为空,无效表达式
+         *     2. 左右括号的差值,只有1或2,直接使用当前byte和pop的做差值运算。不等于1或2,就不匹配
+         *     3. 性能不太好
+         * 
+ * @param s + * @return + */ + public boolean isValid(String s) { + if (s == null) { + return true; + } + Stack stack = new Stack<>(); + char[] chars = s.toCharArray(); + + for (int i = 0; i < chars.length; i++) { + if (chars[i] == A[0] || chars[i] == A[1] || chars[i] == A[2]) { + stack.push(chars[i]); + } else if (chars[i] == A[3] || chars[i] == A[4] || chars[i] == A[5]) { + if(stack.empty()){ + return false; + } + /** + *
+                     *     40  (
+                     *     41  )
+                     *     91  [
+                     *     93  ]
+                     *    123  {
+                     *    125  }
+                     * 
+ */ + int tmp = chars[i] - stack.pop(); + if (tmp != 1 && tmp != 2) { + return false; + } + } + } + + return stack.empty(); + } + } + +} +/** + * 执行用时 : 7 ms, 在Valid Parentheses的Java提交中击败了79.22% 的用户 + * 内存消耗 : 34 MB, 在Valid Parentheses的Java提交中击败了89.16% 的用户 + */ diff --git a/Week_01/id_14/LeetCode_21_14.java b/Week_01/id_14/LeetCode_21_14.java new file mode 100644 index 00000000..c285e3e0 --- /dev/null +++ b/Week_01/id_14/LeetCode_21_14.java @@ -0,0 +1,229 @@ +/** + * https://leetcode-cn.com/problems/merge-two-sorted-lists/submissions/ + * + *

链表 + *

简单 + * + * @author Yunjian Liu + * @date 2019/04/17 + */ +public class LeetCode_21_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + + ListNode listNode1 = new ListNode(1); + listNode1.next = new ListNode(2); + //listNode1.next.next = new ListNode(4); + listNode1.next.next = new ListNode(2); + listNode1.next.next.next = new ListNode(2); + listNode1.next.next.next.next = new ListNode(4); + listNode1.next.next.next.next.next = new ListNode(5); + listNode1.next.next.next.next.next.next = new ListNode(5); + + ListNode listNode2 = new ListNode(1); + listNode2.next = new ListNode(3); + listNode2.next.next = new ListNode(4); + + ListNode listNode3 = solution.mergeTwoLists(listNode1, listNode2); + + while (listNode3 != null) { + System.out.print(listNode3.val + "->"); + listNode3 = listNode3.next; + } + } +} + +class Solution { + /** + *

+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  3  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *       cur
+     *        |
+     *     listNode
+     *        |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        p
+     *        |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  3  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *
+     *     listNode      tmp
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *       cur
+     *        |
+     *        p
+     *        |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  3  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *     listNode       p
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  |     |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        |
+     *        |
+     *       cur
+     *        |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  3  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *
+     *
+     *     listNode       p
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  |     |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        |
+     *        |
+     *       cur          tmp
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  | --> |  3  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *
+     *     listNode       p
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  |     |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        |           ^
+     *        |           |
+     *       cur          |           tmp
+     *        |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     *
+     *     listNode      cur
+     *        |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  |     |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        |           ^
+     *        |           |
+     *       cur          |           p
+     *        |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     *
+     *     listNode      cur         tmp
+     *        |           |           |
+     *     +-----+     +-----+     +-----+
+     *     |  1  |     |  2  | --> |  4  |
+     *     +-----+     +-----+     +-----+
+     *        |           ^
+     *        |           |
+     *       cur          |           p
+     *        |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     *
+     *     listNode      cur                tmp
+     *        |           |                  |
+     *     +-----+     +-----+            +-----+
+     *     |  1  |     |  2  | -->-----   |  4  |
+     *     +-----+     +-----+        |   +-----+
+     *        |           ^           |
+     *        |           |           |
+     *       cur          |           p
+     *        |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     *
+     *     listNode                          p
+     *        |                              |
+     *     +-----+     +-----+            +-----+
+     *     |  1  |     |  2  | -->-----   |  4  |
+     *     +-----+     +-----+        |   +-----+
+     *        |           ^          cur
+     *        |           |           |
+     *       cur          |           |
+     *        |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     *
+     *     listNode                          p
+     *        |                              |
+     *     +-----+     +-----+            +-----+
+     *     |  1  |     |  2  | -->-----   |  4  |
+     *     +-----+     +-----+        |   +-----+
+     *        |           ^           |
+     *        |           |           |
+     *       cur          |           |          cur
+     *        |           |           |           |
+     *     +-----+        |        +-----+     +-----+
+     *     |  1  | -->----         |  3  | --> |  4  |
+     *     +-----+                 +-----+     +-----+
+     *
+     * 
+ * + * 思路: + *
+     *    用一个指针,指向当前节点(cur)。另一个指针(p)指向,另一个链还未合并进来的第一个位置。
+     *    1. 比较p的cur的next,如果p更小,那么就把p接到合并链中。
+     *
+     *    如果一个链已经比较完,把p链直接接到最后
+     * 
+ *

会破坏原来的链表 + * @param l1 + * @param l2 + * @return + */ + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + if(l1 == null) { + return l2; + } + if(l2 == null) { + return l1; + } + ListNode listNode = l1; + + ListNode p = l2; + if (l2.val < l1.val) { + listNode = l2; + p = l1; + } + ListNode cur = listNode; + + while (cur.next != null) { + if (p.val < cur.next.val) { + ListNode tmp = cur.next; + cur.next = p; + cur = p; + p = tmp; + } else { + cur = cur.next; + } + } + cur.next = p; + + return listNode; + } +} diff --git a/Week_01/id_14/LeetCode_242_14.java b/Week_01/id_14/LeetCode_242_14.java new file mode 100644 index 00000000..094e251c --- /dev/null +++ b/Week_01/id_14/LeetCode_242_14.java @@ -0,0 +1,104 @@ +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/valid-anagram/ + *

数组 两个字符串是否是异位词 + *

简单 + */ +public class LeetCode_242_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + Solution2 solution2 = new Solution2(); + + String s = "anagram"; + String t = "nagaram"; + + System.out.println(solution.isAnagram(s, t)); + System.out.println(solution2.isAnagram(s, t)); + System.out.println(solution.isAnagram("rat", "car")); + System.out.println(solution.isAnagram("", "")); + System.out.println(solution2.isAnagram("rat", "car")); + System.out.println(solution2.isAnagram("", "")); + } + + /** + * 假设字符串只包含小写字母,字母异位词。 + *

+     *     1. 异位就是所有的字母个数都是一样的,只是位置不同
+     *     2. 只有小写字母。a-z (26)个字母。数组arr[26]
+     *     3. 看每个字母是否相等
+     * 
+ */ + static class Solution2 { + + public boolean isAnagram(String s, String t) { + if (s == null || t == null) { + return false; + } + + if (s.length() != t.length()) { + return false; + } + + if (s.equals(t)) { + return true; + } + + int[] arr = new int[26]; + + for (int i : s.toCharArray()) { + arr[i - 'a']++; + } + for (int i : t.toCharArray()) { + arr[i - 'a']--; + } + + for (int i = 0; i < arr.length; i++) { + if (arr[i] != 0) { + return false; + } + } + + return true; + } + } + + /** + * 这种解法,先排序,再比较。效率低。其实是可以不排序的,日常开发、设计也需要注意 + * char数组排序直接用JDK实现的排序了。 这个可以尝试自己实现 + *
+     *     1. 先对两个字符串内部char数组排序。O(n)。 O(2n)
+     *     2. 再遍历对比每个char  O(n)
+     *     3. 总体时间复杂度。O(2n)+O(n)=O(3n)=O(n)
+     * 
+ */ + static class Solution { + + public boolean isAnagram(String s, String t) { + if (s == null || t == null) { + return false; + } + + if (s.length() != t.length()) { + return false; + } + + if (s.equals(t)) { + return true; + } + + char[] ss = s.toCharArray(); + Arrays.sort(ss); + char[] ts = t.toCharArray(); + Arrays.sort(ts); + + for (int i = 0; i < ss.length; i++) { + if (ss[i] != ts[i]) { + return false; + } + } + + return true; + } + } +} diff --git a/Week_01/id_14/LeetCode_687_14.java b/Week_01/id_14/LeetCode_687_14.java new file mode 100644 index 00000000..525403e8 --- /dev/null +++ b/Week_01/id_14/LeetCode_687_14.java @@ -0,0 +1,101 @@ +/** +* https://leetcode-cn.com/problems/longest-univalue-path/ +*

递归 二叉树 +*

简单 +*/ +public class LeetCode_687_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + + TreeNode root = new TreeNode(5); + root.left = new TreeNode(4); + root.left.left = new TreeNode(1); + root.left.right = new TreeNode(1); + + root.right = new TreeNode(5); + root.right.right = new TreeNode(5); + + System.out.println("2:" + solution.longestUnivaluePath(root)); + + TreeNode root2 = new TreeNode(-9); + root2.left = new TreeNode(5); + root2.left.left = new TreeNode(-2); + root2.left.right = new TreeNode(-6); + root2.left.left.left = new TreeNode(5); + root2.left.left.left.left = new TreeNode(-3); + root2.left.left.left.right = new TreeNode(6); + root2.left.left.left.left.left = new TreeNode(-5); + root2.left.left.left.left.left.left = new TreeNode(0); + + root2.right = new TreeNode(0); + + System.out.println("0:" + solution.longestUnivaluePath(root2)); + + TreeNode root3 = new TreeNode(5); + root3.left = new TreeNode(5); + root3.left.left = new TreeNode(5); + root3.left.right = new TreeNode(5); + root3.left.left.left = new TreeNode(5); + root3.left.left.left.left = new TreeNode(5); + + root3.right = new TreeNode(4); + + System.out.println("4:" + solution.longestUnivaluePath(root3)); + } + + /** + * 这道题开始的理解、思路都有问题。本身使用递归没有问题。 + *

+     *     1. 开始没有考虑多余一段最长路径的比较
+     *     2. 最大值的保存,开始一直想用参数传递!!! Integer也无法引用传递?! 最后才使用成员变量,每次需要重置为0
+     *     3. 下图的最长=4,而不是5
+     *
+     *       5
+     *      /\
+     *     5 4
+     *    /\
+     *   5 5
+     *   |
+     *   5
+     *  /
+     * 5
+     * 
+ */ + static class Solution { + private int max = 0; + + public int longestUnivaluePath(TreeNode root) { + //注意重置,对象会重用 + max = 0; + getMax(root); + return max; + } + + private int getMax(TreeNode root) { + if (root == null) { + return 0; + } + if (root.left == null && root.right == null) { + return 0; + } + + int left = getMax(root.left); + int right = getMax(root.right); + + int tmpLeft = 0; + if (root.left != null && root.val == root.left.val) { + tmpLeft = left + 1; + } + int tmpRight = 0; + if (root.right != null && root.val == root.right.val) { + tmpRight = right + 1; + } + + max = Math.max(tmpLeft + tmpRight, max); + + //返回左、右的大值,而不是相加的值 + return Math.max(tmpLeft, tmpRight); + + } + } +} diff --git a/Week_01/id_14/LeetCode_83_14.java b/Week_01/id_14/LeetCode_83_14.java new file mode 100644 index 00000000..98a65383 --- /dev/null +++ b/Week_01/id_14/LeetCode_83_14.java @@ -0,0 +1,50 @@ +/** +* https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/ +*

链表 +*

简单 +* +*

这个比较简单,但是正因为简单,所以大家使用的方法都类似,有什么优化发方案? +* @author Yunjian Liu +* @date 2019/04/17 +*/ +public class LeetCode_83_14 { + public static void main(String [] args) { + Solution solution = new Solution(); + + ListNode listNode1 = new ListNode(1); + listNode1.next = new ListNode(1); + //listNode1.next.next = new ListNode(4); + listNode1.next.next = new ListNode(2); + listNode1.next.next.next = new ListNode(2); + listNode1.next.next.next.next = new ListNode(4); + listNode1.next.next.next.next.next = new ListNode(5); + listNode1.next.next.next.next.next.next = new ListNode(5); + + ListNode listNode3 = solution.deleteDuplicates(listNode1); + + while (listNode3 != null) { + System.out.print(listNode3.val + "->"); + listNode3 = listNode3.next; + } + } + + static class Solution { + public ListNode deleteDuplicates(ListNode head) { + if(head == null) { + return head; + } + ListNode cur = head; + + while (cur.next != null) { + if(cur.val == cur.next.val) { + cur.next = cur.next.next; + }else { + cur = cur.next; + + } + } + + return head; + } + } +} diff --git a/Week_01/id_14/LeetCode_905_14.java b/Week_01/id_14/LeetCode_905_14.java new file mode 100644 index 00000000..15fe7fa8 --- /dev/null +++ b/Week_01/id_14/LeetCode_905_14.java @@ -0,0 +1,74 @@ +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/sort-array-by-parity/submissions/ + * + *

数组 + *

简单 + * + * @author Yunjian Liu + * @date 2019/04/16 + */ +public class LeetCode_905_14{ + public static void main(String[] args) { + Solution solution = new Solution(); + + int[] a = {1, 23, 45, 66, 78, 53}; + System.out.println(Arrays.toString(a)); + int[] b = solution.sortArrayByParity(a); + System.out.println(Arrays.toString(b)); + } +} + +class Solution { + /** + * 使用双指针 + *

+	 * 1. 低位,一直检测是否是偶数,是就一直往前走,直到遇到奇数或高位指针
+	 * 

+ * 2. 高位,一直检测是否是期数,是就一直往前走,直到遇到偶数或地位指针 + *

+ * 3. 交换数据 + *

+ * + * @param a + * @return + */ + public int[] sortArrayByParity(int[] a) { + int low = 0; + int high = a.length - 1; + high = Math.min(high, 5000); + + while (low < high) { + while ((a[low] & 1) == 0 && low < high) { + low++; + } + while ((a[high] & 1) == 1 && low < high) { + high--; + } + + if (low < high) { + int tmp = a[low]; + a[low] = a[high]; + a[high] = tmp; + low++; + high--; + } + } + + return a; + } +} +/** + *
+ * 交换时,未使用 low++,high--
+ * 执行用时 : 3 ms, 在Sort Array By Parity的Java提交中击败了99.80% 的用户
+ * 内存消耗 : 42.8 MB, 在Sort Array By Parity的Java提交中击败了81.17% 的用户
+ * 
+ *
+ *     不使用,每次交换后,需要多进行2次比较
+ * 交换时,使用 low++,high--
+ * 执行用时 : 3 ms, 在Sort Array By Parity的Java提交中击败了99.80% 的用户
+ * 内存消耗 : 39.5 MB, 在Sort Array By Parity的Java提交中击败了93.62% 的用户
+ * 
+ */ diff --git a/Week_01/id_14/LeetCode_922_14.java b/Week_01/id_14/LeetCode_922_14.java new file mode 100644 index 00000000..b3a43851 --- /dev/null +++ b/Week_01/id_14/LeetCode_922_14.java @@ -0,0 +1,120 @@ +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/sort-array-by-parity-ii/submissions/ + * + *

数组 + *

简单 + * + * @author Yunjian Liu + * @date 2019/04/17 5:40 AM + */ +public class LeetCode_922_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + + int[] a = {1, 23, 45, 66, 53, 78, 62, 8}; + System.out.println(Arrays.toString(a)); + int[] b = solution.sortArrayByParityII(a); + System.out.println(Arrays.toString(b)); + } +} + +class Solution { + /** + * 和905 类似的思路。 使用双指针 + *

+	 * 1. 偶数下标,一直检测是否是偶数,是就一直往前走,直到遇到奇数
+	 * 

+ * 2. 奇数下标,一直检测是否是奇数,是就一直往前走,直到遇到偶数 + *

+ * 3. 交换数据 + *

+ *
+	 *     even   odd
+	 *       |     |
+	 *       |     |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 1  | 23 | 45 | 66 | 78 | 53 | 62 | 8  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *     even            odd
+	 *       |              |
+	 *       |              |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 1  | 23 | 45 | 66 | 78 | 53 | 62 | 8  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *     even            odd
+	 *       |              |
+	 *       |              |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 66 | 23 | 45 |  1 | 78 | 53 | 62 | 8  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *               even            odd
+	 *                 |              |
+	 *                 |              |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 66 | 23 | 45 |  1 | 78 | 53 | 62 | 8  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *               even                      odd
+	 *                 |                        |
+	 *                 |                        |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 66 | 23 | 45 |  1 | 78 | 53 | 62 | 8  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *               even                      odd
+	 *                 |                        |
+	 *                 |                        |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 66 | 23 |  8 |  1 | 78 | 53 | 62 | 45  |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 *                         even                       odd
+	 *                           |                         |
+	 *                           |                         |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     | 66 | 23 |  8 |  1 | 78 | 53 | 62 | 45 |
+	 *     +----+----+----+----+----+----+----+----+
+	 *     0    1    2    3    4    5    6    7
+	 * 
+ *

+ * 和905类似的思路 + * + * @param a + * @return + **/ + public int[] sortArrayByParityII(int[] a) { + int even = 0; + int odd = 1; + int len = a.length; + while (even < len - 1 && odd < len) { + while ((a[even] & 1) == 0) { + if (even == len - 2) { + return a; + } + even = even + 2; + } + + while ((a[odd] & 1) == 1) { + if (odd == len - 1) { + return a; + } + odd = odd + 2; + } + + if (even < len - 1 && odd < len) { + int tmp = a[even]; + a[even] = a[odd]; + a[odd] = tmp; + even = even + 2; + odd = odd + 2; + } + } + + return a; + } +} diff --git a/Week_01/id_14/ListNode.java b/Week_01/id_14/ListNode.java new file mode 100644 index 00000000..07c15719 --- /dev/null +++ b/Week_01/id_14/ListNode.java @@ -0,0 +1,6 @@ +public class ListNode { + int val; + ListNode next; + + ListNode(int x) { val = x; } +} diff --git a/Week_01/id_14/NOTE.md b/Week_01/id_14/NOTE.md index c684e62f..c38165b3 100644 --- a/Week_01/id_14/NOTE.md +++ b/Week_01/id_14/NOTE.md @@ -1 +1,47 @@ -# 学习笔记 \ No newline at end of file +# 学习笔记 +### 04.19 +#### 基础 + +数组 + * 内存中连续的存储空间 + * 适合指定下标获取元素 + * 中间插入、删除后,移动后续的元素,都需要较高的时间成本 + * 固定大小,需要扩容时,需要一块新的内存空间,而且需要复制元素的时间成本 + * 无序数组, + * 有序数组,要使用二分查找,都必须是有序的数组,如果是不变化的数组,要多次查找,一般采用1次排序,多次查询 + * 典型场景 + > 固定范围(可以通过O(1)映射到具体的数组下标),比如只有小写字母的一个字符串,每个a-z的字符k,可以通过k-a直接映射到数组下标,再通过下标直接获取元素 + +链表 + * 每个节点,独立存在内存中(可以不连续),通过指针/引用建立节点之间的关系 + * 插入、删除节点容易 + * 如果指定一个节点value去删除一个元素,首先得通过遍历,查找到这个节点 + * 如果指定一个节点的前序指针/应用,那么删除就不需要遍历查找(单链表,双链表都可以), + * 如果指定一个节点的指针/引用 ,单链表还是需要遍历找到该节点的前序节点,才能删除;这种情况双向链表就不再需要遍历查询的步骤 + +栈 + > 特殊的后进先出,常用的场景对栈的操作也比较简单,入栈push,出栈pop,查看栈顶元素peek,使用场景也比较特殊,比如常见的:表达式的合法性检查、浏览器的前进后退;基本也不会对栈进行排序、查找等操作。可以进一步自己使用数组、链表自己实现一个栈。(后面根据Leetcode的题练习一下其他的使用场景) + +递归 + 有2点真的很重要 + * 终止条件 + * 思维上一定要"信任"下一层的递归结果,不用把自己带入下一层的逻辑中 + +排序 + > 以前学习排序,老是去记各种排序算法的具体实现,没有总结特殊、使用背景,一段时间就忘记。这次根据老师分析的时间复杂度、是否是稳定性排序、是否是原地排序;为什么要用稳定排序,像订单排序中,先用订单号(不重复)排序,再用金额(重复)排序,后面的用金额排序就需要用稳定排序。归并、快排都是利用分治思想,快排不稳定,归并不是原地排序,都需要根据场景来决定排序,不死记硬背。 + +二分查找 + > 基本都是使用在排序好的数组上? + * 有序的才有意义,否则不好比较 + * 数组能快速容易的用下标折半定位 + +进阶 + * 更多的场景是有相同的元素,注意边界条件 + * 大于某个数的第一个元素的查找等 + +### 学习态度 +> 追求掌握,训练思维,有没学会自己最清楚;一定要动手实现 + + +### 04.10 test +> 马上开课了 diff --git a/Week_01/id_14/README.md b/Week_01/id_14/README.md new file mode 100644 index 00000000..dad27083 --- /dev/null +++ b/Week_01/id_14/README.md @@ -0,0 +1,7 @@ +* LeetCode_905_14.java array sort-array-by-parity +* LeetCode_922_14.java array sort-array-by-parity-ii +* LeetCode_242_14.java array valid-anagram 异位词 +* LeetCode_20_14.java stack valid-parentheses +* LeetCode_21_14.java list merge-two-sorted-lists +* LeetCode_83_14.java list remove-duplicates-from-sorted-list +* LeetCode_687_14.java tree(binary tree)/recursion longest-univalue-path. use recursion diff --git a/Week_01/id_14/TreeNode.java b/Week_01/id_14/TreeNode.java new file mode 100644 index 00000000..1b9907a3 --- /dev/null +++ b/Week_01/id_14/TreeNode.java @@ -0,0 +1,16 @@ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { val = x; } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("val:").append(val) + .append(" -left:").append(left == null ? -1 : left.val) + .append(" -right:").append(right == null ? -1 : right.val); + return stringBuilder.toString(); + } +} diff --git a/Week_01/id_14/insert_sort.c b/Week_01/id_14/insert_sort.c new file mode 100644 index 00000000..47abed1c --- /dev/null +++ b/Week_01/id_14/insert_sort.c @@ -0,0 +1,63 @@ +#include + +#define LEN 5 +int a[LEN] = {10, 5, 2, 4, 8}; + +/** + * 插入排序的特点是: + * 1、左边都是已排序的 + * 2、从左到右,对每个数据排序,看该数据应该插入左边已排序的位置,并插入 + */ +void insertion_sort(void) { + int i, j, key; + for (j = 1; j < LEN; j++) { + printf("%d, %d, %d, %d, %d\n", + a[0], a[1], a[2], a[3], a[4]); + // 从第二个数开始 + key = a[j]; + i = j - 1; + // 如果前一个位置大于key,那么就往后移动一位,小于等于key的位置 + while (i >= 0 && a[i] > key) { + a[i+1] = a[i]; + i--; + } + // 插入到对应的位置 + a[i+1] = key; + } + printf("%d, %d, %d, %d, %d\n", + a[0], a[1], a[2], a[3], a[4]); +} + +void insertion_sort1(int *arr, int len) { + int i, j, key; + for (j = 1; j < len; j++) { + int t; + for(t = 0; t < len; t++) { + printf("%d,",arr[t]); + } + printf("\n"); + + key = arr[j]; + i = j - 1; + while (i >=0 && arr[i] > key) { + arr[i+1] = arr[i]; + i--; + } + arr[i+1] = key; + } +} + +int main(void) { + insertion_sort(); + + printf("-------------\n"); + int arr[] = {10, 5, 15, 12, 90, 80}; + int len = sizeof(arr)/sizeof(arr[0]); + insertion_sort1(arr, len); + int t; + for(t = 0; t < len; t++) { + printf("%d,",arr[t]); + } + printf("\n"); + return 0; +} diff --git a/Week_01/id_14/max_heap.c b/Week_01/id_14/max_heap.c new file mode 100644 index 00000000..3dd8338c --- /dev/null +++ b/Week_01/id_14/max_heap.c @@ -0,0 +1,106 @@ +#include +#include + + +int Heap[512]; +int size = 0, maxSize = 0; + +int parent(int pos) +{ + return pos / 2; +} + +int leftChild(int pos) +{ + return (2 * pos); +} + +int rightChild(int pos) +{ + return (2 * pos) + 1; +} + +bool isLeaf(int pos) +{ + if (pos >= (size / 2) && pos <= size) { + return true; + } + return false; +} + +void swap(int fpos, int spos) +{ + int tmp; + tmp = Heap[fpos]; + Heap[fpos] = Heap[spos]; + Heap[spos] = tmp; +} + +/** + * 大顶堆,堆化 + */ +void maxHeapify(int pos) +{ + if (isLeaf(pos)) + return; + + if (Heap[pos] < Heap[leftChild(pos)] || + Heap[pos] < Heap[rightChild(pos)]) { + + if (Heap[leftChild(pos)] > Heap[rightChild(pos)]) { + swap(pos, leftChild(pos)); + maxHeapify(leftChild(pos)); + } + else { + swap(pos, rightChild(pos)); + maxHeapify(rightChild(pos)); + } + } +} + +void insert(int element) +{ + Heap[++size] = element; + + // Traverse up and fix violated property + // 插入时,一直向上堆化 + int current = size; + while (Heap[current] > Heap[parent(current)]) { + swap(current, parent(current)); + current = parent(current); + } +} + +void print() +{ + int i = 1; + for (i = 1; i <= size / 2; i++) { + printf(" PARENT : %d LEFT CHILD : %d RIGHT CHILD : %d ", Heap[i], Heap[2 * i], Heap[2 * i + 1]); + printf("\n"); + } +} + +int extractMax() +{ + int popped = Heap[1]; + Heap[1] = Heap[size--]; + maxHeapify(1); + return popped; +} + +int main(void) { + Heap[0] = 210000000; + maxSize = 512; + insert(5); + insert(3); + insert(17); + insert(10); + insert(84); + insert(19); + insert(6); + insert(22); + insert(9); + + print(); + +} diff --git a/Week_01/id_14/maze.c b/Week_01/id_14/maze.c new file mode 100644 index 00000000..39a85063 --- /dev/null +++ b/Week_01/id_14/maze.c @@ -0,0 +1,108 @@ +#include +/** + * https://akaedu.github.io/book/ch12s03.html + * 深度优先搜索(DFS,Depth First Search) + * 回溯(Backtrack),一个典型的例子是很多编程书上都会讲的八皇后问题。 + */ + +#define MAX_ROW 5 +#define MAX_COL 5 + +struct point { + int row,col; +} stack[512]; +int top = 0; + +void push(struct point p) { + //printf("pushhhhh (%d, %d, top=%d)\n", p.row, p.col, top); + stack[top++] = p; +} + +struct point pop(void) { + return stack[--top]; +} + +int is_empty(void) { + return top == 0; +} + +int maze[MAX_ROW][MAX_COL] = { + 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 1, 0, +}; + +void print_maze(void) +{ + int i, j; + for (i = 0; i < MAX_ROW; i++) { + for (j = 0; j < MAX_COL; j++) + printf("%d ", maze[i][j]); + putchar('\n'); + } + printf("*********\n"); +} +/** + * 存放前序节点,用于路径记录 + */ +struct point predecessor[MAX_ROW][MAX_COL] = { + {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}}, + {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}}, + {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}}, + {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}}, + {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}}, +}; + +/** +* 走过的标为 2 +*/ +void visit(int row, int col, struct point pre) +{ + struct point visit_point = { row, col }; + maze[row][col] = 2; + printf("push (%d, %d)\n", visit_point.row, visit_point.col); + predecessor[row][col] = pre; + push(visit_point); +} + +int main(void) +{ + struct point p = { 0, 0 }; + + maze[p.row][p.col] = 2; + push(p); + + while (!is_empty()) { + p = pop(); + printf("(%d, %d)\n", p.row, p.col); + if (p.row == MAX_ROW - 1 /* goal */ + && p.col == MAX_COL - 1) + break; + if (p.col+1 < MAX_COL /* right */ + && maze[p.row][p.col+1] == 0) + visit(p.row, p.col+1, p); + if (p.row+1 < MAX_ROW /* down */ + && maze[p.row+1][p.col] == 0) + visit(p.row+1, p.col, p); + if (p.col-1 >= 0 /* left */ + && maze[p.row][p.col-1] == 0) + visit(p.row, p.col-1, p); + if (p.row-1 >= 0 /* up */ + && maze[p.row-1][p.col] == 0) + visit(p.row-1, p.col, p); + print_maze(); + } + if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) { + printf("(%d, %d)\n", p.row, p.col); + printf("path:\n"); + while (predecessor[p.row][p.col].row != -1) { + p = predecessor[p.row][p.col]; + printf("(%d, %d)\n", p.row, p.col); + } + } else + printf("No path!\n"); + + return 0; +} diff --git a/Week_01/id_14/maze_bfs.c b/Week_01/id_14/maze_bfs.c new file mode 100644 index 00000000..507d3ffe --- /dev/null +++ b/Week_01/id_14/maze_bfs.c @@ -0,0 +1,104 @@ +#include +/** + * https://akaedu.github.io/book/ch12s04.html + * 广度优先搜索(BFS,Breadth First Search) + * 1. 利用队列的FIFO + * 2. 每个方向各进一步,这就是最短路径的关键? + * 3. 一旦被走了的节点,其他的就不能走了 + */ + +#define MAX_ROW 5 +#define MAX_COL 5 + +struct point { + int row, col, predecessor; +} queue[512]; +int head = 0, tail = 0; + +void enqueue(struct point p) +{ + queue[tail++] = p; +} + +struct point dequeue(void) +{ + return queue[head++]; +} + +int is_empty(void) { + return head == tail; +} + +int maze[MAX_ROW][MAX_COL] = { + 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 1, 0, +}; + +void print_maze(void) +{ + int i, j; + for (i = 0; i < MAX_ROW; i++) { + for (j = 0; j < MAX_COL; j++) + printf("%d ", maze[i][j]); + putchar('\n'); + } + printf("*********\n"); +} + +/** +* 走过的标为 2 +*/ +void visit(int row, int col) +{ + struct point visit_point = { row, col, head-1 }; + maze[row][col] = 2; + printf("enqueue(%d, %d)\n", visit_point.row, visit_point.col); + enqueue(visit_point); +} + +int main(void) +{ + struct point p = { 0, 0, -1}; + + maze[p.row][p.col] = 2; + enqueue(p); + + while (!is_empty()) { + p = dequeue(); + printf("(%d, %d)\n", p.row, p.col); + if (p.row == MAX_ROW - 1 /* goal */ + && p.col == MAX_COL - 1) + break; + if (p.col+1 < MAX_COL /* right */ + && maze[p.row][p.col+1] == 0) + visit(p.row, p.col+1); + if (p.row+1 < MAX_ROW /* down */ + && maze[p.row+1][p.col] == 0) + visit(p.row+1, p.col); + if (p.col-1 >= 0 /* left */ + && maze[p.row][p.col-1] == 0) + visit(p.row, p.col-1); + if (p.row-1 >= 0 /* up */ + && maze[p.row-1][p.col] == 0) + visit(p.row-1, p.col); + print_maze(); + } + if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) { + printf("(%d, %d)\n", p.row, p.col); + printf("path:\n"); + // 最后用3标识走过的路径 + maze[p.row][p.col] = 3; + while (p.predecessor != -1) { + p = queue[p.predecessor]; + maze[p.row][p.col] = 3; + printf("(%d, %d)\n", p.row, p.col); + } + print_maze(); + } else + printf("No path!\n"); + + return 0; +} diff --git a/Week_01/id_14/merge_sort.c b/Week_01/id_14/merge_sort.c new file mode 100644 index 00000000..576536aa --- /dev/null +++ b/Week_01/id_14/merge_sort.c @@ -0,0 +1,74 @@ +#include +/** + * aiter + * 4.24 07:03 + */ + +#define LEN 8 +int a[LEN] = { 5, 2, 4, 7, 1, 3, 2, 6 }; + +/** + * 稳定的排序 + * 空间复杂度O(n) + * 时间复杂度O(nlogn) + * + * 归并的过程: + * 1. 申请2个归并区间的数组 + * 2. 两个区间内部是有序的 + * 3. 按大小顺序合并两个区间 + * 4. 把剩余的添加在最后 + */ +void merge(int start, int mid, int end) { + int n1 = mid - start + 1; + int n2 = end - mid; + int left[n1], right[n2]; + int i, j, k; + for (i = 0; i < n1; i++) { + left[i] = a[start+i]; + } + for (j = 0; j < n2; j++) { + right[j] = a[mid+1+j]; + } + i = j = 0; + k = start; + while (i < n1 && j < n2) { + if (left[i] < right[j]) { + a[k++] = left[i++]; + } else { + a[k++] = right[j++]; + } + } + //如果left有剩余,放在最后 + while (i < n1) { + a[k++] = left[i++]; + } + //如果right有剩余,放在最后 + while (j < n2) { + a[k++] = right[j++]; + } +} + +void sort(int start, int end) { + int mid = 0; + if (start < end) { + mid = start + (end - start)/2; + int t; + for(t = 0; t < LEN; t++) { + printf("%d,",a[t]); + } + printf("(%d-%d),(%d-%d)\n", start, mid, mid+1, end); + sort(start, mid); + sort(mid+1, end); + merge(start, mid, end); + for(t = 0; t < LEN; t++) { + printf("%d,",a[t]); + } + printf("(%d-%d),(%d-%d)\n", start, mid, mid+1, end); + } +} + +int main(void) { + sort(0, LEN-1); + return 0; +} + diff --git a/Week_01/id_14/stack.c b/Week_01/id_14/stack.c new file mode 100644 index 00000000..57e4ecb1 --- /dev/null +++ b/Week_01/id_14/stack.c @@ -0,0 +1,58 @@ +#include + +struct stack_1 { + char stack[512]; + int top; + void (*push) (struct stack_1 * this,char c); + char (*pop) (struct stack_1 * this); + int (*is_empty) (struct stack_1 * this); +}; + +//char stack[512]; +//int top = 0; + +void push1(struct stack_1 * this,char c) { + this->stack[this->top++] = c; +} + +char pop1(struct stack_1 * this) { + return this->stack[--this->top]; +} + +int is_empty1(struct stack_1 * this) { + return this->top == 0; +} + +int main(void) { + struct stack_1 st1 = { + .stack="", + .top = 0, + .push = push1, + .pop = pop1, + .is_empty = is_empty1, + }; + st1.push(&st1,'a'); + st1.push(&st1,'b'); + st1.push(&st1,'c'); + + while ( !st1.is_empty(&st1)) { + putchar(st1.pop(&st1)); + } + putchar('\n'); + struct stack_1 st2 = { + "", + 0, + push1, + pop1, + is_empty1, + }; + struct stack_1 *st3 = &st2; + st3->push(st3,'b'); + st3->push(st3,'5'); + st3->push(st3,'3'); + while ( !st3->is_empty(st3)) { + putchar(st3->pop(st3)); + } + putchar('\n'); + return 0; +} diff --git a/Week_02/id_14/LeetCode_671_14.java b/Week_02/id_14/LeetCode_671_14.java new file mode 100644 index 00000000..ebdb303b --- /dev/null +++ b/Week_02/id_14/LeetCode_671_14.java @@ -0,0 +1,91 @@ +/** + * https://leetcode-cn.com/problems/second-minimum-node-in-a-binary-tree/ + * + *

二叉树 + *

简单 + * + * https://leetcode-cn.com/submissions/detail/17526993/ + * + * @author aiter + * @date 2019/04/22 9:22 PM + */ +public class LeetCode_671_14 { + + public static void main(String[] args) { + Solution solution = new Solution(); + + TreeNode root = new TreeNode(1); + root.left = new TreeNode(4); + root.left.left = new TreeNode(4); + root.left.right = new TreeNode(5); + + root.right = new TreeNode(5); + root.right.left = new TreeNode(6); + root.right.right = new TreeNode(7); + + System.out.println(solution.findSecondMinimumValue(root)); + } + + static class Solution { + private int second = -1; + + /** + *

+         *     1. 非空的二叉树
+         *     2. 都是正数
+         *     3. 子节点数量:0/2
+         *     4. 有子节点。 value <= 子节点value
+         * 
+ * + * @param root + * @return 第2小的值,或者-1 + */ + public int findSecondMinimumValue(TreeNode root) { + second = -1; + getNum(root); + return second; + } + + /** + * @param node + * @return 返回当前节点及子节点中的最小值 + */ + private int getNum(TreeNode node) { + if (node == null) { + return -1; + } + + int left = getNum(node.left); + int right = getNum(node.right); + + /** + * 只需要判断左子树, 子节点要么是2,要么是0 + */ + if (left == -1) { + return node.val; + } + + if (left == right) { + if (node.val < left) { + second = second == -1 ? left : Math.min(left, second); + } + } else { + if (left < right) { + if (node.val < left) { + second = second == -1 ? left : Math.min(left, second); + } else { + second = second == -1 ? right : Math.min(right, second); + } + } else { + if (node.val < right) { + second = second == -1 ? right : Math.min(right, second); + } else { + second = second == -1 ? left : Math.min(left, second); + } + } + } + + return node.val; + } + } +} diff --git a/Week_02/id_14/LeetCode_692_14.java b/Week_02/id_14/LeetCode_692_14.java new file mode 100644 index 00000000..24eff437 --- /dev/null +++ b/Week_02/id_14/LeetCode_692_14.java @@ -0,0 +1,130 @@ +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/top-k-frequent-words/submissions/ + * + *

哈希表 + 链表 + *

中等 + *

+ * https://leetcode-cn.com/submissions/detail/17527494/ + * + * @author aiter + * @date 2019/04/23 7:18 AM + */ +public class LeetCode_692_14 { + + public static void main(String[] args) { + Solution solution = new Solution(); + + System.out.println("i".compareTo("love")); + System.out.println("love".compareTo("lpve")); + System.out.println("aa".compareTo("aaa")); + System.out.println("aaa".compareTo("aa")); + + String[] words = {"i", "love", "leetcode", "i", "love", "coding"}; + //String[] words = {"a", "aa", "aaa"}; + + int k = 5; + List list = solution.topKFrequent(words, k); + System.out.println(list); + } + + static class Solution { + public List topKFrequent(String[] words, int k) { + + HashMap hashMap = new HashMap<>(words.length); + + DListNode head = new DListNode(-1, ""); + DListNode tail = new DListNode(-1, ""); + head.next = tail; + tail.pre = head; + + for (String word : words) { + /** + * 利用hash表,记录word对应的统计数据 O(1) + */ + DListNode node = hashMap.get(word); + if (node == null) { + node = new DListNode(1, word); + hashMap.put(word, node); + } else { + node.val = node.val + 1; + } + + /** + * 操作双向链表 + */ + //从链表中,删除。如果已经在双向链表中,先删除 + if (node.next != null) { + node.pre.next = node.next; + node.next.pre = node.pre; + node.pre = null; + node.next = null; + } + + DListNode cur = head.next; + + // 遍历比较,选择应该插入的位置 + while (cur != tail) { + //如果计数大于当前值,插入前面 + if (node.val > cur.val) { + cur.pre.next = node; + node.pre = cur.pre; + node.next = cur; + cur.pre = node; + break; + //如果计数等于当前值,那么比较字母顺序 + } else if (node.val == cur.val) { + int c = node.word.compareTo(cur.word); + //字母顺序更小,插入前面 + if (c < 0) { + cur.pre.next = node; + node.pre = cur.pre; + node.next = cur; + cur.pre = node; + break; + } else { + //指针后移 + cur = cur.next; + } + + } else { + cur = cur.next; + } + } + // 如果到tail了,还未插入,就插入到tail前面。比如:第一个元素 and 和最后一个元素的val相等,但是字母顺序靠后的({"a", "aa", "aaa"};)。 + if (node.next == null) { + tail.pre.next = node; + node.next = tail; + node.pre = tail.pre; + tail.pre = node; + } + } + + List list = new ArrayList<>(k); + DListNode cur = head.next; + for (int i = 0; i < k; i++) { + if (cur != tail) { + list.add(cur.word); + cur = cur.next; + } + } + + return list; + } + } + + static class DListNode { + int val; + String word; + DListNode pre; + DListNode next; + + DListNode(int x, String w) { + val = x; + word = w; + } + } +} diff --git a/Week_02/id_14/LeetCode_783_14.java b/Week_02/id_14/LeetCode_783_14.java new file mode 100644 index 00000000..cd492b12 --- /dev/null +++ b/Week_02/id_14/LeetCode_783_14.java @@ -0,0 +1,163 @@ +/** + * https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/ + *

+ * 783. 二叉搜索树结点最小距离 + * + *

简单 + *

树 binary tree + * + * @author aiter + * @date 2019/04/26 6:42 AM + */ +public class LeetCode_783_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + + TreeNode root = new TreeNode(4); + root.left = new TreeNode(2); + root.left.left = new TreeNode(1); + root.left.right = new TreeNode(3); + + root.right = new TreeNode(6); + + int ret = solution.minDiffInBST(root); + + System.out.println(String.format("预期值:1,实际值:%d", ret)); + + Solution2 solution2 = new Solution2(); + System.out.println(String.format("预期值:1,实际值:%d", solution2.minDiffInBST(root))); + } + + /** + *

+     *     要求:
+     *        任意两节点的差的最小值
+     *        开始理解为父子节点的差值
+     *     二叉搜索树:
+     *     1. 是有序的
+     *       2.1 中序遍历,就是一个有序的数列,再求相邻数组元素之间的最小差值
+     *       2.2 每个节点,查找他的中序遍历前序、后序节点,最小值只会出现在这里
+     *
+     *
+     *       2.1  结果,为什么?中序遍历O(n)+遍历求值O(n)。总:O(n)
+     *       执行用时 : 27 ms, 在Minimum Distance Between BST Nodes的Java提交中击败了5.25% 的用户
+     *       内存消耗 : 35.1 MB, 在Minimum Distance Between BST Nodes的Java提交中击败了55.36% 的用户
+     *       https://leetcode-cn.com/submissions/detail/17726878/
+     *
+     *       2.2
+     *       执行用时 : 1 ms, 在Minimum Distance Between BST Nodes的Java提交中击败了100.00% 的用户
+     *       内存消耗 : 34.6 MB, 在Minimum Distance Between BST Nodes的Java提交中击败了74.41% 的用户
+     * 
+ */ + static class Solution2 { + private final static int MAX = 100; + + public int minDiffInBST(TreeNode root) { + if (root == null) { + return MAX; + } + + int left = -1; + // 如果有左子树,那么最大值,一定是左子节点或左子树的最右节点 + if (root.left != null) { + left = root.left.val; + TreeNode cur = root.left.right; + while (cur != null) { + left = cur.val; + cur = cur.right; + } + } + + int min = MAX; + if (left > 0) { + min = Math.min(min, root.val - left); + } + + int right = -1; + // 如果有右子树,那么最小值,一定是右子节点或右子树的最左节点 + if (root.right != null) { + right = root.right.val; + TreeNode cur = root.right.left; + while (cur != null) { + right = cur.val; + cur = cur.left; + } + } + if (right > 0) { + min = Math.min(min, right - root.val); + } + + //递归计算左子树、右子树的最小值k,在比较k与min的最小值 + return Math.min(min, Math.min(minDiffInBST(root.left), minDiffInBST(root.right))); + + } + + } + + static class Solution { + private final static int MAX = 100; + + public int minDiffInBST(TreeNode root) { + + DListNode inorderNode = new DListNode(root.val); + + inorder(root, inorderNode); + + DListNode cur = inorderNode; + System.out.println("-" + cur.val); + int min = MAX; + while (cur.pre != null) { + + min = Math.min(min, Math.abs(cur.val - cur.pre.val)); + cur = cur.pre; + System.out.println("pre-" + cur.val); + } + cur = inorderNode; + System.out.println("-" + cur.val); + while (cur.next != null) { + + min = Math.min(min, Math.abs(cur.val - cur.next.val)); + cur = cur.next; + System.out.println("next-" + cur.val); + } + return min; + } + + private void inorder(TreeNode root, DListNode inorderNode) { + if (root == null) { + return; + } + + if (root.left != null) { + DListNode pre = inorderNode.pre; + inorderNode.pre = new DListNode(root.left.val); + inorderNode.pre.next = inorderNode; + if (pre != null) { + inorderNode.pre.pre = pre; + pre.next = inorderNode.pre; + } + inorder(root.left, inorderNode.pre); + } + if (root.right != null) { + DListNode next = inorderNode.next; + inorderNode.next = new DListNode(root.right.val); + inorderNode.next.pre = inorderNode; + if (next != null) { + inorderNode.next.next = next; + next.pre = inorderNode.next; + } + inorder(root.right, inorderNode.next); + } + } + } + + static class DListNode { + int val; + DListNode pre; + DListNode next; + + DListNode(int x) { + val = x; + } + } +} diff --git a/Week_02/id_14/NOTE.md b/Week_02/id_14/NOTE.md index c684e62f..22c51df8 100644 --- a/Week_02/id_14/NOTE.md +++ b/Week_02/id_14/NOTE.md @@ -1 +1,55 @@ -# 学习笔记 \ No newline at end of file +# 学习笔记 +> 本周重点:跳表、散列表、哈希算法、二叉树、红黑树 + +本周主要还是练习,课程学习相对少一些。 +* 二叉树& 二叉搜索树 + * binary tree 本身很基础,使用也是最多的一种。本身限定为二叉,0、1、2个子节点都符合要求,运用于实际时,可能是更多的限定,比如: + * 完全二叉树,那么只有1个子节点时,必须是左子字节。 + * leetcode上的求第二小的数字。限定子节点只能是0或2,不能是1 + * 为了避免二叉树的高度太高,退化成链表,这样就基本失去了树的特征,这又引出了: + * 平衡二叉树,左右子树的高度不能“相差太多” + * 完全二叉树,严格的平衡二叉树,左右高度相差最多为1。而且1个子节点时,必须是左子节点 + * 满二叉树,一种特殊的完全二叉树。所有的非叶子节点数都是2 + * 红黑树,比较难,后面再详细学习 +``` + 2 2 2 2 + / \ / \ / \ / \ + 2 5 2 5 2 5 2 5 + / \ / \ / \ / / \ / \ + 5 7 5 7 5 7 6 5 7 6 7 + / + 5 + 二叉树 平衡二叉树 完全二叉树 满二叉树 +``` + > 都是二叉树,2、3、4都是平衡二叉树, 3、4都是完成二叉树。4是满二叉树 + * 堆,就是一种完全二叉树。外加了一条,节点值大/小于左、右子节点值,分别就是大、小顶堆 + * 二叉搜索树 + * 本身是有序的,左子节点的值小于节点值,右子节点值大于节点值 + * 基于上一条,中序遍历后,就是一个有序的序列 + * leetcode上《783. 二叉搜索树结点最小距离》,就是典型的应用。开始笔者使用了中序遍历成一个有序的双向链表序列,再遍历链表,求相邻数值的最小差值,这样时间复杂度比较高(中序遍历O(n)+遍历链表求差值O(n)???)。后面利用二叉搜索树,任何节点的中序遍历前序节点要么是左子节点,要么是左子树的最右节点。后序节点同样的道理。最小距离肯定出现在当前节点与前序、后序节点的差值中。 +``` + 4 4 + / \ / \ + 2 6 2 6 + / \ ^ / \ + 1 3 | 5 8 + ^ + | +``` + * 未完成:前、中、后序、层次遍历的实现 +* top k frequent words + > 使用了hash表+双向链表的实现 + * hash表,主要是O(1)时间复杂度定位对应的元素(也是双向链表中的节点) + * 把当前节点,从链中移除 + * 再从链表头进行比较,选择插入的位置 + * 最后从链表头,输出top K的 +* 利用队列实现广度优先搜索(BFS ,Breadth First Search) +* 利用栈实现深度优先搜索(DFS ,Depth First Search) +* 插入排序实现 +* 归并排序实现 +* 堆及堆排序 +上面几种实现本身没什么特别, 也是参考例子去实现的,有一点比较重要,以前都是学习理论,没有动手自己去实现。理解上还是不太深,自己实现一遍,加深了理解。比如: + * 广度优先,没实现前,记得当时的一个图,一层层去遍历,也记得有个队列。自己实现是走迷宫,实际实现就是从当前点,依次查看当前点的右、下、左、上是否可走,可走的话,就把右、下、左、上的点,加入队尾,然后从队头中取一个元素作为当前点,再重复这个过程。利用队列的先进先出(FIFO),就实现了"层层推进"的效果。 + * 而深度优先,则是采用栈的后进先出(LIFO)特性。实现了"一条路走到黑"的效果,只有真正没路可走了,而且还没走出迷宫,这时才能回到"岔路口"从另一条路再这样"一条路走到黑",每个岔路口,都是这样干的。这就是回溯(Backtrack)? + +> * 跳表:本周未涉及,周末有时间可以实现一下 diff --git a/Week_02/id_14/TreeNode.java b/Week_02/id_14/TreeNode.java new file mode 100644 index 00000000..1b9907a3 --- /dev/null +++ b/Week_02/id_14/TreeNode.java @@ -0,0 +1,16 @@ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { val = x; } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("val:").append(val) + .append(" -left:").append(left == null ? -1 : left.val) + .append(" -right:").append(right == null ? -1 : right.val); + return stringBuilder.toString(); + } +} diff --git a/Week_03/id_14/LeetCode_104_14.java b/Week_03/id_14/LeetCode_104_14.java new file mode 100644 index 00000000..56ed78bd --- /dev/null +++ b/Week_03/id_14/LeetCode_104_14.java @@ -0,0 +1,102 @@ +import java.util.HashMap; +import java.util.Stack; + +/** + * https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ + * + *

简单 + *

二叉树 + *

DFS + * + * @author aiter + * @date 2019/05/04 8:54 AM + */ +public class LeetCode_104_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + Solution2 solution2 = new Solution2(); + + /** + *

+         *            1
+         *          /   \
+         *         4     5
+         *        / \   / \
+         *       4  5  6  7
+         *      / \
+         *     12 5
+         * 
+ */ + TreeNode root = new TreeNode(1); + root.left = new TreeNode(4); + root.left.left = new TreeNode(4); + root.left.right = new TreeNode(5); + root.left.left.left = new TreeNode(12); + root.left.left.right = new TreeNode(5); + + root.right = new TreeNode(5); + root.right.left = new TreeNode(6); + root.right.right = new TreeNode(7); + + System.out.println(String.format("期望值:4,实际值:%d", solution.maxDepth(root))); + System.out.println(String.format("期望值:4,实际值:%d", solution2.maxDepth(root))); + } + + /** + * 难得遇到一道送分题! + */ + static class Solution { + public int maxDepth(TreeNode root) { + if (root == null) { + return 0; + } + + int left = maxDepth(root.left); + int right = maxDepth(root.right); + + return ((left >= right) ? left : right) + 1; + + } + } + + /** + * 用stack和哈希表,实现一下。 + * 哈希表记录当前节点的深度 + */ + static class Solution2 { + + public int maxDepth(TreeNode root) { + if (root == null) { + return 0; + } + + int max = 0; + + Stack stack = new Stack<>(); + HashMap map = new HashMap<>(); + + stack.push(root); + map.put(root, map.getOrDefault(root, 1)); + + while (!stack.empty()) { + TreeNode cur = stack.pop(); + + int tmp = map.getOrDefault(cur, 1); + + max = max > tmp ? max : tmp; + + if (cur.right != null) { + stack.push(cur.right); + map.put(cur.right, tmp + 1); + } + + if (cur.left != null) { + stack.push(cur.left); + map.put(cur.left, tmp + 1); + } + + } + return max; + } + } +} diff --git a/Week_03/id_14/LeetCode_429_14.java b/Week_03/id_14/LeetCode_429_14.java new file mode 100644 index 00000000..7046a2fe --- /dev/null +++ b/Week_03/id_14/LeetCode_429_14.java @@ -0,0 +1,123 @@ +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ + * + *

简单 + *

N叉树 + * + * @author aiter + * @date 2019/05/04 7:51 PM + */ +public class LeetCode_429_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + Node root = new Node(1, new ArrayList() {{ + add(new Node(3, new ArrayList() {{ + add(new Node(5, new ArrayList())); + add(new Node(6, new ArrayList())); + }})); + + add(new Node(2, new ArrayList())); + add(new Node(4, new ArrayList())); + }}); + + List> list = solution.levelOrder(root); + System.out.println(list); + System.out.println(new Solution2().levelOrder(root)); + } + + /** + * 使用递归的方式。 + * 递归的方式,无论是效率还是代码清晰度都比较好。 + */ + static class Solution2 { + public List> levelOrder(Node root) { + List> list = new ArrayList<>(); + getList(list, root, 0); + + return list; + + } + + private void getList(List> list, Node root, int l) { + if (root == null) { + return; + } + if (l >= list.size()) { + list.add(new ArrayList<>()); + } + List levelList = list.get(l); + levelList.add(root.val); + + int nextLevel = l + 1; + for (Node node : root.children) { + getList(list, node, nextLevel); + } + } + } + + /** + * 使用遍历的方式。 + *

+     *     1. 队列记录层次的元素
+     * 
+ */ + static class Solution { + public List> levelOrder(Node root) { + List> list = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + if (root == null) { + return list; + } + Node cur = root; + //当前层的遍历数量 + int cNum = 1; + //下一层的总量 + int nNum = 0; + List iList = new ArrayList<>(); + list.add(iList); + while (cNum-- > 0) { + List cList = cur.children; + nNum += cList.size(); + queue.addAll(cList); + + iList.add(cur.val); + //System.out.println(String.format("当前数量:%d,下一层数量:%d", cNum, nNum)); + //如果当前层,遍历完成。将下一层数量赋值给当前层,并将下一层重置为0 + if (cNum == 0) { + cNum = nNum; + nNum = 0; + if (cNum > 0) { + iList = new ArrayList<>(); + list.add(iList); + } + } + + if (queue.size() > 0) { + cur = queue.poll(); + } else { + break; + } + } + + return list; + + } + } + + static class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val, List _children) { + val = _val; + children = _children; + } + } +} + diff --git a/Week_03/id_14/LeetCode_703_14.java b/Week_03/id_14/LeetCode_703_14.java new file mode 100644 index 00000000..65157eed --- /dev/null +++ b/Week_03/id_14/LeetCode_703_14.java @@ -0,0 +1,190 @@ +import java.util.*; + +/** + * https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/ + *

+ * 第K大元素 + * + *

简单 + *

堆 + * + * @author aiter + * @date 2019/05/03 8:20 PM + */ +public class LeetCode_703_14 { + + public static void main(String[] args) { + int k = 3; + int[] arr = {4, 5, 8, 2}; + KthLargest3 kthLargest = new KthLargest3(k, arr); + System.out.println(String.format("添加%d , 期望值:4,实际值:%d", 3, kthLargest.add(3))); // returns 4 + System.out.println(String.format("添加%d , 期望值:5,实际值:%d", 5, kthLargest.add(5))); // returns 5 + System.out.println(String.format("添加%d , 期望值:5,实际值:%d", 10, kthLargest.add(10))); // returns 5 + System.out.println(String.format("添加%d , 期望值:8,实际值:%d", 9, kthLargest.add(9))); // returns 8 + System.out.println(String.format("添加%d , 期望值:8,实际值:%d", 4, kthLargest.add(4))); // returns 8 + } + + /** + * 利用java本身的优先级队列(堆)。一定要注意是只维护top K大小的堆。 + *

+     *     不到k,就直接插入
+     *     大于等于k,而且插入元素大于堆顶元素,先删除堆顶,再插入
+     * 
+ */ + static class KthLargest3 { + private PriorityQueue heap; + private int topK; + + public KthLargest3(int k, int[] nums) { + topK = k; + heap = new PriorityQueue<>((o1, o2) -> o1.compareTo(o2)); + + for (int i = 0; i < nums.length; i++) { + add(nums[i]); + } + } + + public int add(int val) { + if (heap.size() < topK) { + heap.add(val); + } else if (heap.peek() < val) { + heap.poll(); + heap.add(val); + } + + return heap.peek(); + } + } + + /** + * 先添加所有的元素,在删除多余top k的数据(这种方式不好) + */ + static class KthLargest2 { + private PriorityQueue heap; + private int topK; + + public KthLargest2(int k, int[] nums) { + topK = k; + heap = new PriorityQueue<>((o1, o2) -> o1.compareTo(o2)); + + for (int i = 0; i < nums.length; i++) { + heap.add(nums[i]); + } + + delete(); + } + + public int add(int val) { + + heap.add(val); + delete(); + return heap.peek(); + } + + void delete() { + while (heap.size() > topK) { + heap.poll(); + } + } + } + + /** + * 自行实现小顶堆。 + *
+     *     如果堆大小还不到 k的大小,最后位置插入元素,并从下到上堆化
+     *     如果堆大小已经k的大小。
+     *      1. 插入值,小于等于堆顶元素,直接返回堆顶元素
+     *      2. 插入值,大于堆顶元素。直接替换堆顶元素,并从上到下堆化(使用java优先队列,这一步需要先删堆顶,再添加元素,2次堆化)
+     * 
+ */ + static class KthLargest { + private int k = 0; + private int size; + private int[] items; + + //nums 的长度≥ k-1 且k ≥ 1。 + public KthLargest(int k, int[] nums) { + this.k = k; + this.items = new int[k]; + for (int i = 0; i < nums.length; i++) { + add(nums[i]); + } + } + + public int add(int val) { + if (size >= k) { + if (items[0] < val) { + items[0] = val; + siftDown(items, size, 0); + } + return items[0]; + } else { + items[size++] = val; + + siftup(size - 1, items[size - 1]); + + return items[0]; + } + } + + void printArray(int arr[]) { + int n = arr.length; + for (int i = 0; i < n; ++i) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + /** + * 从下往上堆化(小顶堆) + * + * @param t 下标值 + * @param key 下标元素值 + */ + void siftup(int t, int key) { + while (t > 0) { + int parent = (t - 1) >>> 1; + //System.out.println(t + ":" + key + ":" + parent); + int e = items[parent]; + if (e <= key) { break; } + items[t] = e; + t = parent; + } + items[t] = key; + } + + /** + * 从上往下堆化(小顶堆) + * + * @param arr 数组 + * @param n 数组大小 + * @param i 下标值 + */ + void siftDown(int arr[], int n, int i) { + // 初始化i为最小值 + int smallest = i; + // 左节点 = 2*i + 1 + int l = 2 * i + 1; + // 右节点 = 2*i + 2 + int r = 2 * i + 2; + + // 左节点比当前节点小 + if (l < n && arr[l] < arr[smallest]) { smallest = l; } + + // 右节点更小 + if (r < n && arr[r] < arr[smallest]) { smallest = r; } + + // 如果,最小值不是当前节点(左、右),就交换。并继续堆化 + if (smallest != i) { + int swap = arr[i]; + arr[i] = arr[smallest]; + arr[smallest] = swap; + + // Recursively heapify the affected sub-tree + siftDown(arr, n, smallest); + } + } + } + +} + diff --git a/Week_03/id_14/LeetCode_997_14.java b/Week_03/id_14/LeetCode_997_14.java new file mode 100644 index 00000000..67b1d01f --- /dev/null +++ b/Week_03/id_14/LeetCode_997_14.java @@ -0,0 +1,115 @@ +/** + * https://leetcode-cn.com/problems/find-the-town-judge/ + * + *

简单 + *

图 + * + * @author aiter + * @date 2019/04/30 6:32 AM + */ +public class LeetCode_997_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + + int N = 3; + int[][] trust = {{1, 3}, {2, 3}}; + + int r = solution.findJudge(N, trust); + + System.out.println(String.format("期望值:3,实际值:%d", r)); + + Solution2 solution2 = new Solution2(); + + System.out.println(String.format("期望值:3,实际值:%d", solution2.findJudge(N, trust))); + } + + /** + *

+     *   需求描述:
+     *     1. 小镇的法官不相信任何人。
+     *          没有指向的顶点。   出度=0
+     *     2. 每个人(除了小镇法官外)都信任小镇的法官。
+     *          其他的点,都指向这个顶点。秘密的法官的  入度=N-1
+     *     3. 只有一个人同时满足属性 1 和属性 2 。
+     *          只能有一个顶点的。出度=0,入度=N-1
+     *
+     *     +-----+----+
+     *     |入度 |出度 |
+     *     +-----+----+
+     *     | N-1 | 0  |  -->  秘密法官
+     *     +-----+----+
+     *     |0.N-1| >0 |  -->  其他
+     *     +-----+----+
+     *
+     * 
+ */ + static class Solution { + public int findJudge(int N, int[][] trust) { + //这种方法可以不判断,persons二维数组遍历时,N=1时,也能满足 + //if (N == 1 && trust.length == 0) { + // return 1; + //} + int[][] persons = new int[N + 1][2]; + + for (int[] items : trust) { + //统计入度 + persons[items[1]][0] = persons[items[1]][0] + 1; + //统计出度 + persons[items[0]][1] = persons[items[0]][1] + 1; + } + + int judgeCnt = 0; + int idx = 0; + for (int i = 1; i < persons.length; i++) { + int[] items = persons[i]; + //入度=N-1 && 出度=0 + if (items[0] == N - 1 && items[1] == 0) { + judgeCnt++; + idx = i; + } + } + if (judgeCnt == 1) { + return idx; + } + + return -1; + } + } + + static class Solution2 { + public int findJudge(int N, int[][] trust) { + //这个条件开始没注意 + if (N == 1 && trust.length == 0) { + return 1; + } + int[][] persons = new int[N + 1][2]; + + int judgeCnt = 0; + int idx = 0; + for (int[] items : trust) { + //统计入度 + persons[items[1]][0] = persons[items[1]][0] + 1; + if (persons[items[1]][0] == N - 1) { + //入度=N-1 && 出度=0 就是法官 + if (persons[items[1]][1] == 0) { + judgeCnt++; + idx = items[1]; + } + } + //统计出度 + persons[items[0]][1] = persons[items[0]][1] + 1; + //被选中的法官,有出度了,就不是法官 + if (items[0] == idx) { + judgeCnt--; + idx = 0; + } + } + + if (judgeCnt == 1) { + return idx; + } + + return -1; + } + } +} diff --git a/Week_03/id_14/NOTE.md b/Week_03/id_14/NOTE.md index c684e62f..7c855207 100644 --- a/Week_03/id_14/NOTE.md +++ b/Week_03/id_14/NOTE.md @@ -1 +1,46 @@ -# 学习笔记 \ No newline at end of file +# 学习笔记 +> 递归树、堆和排序、图、深度和广度优先搜索、字符串匹配 + +递归树 +> 主要就是用来分析递归代码的时间复杂度? + +堆 +> 很重要的一种数据结构,日常工作中使用率也比较高。比如:常用的优先级队列、堆排序。 +一些理解点: + * 本身是一棵完全二叉树(除最后一层,其他层节点都是满的,最后一层如果只有一个节点,必须考左) + * 一般用数组来存储,而且可以利用数组下标快速的方法定位父节点((i-1)/2)、左右子节点(l=2i*-1,r=2i*2) + * 大顶堆,当前节点值,大于等于左右节点值。小顶堆,当前节点值,小于等于左右节点值。 +> 一个大顶堆,存储它的数组是有序的吗? +``` + 67 + / \ + 56 23 + / \ / \ + 45 11 7 13 + / \ / + 12 5 6 + +----+----+----+----+----+----+----+----+----+----+ + | 67 | 56 | 23 | 45 | 11 | 7 | 13 | 12 | 5 | 6 | + +----+----+----+----+----+----+----+----+----+----+ + 这个一个大顶堆,下面是数组的存储情况。 显然,存储它的数组不是有序的。 +``` +> 基于上一点的特性,我们取堆中的top k按大小顺序的元素,要么不断的删除堆顶元素,然后再次堆化;要么将堆排序输出,其实也是,不断的删除堆顶元素,然后在0...n-1的范围内堆化。动态的数据,要是把堆顶元素删除了,元素就不存在了,如果是top k问题,其实是使用小顶堆。 +建堆& 动态操作: + * 原始数组的堆化,一种从前往后(插入,也是从下往上堆化?);一种从后往前(只需要从n/2-1的节点开始,往前遍历建堆) + * 堆排序:建堆后,迭代删除堆顶,并堆化剩余的N-1的堆 + * 删除堆顶&插入新元素:删除一般采用:用n-1位置的元素放入下标为0的堆顶位置,然后向下堆化。插入到n-1的位置,然后向上堆化。 + +图: +> 无向图、有向图、带权图、无向/有向带权图 + +注意跟进实际场景,选择图的存储方式,是使用邻接矩阵还是邻接表。比如:顶点比较多,但是顶点间关系(边)不多的,邻接表更适合。可以把王争老师说的微信、微博这些社交关系的仔细推导、实现一下。 +BFS & DFS +> 图的搜索遍历。上周也简单总结了一下。整体的感觉:离融会贯通还有很长的距离,现在很多点,还是各自游离状态。 +``` + 广度优先,没实现前,记得当时的一个图,一层层去遍历,也记得有个队列。自己实现是走迷宫,实际实现就是从当前点,依次查看当前点的右、下、左、上是否可走,可走的话,就把右、下、左、上的点,加入队尾,然后从队头中取一个元素作为当前点,再重复这个过程。利用队列的先进先出(FIFO),就实现了"层层推进"的效果。 + 深度优先,则是采用栈的后进先出(LIFO)特性。实现了"一条路走到黑"的效果,只有真正没路可走了,而且还没走出迷宫,这时才能回到"岔路口"从另一条路再这样"一条路走到黑",每个岔路口,都是这样干的。这就是回溯(Backtrack)? +``` + +字符串匹配 +> BF当然是最好理解,但是由于效率低。出现了RK算法。本质上是:RK利用哈希表,来提高字符串比较的效率。 +> BM/KMP,思路上主要也是,减少字符串比较的次数。有机会多滑动几位,就可以减少模式串和主串的比较次数。KMP的next数组还没看明白:( diff --git a/Week_03/id_14/TreeNode.java b/Week_03/id_14/TreeNode.java new file mode 100644 index 00000000..79021550 --- /dev/null +++ b/Week_03/id_14/TreeNode.java @@ -0,0 +1,17 @@ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { val = x; } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("val:").append(val) + .append(" -left:").append(left == null ? -1 : left.val) + .append(" -right:").append(right == null ? -1 : right.val); + return stringBuilder.toString(); + } +} + diff --git a/Week_04/id_14/LeetCode_455_14.java b/Week_04/id_14/LeetCode_455_14.java new file mode 100644 index 00000000..bd071cf9 --- /dev/null +++ b/Week_04/id_14/LeetCode_455_14.java @@ -0,0 +1,88 @@ +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/assign-cookies/ + * + *

分发饼干 + *

简单 + *

贪心算法 + * + * @author aiter + * @date 2019/05/10 7:39 AM + */ +public class LeetCode_455_14 { + + public static void main(String[] args) { + Solution2 solution = new Solution2(); + + System.out.println(String.format("期望值:%d,实际值:%d", + 1, solution.findContentChildren(new int[] {1, 2, 3}, new int[] {1, 1}))); + + System.out.println(String.format("期望值:%d,实际值:%d", + 2, solution.findContentChildren(new int[] {1, 2}, new int[] {1, 2, 3}))); + + System.out.println(String.format("期望值:%d,实际值:%d", + 1, solution.findContentChildren(new int[] {1, 2, 3}, new int[] {3}))); + } + + static class Solution2 { + /** + * @param g 孩子 + * @param s 饼干 + * @return 满足的孩子的数量 + */ + public int findContentChildren(int[] g, int[] s) { + int count = 0; + + //O(n) + Arrays.sort(g); + //O(m) + Arrays.sort(s); + + int i = 0, j = 0; + while (i < g.length && j < s.length) { + //满足 + if (g[i] <= s[j]) { + count++; + i++; + j++; + } else { + j++; + } + } + return count; + } + } + + static class Solution { + /** + * @param g 孩子 + * @param s 饼干 + * @return 满足的孩子的数量 + */ + public int findContentChildren(int[] g, int[] s) { + int count = 0; + + //O(n) + Arrays.sort(g); + //O(m) + Arrays.sort(s); + + //O(m*n) + for (int i = 0; i < s.length; i++) { + for (int j = count; j < g.length; j++) { + //满足 + if (g[j] <= s[i]) { + count++; + } + + //满足,不满足,都需要看更大的饼干 + break; + } + } + + return count; + } + } +} + diff --git a/Week_04/id_14/LeetCode_720_14.java b/Week_04/id_14/LeetCode_720_14.java new file mode 100644 index 00000000..316473cb --- /dev/null +++ b/Week_04/id_14/LeetCode_720_14.java @@ -0,0 +1,213 @@ +import java.util.*; + +/** + * https://leetcode-cn.com/problems/longest-word-in-dictionary/ + * + *

简单 + *

trie树 + * + * @author aiter + * @date 2019/05/08 7:12 AM + */ +public class LeetCode_720_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + //solution.use = 1; + + System.out.println(String.format("期望值:world,实际值:%s", + solution.longestWord(new String[] {"w", "wo", "wor", "worl", "world"}))); + System.out.println(String.format("期望值:apple,实际值:%s", + solution.longestWord(new String[] {"a", "banana", "app", "appl", "ap", "apply", "apple"}))); + + System.out.println(String.format("期望值:ap,实际值:%s", + solution.longestWord(new String[] {"a", "banana", "appl", "ap", "apply", "apple"}))); + + System.out.println(String.format("期望值:latte,实际值:%s", + solution.longestWord( + new String[] {"m", "mo", "moc", "moch", "mocha", "l", "la", "lat", "latt", "latte", "c", "ca", + "cat"}))); + + System.out.println(String.format("期望值:eyj,实际值:%s", + solution.longestWord( + new String[] {"ogz", "eyj", "e", "ey", "hmn", "v", "hm", "ogznkb", "ogzn", "hmnm", "eyjuo", "vuq", + "ogznk", "og", "eyjuoi", "d"}))); + + } + + /** + * 最长 + *

+ * 有时不愿意用递归 + */ + static class Solution { + private TrieNode root; + private String longest; + //辅助变量,用于选择执行逻辑 + int use = 0; + + public String longestWord(String[] words) { + if (words.length == 0) { + return ""; + } + //初始化trie树 + root = new TrieNode(""); + for (String word : words) { + insert(word); + } + + //查找 + if (use == 0) { + longest = root.val; + longestWordByRecursion(root, 0); + return longest; + } else { + return longestWordByIteration(); + } + } + + /** + * 递归实现。每到一层,就和已有最长串比较。 + * + * @param root + * @param n + */ + private void longestWordByRecursion(TrieNode root, int n) { + if (!root.isEnd && n > 0) { + return; + } + + if (n > longest.length()) { + longest = root.val; + } else if (n == longest.length() && root.val.compareTo(longest) < 0) { + longest = root.val; + } + + Map sonMap = root.son; + if (sonMap.size() > 0) { + int m = n + 1; + for (TrieNode son : sonMap.values()) { + longestWordByRecursion(son, m); + } + } + } + + /** + private void longestWordByRecursion2(TrieNode root, int n, String pVal) { + if (!root.isEnd && n > 0) { + if (n - 1 > longest.length()) { + longest = pVal; + } else if (n-1 == longest.length() && pVal.compareTo(longest) < 0) { + longest = pVal; + } + return; + } + + //减少比较 + if (root.son.size() == 0) { + if (n > longest.length()) { + longest = root.val; + } else if (n == longest.length() && root.val.compareTo(longest) < 0) { + longest = root.val; + } + } else { + int m = n + 1; + for (TrieNode son : root.son.values()) { + longestWordByRecursion2(son, m, root.val); + } + } + } + */ + + /** + * 使用队列实现 + * + * @return + */ + private String longestWordByIteration() { + List list = new ArrayList<>(); + + Queue queue = new LinkedList<>(); + queue.add(root); + + while (queue.size() > 0) { + + TrieNode cur = queue.poll(); + if (cur.son.size() > 0) { + boolean added = false; + for (TrieNode son : cur.son.values()) { + if (son.isEnd) { + queue.add(son); + added = true; + } + } + //如果没有下一个字母是完整的单词 + if (!added) { + list.add(cur.val); + } + } else { + list.add(cur.val); + } + + while (queue.size() > 0 && !queue.peek().isEnd) { + queue.poll(); + } + } + + if (list.size() == 0) { + return ""; + } else if (list.size() == 1) { + return list.get(0); + } else { + Collections.sort(list, new Comparator() { + @Override + public int compare(String o1, String o2) { + if (o1.length() > o2.length()) { + return -1; + } else if (o1.length() < o2.length()) { + return 1; + } + return o1.compareTo(o2); + } + }); + return list.get(0); + } + } + + /** + * 将一个字符串插入trie树。 + * + * @param str + */ + private void insert(String str) { + if (str == null || str.length() == 0) { + return; + } + TrieNode node = root; + str = str.toLowerCase(); + char[] letters = str.toCharArray(); + for (int i = 0, len = str.length(); i < len; i++) { + char c = letters[i]; + if (!node.son.containsKey(c)) { + TrieNode son = new TrieNode(""); + node.son.put(c, son); + } + + node = node.son.get(c); + } + node.isEnd = true; + node.val = str; + } + } + + static class TrieNode { + Map son; + boolean isEnd;// end of a string. + String val;// value + + TrieNode(String val) { + this.son = new HashMap<>(); + this.isEnd = false; + this.val = val; + } + } +} diff --git a/Week_04/id_14/LeetCode_746_14.java b/Week_04/id_14/LeetCode_746_14.java new file mode 100644 index 00000000..257a7133 --- /dev/null +++ b/Week_04/id_14/LeetCode_746_14.java @@ -0,0 +1,91 @@ +import java.util.Arrays; +/** + * https://leetcode-cn.com/problems/min-cost-climbing-stairs/ + *

简单 + *

dynamic programming + *

爬楼梯,只能爬一梯或者二梯 + * + * @author aiter + * @date 2019/05/10 8:29 AM + */ +public class LeetCode_746_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + //Solution2 solution = new Solution2(); + + System.out.println(String.format("期望值:%d,实际值:%d", + 6, solution.minCostClimbingStairs(new int[] {1, 100, 1, 1, 1, 100, 1, 1, 100, 1}))); + System.out.println(String.format("期望值:%d,实际值:%d", + 15, solution.minCostClimbingStairs(new int[] {10, 15, 20}))); + + System.out.println(String.format("期望值:%d,实际值:%d", + 0, solution.minCostClimbingStairs(new int[] {0, 0, 0, 0}))); + + System.out.println(String.format("期望值:%d,实际值:%d", + 0, solution.minCostClimbingStairs(new int[] {0, 0, 1, 0}))); + + System.out.println(String.format("期望值:%d,实际值:%d", + 1, solution.minCostClimbingStairs(new int[] {0, 0, 1, 1}))); + } + + static class Solution2 { + public int minCostClimbingStairs(int[] cost) { + int[] dp = new int[cost.length + 1]; + dp[0] = 0; + dp[1] = 0; + for (int i = 2; i <= cost.length; i++) { + dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]); + } + + return dp[cost.length]; + + } + } + + /** + * 被动态规划,搞疯了。{@link Solution2#minCostClimbingStairs(int[])} 使用的是状态转移方程? 而我费时费力用的是状态转移表? + */ + static class Solution { + + public int minCostClimbingStairs(int[] cost) { + int n = cost.length;//楼梯数量 + int[][] status = new int[n][2]; + + for (int i = 0; i < n; i++) { + status[i][0] = -1; + status[i][1] = -1; + } + + status[0][0] = 0; + status[0][1] = cost[0]; + + for (int i = 1; i < n; i++) { + //上一梯没走,这一梯必须走。上一梯没走,那么上上一梯,肯定是走了的。 + if (i == 1 || status[i - 1][0] == status[i - 2][1]) { + if (status[i][1] >= 0) { + status[i][1] = Math.min(status[i][1], status[i - 1][0] + cost[i]); + } else { + status[i][1] = status[i - 1][0] + cost[i]; + } + } + + //上一梯走了。 + if (status[i - 1][1] >= 0) { + //不走这一梯 + status[i][0] = status[i - 1][1]; + + //走这一梯 + if (status[i][1] >= 0) { + status[i][1] = Math.min(status[i][1], status[i - 1][1] + cost[i]); + } else { + status[i][1] = status[i - 1][1] + cost[i]; + } + } + + System.out.println(i + ":" + Arrays.toString(status[i])); + } + return status[n - 1][0] < status[n - 1][1] ? status[n - 1][0] : status[n - 1][1]; + } + } +} + diff --git a/Week_04/id_14/LeetCode_784_14.java b/Week_04/id_14/LeetCode_784_14.java new file mode 100644 index 00000000..22057aad --- /dev/null +++ b/Week_04/id_14/LeetCode_784_14.java @@ -0,0 +1,102 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode-cn.com/problems/letter-case-permutation/ + * + *

简单 + *

backtracking + * S 的长度不超过12。 S 仅由数字和字母组成。 + * + * @author aiter + * @date 2019/05/10 6:37 AM + */ +public class LeetCode_784_14 { + public static void main(String[] args) { + Solution solution = new Solution(); + Solution2 solution2 = new Solution2(); + + System.out.println(String.format("输出值:%s", solution.letterCasePermutation("a1b2"))); + System.out.println(String.format("输出值:%s", solution2.letterCasePermutation("a1b2"))); + System.out.println(String.format("输出值:%s", solution.letterCasePermutation("a12b"))); + System.out.println(String.format("输出值:%s", solution2.letterCasePermutation("a12b"))); + System.out.println(String.format("输出值:%s", solution.letterCasePermutation("a1b2c3"))); + System.out.println(String.format("输出值:%s", solution2.letterCasePermutation("a1b2c3"))); + System.out.println(String.format("输出值:%s", solution.letterCasePermutation("3z4"))); + System.out.println(String.format("输出值:%s", solution2.letterCasePermutation("3z4"))); + System.out.println(String.format("输出值:%s", solution.letterCasePermutation("12345"))); + System.out.println(String.format("输出值:%s", solution2.letterCasePermutation("12345"))); + } + + /** + * 这种方式更优雅一下。终止条件的地方添加list元素 + */ + static class Solution2 { + public List letterCasePermutation(String S) { + char[] chars = S.toCharArray(); + List list = new ArrayList<>(); + + getWord(list, chars, 0); + + return list; + } + + private void getWord(List list, char[] chars, int n) { + if (n == chars.length) { + list.add(new String(chars)); + return; + } + char c = chars[n]; + //递归下一个字符 + getWord(list, chars, n + 1); + + if (c >= 'a' && c <= 'z') { + chars[n] = Character.toUpperCase(c); + getWord(list, chars, n + 1); + } else if (c >= 'A' && c <= 'Z') { + chars[n] = Character.toLowerCase(c); + getWord(list, chars, n + 1); + } + } + } + + /** + * 自己实现的方式,感觉有点绕。 + */ + static class Solution { + public List letterCasePermutation(String S) { + char[] chars = S.toCharArray(); + List list = new ArrayList<>(); + + list.add(S); + getWord(list, chars, 0); + + return list; + } + + private void getWord(List list, char[] chars, int n) { + if (n == chars.length) { + return; + } + char c = chars[n]; + if (c >= 'A' && c <= 'z') { + int m = n + 1; + //如果是字母,先使用本身递归,然后大小写转换后再递归一次 + getWord(list, chars, m); + + if (c <= 'Z') { + chars[n] = Character.toLowerCase(c); + list.add(new String(chars)); + getWord(list, chars, m); + } else { + chars[n] = Character.toUpperCase(c); + list.add(new String(chars)); + getWord(list, chars, m); + } + } else { + //非字母,就直接递归下一个字符 + getWord(list, chars, n + 1); + } + } + } +} diff --git a/Week_04/id_14/NOTE.md b/Week_04/id_14/NOTE.md index c684e62f..89672306 100644 --- a/Week_04/id_14/NOTE.md +++ b/Week_04/id_14/NOTE.md @@ -1 +1,58 @@ -# 学习笔记 \ No newline at end of file +## 毕业总结 +## 这两天的感受 +比起前几周的紧张,这两天感觉轻松了(没有作业、考试通过:) ),决定给自己来个休息时间。然后下周开始进入无监督、兴奋点低的长期积累期。 + +## 牛B的梦想要有,如果还没出现,就先SB一样的坚持 +像很多学习、健身、跑步一样,今天立的flag,几天后可能就放弃了。集中刻意的1个月练习马上结束,不会再有固定的作业与总结,大家的交流也会变得更少,能让你豁然开朗的一刻也可能慢慢变少,自己可以找的借口倒是可能变多,这个阶段能否处理好,会更加重要。相信自己能把5年的坚持健身经验用到算法学习这上面!心中有个想象的样子,那就坚持的走下去。 + +## 提醒自己 +* 追求真正的学会 +* 很多事情能让你分心,专注 +* 坚持 + +## 感谢 +* 谢谢极客时间提供这样的机会! +* 谢谢相遇的伙伴们! +* 谢谢越来越坚定的自己! + +------ +## trie树。字典树 +> 本质:利用公共前缀,合并这些公共的前缀 + +* 根节点无内容,多叉树,适合前缀比较多多场景 +* 一般不使用在字符串的精确查找,适合前缀查找 +* 主要操作是构建树,查找前缀 +* 以前做搜索提示,将中文转成拼音,然后用户输入单个字母后就能提示 + + +## 动态规划 +> 看了好几遍,使用起来还是没有太多头绪,想想王争老师的解题思路,运用起来还是难 + +### 解题思路 +#### 状态转移表法:通过前序的多个点,找最优的值填表。从“小”往“大”推进 +> 回溯算法实现—定义状态—画递归树—找重复子问题—画状态转移表—根据递推关系填表—将填表过程翻译成代码 + +#### 状态转移方程:类似递归的解题思路。 +> 找最优子结构—写状态转移方程—将状态转移方程翻译成代码 + +比如这周第一题的动态规划,做了比较久,使用的是状态转移表法,做完再看看其他人的解法,原来可以那么简单的实现。:( + +## 回溯 +> 有一种情况可以辅助理解,就是树的分叉(走路的岔路口),每走到一个路口,先选一条,走到终点(或无路可走了),就回到上一个岔路口,走另一条路,走迷宫也是。然后在所有的可行解中,选择最优解。比如:最短路径/最低花费等等。 +> 在具体的现实中,可以利用剪枝,比如:通过多条路,都走到了同一个岔路口,那么第二次走到这里时,下面的路,就不用再走一遍了。这样可以提高效率。 + +**还是得多实现** + +## 本周做题心得 +前面对数据结构的学习,相对来说,几种常用的数据结构,是比较容易理解他的特性,在这些数据结构上练习使用的算法也相对比较固定,学习起来相对容易一些。本周,几种算法思想,虽然也有总结,也把课程过了好几次,但是使用起来,一点也不容易,就拿几道*简单*的题,trie树/动态规划这两题,都花了大量的时间,一度信心受挫:(。现在总结来说,也很容易理解,主要原因: +* 其实还是只知道理论,动手太少 +* 理论掌握也不牢固,遇到实际问题,抽象不到对应的算法上 +* 时间花得还是少,练习太少 +### 后续学习: +* 不忘初心,是为了真正学会 +* 持续学习,坚持再坚持,不找借口 +* 细节是魔鬼,大概看一下,很多觉得能理解,实际实现、总结,才知道理解如何 + + +# 学习笔记 +