diff --git a/Week_01/MyDeque.java b/Week_01/MyDeque.java new file mode 100644 index 00000000..0a2c68b6 --- /dev/null +++ b/Week_01/MyDeque.java @@ -0,0 +1,21 @@ +import java.util.Deque; +import java.util.LinkedList; + +public class MyDeque { + public static void main(String[] args) {//改写Deque + Deque deque = new LinkedList(); + deque.offerFirst("a"); + deque.offerFirst("b"); + deque.offerFirst("c"); + System.out.println(deque); + + String str = deque.peekFirst(); + System.out.println(str); + System.out.println(deque); + + while (deque.size() > 0) { + System.out.println(deque.pollFirst()); + } + System.out.println(deque); + } +} diff --git "a/Week_01/Queue\345\222\214PriorityQueue.md" "b/Week_01/Queue\345\222\214PriorityQueue.md" new file mode 100644 index 00000000..9e154a56 --- /dev/null +++ "b/Week_01/Queue\345\222\214PriorityQueue.md" @@ -0,0 +1,229 @@ +# Java中Queue和PriorityQueue的实现 + +## Queue的实现 + +Java中`Queue`是一个接口,定义了如下几个方法: + +- `add(E): boolean`:往queue中插入元素,成功返回true,失败抛出相应的异常: + - 空间不足:`IllegalStateException` + - 空指针:`NullPointerException` + - 类型转换:`ClassCastException` + - 添加的元素的某些属性使其不能被加入queue中:`IllegalArgumentException`(不太清楚具体是什么情形) +- `offer(E): boolean`: 往queue中插入元素,成功返回true,失败返回false。与`add(E)`相比,不会抛出`IllegalStateException`,遇到其他情况仍然会抛出异常。 +- `remove(): E`:从头部移除一个元素并返回。如果queue为空将抛出`NoSuchElementException` +- `poll(): E`:从头部移除一个元素并返回。如果queue为空将返回`null` +- `element(): E`:取得queue的头部元素(第一个元素),并且不会删除该元素。如果queue为空,抛出`NoSuchElementException` +- `peek(): E`:取得queue的头部元素(第一个元素),并且不会删除该元素。如果queue为空,返回`null` + +## PriorityQueue + +`PriorityQueue`继承了`AbstractQueue`抽象类,该抽象类实现了`Queue`接口。根据注释,Java中的`PriorityQueue`应该是根据最大/最小堆实现的(完全二叉树,结点`queue[i]`的左右孩子分别为`queue[2*i+1]和queue[2*i+2]`)。 + +### 关键字段 + +- `transient Object[] queue;`:数据存放 +- `private int size = 0`:当前大小 +- `private final Comparator comparator`:用于对堆中元素比较。如果为空,则使用数据元素自带的排序方式 +- `private static final int DEFAULT_INITIAL_CAPACITY = 11;`:初始默认的大小,`queue = new Object[initialCapacity]` + +### 核心方法 + +1. `add(E): boolean`:调用`offer(E): boolean` + +2. `offer(E): boolean`: + + ```java + public boolean offer(E e) { + if (e == null) + throw new NullPointerException(); + modCount++; // 修改过的次数增加 + int i = size; + if (i >= queue.length) + grow(i + 1); // 增加queue这个数组的大小 + size = i + 1; + if (i == 0) + queue[0] = e; + else + siftUp(i, e); // 从底部向上,将上面元素往下过滤。因此Java中PriorityQueue的插入应该不是O(1)的,而是O(logn) + return true; + } + ``` + +3. `peek(): E`: + + ```java + public E peek() { + // 如果当前数组不是空的,返回第一个元素,否则返回null + return (size == 0) ? null : (E) queue[0]; + } + ``` + +4. `remove(Object): boolean`: + + ```java + public boolean remove(Object o) { + int i = indexOf(o); + if (i == -1) + return false; + else { + removeAt(i); // 调用removeAt()方法,该方法中会对堆进行调整 + return true; + } + } + ``` + +5. `poll(): E` + + ```java + public E poll() { + if (size == 0) + return null; + int s = --size; + modCount++; + E result = (E) queue[0]; + E x = (E) queue[s]; + queue[s] = null; + if (s != 0) + siftDown(0, x); // 删除后调整堆,将堆最后一个元素放到堆顶,自顶向下过滤,将较小/较大的元素往上移动 + return result; + } + ``` + +6. `removeAt(int): E`: + + ```java + private E removeAt(int i) { + // assert i >= 0 && i < size; + modCount++; + int s = --size; + if (s == i) // removed last element + queue[i] = null; // 移除最后一个元素不需要对堆进行调整 + else { + E moved = (E) queue[s]; + queue[s] = null; + // 可以看成是对以queue[i]为根节点的堆进行删除操作,将该子堆的最后一个元素移动到堆顶向下过滤 + siftDown(i, moved); + if (queue[i] == moved) { + // 如果queue[i] == moved,说明被移动到堆顶的元素已经是该元素中最小/最大的,此时从该位置进行向上过滤,保持堆的性质 + siftUp(i, moved); // + if (queue[i] != moved) + return moved; + } + } + return null; + } + ``` + +#### `siftDown`和`siftUp` + +为方便起见,假设下面的操作都是基于最小堆的。 + +1. `siftUp()` + + ```java + private void siftUp(int k, E x) { + // 调用具体的siftUp方法 + if (comparator != null) + siftUpUsingComparator(k, x); + else + siftUpComparable(k, x); + } + + private void siftUpComparable(int k, E x) { + Comparable key = (Comparable) x; + while (k > 0) { + int parent = (k - 1) >>> 1; + Object e = queue[parent]; + if (key.compareTo((E) e) >= 0) // 将当前元素与其父亲进行比较,若结果<0,则将该元素往上移动,否则该元素已经找到了合适的位置,堆调整完毕 + break; + queue[k] = e; // 父亲向下移动 + k = parent; + } + queue[k] = key; + } + + @SuppressWarnings("unchecked") + private void siftUpUsingComparator(int k, E x) { + // 与上面的方法是一样的逻辑,只是比较时使用的方法有差异 + while (k > 0) { + int parent = (k - 1) >>> 1; + Object e = queue[parent]; + if (comparator.compare(x, (E) e) >= 0) + break; + queue[k] = e; + k = parent; + } + queue[k] = x; + } + ``` + +2. `siftDown()` + + ```java + private void siftDown(int k, E x) { + // siftDown入口 + if (comparator != null) + siftDownUsingComparator(k, x); + else + siftDownComparable(k, x); + } + + @SuppressWarnings("unchecked") + private void siftDownComparable(int k, E x) { + // 为了保证最小堆的性质,可以认为将当前堆中最后一个元素移动到堆顶,每次将当前结点及其左右孩子中最小的元素放在当前位置,从而保证最小堆的性质得到满足。 + Comparable key = (Comparable)x; + // 层序遍历中最后一个非叶子结点,如果遍历过程中索引超过了这个值,说明已经不需要再继续循环进行调整了 + int half = size >>> 1; // loop while a non-leaf + while (k < half) { + int child = (k << 1) + 1; // assume left child is least + Object c = queue[child]; // 左孩子 + int right = child + 1; // 右孩子 + if (right < size && + ((Comparable) c).compareTo((E) queue[right]) > 0) + // 若右孩子存在,并且左孩子大于右孩子,说明右孩子是三者中最小的,将右孩子往上移动 + c = queue[child = right]; + // 否则右孩子不存在或左孩子小于右孩子。此时需要比较左孩子和当前结点 + if (key.compareTo((E) c) <= 0) + // 若当前结点的值已经小于左孩子,那么当前结点已经到达了合适的位置,堆重新调整到满足最小堆的状态,退出循环 + break; + // 否则左孩子最小,左孩子往上移动 + queue[k] = c; + k = child; + } + queue[k] = key; + } + + @SuppressWarnings("unchecked") + private void siftDownUsingComparator(int k, E x) { + int half = size >>> 1; + while (k < half) { + int child = (k << 1) + 1; + Object c = queue[child]; + int right = child + 1; + if (right < size && + comparator.compare((E) c, (E) queue[right]) > 0) + c = queue[child = right]; + if (comparator.compare(x, (E) c) <= 0) + break; + queue[k] = c; + k = child; + } + queue[k] = x; + } + ``` + + #### `heapify()`:建堆过程,时间复杂度O(n) + + ```java + private void heapify() { + // 时间复杂度O(n),具体推导忘记了…… + // 从底部开始,调整每一棵子树,使其成为一个最小堆 + // 对于最底部的子树而言,它高度最多为2,对其进行siftDown,可以保证该子树为最小堆 + // 每次循环中,对于每个子堆的堆顶而言,它的左右孩子经过之前的调整一定是最小堆,这时再siftDown,形成一个更大的最小堆。(每次当前元素移动时,不满足堆的性质一定只有以当前元素所在位置为根的子树,当前元素不能再向下移动时,调整完成) + for (int i = (size >>> 1) - 1; i >= 0; i--) + siftDown(i, (E) queue[i]); + } + ``` + + + diff --git a/Week_01/README.md b/Week_01/README.md index 50de3041..a7e86563 100644 --- a/Week_01/README.md +++ b/Week_01/README.md @@ -1 +1,102 @@ -学习笔记 \ No newline at end of file +学习笔记 学习使我快乐 +本周总结9/27 + +1.五遍刷题法: + 第一遍:5分钟:读题加思考;直接看解法;背诵、默写好的解法; + 第二遍:马上自己写;多种解法比较、优化; + 第三遍:过了一天后,重复做题;不同解法熟练程度; + 第四遍:一周后:反复练习; + 第五遍:面试前一周恢复性训练; +2.学会使用谷歌搜索java源代码、文档 +3.学会升维的思想空间换时间 +4.双指针法,把问题拆解成最近的子问题 + + + +[TOC] + +#数据结构 + +• 一维: + • 基础:数组 array (string), 链表 linked list + • 高级:栈 stack, 队列 queue, 双端队列 deque, 集合 set, 映射 map (hash or map), etc + +• 二维: + • 基础:树 tree, 图 graph + • 高级:二叉搜索树 binary search tree (red-black tree, AVL), 堆 heap, 并查集 disjoint set, 字典树 Trie, etc + +• 特殊: + • 位运算 Bitwise, 布隆过滤器 BloomFilter • LRU Cache + +#算法 + +- If-else, switch —> branch +- for, while loop —> Iteration +- 递归 Recursion (Divide & Conquer, Backtrace) +- 搜索 Search: 深度优先搜索 Depth first search, 广度优先搜索 Breadth first search, A*, etc +- 动态规划 Dynamic Programming +- 二分查找 Binary Search +- 贪心 Greedy +- 数学 Math , 几何 Geometry + +#Big O notation + +* O(1): Constant Complexity 常数复杂度 +* O(log n): Logarithmic Complexity 对数复杂度 +* O(n): Linear Complexity 线性时间复杂度 +* O(n^2): N square Complexity 平方 +* O(n^3): N cubic Complexity 立方 +* O(2^n): Exponential Growth 指数 +* O(n!): Factorial 阶乘 + +# 空间复杂度 + +* 数组的长度 +* 递归的深度 + +# 数组 + +* 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。 +* 下标查询的时间复杂度为O(1), 增加和删除的时间复杂度为O(n) + +# 链表 + +* 通过“指针”将一组零散的内存块串联起来使用 +* 查询时间复杂度O(n),增加和删除的时间复杂度为O(1) +* 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。 + +~~~ +单链表插入:插入x节点,当前指针指向p +注意顺序,防止内存泄漏 +1. x->next = p->next; // 将x的结点的next指针指向b结点; +2. p->next = x; // 将p的next指针指向x结点; +当向一个空链表中插入第一个结点,需进行下面这样的特殊处理,其中 head 表示链表的头结点。所以,对于单链表的插入操作,第一个结点和其他结点的插入逻辑是不一样的。 +if (head == null) { head = new_node;} +~~~ + +~~~ +单链表结点删除操作,如果要删除结点 p 的后继结点: +p->next = p->next->next; +如果要删除链表中的最后一个结点,需要特殊处理。 +if (head->next == null) { head = null;} +~~~ + +# 跳表 + +* 给链表升维,在链表上加入多级索引的结构 +* 第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k级索引结点的个数就是 n/(2k) +* 跳表中查询任意数据的时间复杂度就是 O(logn),插入和删除也是O(logn) + +# 栈 + +* 当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,就应该首选“栈”这种数据结构。 +* 栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。 +* 栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。 +* 时间复杂度和空间复杂度都为O(1) + +# 队列 + +* 先进者先出,这就是典型的“队列” +* 最基本的操作也是两个:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。 +* 跟栈一样,队列可以用数组来实现,也可以用链表来实现。用数组实现的栈叫作顺序栈,用链表实现的栈叫作链式栈。同样,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。 + diff --git "a/Week_01/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" "b/Week_01/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" new file mode 100644 index 00000000..b67bbf21 --- /dev/null +++ "b/Week_01/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" @@ -0,0 +1,46 @@ +// 已知每种输入只会对应一个答案 + +//方法一 O(n^2) 暴力法 nums[i] + nums[j] == target +//方法二 两遍哈希表 +//方法三 一遍哈希表 + + +class Solution { + // public int[] twoSum2(int[] nums, int target) {//两遍哈希法 + // //将所有值 以及下标存入哈希表中 + // //再次遍历,判断是否存在 key 值为 target-num[i] 且 value值不等于本身的i的元素 + // //如果有相同key值的元素,后面会取代前面的,多个重复元素时,最后结果会输出最前面与最后面的元素下标 + // Map res = new HashMap<>(); + // for (int i = 0; i < nums.length; i++) res.put(nums[i], i); + // for (int i = 0; i < nums.length; i++) { + // if (res.containsKey(target - nums[i]) && res.get(target - nums[i]) != i) + // return new int[]{i, res.get(target-nums[i])}; + // } + // return new int[0]; + // } + + public int[] twoSum(int[] nums, int target) {//一遍哈希法 + //遍历数组 每次哈希查询是否存在这样的 key 值= target - num[i], 有就说明找到目标值了 + //返回 new int {hash.get(target-num[i]),i}; + //否则 将num[i]的值 以及下标 i 存入哈希表中 + Map res = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (res.containsKey(target - nums[i])) + return new int[] {res.get(target-nums[i]), i}; + res.put(nums[i], i); + } + return new int[0]; + } + + // public int[] twoSum1(int[] nums, int target) {//暴力法 + // for (int i = 0; i < nums.length-1; i++) { + // for (int j = i + 1; j < nums.length; j++) { + // if (nums[i] + nums[j] == target) { + // return new int[]{i,j}; + // } + // } + // } + // return null; + // } + +} \ No newline at end of file diff --git "a/Week_01/\345\212\240\344\270\200PlusOne.java" "b/Week_01/\345\212\240\344\270\200PlusOne.java" new file mode 100644 index 00000000..b0e80129 --- /dev/null +++ "b/Week_01/\345\212\240\344\270\200PlusOne.java" @@ -0,0 +1,25 @@ +//普通情况直接digits[digits.length-1]++即可 +//当为 19,29,39,等等时上一位也需要加一 +//当为 9 , 99, 999 等等时需要进位,以前的数组将不够用 + +//模拟操作,从最后一位加起,直至上一位没有9 + +class Solution { + public int[] plusOne(int[] digits) { + int i = digits.length-1; + while (i >= 0) { + if(digits[i] == 9) + digits[i] = 0; + else { + digits[i]++; + break;//优化为 return digits; + } + i--; + } + if (i < 0) {//优化后 可以忽略此if判断 + digits = new int[digits.length+1]; + digits[0] = 1; + } + return digits; + } +} \ No newline at end of file diff --git "a/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204MergeSortedArray.java" "b/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204MergeSortedArray.java" new file mode 100644 index 00000000..b42abbb9 --- /dev/null +++ "b/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204MergeSortedArray.java" @@ -0,0 +1,50 @@ +class Solution { + public void merge(int[] nums1, int m, int[] nums2, int n) {//方法三 尾部插法,双指针 + //从nums1尾部开始,i指针指向m-1,j指针指向n-1,从尾部插入,这样不用担心覆盖到nums1的元素 + int i = m - 1, j = n - 1; + for( ; i >= 0 && j >= 0; ) { + if (nums1[i] <= nums2[j]) nums1[i + j + 1] = nums2[j--]; + else nums1[i + j + 1] = nums1[i--]; + } + while (j >= 0) { + nums1[i + j + 1] = nums2[j--]; + } + } + + public void merge2(int[] nums1, int m, int[] nums2, int n) {//方法二 额外数组O(m+n) O(m+n) + { + // int[] nums = new int[m+n]; + // int i = 0, j = 0; + // while (i < m && j < n) { + // if (nums1[i] < nums2[j]) nums[i+j] = nums1[i++]; + // else nums[i+j] = nums2[j++]; + // } + // while (i < m) { + // nums[i+j] = nums1[i++]; + // } + // while (j < n) { + // nums[i+j] = nums2[j++]; + // } + // for (int k = 0; k < m+n; k++) + // nums1[k] = nums[k]; + } + //优化版 O(m+n) O(m) + int[] nums1_copy = new int[m]; + System.arraycopy(nums1, 0, nums1_copy, 0, m);//nums1从0开始拷贝到nums1_copy从0开始长度为m + int i = 0, j = 0; + while (i < m && j < n) { + if (nums1_copy[i] < nums2[j]) nums1[i+j] = nums1_copy[i++]; + else nums1[i+j] = nums2[j++]; + } + if (i < m) System.arraycopy(nums1_copy, i, nums1, i + j, m + n - i - j); + if (j < n) System.arraycopy(nums2, j, nums1, i + j, m + n - i -j); + } + + public void merge1(int[] nums1, int m, int[] nums2, int n) {//方法一 直接nums2插入到num1尾部,再sort + //O(mlogm) O(1) + for (int i = 0; i < n; i++) + nums1[m+i] = nums2[i]; + Arrays.sort(nums1); + } + +} \ No newline at end of file diff --git "a/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250MergeTwoSortedLists.java" "b/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250MergeTwoSortedLists.java" new file mode 100644 index 00000000..5372c8ec --- /dev/null +++ "b/Week_01/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250MergeTwoSortedLists.java" @@ -0,0 +1,51 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ + + +class Solution { + public ListNode mergeTwoLists(ListNode l1, ListNode l2) {//方法二 递归解法 O(m+n) l1l2元素个数 + //通过递归找到最小的值,然后依次返回作为上一个节点的next + if (l1 == null) + return l2; + else if (l2 == null) + return l1; + else if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } + } + public ListNode mergeTwoLists1(ListNode l1, ListNode l2) {//方法一 一般解法 + //方法一 构建一个新的节点,一边遍历一边比较,小的插入节点 + //如果有某一条遍历完直接插入尾部不在比较 + ListNode head = new ListNode(); + ListNode temp = head; + while (l1 != null && l2 != null) { + if(l1.val < l2.val) { + temp.next = l1; + l1 = l1.next; + temp = temp.next; + } + else { + temp.next = l2; + l2 = l2.next; + temp = temp.next; + } + } + if(l1 == null) + temp.next = l2; + if(l2 == null) + temp.next = l1; + return head.next; + } +} \ No newline at end of file diff --git "a/Week_01/\346\216\245\351\233\250\346\260\264TrappingRainWater.java" "b/Week_01/\346\216\245\351\233\250\346\260\264TrappingRainWater.java" new file mode 100644 index 00000000..9d33bfb9 --- /dev/null +++ "b/Week_01/\346\216\245\351\233\250\346\260\264TrappingRainWater.java" @@ -0,0 +1,154 @@ +//方法一 按行求 +//方法二 按列求 +//方法三 动态规划 +//方法四 双指针 +//方法五 栈 +//方法六 固定最高的柱子,左右双指针夹逼法 +//方法七 数学解法 +class Solution { + public int trap(int[] height) { //方法七 数学解法 + //从左到右遍历出最高左边高度 + //以及从右遍历出最高右边高度 + //减去柱子的高度 + int max_left = -1, max_right = -1; + int sum = 0, sum1 = 0, sum2 = 0; + int len = height.length; + // int sum3 = 0; + + for (int i = 0; i < len; i++) { + if(height[i] > max_left) + max_left = height[i]; + if(height[len-i-1] > max_right) + max_right = height[len - i - 1]; + sum1 += max_left; + sum2 += max_right; + sum -= height[i]; + } + sum += sum1 + sum2 - max_left * len; + return sum; + } + public int trap6(int[] height) { //方法六 固定最高柱子 + int max_height = -1; + int max_index = 0; + int sum = 0, max_left = 0, max_right = 0; + for (int i = 0; i < height.length; i++) { + if (height[i] > max_height) { + max_height = height[i]; + max_index = i; + } + } + for (int i = 0 ; i < max_index; i++) { + if (height[i] > max_left) + max_left = height[i]; + else + sum += max_left - height[i]; + } + for (int i = height.length-1; i > max_index; i--) { + if (height[i] > max_right) + max_right = height[i]; + else + sum += max_right - height[i]; + } + return sum; + } + public int trap5(int[] height) { //方法五 栈 + //创建一个栈,只要有比当前栈顶小或相等的就入栈,当遇见比栈顶大的时就说明可以确定中间的水量了 + //栈不为空且大于栈顶元素才可以开始出栈 + //如果出栈后栈空了就不用继续计算雨量了 + Stack stack = new Stack<>(); + int sum = 0; + for (int i = 0; i < height.length; i++) { + while (!stack.empty() && height[i] > height[stack.peek()]) { + int h = stack.pop(); + if(stack.empty()) + break; + int distance = i - stack.peek() - 1; + int min = Math.min(height[i], height[stack.peek()]); + sum += distance * (min - height[h]); + } + stack.push(i); + } + return sum; + } + public int trap4(int[] height) { //方法四 双指针 O(n) O(1) 通过判断max_left与max_right来改变遍历顺序 + //与动态规划类似,不过进行优化,这样在遍历的时候更新max_left与max_right + //省去了O(n)的空间 + int sum = 0; + int max_left = -1, max_right = -1; + int left = 1, right = height.length - 2; + while (left <= right) { + if(height[left-1] < height[right+1]) { + max_left = Math.max(max_left, height[left-1]); + if (max_left > height[left]) + sum += max_left - height[left]; + left++; + } else { + max_right = Math.max(max_right, height[right+1]); + if (max_right > height[right]) + sum += max_right - height[right]; + right--; + } + } + return sum; + } + public int trap3(int[] height) { //方法三 动态规划 O(n) O(n) + //我们发现按列求每次遍历左右最大值有些耗费时间,可以利用一个数组存储当前的最大值,避免重复操作 + int[] max_left = new int[height.length]; + int[] max_right = new int[height.length]; + int sum = 0; + for (int i = 1; i < height.length; i++) + max_left[i] = Math.max(max_left[i-1], height[i-1]); + for (int i = height.length - 2; i >= 0; i--) + max_right[i] = Math.max(max_right[i+1], height[i+1]); + for (int i = 1; i < height.length; i++) { + int min = max_left[i] < max_right[i] ? max_left[i] : max_right[i]; + if(min > height[i]) + sum += min - height[i]; + } + return sum; + } + + public int trap2(int[] height) { //方法二 按列求 O(n^2) + //从第1列开始直到length-2列结束,因为第0列和length-1列不可能存有雨水 + //每次遍历找出左边最高以及右边最高的的 + //取min 雨水量 += min - height[i] + int sum = 0; + for (int i = 1; i < height.length - 1; i++) { + int left_max = -1; + for (int k = i - 1; k >= 0; k--) + left_max = Math.max(left_max, height[k]); + int right_max = -1; + for (int k = i + 1; k < height.length; k++) + right_max = Math.max(right_max, height[k]); + int min = left_max < right_max ? left_max : right_max; + if(min > height[i]) + sum += min - height[i]; + } + return sum; + } + + public int trap1(int[] height) { //方法一 按行求 O(m*n) m为最大高度 + //找出最大高度,根据最大高度确定遍历层数 + //从第一层水开始 + //如果有start && height[j] < i ,temp++ + //如果有height[j] >= i ,开始计数 start = true, sum += temp, temp = 0 + int max_height = -1; + int sum = 0; + for (int i = 0; i < height.length; i++) + max_height = Math.max(max_height, height[i]); + for (int i = 1; i <= max_height; i++) { //i代表雨的层数 + boolean start = false; + int temp = 0; + for (int j = 0; j < height.length; j++) { + if (start && height[j] < i) + temp++; + if (height[j] >= i) { + start = true; + sum += temp; + temp = 0; + } + } + } + return sum; + } +} \ No newline at end of file diff --git "a/Week_01/\346\227\213\350\275\254\346\225\260\347\273\204RotateArray.java" "b/Week_01/\346\227\213\350\275\254\346\225\260\347\273\204RotateArray.java" new file mode 100644 index 00000000..03fa78e2 --- /dev/null +++ "b/Week_01/\346\227\213\350\275\254\346\225\260\347\273\204RotateArray.java" @@ -0,0 +1,64 @@ +//方法二 创建一个新的数组,将移动后的位置拷贝到新的数组(不符合题意,空间复杂度超标) + +class Solution { + public void rotate(int[] nums, int k) {//方法四 模拟换座位,每次移动一个元素,一直换到与开始的位置重复,移动下一个元素 + //首先k %= len; + //首先从0号元素开始,它将移到k下标的位置,而k号元素将移到2k%len下标位置 + //直至等于0下标或者已经移动n个元素了 + //若等于0下标,则从1号元素开始,依次类推 + int len = nums.length; + k %= len; + int count = 0;//计数,移动了多少个元素 + for (int i = 0; count < len; i++) { + { //逻辑一 是每次先找到下一位置,直到要换的位置已经是之前找到过的位置了 + // int next = (i + k) % len; + // int outseat = nums[i]; + // do{ + // int temp = nums[next]; + // nums[next] = outseat; + // outseat = temp; + // next = (next + k) % len; + // count++; + // }while(next != (i + k) % len); + } + //逻辑二 找到当前位置,与下一位置交换,直到当前位置已经被交换过 + int cur = i; + int outseat = nums[i]; + do { + int next = (cur + k) % len; + int temp = nums[next]; + nums[next] = outseat; + outseat = temp; + cur = next; + count ++; + } while(cur != i); + } + } + + public void rotate3(int[] nums, int k) {//方法三 三次翻转,逆转所有元素,逆转前k个, 再逆转后n-k个 + int len = nums.length; + k %= len; + reverse2(nums, 0, len-1); + reverse2(nums, 0, k-1); + reverse2(nums, k, len-1); + } + public void reverse2(int[]nums, int start, int end) { + while (start < end) { + int temp = nums[start]; + nums[start] = nums[end]; + nums[end] = temp; + start++; + end--; + } + } + + public void rotate1(int[] nums, int k) {//方法一 暴力法 一次移动一位,移动k次 O(KN) + int len = nums.length; + for (int i = 0; i < k; i++) { + int temp = nums[len-1]; + for(int j = len-1; j > 0; j--) + nums[j] = nums[j-1]; + nums[0] = temp; + } + } +} \ No newline at end of file diff --git "a/Week_01/\346\234\200\345\260\217\346\240\210MinStack.java" "b/Week_01/\346\234\200\345\260\217\346\240\210MinStack.java" new file mode 100644 index 00000000..5c4608d7 --- /dev/null +++ "b/Week_01/\346\234\200\345\260\217\346\240\210MinStack.java" @@ -0,0 +1,239 @@ +//方法一 利用数组模拟栈实现 +//方法二 利用栈与辅助栈实现 +//方法三 利用链表模拟栈实现 + +// class MinStack { +// //方法一 +// int[] min;//存储每次最小值 +// int[] stack; +// int head; +// /** initialize your data structure here. */ +// public MinStack() { +// stack = new int[10000]; +// min = new int[10000]; +// head = 0; +// }//方法一 用已存在的栈类 实现 +//方法二 用数组实现(不推荐,不能动态处理问题) +//方法三 用链表实现 +class MinStack { + //用链表 + class Node { + int val; + int min; + Node next; + public Node(int val, int min, Node next) { + this.val = val; + this.min = min; + this.next = next; + } + } + Node head; + public MinStack() { + } + + public void push(int x) { + if (head != null) + head = new Node(x, Math.min(x, head.min), head); + else + head = new Node(x, x, null); + } + + public void pop() { + head = head.next; + } + + public int top() { + return head.val; + } + + public int getMin() { + return head.min; + } +} + +// class MinStack { +// //用java内置的stack实现 +// //且辅助栈与数据栈不同步(可以节约一些空间) +// Stack data; +// Stack helper; +// public MinStack() { +// data = new Stack(); +// helper = new Stack(); +// } + +// public void push(int x) { +// data.push(x); +// if (helper.empty() || x <= helper.peek()) +// helper.push(x); +// } + +// public void pop() { +// // int x = data.pop(); +// // if (x == helper.peek()) +// // helper.pop(); +// Integer x = data.pop(); +// if (x.equals(helper.peek())) +// helper.pop(); +// } + +// public int top() { +// return data.peek(); +// } + +// public int getMin() { +// return helper.peek(); +// } +// } + + +// class MinStack { +// //用已有的栈实现 且辅助栈与数据栈同步 +// Stack stack; +// Stack minStack; +// /** initialize your data structure here. */ +// public MinStack() { +// stack = new Stack(); +// minStack = new Stack(); +// } + +// public void push(int x) { +// stack.push(x); +// if (minStack.empty() || x <= minStack.peek()) { +// minStack.push(x); +// } else { +// minStack.push(minStack.peek()); +// } +// } + +// public void pop() { +// stack.pop(); +// minStack.pop(); +// } + +// public int top() { +// return stack.peek(); +// } + +// public int getMin() { +// return minStack.peek(); +// } +// } +// class MinStack { + +// /** initialize your data structure here. */ +// public MinStack() { + +// } + +// public void push(int x) { + +// } + +// public void pop() { + +// } + +// public int top() { + +// } + +// public int getMin() { + +// } +// } + +/** + * Your MinStack object will be instantiated and called as such: + * MinStack obj = new MinStack(); + * obj.push(x); + * obj.pop(); + * int param_3 = obj.top(); + * int param_4 = obj.getMin(); + */ + +// public void push(int x) { +// if (head < stack.length) { +// stack[head] = x; +// if(head == 0 || min[head-1] > x) +// min[head] = x; +// else +// min[head] = min[head-1]; +// head++; +// } +// // System.out.print(head); +// } + +// public void pop() { +// if(head > 0) +// head--; +// } + +// public int top() { +// if(head > 0) +// return stack[head-1]; +// else +// return stack[0]; +// } + +// public int getMin() { +// // int min = stack[0]; +// // for(int i = 0 ; i < head; i++) { +// // if(min > stack[i]) +// // min = stack[i]; +// // } +// if(head > 0) +// return min[head-1]; +// else +// return min[head]; +// } + + +// } + +class MinStack { + private Node head; + + public void push(int x) { + if(head == null) + head = new Node(x, x); + else + head = new Node(x, Math.min(x, head.min), head); + } + + public void pop() { + head = head.next; + } + + public int top() { + return head.val; + } + + public int getMin() { + return head.min; + } + + private class Node { + int val; + int min; + Node next; + + private Node(int val, int min) { + this(val, min, null); + } + + private Node(int val, int min, Node next) { + this.val = val; + this.min = min; + this.next = next; + } + } +} + +/** + * Your MinStack object will be instantiated and called as such: + * MinStack obj = new MinStack(); + * obj.push(x); + * obj.pop(); + * int param_3 = obj.top(); + * int param_4 = obj.getMin(); + */ \ No newline at end of file diff --git "a/Week_01/\347\247\273\345\212\250\351\233\266MoveZeroes.java" "b/Week_01/\347\247\273\345\212\250\351\233\266MoveZeroes.java" new file mode 100644 index 00000000..bcb2abc8 --- /dev/null +++ "b/Week_01/\347\247\273\345\212\250\351\233\266MoveZeroes.java" @@ -0,0 +1,29 @@ +//方法一 暴力法 挨着挨着移动 +//方法二 交换法 双指针,cur指向当前非零位置,i遍历,如果当前有非零元素那么就放到cur位置,当前元素变成0,cur++,i继续++ +//方法三 两次遍历法 第一次把所有非零元素移到规定位置,第二次遍历把剩下的元素置零 + +class Solution { + // public void moveZeroes(int[] nums) { + // int cur = 0; + // for (int i = 0; i < nums.length; i++) { + // if(nums[i] != 0) { + // nums[cur] = nums[i]; + // if(i != cur) {//简化交换,同时避免一开始ij相等时被置为0 + // nums[i] = 0; + // } + // cur++; + // } + // } + // } + public void moveZeroes(int[] nums) {//方法三 + int cur = 0; + for (int i = 0; i < nums.length; i++) { + if(nums[i] != 0) { + nums[cur++] = nums[i]; + } + } + while(cur < nums.length) { + nums[cur++] = 0; + } + } +} \ No newline at end of file diff --git "a/Week_01/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227DesignCircularDeque.java" "b/Week_01/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227DesignCircularDeque.java" new file mode 100644 index 00000000..629ec8c6 --- /dev/null +++ "b/Week_01/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227DesignCircularDeque.java" @@ -0,0 +1,90 @@ +//方法一 利用数组实现 +//方法二 利用循环双向链表实现 + +class MyCircularDeque { + + int[] cirdeque; + int head, tail, size; + /** Initialize your data structure here. Set the size of the deque to be k. */ + public MyCircularDeque(int k) { + size = k+1; + cirdeque = new int[size]; + head = 0; + tail = 0; + } + + /** Adds an item at the front of Deque. Return true if the operation is successful. */ + public boolean insertFront(int value) { + if (isFull()) { + return false; + } else { + head = (head - 1 + size) % size; + cirdeque[head] = value; + return true; + } + } + + /** Adds an item at the rear of Deque. Return true if the operation is successful. */ + public boolean insertLast(int value) { + if (isFull()) { + return false; + } else { + cirdeque[tail] = value; + tail = (tail + 1) % size; + return true; + } + } + + /** Deletes an item from the front of Deque. Return true if the operation is successful. */ + public boolean deleteFront() { + if (isEmpty()) { + return false; + } else { + head = (head + 1) % size; + return true; + } + } + + /** Deletes an item from the rear of Deque. Return true if the operation is successful. */ + public boolean deleteLast() { + if (isEmpty()) { + return false; + } else { + tail = (tail - 1 + size) % size; + return true; + } + } + + /** Get the front item from the deque. */ + public int getFront() { + return isEmpty() ? -1 : cirdeque[head]; + } + + /** Get the last item from the deque. */ + public int getRear() { + return isEmpty() ? -1 : cirdeque[(tail - 1 + size) % size]; + } + + /** Checks whether the circular deque is empty or not. */ + public boolean isEmpty() { + return head == tail; + } + + /** Checks whether the circular deque is full or not. */ + public boolean isFull() { + return head == (tail + 1) % size; + } +} + +/** + * Your MyCircularDeque object will be instantiated and called as such: + * MyCircularDeque obj = new MyCircularDeque(k); + * boolean param_1 = obj.insertFront(value); + * boolean param_2 = obj.insertLast(value); + * boolean param_3 = obj.deleteFront(); + * boolean param_4 = obj.deleteLast(); + * int param_5 = obj.getFront(); + * int param_6 = obj.getRear(); + * boolean param_7 = obj.isEmpty(); + * boolean param_8 = obj.isFull(); + */ \ No newline at end of file diff --git "a/Week_02/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206N-aryTreePreorderTraversal.java" "b/Week_02/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206N-aryTreePreorderTraversal.java" new file mode 100644 index 00000000..b35cfebc --- /dev/null +++ "b/Week_02/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206N-aryTreePreorderTraversal.java" @@ -0,0 +1,76 @@ +/* +// Definition for a Node. +class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } +}; +*/ + +class Solution { + +LinkedList temp=new LinkedList<>(); + public List preorder(Node root) {//方法二 迭代法 + List res = new ArrayList<>(); + if(root == null) return res; + Deque stk = new ArrayDeque<>(); + stk.push(root); + while(!stk.isEmpty()) { + Node node = stk.pop(); + res.add(node.val); + for(int i = node.children.size() - 1 ; i >= 0 ; i--) + stk.push(node.children.get(i)); + } + return res; + } + + public List preorder_2_1(Node root) {//方法二 迭代法 + List ans = new ArrayList(); + if (root == null) return res; + Deque deque = new ArrayDeque(); + deque.offerLast(root); + while (!deque.isEmpty()) { + Node node = deque.pollLast(); + res.add(node.val); + Collections.reverse(node.children); + for (Node child : node.children) + deque.offerLast(child); + } + return res; + } + + List res = new ArrayList(); + public List preorder_1_2(Node root) {//方法一 递归(不使用辅助函数) + // List res = new ArrayList(); + if(root == null) return res; + res.add(root.val); + for (Node node : root.children) { + // res.addAll(preorder(node)); + preorder(node); + } + return res; + } + + public List preorder_1_1(Node root) {//方法一 递归(使用辅助函数) + List res = new ArrayList(); + helper(root, res); + return res; + } + public void helper(Node root, List res) { + if (root == null) return; + res.add(root.val); + for (Node node : root.children) { + helper(node, res); + } + } +} \ No newline at end of file diff --git "a/Week_02/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206N-ary TreeLevelOrderTraversal.java" "b/Week_02/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206N-ary TreeLevelOrderTraversal.java" new file mode 100644 index 00000000..07ac1242 --- /dev/null +++ "b/Week_02/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206N-ary TreeLevelOrderTraversal.java" @@ -0,0 +1,66 @@ +/* +// Definition for a Node. +class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } +}; +*/ +//广度优先搜索。我们使用队列来进行广度优先搜索,队列具有先进先出的特性。 +//栈应用于深度优先搜索。 +class Solution { + + public List> levelOrder_2(Node root) {//方法二 队列 O(n) O(n) + List> ans = new ArrayList<>(); + if(root == null) return ans; + Queue queue = new ArrayDeque<>(); + queue.offer(root); + while (!queue.isEmpty()) { + List level = new ArrayList<>(); + int size = queue.size();//通过size的大小来确定每次处理一层 + for (int i = 0; i < size; i++) { + Node node = queue.poll(); + level.add(node.val); + queue.addAll(node.children); + } + ans.add(level); + } + return ans; + } + + + + public List> levelOrder(Node root) {//方法一 递归 另外一种版本(避免全局变量) + return dfs(root, new ArrayList<>(), 0); + } + private List> dfs(Node node, List> res, int level) { + if (node == null) return res; + if (res.size() == level) res.add(new ArrayList<>()); + res.get(level).add(node.val); + for (Node child : node.children) dfs(child, res, level + 1); + return res; + } + + List> res = new ArrayList<>(); + public List> levelOrder_1(Node root) {//方法一 递归法 时间O(n) 空间O(logn)最坏时O(n) + if(root != null) helper(root, 0); + return res; + } + public void helper(Node root, int level) {//递归的辅助函数 + if (res.size() == level) + res.add(new ArrayList<>());//初始时,res为空,每添加一个list,size大小加一,正好代表第几层 + res.get(level).add(root.val); + for (Node node : root.children) + helper(node, level + 1); + } +} \ No newline at end of file diff --git a/Week_02/README.md b/Week_02/README.md index 50de3041..5fdae7c9 100644 --- a/Week_02/README.md +++ b/Week_02/README.md @@ -1 +1,52 @@ -学习笔记 \ No newline at end of file +2020/10/11 学习笔记 + +哈希的概念 + + *hash算法的概念 Hash: 一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 + +树的一些相关概念 + + *链表是特殊的树,树是特殊的图(无环) + *前序:根-左-右 + *中序:左-根-右 + *后序:左-右-根 + *二叉搜索树-中序遍历:升序排列 +树的面试题解法一般都是递归,为什么? + + 1. 该问题可以被分解成若干个重复的子问题; + 2. 该问题与它分解出的子问题可以使用相同的算法来解决; + 3. 有明确的终止条件 树这种数据结构的特点和上述三个特点高度一致,一棵树的每个非叶子节点的子节点也都是一棵树,都是树自然可以使用相同的算法来处理,因为没有环所以天然具有终止条件。 + 4. 另外一方面,树本身是一种非线性的数据结构,循环遍历不易。当然循环遍历也是可以做,树是一种特殊的图,我们完全可以使用图的广度优先遍历算法一层一层的循环遍历整棵树。 + *综上,我们一般还是选择递归的方式来解决树的问题。 +-------------------------------------------------------------------------------------------------------------------------------------------- +学习到的一些新的函数方法或者关键字 + + instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:boolean result = obj instanceof Class + + Arrays.sort(char[] ch);//将ch字符数组按照字母顺序排序 + + Arrays.equals(char[] ch1, char[] ch2);//比较两个字符数组是否相等 + + Integer.MAX_VALUE代表int的最大值,当常数使用 + + Integer.MIN_VALUE代表int的最小值 + + Integer类型对象 比较值用"equals", 比较 引用 用"==" + + System.arraycopy(nums1, 0, nums_copy, 0, m);//nums1从0开始拷贝到nums_copy从0开始长度为m + + 构建最小堆 + PriorityQueue pq = new PriorityQueue();//默认最小堆 + + 构建最大堆 + PriorityQueue pq = new PriorityQueue(new Comparator() { + @Override + public int compare(Integer a, Integer b) { + return b - a; + } + }); + + HashMap getOrDeFault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。 + 语法为 hashmap.getOrDeFault(Object key, V defaultValue); + + diff --git "a/Week_02/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" "b/Week_02/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" new file mode 100644 index 00000000..b67bbf21 --- /dev/null +++ "b/Week_02/\344\270\244\346\225\260\344\271\213\345\222\214TwoSum.java" @@ -0,0 +1,46 @@ +// 已知每种输入只会对应一个答案 + +//方法一 O(n^2) 暴力法 nums[i] + nums[j] == target +//方法二 两遍哈希表 +//方法三 一遍哈希表 + + +class Solution { + // public int[] twoSum2(int[] nums, int target) {//两遍哈希法 + // //将所有值 以及下标存入哈希表中 + // //再次遍历,判断是否存在 key 值为 target-num[i] 且 value值不等于本身的i的元素 + // //如果有相同key值的元素,后面会取代前面的,多个重复元素时,最后结果会输出最前面与最后面的元素下标 + // Map res = new HashMap<>(); + // for (int i = 0; i < nums.length; i++) res.put(nums[i], i); + // for (int i = 0; i < nums.length; i++) { + // if (res.containsKey(target - nums[i]) && res.get(target - nums[i]) != i) + // return new int[]{i, res.get(target-nums[i])}; + // } + // return new int[0]; + // } + + public int[] twoSum(int[] nums, int target) {//一遍哈希法 + //遍历数组 每次哈希查询是否存在这样的 key 值= target - num[i], 有就说明找到目标值了 + //返回 new int {hash.get(target-num[i]),i}; + //否则 将num[i]的值 以及下标 i 存入哈希表中 + Map res = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (res.containsKey(target - nums[i])) + return new int[] {res.get(target-nums[i]), i}; + res.put(nums[i], i); + } + return new int[0]; + } + + // public int[] twoSum1(int[] nums, int target) {//暴力法 + // for (int i = 0; i < nums.length-1; i++) { + // for (int j = i + 1; j < nums.length; j++) { + // if (nums[i] + nums[j] == target) { + // return new int[]{i,j}; + // } + // } + // } + // return null; + // } + +} \ No newline at end of file diff --git "a/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206BinaryTreeInorderTraversal.java" "b/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206BinaryTreeInorderTraversal.java" new file mode 100644 index 00000000..6d7af91e --- /dev/null +++ "b/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206BinaryTreeInorderTraversal.java" @@ -0,0 +1,136 @@ +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public List inorderTraversal_4_2(TreeNode root) {//Morris 中序遍历 O(N) O(1) + //破坏法 + List res = new ArrayList(); + TreeNode pre = null; + while(root!=null) { + //如果左节点不为空,就将当前节点连带右子树全部挂到 + //左节点的最右子树下面 + if(root.left!=null) { + pre = root.left; + while(pre.right!=null) { + pre = pre.right; + } + pre.right = root; + //将root指向root的left + TreeNode tmp = root; + root = root.left; + tmp.left = null; + //左子树为空,则打印这个节点,并向右边遍历 + } else { + res.add(root.val); + root = root.right; + } + } + return res; + } + + public List inorderTraversal_4_1(TreeNode root) {//Morris 中序遍历 O(N) O(1) + //完整法 + // 莫里斯遍历的优点是没有使用任何辅助空间。缺点是改变了整个树的结构,强行把一棵二叉树改成一段链表结构。 + List res = new ArrayList(); + TreeNode predecessor = null; + + while (root != null) { + if (root.left != null) { + // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止 + predecessor = root.left; + while (predecessor.right != null && predecessor.right != root) { + predecessor = predecessor.right; + } + + // 让 predecessor 的右指针指向 root,继续遍历左子树 + if (predecessor.right == null) { + predecessor.right = root; + root = root.left; + } + // 说明左子树已经访问完了,我们需要断开链接 + else { + res.add(root.val); + predecessor.right = null; + root = root.right; + } + } + // 如果没有左孩子,则直接访问右孩子 + else { + res.add(root.val); + root = root.right; + } + } + return res; + } + + public List inorderTraversal(TreeNode root) {//方法三 栈 反序入栈 + //非递归-另一种解法(颜色标记法) 将节点与结点的值一起压入栈,通过判断对象是否为节点类型继续进行操作 + //思路 中序遍历 左根右 入栈顺序 右根左 O(N) O(N) + List res = new ArrayList(); + if(root == null) return res; + Deque stk = new ArrayDeque(); + stk.push(root); + while (!stk.isEmpty()) { + Object o = stk.pop(); + //因为中序遍历是左节点--根节点--右节点 + //即出栈顺序为左节点--根节点--右节点,入栈顺序相反 + if( o instanceof TreeNode) { + TreeNode node = (TreeNode)o; + if(node.right != null) stk.push(node.right); + stk.push(node.val); + if(node.left != null) stk.push(node.left); + } else { + res.add((int)o); + } + } + return res; + } + + public List inorderTraversal_2(TreeNode root) {//方法二 栈 + //思路 手动用栈来模拟 递归方法的程序栈 + //中序遍历 左根右 O(N) O(N) + List res = new ArrayList(); + Deque stk = new ArrayDeque(); + while (root != null || !stk.isEmpty() ) { + //不断往左子树方向走,每走一次就将当前节点保存到栈中 + //这是模拟递归的调用 + while(root != null) { + stk.push(root); + root = root.left; + } + //当前节点为空,说明左边走到头了,从栈中弹出节点并保存 + //然后转向右边节点,继续上面整个过程 + root = stk.pop(); + res.add(root.val); + root = root.right; + } + return res; + } + + public List inorderTraversal_1(TreeNode root) {//方法一 递归解法 + //思路 中序遍历-左根右 时间复杂度 O(n)二叉树的遍历中每个节点会被访问一次且只会被访问一次。 + //空间复杂度 O(n)空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到O(n) 的级别。 + List res = new ArrayList(); + if (root == null) return res; + inorder_1(root, res); + return res; + } + public void inorder_1(TreeNode root, List res) { + if (root == null) return; + inorder_1(root.left, res); + res.add(root.val); + inorder_1(root.right, res); + } +} \ No newline at end of file diff --git "a/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206BinaryTreePreorderTraversal.java" "b/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206BinaryTreePreorderTraversal.java" new file mode 100644 index 00000000..857f42df --- /dev/null +++ "b/Week_02/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206BinaryTreePreorderTraversal.java" @@ -0,0 +1,105 @@ +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public List postorderTraversal(TreeNode root) {//二叉树的后序遍历 迭代 中序遍历的变形 + List ans = new ArrayList<>(); + Stack stack = new Stack<>(); + while(root != null || !stack.isEmpty()) { + while(root != null) { + stack.add(root); + ans.add(0, root.val); + root = root.right; + } + root = stack.pop(); + root = root.left; + } + + return ans; + } + + public List preorderTraversal_3(TreeNode root) {//方法四 Morris解法 最后会还原树的结构 + List res = new ArrayList(); + if (root == null) { + return res; + } + TreeNode cur1 = root; + TreeNode cur2 = null; + while (cur1 != null) { + cur2 = cur1.left; + if (cur2 != null) { + while (cur2.right != null && cur2.right != cur1) { + cur2 = cur2.right; + } + if (cur2.right == null) { + cur2.right = cur1; + // System.out.print(cur1.value + " "); + res.add(cur1.val); + cur1 = cur1.left; + continue; + } else { + cur2.right = null;//断开连接(伪边) + } + } else { + // System.out.print(cur1.value + " "); + res.add(cur1.val); + } + cur1 = cur1.right; + } + return res; + } + public List preorderTraversal_2_2(TreeNode root) {//方法三 迭代 中序遍历修改版 + Stack stack = new Stack<>(); + List list = new ArrayList<>(); + TreeNode cur = root; + while(!stack.isEmpty() || cur != null){ + while(cur != null){ + list.add(cur.val); + stack.push(cur); + cur = cur.left; + } + cur = stack.pop(); + cur = cur.right; + } + return list; + } + public List preorderTraversal(TreeNode root) {//方法二 栈 + //思路 手动用栈模拟递归中程序栈 + List res = new ArrayList(); + if (root == null) return res; + Deque stk = new ArrayDeque(); + stk.push(root); + while (!stk.isEmpty()) { + TreeNode node = stk.pop(); + res.add(node.val); + if(node.right != null) stk.push(node.right); + if(node.left != null) stk.push(node.left); + } + return res; + } + + public List preorderTraversal_1(TreeNode root) {//方法一 递归 + List res = new ArrayList(); + if (root == null) return res; + preorder(root,res); + return res; + } + public void preorder(TreeNode root, List res) { + if(root == null) return; + res.add(root.val); + preorder(root.left, res); + preorder(root.right, res); + } +} \ No newline at end of file diff --git "a/Week_02/\345\220\204\347\247\215\345\240\206\347\232\204\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.PNG" "b/Week_02/\345\220\204\347\247\215\345\240\206\347\232\204\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.PNG" new file mode 100644 index 00000000..abf0bf99 Binary files /dev/null and "b/Week_02/\345\220\204\347\247\215\345\240\206\347\232\204\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.PNG" differ diff --git "a/Week_02/\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204GroupAnagrams.java" "b/Week_02/\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204GroupAnagrams.java" new file mode 100644 index 00000000..f8e4bb7a --- /dev/null +++ "b/Week_02/\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204GroupAnagrams.java" @@ -0,0 +1,93 @@ +//还有一种质数相乘法 风险时 容易溢出 +// 算术基本定理,又称为正整数的唯一分解定理,即:每个大于1的自然数,要么本身就是质数,要么可以写为2个以上的质数的积,而且这些质因子按大小排列之后,写法仅有一种方式。 +//故可以利用 每个字母用不同的质数表示,字符串的键值为 质数之积 来表示 + + +class Solution {//按计数分类 +//构建一个StringBuilder类型变量作为 key 值 #a#b#c#d.....#z +//每个字母的位置存储对应字母的出现次数,依次分类 +//利用一个哈希表存储分类的结果,最后返回 +// // 时间复杂度:O(NK),其中 N 是 strs 的长度,而 K 是 strs 中字符串的最大长度。计算每个字符串的字符串大小是线性的,我们统计每个字符串。 +// // 空间复杂度:O(NK),排序存储在 res 中的全部信息内容 + public List> groupAnagrams(String[] strs) { + if (strs.length == 0) return new ArrayList<>(); + Map res = new HashMap(); + for (String s : strs) { + int[] arr = new int[26]; + for (char ch : s.toCharArray()) { + arr[ch - 'a']++; + } + StringBuilder temp = new StringBuilder(""); + for (int i = 0; i < 26; i++) { + temp.append('#'); + temp.append(arr[i]); + } + String key = temp.toString(); + if (!res.containsKey(key)) res.put(key, new ArrayList()); + res.get(key).add(s); + } + return new ArrayList(res.values()); + } +} + +// class Solution {//排序字符串解法,利用哈希表存储分类后的字符串 +// //创建一个 Map 哈希表存储 分类结果 +// //每次遍历strs,将其转化为字符数组再排序,判断排序好的作为key查询是否存在在hash表中 +// //存在即添加到value-List中,不存在则将该key值与value存进去 +// // 时间复杂度:O(NKlogK),其中 N 是 strs 的长度,而 K 是 strs 中字符串的最大长度。当我们遍历每个字符串时,外部循环具有的复杂度为 O(N)。然后,我们在 O(KlogK) 的时间内对每个字符串排序。 + +// // 空间复杂度:O(NK),排序存储在 res 中的全部信息内容。 +// public List> groupAnagrams(String[] strs) { +// if (strs.length == 0) return new ArrayList<>(); +// Map res = new HashMap(); +// for (String s : strs) { +// char[] ch = s.toCharArray(); +// Arrays.sort(ch); +// String key = String.valueOf(ch);//将字符数组转化为字符串 +// if (!res.containsKey(key)){ +// res.put(key, new ArrayList()); +// } +// res.get(key).add(s); +// } +// return new ArrayList(res.values()); +// } +// } + + +// class Solution {//暴力法 +// //遍历每个元素 与其后面元素比较,如果相等就加入一个List,最后一起加入string[][]中 +// //再继续访问下一个元素 +// //创建一个辅助boolean类型数组,用来判断元素是否已经访问过 +// public List> groupAnagrams(String[] strs) { +// List> res = new ArrayList<>();//创建结果数组 +// boolean[] used = new boolean[strs.length]; +// for (int i = 0; i < strs.length; i++) { +// List temp = null; +// if (!used[i]) { +// temp = new ArrayList(); +// temp.add(strs[i]); +// for (int j = i+1; j < strs.length; j++) { +// if (!used[j] && equals(strs[i], strs[j]) ) { +// temp.add(strs[j]); +// used[j] = true; +// } +// } +// } +// if (temp != null) res.add(temp); +// used[i] = true; +// } +// return res; +// } +// public boolean equals(String s1, String s2) { +// if(s1.length() != s2.length()) return false; +// int[] alphabet = new int[26]; +// for (char ch : s1.toCharArray()) { +// alphabet[ch - 'a']++; +// } +// for (char ch : s2.toCharArray()) { +// alphabet[ch - 'a']--; +// if (alphabet [ch - 'a'] < 0) return false; +// } +// return true; +// } +// } \ No newline at end of file diff --git "a/Week_02/\346\211\213\345\212\250\345\256\236\347\216\260\344\272\214\345\217\211\345\240\206MyBinaryHeap.java" "b/Week_02/\346\211\213\345\212\250\345\256\236\347\216\260\344\272\214\345\217\211\345\240\206MyBinaryHeap.java" new file mode 100644 index 00000000..c4a15761 --- /dev/null +++ "b/Week_02/\346\211\213\345\212\250\345\256\236\347\216\260\344\272\214\345\217\211\345\240\206MyBinaryHeap.java" @@ -0,0 +1,103 @@ +import java.sql.SQLOutput; +import java.util.Arrays; +import java.util.NoSuchElementException; + +public class MyBinaryHeap { + private static final int d = 2;//代表分叉个数 + private int[] heap; + private int size; + public MyBinaryHeap(int capacity) { + size = 0; + heap = new int[capacity + 1]; + Arrays.fill(heap, -1); + } + public boolean isEmpty() {//判断是否为空 + return size == 0; + } + public boolean isFull() { + return size == heap.length; + } + + private int parent(int i) {//找父母节点 + return (i - 1) / d; + } + private int child(int i, int k) {//找孩子节点 + return i * d + k; + } + + public void insert(int x) {//先插入到末尾,再进行堆排序 + if (isFull()) { + throw new NoSuchElementException("Heap is full,no space to insert new element."); + } + heap[size] = x; + size++; + heapifyUp(size - 1); + } + private void heapifyUp(int i) { + int inserValue = heap[i]; + while (i > 0 && heap[i] > heap[parent(i)]) { + heap[i] = heap[parent(i)]; + i = parent(i); + } + heap[i] = inserValue; + } + + public int delete(int x) { + if (isEmpty()) { + throw new NoSuchElementException("Heap is empty,no element to delete"); + } + int maxElement = heap[x]; + heap[x] = heap[size - 1]; + size--; + heapifyDown(x); + return maxElement; + } + private void heapifyDown(int i) { + int child_index; + int temp = heap[i];//临时保存要修改的值 + while (child(i, 1) < size) {//确保孩子的下标不会超过size + child_index = maxChild(i); + if (temp > heap[child_index]) + break; + heap[i] = heap[child_index]; + i = child_index; + } + heap[i] = temp; + } + private int maxChild(int i) { + int leftChild = child(i,1); + int rightChild = child(i, 2); + return heap[leftChild] > heap[rightChild] ? leftChild : rightChild; + } + + public void printHeap() { + System.out.print("nHeap = "); + for (int i = 0; i < size; i++) + System.out.print(heap[i] + " "); + System.out.println(); + } + + public int findMax() { + if (isEmpty()) + throw new NoSuchElementException("Heap is empty."); + return heap[0]; + } + + public static void main(String[] args) { + MyBinaryHeap maxHeap = new MyBinaryHeap(10); + maxHeap.insert(10); + maxHeap.insert(4); + maxHeap.insert(9); + maxHeap.insert(1); + maxHeap.insert(7); + maxHeap.insert(5); + maxHeap.insert(3); + + + maxHeap.printHeap(); + maxHeap.delete(5); + maxHeap.printHeap(); + maxHeap.delete(2); + maxHeap.printHeap(); + } +} diff --git "a/Week_02/\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215ValidAnagram.java" "b/Week_02/\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215ValidAnagram.java" new file mode 100644 index 00000000..01c3f1c1 --- /dev/null +++ "b/Week_02/\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215ValidAnagram.java" @@ -0,0 +1,26 @@ +class Solution { + public boolean isAnagram1(String s, String t) {//方法一利用(哈希)辅助数组 O(n) O(1) + //遍历s,将每个出现过的元素在对应辅助数组中++ + //遍历t,将每个出现过的元素在对应辅助数组置--,如果有某个元素小于0就返回false + //返回true + //hash算法的概念 Hash: 一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。 ... 简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 + if(s.length() != t.length()) return false; + int[] hash = new int[26]; + for (char ch : s.toCharArray()) { + hash[ch - 'a']++; + } + for (char ch : t.toCharArray()) { + hash[ch - 'a']--; + if(hash[ch - 'a'] < 0) return false; + } + return true; + } + public boolean isAnagram(String s, String t) {//方法二 排序 O(nlogn) O(1)n为s的长度 + //将两个字符串排序,再进行比较equals + char[] s1 = s.toCharArray(); + char[] t1 = t.toCharArray(); + Arrays.sort(s1); + Arrays.sort(t1); + return Arrays.equals(s1,t1); + } +} \ No newline at end of file diff --git a/Week_03/README.md b/Week_03/README.md index 50de3041..6092bb44 100644 --- a/Week_03/README.md +++ b/Week_03/README.md @@ -1 +1,32 @@ -学习笔记 \ No newline at end of file +2020/10/15 学习笔记 + +递归 + + 四个结构模块 + 1.终止条件 + 2.处理当前逻辑 + 3.下探到下一层 + 4.清理当前层 + + 思维要点 + 1.不要人肉递归 + 2.找到最近重复子问题 + 3.数学归纳法思维 + +代码模板 + + // Java + public void recur(int level, int param) { + // terminator + if (level > MAX_LEVEL) { + // process result + return; + } + // process current logic + process(level, param); + // drill down + recur( level: level + 1, newParam); + // restore current status + + } +