diff --git a/Week_01/homework.md b/Week_01/homework.md new file mode 100644 index 00000000..ebd5201c --- /dev/null +++ b/Week_01/homework.md @@ -0,0 +1,310 @@ +### 第一周 + +#### 简单: + +* 用 add first 或 add last 这套新的 API 改写 Deque 的代码 +* 分析 Queue 和 Priority Queue 的源码 +* [删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/)(Facebook、字节跳动、微软在半年内面试中考过) +* [旋转数组](https://leetcode-cn.com/problems/rotate-array/)(微软、亚马逊、PayPal 在半年内面试中考过) +* [合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)(亚马逊、字节跳动在半年内面试常考) +* [合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)(Facebook 在半年内面试常考) +* [两数之和](https://leetcode-cn.com/problems/two-sum/)(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) +* [移动零](https://leetcode-cn.com/problems/move-zeroes/)(Facebook、亚马逊、苹果在半年内面试中考过) +* [加一](https://leetcode-cn.com/problems/plus-one/)(谷歌、字节跳动、Facebook 在半年内面试中考过) +#### 中等: + +* [设计循环双端队列](https://leetcode.com/problems/design-circular-deque)(Facebook 在 1 年内面试中考过) +#### 困难: + +* [接雨水](https://leetcode.com/problems/trapping-rain-water/)(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + +--- + + +1、用 add first 或 add last 这套新的 API 改写 Deque 的代码 + +2、分析 Queue 和 Priority Queue 的源码 + +3、[删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/)(Facebook、字节跳动、微软在半年内面试中考过) + +双指针: + +关键点 + +1、使用 i 和 j 两个指针遍历数组 ,j 在 i 前寻找不重复元素 + +2、发现不重复元素往前移动至第一个重复元素位置 + +3、最终数组长度为指针 i 走过的路程长度 + +```plain +public int removeDuplicates(int[] nums) { +    if (nums == null || nums.length <= 0) { +        return 0; +    } +    int i = 0; +    int j = 1; +    while (j < nums.length) { +        if (nums[i] != nums[j]) { +            nums[i + 1] = nums[j]; +            i++; +        } +        j++; +    } +    return i + 1; +} +``` +4、[旋转数组](https://leetcode-cn.com/problems/rotate-array/)(微软、亚马逊、PayPal 在半年内面试中考过) +5、[合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)(亚马逊、字节跳动在半年内面试常考) + +解题思路: + +递归找到两个链表中相对较小的结点 + +递归三要素: + +终止条件:l1 、l2 结点为null + +递归体: 找到相对较小的一个结点 + +返回值:将找到的结点返回 + +```plain +public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + if (l1 == null) + return l2; + if (l2 == null) + return l1; + ListNode head = new ListNode(); + if (l1.val < l2.val) { + head.val = l1.val; + head.next = mergeTwoLists(l1.next, l2); + } else { + head.val = l2.val; + head.next = mergeTwoLists(l2.next, l1); + } + return head; +} +``` +6、[合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)(Facebook 在半年内面试常考) +7、[两数之和](https://leetcode-cn.com/problems/two-sum/)(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) + +暴力法:枚举所有两个数相加的和 + +```plain +public int[] twoSum(int[] nums, int target) { + for (int i = 0; i < nums.length - 1; i++) { + for (int j = i + 1; j < nums.length; j++) { + if (target == (nums[i] + nums[j])) { + return new int[]{i, j}; + } + } + } + return new int[0]; +} +``` +哈希映射法: +```plain +public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); + } + return new int[0]; +} +``` +8、[移动零](https://leetcode-cn.com/problems/move-zeroes/)(Facebook、亚马逊、苹果在半年内面试中考过) +解题思路: + +第一层循环:将所有非零元素移动到数组开头 + +第二层循环:将剩余元素皆赋值为0 + +```plain +public void moveZeroes(int[] nums) { + int p = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + nums[p++] = nums[i]; + } + } + for (int j = p; j < nums.length; j++) { + nums[j] = 0; + } +} +``` +9、[加一](https://leetcode-cn.com/problems/plus-one/)(谷歌、字节跳动、Facebook 在半年内面试中考过) +10、[设计循环双端队列](https://leetcode.com/problems/design-circular-deque)(Facebook 在 1 年内面试中考过) + +11、[接雨水](https://leetcode.com/problems/trapping-rain-water/)(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + +### 第二周 + +#### 简单: + +* 写一个关于 HashMap 的小总结。 +说明:对于不熟悉 Java 语言的同学,此项作业可选做。 +* [有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/description/)(亚马逊、Facebook、谷歌在半年内面试中考过) +* [两数之和](https://leetcode-cn.com/problems/two-sum/description/)(近半年内,亚马逊考查此题达到 216 次、字节跳动 147 次、谷歌 104 次,Facebook、苹果、微软、腾讯也在近半年内面试常考) +* [N 叉树的前序遍历](https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/description/)(亚马逊在半年内面试中考过) +* HeapSort :自学[https://www.geeksforgeeks.org/heap-sort/](https://www.geeksforgeeks.org/heap-sort/) +#### 中等: + +* [字母异位词分组](https://leetcode-cn.com/problems/group-anagrams/)(亚马逊在半年内面试中常考) +* [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)(亚马逊、字节跳动、微软在半年内面试中考过) +* [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/)(字节跳动、谷歌、腾讯在半年内面试中考过) +* [N 叉树的层序遍历](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/)(亚马逊在半年内面试中考过) +* [丑数](https://leetcode-cn.com/problems/chou-shu-lcof/)(字节跳动在半年内面试中考过) +* [前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/)(亚马逊在半年内面试中常考) + +--- + + +1、写一个关于 HashMap 的小总结。 + +[https://shimo.im/docs/RXVpdYJTGwQRjtkQ/](https://shimo.im/docs/RXVpdYJTGwQRjtkQ/)《java8 HashMap 源码分析》,可复制链接后用石墨文档 App 或小程序打开 + +2、有效的字母异位词 + +给定两个字符串*s*和*t*,编写一个函数来判断*t*是否是*s*的字母异位词。 + +异位词:长度一样,包含的字母都一样,每个字符出现的频率也一样,只是顺序不同而已 + +思路: + +(1)、将字符串转换为字符数组 + +(2)、对字符数组进行排序 + +(3)、比较排序后的字符数组,如果相等就是异位词 + +```plain +public boolean isAnagram1(String s, String t) { + if (s.isEmpty() && t.isEmpty()) { + return true; + } + if (s.length() != t.length()) { + return false; + } + char[] sChars = s.toCharArray(); + char[] tChars = t.toCharArray(); + Arrays.sort(sChars); + Arrays.sort(tChars); + return Arrays.equals(sChars, tChars); +} +``` +3、两数之和 +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + +来源:力扣(LeetCode) + +链接:[https://leetcode-cn.com/problems/two-sum](https://leetcode-cn.com/problems/two-sum) + +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + +思路:a+b = target , 及 a = target - b ,也就是说只要在整数数组中找到于 target - nums[i] 相等数的下标就可以了,使用map集合,用数组中的数作为key,相对应的下标作为value,循环遍历寻找是否存在key为target - nums[i]的元素 + +```plain +public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); + } + return new int[0]; +} +``` +4、N叉树的前序遍历 +前序:根-子 + +递归: + +```plain +public List preorder(Node root) { + List res = new ArrayList<>(); + order(root, res); + return res; +} +private void order(Node root, List res) { + if (root == null) { + return; + } + res.add(root.val); + for (Node node : root.children) { + order1(node, res); + } +} +``` +5、字母异位词分组 +思路:排序+哈希 + +1)、将字符串转化为字符数组并排序 + +2)、将异位词作为key值,对比转换后的每一个字符串是否是异位词,是的存相同key值下的集合中,不同存新创建的集合中 + +```plain +public List> groupAnagrams(String[] strs) { + if (strs.length == 0) { + return new ArrayList<>(); + } + Map> map = new HashMap<>(); + for (String s : strs) { + char[] chars = s.toCharArray(); + Arrays.sort(chars); + String key = String.valueOf(chars); + if (!map.containsKey(key)) { + map.put(key, new ArrayList<>()); + } + map.get(key).add(s); + } + return new ArrayList<>(map.values()); +} +``` +6、二叉树的中序遍历 +递归思想:中序,左-根-右 + +时间复杂度:O(N) + +```plain +public List inorderTraversal1(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + inorder2(root.left, res); + res.add(root.val); + inorder2(root.right, res); +} +``` +7、二叉树的前序遍历 +前序:根-左-右 + +递归思想:递归三要素:结束条件、递归体、返回值 + +```plain +public List inorderTraversal1(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + res.add(root.val); + inorder2(root.left, res); + inorder2(root.right, res); +} +``` +8、 diff --git "a/Week_01/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\346\200\273\350\247\210.md" "b/Week_01/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\346\200\273\350\247\210.md" new file mode 100644 index 00000000..7b885c14 --- /dev/null +++ "b/Week_01/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\346\200\273\350\247\210.md" @@ -0,0 +1,21 @@ +#### **数据结构:** + +* 一维 + * 基础:数组 array(string),链表 linked list + * 高级:栈 stack,队列 queue,双端队列 deque,集合 set,映射 map(hash or map) +* 二维 + * 基础:树 tree,图 graph + * 高级:二叉搜索树 binary search tree (red-black tree ,AVL),堆 heap,并查集 disjoint set,字典树 Trie +* 特殊 + * 位运算 Bitwise ,布隆过滤器 BloomFilter + * LRU Cache +#### **算法:** + +* **if-else ,switch ----> branch** +* **for ,while loop -----> Iteratioin** +* 递归 Recursion (Divide & Conquer ,Backtrace) +* 搜索 Search :深度优先搜索 Depth first search ,广度优先搜索 Breadth first search +* 动态规划 Dynamic Programming +* 二分查找 Binary Search +* 贪心 Greedy +* 数学 Math ,几何 Geometry diff --git "a/Week_01/\346\225\260\347\273\204\343\200\201\351\223\276\350\241\250\343\200\201\350\267\263\350\241\250.md" "b/Week_01/\346\225\260\347\273\204\343\200\201\351\223\276\350\241\250\343\200\201\350\267\263\350\241\250.md" new file mode 100644 index 00000000..77e16bfa --- /dev/null +++ "b/Week_01/\346\225\260\347\273\204\343\200\201\351\223\276\350\241\250\343\200\201\350\267\263\350\241\250.md" @@ -0,0 +1,41 @@ +数组、链表、跳表的基本实现和特性 + +#### Array : + +内存空间中一串连续的空间 + +![图片](https://uploader.shimo.im/f/4dhy5vzCur6kb2E1.png!thumbnail?fileGuid=8DhPcW9VpxDKjgYD) + +时间复杂度: + + +#### Linked List : 链表 + +是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 + +![图片](https://uploader.shimo.im/f/mBOGE5n9JyyJeGL5.png!thumbnail?fileGuid=8DhPcW9VpxDKjgYD) + +![图片](https://uploader.shimo.im/f/OFoE3HCvdMSS5AQX.png!thumbnail?fileGuid=8DhPcW9VpxDKjgYD) + +时间复杂度: + +prepend O(1) + +append O(1) + +lookup O(n) + +insert O(1) + +delete O(1) + +#### Skip List 跳表 + +特点:在链表元素有序的基础上实现,所以,跳表(skip list)对标的是平衡树(AVL Tree)和二分查找, 是一种 插入/删除/搜索 都是 O(log n) 的数据结构。1989 年出现。 它最大的优势是原理简单、容易实现、方便扩展、效率更高。因此 在一些热门的项目里用来替代平衡树,如 Redis、LevelDB 等。 + +![图片](https://uploader.shimo.im/f/it5YGjKpRWg3LjUm.png!thumbnail?fileGuid=8DhPcW9VpxDKjgYD) + +时间复杂度:O(logN) + +空间复杂度:O(n) + diff --git "a/Week_01/\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\345\222\214\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" "b/Week_01/\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\345\222\214\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" new file mode 100644 index 00000000..1e1b0b6c --- /dev/null +++ "b/Week_01/\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\345\222\214\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" @@ -0,0 +1,30 @@ +常见复杂度 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 阶乘 + +时间复杂度曲线 + +![图片](https://uploader.shimo.im/f/n2hVVdn95TNMVH2R.png!thumbnail?fileGuid=WHV8rKYhDgDCJ3cq) + +时间复杂度之 + +二叉树遍历-前序、中序、后序:O(N) + +图的遍历:O(N) + +搜索算法:DFS、BFS - O(N) + +二分查找:O(logN) + diff --git "a/Week_01/\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\345\217\214\347\253\257\351\230\237\345\210\227\343\200\201\344\274\230\345\205\210\351\230\237\345\210\227.md" "b/Week_01/\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\345\217\214\347\253\257\351\230\237\345\210\227\343\200\201\344\274\230\345\205\210\351\230\237\345\210\227.md" new file mode 100644 index 00000000..61c0c0ee --- /dev/null +++ "b/Week_01/\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\345\217\214\347\253\257\351\230\237\345\210\227\343\200\201\344\274\230\345\205\210\351\230\237\345\210\227.md" @@ -0,0 +1,176 @@ +>java api 查询:(中文)[https://www.matools.com/api/java10](https://www.matools.com/api/java10) +>[http://developer.classpath.org/doc/](http://developer.classpath.org/doc/)(源码)[http://fuseyism.com/classpath/doc/](http://fuseyism.com/classpath/doc/) +### 栈:Stack 后进先出(Last in - First out)LIFO + +![图片](https://uploader.shimo.im/f/B6NNKY9b2jA9mfsp.png!thumbnail?fileGuid=jgwjdYJrRkx6w39g) + +#### 源码: + +[http://fuseyism.com/classpath/doc/java/util/Stack-source.html](http://fuseyism.com/classpath/doc/java/util/Stack-source.html) + +#### 继承关系: + +```plain +java.lang.Object + java.util.AbstractCollection + java.util.AbstractList + java.util.Vector + java.util.Stack +All Implemented Interfaces: +Serializable , Cloneable , Iterable , Collection , List , RandomAccess +``` +#### 类关系图: + +#### 数据结构: + +Stack是继承于Vector,由于Vector是通过数组实现的,意味着Stack也是通过数组实现的,而非链表。 + +#### 常用方法: + +* Tpush(T item) ,Pushes an Object onto the top of the stack. +* T pop() , Pops an item from the stack and retuens it . The item popped is remove from the Stack +* T peek() , Return the pop Object on the stack without removing it. +* boolen empty() , Tests if the stack is empty. +* int seatch(Object o) , Return the position of an Object on the stack. +### 队列:Queue 先进先出(First in - First out)LIFO + +![图片](https://uploader.shimo.im/f/iTZ40YcPV95N9CYE.png!thumbnail?fileGuid=jgwjdYJrRkx6w39g) + +注:栈和队列添加和删除元素的时间复杂度都是O(1); + +#### 源码: + +[http://fuseyism.com/classpath/doc/java/util/Queue-source.html](http://fuseyism.com/classpath/doc/java/util/Queue-source.html) + +#### 继承关系: + +```plain +Interface Queue +参数类型 +E - 保存在此集合中的元素的类型 +All Superinterfaces: +Collection , Iterable  +All Known Subinterfaces: +BlockingDeque , BlockingQueue , Deque , TransferQueue  +所有已知实现类: +AbstractQueue , ArrayBlockingQueue , ArrayDeque , ConcurrentLinkedDeque , ConcurrentLinkedQueue , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedList , LinkedTransferQueue , PriorityBlockingQueue , PriorityQueue , SynchronousQueue +``` +#### 类关系图: + +#### 数据结构: + +参考具体的实现类,不同的实现类使用不同的数据结构 + +#### 常用方法: + +* boolean add(E e) , Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions , returning true upon success and throwing an IllegalStateException if no space is currently available. +* boolean offer(E e) , Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions. When using a capacity-restricted queue , this method is generally preferable to {@link #add} , which can fail to insert an element only by throwing a exception. +* E remove() , Retrieves and removes the head of this queue , This method differs from {@link #poll} only in that it throws an exception if this queue is empty. +* E poll() , Retrieves and removes the head of this queue , or returns null if this queue is empty. +* E element() , Retrieves , but does not remove , the head of this queue . This method differs from {@link #peek peek} only in that it throws an exception if this queue is empty. +* E peek() , Retrieves , but does not remove , the head of this queue , or returns null if this queue is empty. +### 双端队列 Deque :Double-End Queue 头和尾皆可添加删除元素 + +![图片](https://uploader.shimo.im/f/8ymuEIGkdPs0YfNX.png!thumbnail?fileGuid=jgwjdYJrRkx6w39g) + +注:插入和删除都是O(1)操作 + +#### 源码: + +[http://fuseyism.com/classpath/doc/java/util/Queue-source.html](http://fuseyism.com/classpath/doc/java/util/Queue-source.html) + +#### 继承关系: + +```plain +Interface Deque +参数类型 +E - 在此集合中保存的元素的类型 +All Superinterfaces: +Collection , Iterable , Queue  +All Known Subinterfaces: +BlockingDeque  +所有已知实现类: +ArrayDeque , ConcurrentLinkedDeque , LinkedBlockingDeque , LinkedList +``` +#### 类关系图: + +#### 数据结构: + +支持两端元素插入和移除的线性集合 + +#### 常用方法: + +**Summary of Deque methods:** + +| |First Element (Head)| |Last Element (Tail)| | +|:----|:----|:----|:----|:----| +| |*Throws exception*|*Special value*|*Throws exception*|*Special value*| +|Insert|[addFirst(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#addFirst(E))|[offerFirst(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#offerFirst(E))|[addLast(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#addLast(E))|[offerLast(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#offerLast(E))| +|Remove|[removeFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#removeFirst())|[pollFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#pollFirst())|[removeLast()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#removeLast())|[pollLast()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#pollLast())| +|Examine|[getFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#getFirst())|[peekFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peekFirst())|[getLast()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#getLast())|[peekLast()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peekLast())
| + +**Comparison of Queue and Deque methods** + +|Queue Method|Equivalent Deque Method| +|:----|:----| +|[add(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#add(E))|[addLast(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#addLast(E))| +|[offer(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#offer(E))|[offerLast(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#offerLast(E))| +|[remove()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#remove())|[removeFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#removeFirst())| +|[poll()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#poll())|[pollFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#pollFirst())| +|[element()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#element())|[getFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#getFirst())| +|[peek()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peek())|[peekFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peekFirst())
| + +**Comparison of Stack and Deque methods** + +|Stack Method|Equivalent Deque Method| +|:----|:----| +|[push(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#push(E))|[addFirst(e)](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#addFirst(E))| +|[pop()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#pop())|[removeFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#removeFirst())| +|[peek()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peek())|[peekFirst()](https://docs.oracle.com/javase/10/docs/api/java/util/Deque.html#peekFirst())| + +### 优先队列:PriorityQueue ,按元素的优先级取出 + +#### 源码: + +[http://fuseyism.com/classpath/doc/java/util/PriorityQueue-source.html](http://fuseyism.com/classpath/doc/java/util/PriorityQueue-source.html) + +#### 继承关系: + +```plain +Class PriorityQueue + java.lang.Object + java.util.AbstractCollection + java.util.AbstractQueue + java.util.PriorityQueue +Type Parameters: +E - the type of elements held in this queue +All Implemented Interfaces: +Serializable, Iterable, Collection, Queue +``` +#### 类关系图: + +#### 数据结构: + +底层具体实现的数据结构较为多样和复杂:heap、bst、treap + +An unbounded priority[queue](https://docs.oracle.com/javase/10/docs/api/java/util/Queue.html)based on a priority heap.基于优先级堆的无界优先级队列。 + +常用方法: + +* boolean add(E e) , Inserts the specified element into this priority queue. +* clear() , Remove all of the elements this priority queue. +* Comparator comparator() , Returns the comparator used order the elements int this queue , or null if this queue is sorted according to the natural ordering of this elements. +* boolean contain(Object o) , Returns true if this queue contains the specified element. +* Iterator iterator() , Returns an iterator over the elements in thsi queue. +* boolean offer(E e) Inserts the specified element into this priority queue. +* boolean remove(Object o) , Removes a single instance of the specified element from this queue ,if it is present. +* Spliterator spliterator() Creates a late-binding and fall-fast Spliterator over the elements in this queue. +* Object[] toArray() , Returns an array containing all of the elements in this queue. +* T[] toArray(T[] a) , Returns an array containing all of the elements in this queue; the runtime type of the returned array is that of the specified array. + +注:插入操作:O(1) 取出操作:O(logN) + +复杂度差异分析 + +![图片](https://uploader.shimo.im/f/R9G49qmqdImvQwDp.png!thumbnail?fileGuid=jgwjdYJrRkx6w39g) + diff --git a/Week_02/homework.md b/Week_02/homework.md new file mode 100644 index 00000000..ebd5201c --- /dev/null +++ b/Week_02/homework.md @@ -0,0 +1,310 @@ +### 第一周 + +#### 简单: + +* 用 add first 或 add last 这套新的 API 改写 Deque 的代码 +* 分析 Queue 和 Priority Queue 的源码 +* [删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/)(Facebook、字节跳动、微软在半年内面试中考过) +* [旋转数组](https://leetcode-cn.com/problems/rotate-array/)(微软、亚马逊、PayPal 在半年内面试中考过) +* [合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)(亚马逊、字节跳动在半年内面试常考) +* [合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)(Facebook 在半年内面试常考) +* [两数之和](https://leetcode-cn.com/problems/two-sum/)(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) +* [移动零](https://leetcode-cn.com/problems/move-zeroes/)(Facebook、亚马逊、苹果在半年内面试中考过) +* [加一](https://leetcode-cn.com/problems/plus-one/)(谷歌、字节跳动、Facebook 在半年内面试中考过) +#### 中等: + +* [设计循环双端队列](https://leetcode.com/problems/design-circular-deque)(Facebook 在 1 年内面试中考过) +#### 困难: + +* [接雨水](https://leetcode.com/problems/trapping-rain-water/)(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + +--- + + +1、用 add first 或 add last 这套新的 API 改写 Deque 的代码 + +2、分析 Queue 和 Priority Queue 的源码 + +3、[删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/)(Facebook、字节跳动、微软在半年内面试中考过) + +双指针: + +关键点 + +1、使用 i 和 j 两个指针遍历数组 ,j 在 i 前寻找不重复元素 + +2、发现不重复元素往前移动至第一个重复元素位置 + +3、最终数组长度为指针 i 走过的路程长度 + +```plain +public int removeDuplicates(int[] nums) { +    if (nums == null || nums.length <= 0) { +        return 0; +    } +    int i = 0; +    int j = 1; +    while (j < nums.length) { +        if (nums[i] != nums[j]) { +            nums[i + 1] = nums[j]; +            i++; +        } +        j++; +    } +    return i + 1; +} +``` +4、[旋转数组](https://leetcode-cn.com/problems/rotate-array/)(微软、亚马逊、PayPal 在半年内面试中考过) +5、[合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)(亚马逊、字节跳动在半年内面试常考) + +解题思路: + +递归找到两个链表中相对较小的结点 + +递归三要素: + +终止条件:l1 、l2 结点为null + +递归体: 找到相对较小的一个结点 + +返回值:将找到的结点返回 + +```plain +public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + if (l1 == null) + return l2; + if (l2 == null) + return l1; + ListNode head = new ListNode(); + if (l1.val < l2.val) { + head.val = l1.val; + head.next = mergeTwoLists(l1.next, l2); + } else { + head.val = l2.val; + head.next = mergeTwoLists(l2.next, l1); + } + return head; +} +``` +6、[合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)(Facebook 在半年内面试常考) +7、[两数之和](https://leetcode-cn.com/problems/two-sum/)(亚马逊、字节跳动、谷歌、Facebook、苹果、微软在半年内面试中高频常考) + +暴力法:枚举所有两个数相加的和 + +```plain +public int[] twoSum(int[] nums, int target) { + for (int i = 0; i < nums.length - 1; i++) { + for (int j = i + 1; j < nums.length; j++) { + if (target == (nums[i] + nums[j])) { + return new int[]{i, j}; + } + } + } + return new int[0]; +} +``` +哈希映射法: +```plain +public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); + } + return new int[0]; +} +``` +8、[移动零](https://leetcode-cn.com/problems/move-zeroes/)(Facebook、亚马逊、苹果在半年内面试中考过) +解题思路: + +第一层循环:将所有非零元素移动到数组开头 + +第二层循环:将剩余元素皆赋值为0 + +```plain +public void moveZeroes(int[] nums) { + int p = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + nums[p++] = nums[i]; + } + } + for (int j = p; j < nums.length; j++) { + nums[j] = 0; + } +} +``` +9、[加一](https://leetcode-cn.com/problems/plus-one/)(谷歌、字节跳动、Facebook 在半年内面试中考过) +10、[设计循环双端队列](https://leetcode.com/problems/design-circular-deque)(Facebook 在 1 年内面试中考过) + +11、[接雨水](https://leetcode.com/problems/trapping-rain-water/)(亚马逊、字节跳动、高盛集团、Facebook 在半年内面试常考) + +### 第二周 + +#### 简单: + +* 写一个关于 HashMap 的小总结。 +说明:对于不熟悉 Java 语言的同学,此项作业可选做。 +* [有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/description/)(亚马逊、Facebook、谷歌在半年内面试中考过) +* [两数之和](https://leetcode-cn.com/problems/two-sum/description/)(近半年内,亚马逊考查此题达到 216 次、字节跳动 147 次、谷歌 104 次,Facebook、苹果、微软、腾讯也在近半年内面试常考) +* [N 叉树的前序遍历](https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/description/)(亚马逊在半年内面试中考过) +* HeapSort :自学[https://www.geeksforgeeks.org/heap-sort/](https://www.geeksforgeeks.org/heap-sort/) +#### 中等: + +* [字母异位词分组](https://leetcode-cn.com/problems/group-anagrams/)(亚马逊在半年内面试中常考) +* [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)(亚马逊、字节跳动、微软在半年内面试中考过) +* [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/)(字节跳动、谷歌、腾讯在半年内面试中考过) +* [N 叉树的层序遍历](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/)(亚马逊在半年内面试中考过) +* [丑数](https://leetcode-cn.com/problems/chou-shu-lcof/)(字节跳动在半年内面试中考过) +* [前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/)(亚马逊在半年内面试中常考) + +--- + + +1、写一个关于 HashMap 的小总结。 + +[https://shimo.im/docs/RXVpdYJTGwQRjtkQ/](https://shimo.im/docs/RXVpdYJTGwQRjtkQ/)《java8 HashMap 源码分析》,可复制链接后用石墨文档 App 或小程序打开 + +2、有效的字母异位词 + +给定两个字符串*s*和*t*,编写一个函数来判断*t*是否是*s*的字母异位词。 + +异位词:长度一样,包含的字母都一样,每个字符出现的频率也一样,只是顺序不同而已 + +思路: + +(1)、将字符串转换为字符数组 + +(2)、对字符数组进行排序 + +(3)、比较排序后的字符数组,如果相等就是异位词 + +```plain +public boolean isAnagram1(String s, String t) { + if (s.isEmpty() && t.isEmpty()) { + return true; + } + if (s.length() != t.length()) { + return false; + } + char[] sChars = s.toCharArray(); + char[] tChars = t.toCharArray(); + Arrays.sort(sChars); + Arrays.sort(tChars); + return Arrays.equals(sChars, tChars); +} +``` +3、两数之和 +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + +来源:力扣(LeetCode) + +链接:[https://leetcode-cn.com/problems/two-sum](https://leetcode-cn.com/problems/two-sum) + +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + +思路:a+b = target , 及 a = target - b ,也就是说只要在整数数组中找到于 target - nums[i] 相等数的下标就可以了,使用map集合,用数组中的数作为key,相对应的下标作为value,循环遍历寻找是否存在key为target - nums[i]的元素 + +```plain +public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); + } + return new int[0]; +} +``` +4、N叉树的前序遍历 +前序:根-子 + +递归: + +```plain +public List preorder(Node root) { + List res = new ArrayList<>(); + order(root, res); + return res; +} +private void order(Node root, List res) { + if (root == null) { + return; + } + res.add(root.val); + for (Node node : root.children) { + order1(node, res); + } +} +``` +5、字母异位词分组 +思路:排序+哈希 + +1)、将字符串转化为字符数组并排序 + +2)、将异位词作为key值,对比转换后的每一个字符串是否是异位词,是的存相同key值下的集合中,不同存新创建的集合中 + +```plain +public List> groupAnagrams(String[] strs) { + if (strs.length == 0) { + return new ArrayList<>(); + } + Map> map = new HashMap<>(); + for (String s : strs) { + char[] chars = s.toCharArray(); + Arrays.sort(chars); + String key = String.valueOf(chars); + if (!map.containsKey(key)) { + map.put(key, new ArrayList<>()); + } + map.get(key).add(s); + } + return new ArrayList<>(map.values()); +} +``` +6、二叉树的中序遍历 +递归思想:中序,左-根-右 + +时间复杂度:O(N) + +```plain +public List inorderTraversal1(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + inorder2(root.left, res); + res.add(root.val); + inorder2(root.right, res); +} +``` +7、二叉树的前序遍历 +前序:根-左-右 + +递归思想:递归三要素:结束条件、递归体、返回值 + +```plain +public List inorderTraversal1(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + res.add(root.val); + inorder2(root.left, res); + inorder2(root.right, res); +} +``` +8、 diff --git "a/Week_02/java8-HashMap-\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/Week_02/java8-HashMap-\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 00000000..8dcbeab7 --- /dev/null +++ "b/Week_02/java8-HashMap-\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,363 @@ +基于Hash table 实现 Map 接口映射,允许 key 与 value 为 null 值。在 Java 1.7 版本中是用数组+链表的数据结构实现的,1.8之后使用数组+链表+红黑树的数据结构实现。 + +HashMap 是非同步的,也就是说是线程不安全的,通常是通过在自然封装HashMap的某个对象上同步来实现同步的。如果不存在这样的对象,则应该使用集合“包装”HashMap,synchronizedMap方法。 + +```plain +Map m = Collections.synchronizedMap(new HashMap(...)); +``` +#### 数据结构 + +![图片](https://uploader.shimo.im/f/EmIg2mjkOX0VD3It.png!thumbnail?fileGuid=RXVpdYJTGwQRjtkQ) + + +--- + + +图中数组,也就是bucket(桶),数组的长度,也就是桶的数量: + +```plain +/** + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ +transient Node[] table; +``` + +--- +#### + +数组中存放的链表节点,HashMap中是以 Node 节点数组类型存放数据的。当 + +```plain +/** + * Basic hash bin node, used for most entries. (See below for + * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) + */ +static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + Node(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + public final K getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value; } + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + public final boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry)o; + if (Objects.equals(key, e.getKey()) && + Objects.equals(value, e.getValue())) + return true; + } + return false; + } +} +``` + +--- +#### + +当链表中的元素超过了8个以后,会将链表转换为红黑树,其实最终继承的还是HashMap.Node,因为1.7采用的是数组+链表方式的数据结构,hash冲突后是用单链表进行的纵向延伸,使用头插法来提高插入的效率,但是容易并发出现逆序且环形链表死循环的问题,1.8之后采用红黑树尾插法,能够避免出现此问题。 + +当红黑树中节点数小于 6 个时又会转为单链表来存储数据。 + +```plain +/** + * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn + * extends Node) so can be used as extension of either regular or + * linked node. + */ +static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent;  // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev;    // needed to unlink next upon deletion + boolean red; + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + /** + * Returns root of tree containing this node. + */ + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } + } + ... +} +``` +#### 继承关系 + +```plain +Class HashMap + java.lang.Object + java.util.AbstractMap + java.util.HashMap +Type Parameters: +K - the type of keys maintained by this map +V - the type of mapped values +All Implemented Interfaces: +Serializable, Cloneable, Map +Direct Known Subclasses: +LinkedHashMap, PrinterStateReasons +``` +#### 构造方法 + +* HashMap() , Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75). +* HashMap(int initialCapacity) , Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75). +* HashMap(int initialCapacity , float loadFactor) , Constructs an empty HashMap with the specified initial capacity and load factor. +* HashMap(Map>|[entrySet](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#entrySet())()|Returns a[Set](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Set.html)view of the mappings contained in this map.| +|[V](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)|[get](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#get(java.lang.Object))​([Object](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Object.html)key)|Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.| +|boolean|[isEmpty](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#isEmpty())()|Returns true if this map contains no key-value mappings.| +|[Set](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Set.html)<[K](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)>|[keySet](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#keySet())()|Returns a[Set](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Set.html)view of the keys contained in this map.| +|[V](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)|[put](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#put(K,V))​([K](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)key,[V](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)value)|Associates the specified value with the specified key in this map.| +|void|[putAll](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#putAll(java.util.Map))​([Map](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Map.html) m)|Copies all of the mappings from the specified map to this map.| +|[V](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html)|[remove](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#remove(java.lang.Object))​([Object](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Object.html)key)|Removes the mapping for the specified key from this map if present.| +|int|[size](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/HashMap.html#size())()|Returns the number of key-value mappings in this map.| + +#### 重要方法解析 + +```plain +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 如果为null,则扩容 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // 如果tab对应的数组位置上的数据为null,则新建node,并指向它 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + // 比较hash值和key值都相等,说明插入的键值已经存在,给e节点赋值为p + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + // P 节点是TreeNode类型,执行树节点的插入操作 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + // 遍历插入单链表中,采用尾插法 + else { + for (int binCount = 0; ; ++binCount) { + // 尾插法,遍历到最后一个next节点为null的节点,将next节点赋值为新创建的节点 + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + // 这里判断,当单链表节点数大于等于树化阈值,则转化为红黑树存储,-1是因为 binCount 下标是从 0 开始的。 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + // 在桶中找到了对应的key,赋值给e,退出循环 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + // 没有找到,则继续向下一个节点寻找 + p = e; + } + } + // 根据onlyIfAbsent来判断是否需要修改旧值 + if (e != null) { // existing mapping for key + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + //钩子函数,用于给LinkedHashMap继承后使用,在HashMap里是空的 + afterNodeAccess(e); + return oldValue; + } + } + // 修改计数器大小,+1 + ++modCount; + // 实际条目数+1 ,如果大于阈值,需要重新计算并扩容 + if (++size > threshold) + resize(); + //钩子函数,用于给LinkedHashMap继承后使用,在HashMap里是空的 + afterNodeInsertion(evict); + return null; +} +``` + +--- + + +```plain +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + // 先检查数组下标第一个节点是否满足,如果满足,直接返回 + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + // 数组第一个节点不满足,则循环遍历桶中元素 + if ((e = first.next) != null) { + // 如果是树节点,则采用树节点的方式来获取对应key的值 + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + // 否则就遍历单链表中元素,直到找到为止 + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; +} +``` + +--- + + +```plain +final Node[] resize() { + // 记录当前数组 + Node[] oldTab = table; + // 记录当前数组长度 + int oldCap = (oldTab == null) ? 0 : oldTab.length; + // 记录当前阈值 + int oldThr = threshold; + // 使用变量newCap记录新数组的长度,newThr记录新阈值 + int newCap, newThr = 0; + if (oldCap > 0) { + //容量达到最大容量,则直接将阈值设置为最大值,并返回旧table + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + // 在旧table容量的基础上扩容两倍,但是扩容后的容量必须小于最大容量,并且在旧容量大于默认初始化容量时将旧阈值也扩大至旧阈值的两倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + // 当容量 = 0 && 阈值 > 0 时将容量设置为阈值大小 + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + // 此情况应该是调用无参构造时的初始化操作 + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + // 新阈值==0,将其赋值为 新容量*加载因子乘积大小且最大值为Integer.MAX_VALUE + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + // 将当前阈值修改为新阈值 + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + // 将当前table修改为使用新容量创建的新table + table = newTab; + // 下面逻辑是为了将旧table中的数据迁移至新table中 + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + // 将旧table中元素赋值给临时变量 e 节点,并判断是否为null + if ((e = oldTab[j]) != null) { + // 清空旧table中数据 + oldTab[j] = null; + // e 节点next节点为null,则将e节点重新hash后赋值为新table中的节点数据 + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + // 如果节点e为树节点则执行树节点的拆分方法 + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + //关于下面这段代码执行逻辑请参考下面链接地址(1) + // 大概意思就是将原链表按(e.hash & oldCap) == 0 条件拆分为了两个链表lo和hi,然后将lo链表对应在新表的下标位置与旧表一致的 j 下标下,将hi链表对应在新表的下标 j + oldCap 的下标下 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; +} +``` +(1)[https://segmentfault.com/a/1190000015812438?utm_source=tag-newest](https://segmentfault.com/a/1190000015812438?utm_source=tag-newest) diff --git "a/Week_02/\345\223\210\345\270\214\350\241\250\343\200\201\346\230\240\345\260\204\343\200\201\351\233\206\345\220\210\347\232\204\345\256\236\347\216\260\344\270\216\347\211\271\346\200\247.md" "b/Week_02/\345\223\210\345\270\214\350\241\250\343\200\201\346\230\240\345\260\204\343\200\201\351\233\206\345\220\210\347\232\204\345\256\236\347\216\260\344\270\216\347\211\271\346\200\247.md" new file mode 100644 index 00000000..47987795 --- /dev/null +++ "b/Week_02/\345\223\210\345\270\214\350\241\250\343\200\201\346\230\240\345\260\204\343\200\201\351\233\206\345\220\210\347\232\204\345\256\236\347\216\260\344\270\216\347\211\271\346\200\247.md" @@ -0,0 +1,48 @@ +### Hash table + +哈希表(Hash table),也叫散列表,是根据关键码值(Key value)而直接进行访问的数据结构。 + +它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。 + +这个映射函数叫做散列函数(Hash Function),存放记录的数组叫作哈希表(或散列表)。 + +相比其他数据结构(数组、链表、二叉树),在哈希表中进行添加、删除、查询等操作,性能十分高效,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。 + +我们知道数据结构的物理存储方式只有两种形式:顺序存储和链式存储(像栈、队列、树、图等从逻辑结构去抽象的,映射到内存中,也是这两种物理存储形式)。哈希表利用了数组中根据下标查找某个元素,一次就能定位的原理实现了快速查询的功能,也就是说Hash table 底层就是数组实现的,比如新增或查找某个元素时只需要根据此元素的关键字通过hash函数映射到数组的某个位置,再通过数组下标定位指定元素完成操作。 + +java 中需要重载hashCode()方法来实现hash。 + +![图片](https://uploader.shimo.im/f/FBiNii2eeQ756jq7.png!thumbnail?fileGuid=Vxj3WX9ppkp8jQwr) + +哈希碰撞(发生hash碰撞时添加链表来解决) + +![图片](https://uploader.shimo.im/f/Kf3QUcCpqylf1wtR.png!thumbnail?fileGuid=Vxj3WX9ppkp8jQwr) + +![图片](https://uploader.shimo.im/f/p3rJn3IYOCFYNBmv.png!thumbnail?fileGuid=Vxj3WX9ppkp8jQwr) + +实际开发中使用到Hash table的数据结构的有Map与Set集合 + +* Map:key-value 对, key不重复 ,比如HashMap 、TreeMap +* Set:不重复元素的集合,比如 HashSet 、TreeSet +#### HashMap源码分析: + +[https://docs.oracle.com/javase/8/docs/api/index.html](https://docs.oracle.com/javase/8/docs/api/index.html) + +继承关系: + +```plain +Class HashMap +java.lang.Object + java.util.AbstractMap + java.util.HashMap +Type Parameters: +K - the type of keys maintained by this map +V - the type of mapped values +All Implemented Interfaces: +Serializable, Cloneable, Map +Direct Known Subclasses: +LinkedHashMap, PrinterStateReasons +``` +参考博客:[https://www.cnblogs.com/joemsu/p/7724623.html](https://www.cnblogs.com/joemsu/p/7724623.html) +参考博客:[https://www.cnblogs.com/myseries/p/10876828.html](https://www.cnblogs.com/myseries/p/10876828.html) + diff --git "a/Week_02/\345\240\206\345\222\214\344\272\214\345\217\211\345\240\206\347\232\204\345\256\236\347\216\260\345\222\214\347\211\271\346\200\247.md" "b/Week_02/\345\240\206\345\222\214\344\272\214\345\217\211\345\240\206\347\232\204\345\256\236\347\216\260\345\222\214\347\211\271\346\200\247.md" new file mode 100644 index 00000000..49b51407 --- /dev/null +++ "b/Week_02/\345\240\206\345\222\214\344\272\214\345\217\211\345\240\206\347\232\204\345\256\236\347\216\260\345\222\214\347\211\271\346\200\247.md" @@ -0,0 +1,28 @@ +堆 Heap + +Heap:可以迅速找到一堆数中的最大或者最小值的数据结构。 + +将根节点最大的堆叫做大顶堆或大根堆,根节点最小的堆叫做小顶堆或小根堆。 + +常见的堆有二叉堆、斐波那契堆等。 + +二叉堆性质 + +通过完全二叉树来实现(注意:不是二叉搜索树) + +二叉树它满足下列性质: + +1、是一颗完全树; + +2、树中任意节点的值总是>=其子节点的值; + +二叉堆 + +![图片](https://uploader.shimo.im/f/TG0qg9xVcCPTR9KI.png!thumbnail?fileGuid=thRkRcRHpJ6x63kD) + +二叉堆实现细节 + +1、二叉堆一般都通过“数组”来实现 + +2、假设 + diff --git "a/Week_02/\346\240\221\343\200\201\344\272\214\345\217\211\346\240\221\343\200\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/Week_02/\346\240\221\343\200\201\344\272\214\345\217\211\346\240\221\343\200\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" new file mode 100644 index 00000000..aee09648 --- /dev/null +++ "b/Week_02/\346\240\221\343\200\201\344\272\214\345\217\211\346\240\221\343\200\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" @@ -0,0 +1,38 @@ +#### 树 Tree + +![图片](https://uploader.shimo.im/f/WeV0cCocuFckNBDF.png!thumbnail?fileGuid=QVcRPw93VTQQvqdY) + +二叉树 Binary Tree + +![图片](https://uploader.shimo.im/f/AgkmyadOA90ubSe5.png!thumbnail?fileGuid=QVcRPw93VTQQvqdY) + +图 Graph + +![图片](https://uploader.shimo.im/f/YNZ7p2bWa7YAWM7Y.png!thumbnail?fileGuid=QVcRPw93VTQQvqdY) + +Linked List 是特殊化的Tree + +Tree 是特殊化的Graph + +二叉树遍历: + +1、前序遍历(Pre-order): 根-左-右 + +2、中序遍历(In-order):左-根-右 + +3、后序遍历(Post-order):左-右-根 + +#### 二叉搜索树 Binary Search Tree + +![图片](https://uploader.shimo.im/f/v5tIMXmGjFHJgvAM.png!thumbnail?fileGuid=QVcRPw93VTQQvqdY) + +二叉搜索树,也称二叉排序树、有序二叉树、排序二叉树,是指一颗空树或者具有下列性质的二叉树: + +1、左子树上所有结点的值均小于它的根结点的值; + +2、右子树上所有结点的值均大于它的根结点的值; + +3、以此类推:左右子树也分别为二叉搜索树 + +中序遍历是升序排列 + diff --git "a/Week_02/\347\256\227\346\263\225\345\256\236\346\210\230-\347\273\203\344\271\240\346\255\245\351\252\244.md" "b/Week_02/\347\256\227\346\263\225\345\256\236\346\210\230-\347\273\203\344\271\240\346\255\245\351\252\244.md" new file mode 100644 index 00000000..5ab80aae --- /dev/null +++ "b/Week_02/\347\256\227\346\263\225\345\256\236\346\210\230-\347\273\203\344\271\240\346\255\245\351\252\244.md" @@ -0,0 +1,480 @@ +1、5-10分钟:读题和思考 + +2、有思路:自己开始做和写代码;不然,马上看题解 + +3、默写背诵、熟练 + +4、然后开始自己写(闭卷) + +### 实战题目: + +#### 283、移动零 + +**题目:**给定一个数组`nums`,编写一个函数将所有`0`移动到数组的末尾,同时保持非零元素的相对顺序。 + +链接:[https://leetcode-cn.com/problems/move-zeroes/](https://leetcode-cn.com/problems/move-zeroes/) + +```plain +public void moveZeroes(int[] nums) { +    int j = 0; +    for (int i = 0; i < nums.length; i++) { +        if (nums[i] != 0) { +            nums[j++] = nums[i]; +        } +    } +    for (int i = j; i < nums.length; i++) { +        nums[i] = 0; +    } +} +``` +#### 11、盛最多水的容器 + +**题目:**给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 + +链接:[https://leetcode-cn.com/problems/container-with-most-water](https://leetcode-cn.com/problems/container-with-most-water) + +第一种方式:枚举所有情况,时间复杂度 O(n^2) + +```plain +public int maxArea(int[] height) { +        int are = 0; +        for (int i = 0; i < height.length - 1; i++) { +            for (int j = i + 1; j < height.length; j++) { +                are = Math.max(are, (j - i) * Math.min(height[i], height[j])); +            } +        } +        return are; +    } +``` +第二种方式:双指针,每次移动高度较小的值,时间复杂度 O(n) +```plain +public int maxArea(int[] height) { +    int i = 0; +    int j = height.length - 1; +    int res = 0; +    while (i < j) { +        res = height[i] < height[j] ? +                Math.max(res, (j - i) * height[i++]) : Math.max(res, (j - i) * height[j--]); +    } +    return res; +} +``` +#### 70、爬楼梯 + +**题目:**假设你正在爬楼梯。需要*n*阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? + +**注意:**给定*n*是一个正整数。 + +解法一: + +```plain +public int climbStairs(int n) { + if (n < 2) { + return 1; + } + int[] dp = new int[n + 1]; + dp[1] = 1; + dp[2] = 2; + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} +``` +解法二: +```plain +public int climbStairs(int n) { + if (n <= 2) { + return n; + } + int f1 = 1, f2 = 2, f3 = 3; + for (int i = 3; i <= n; i++) { + f3 = f1 + f2; + f1 = f2; + f2 = f3; + } + return f3; +} +``` +#### 1.两数之和 + +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + +链接:[https://leetcode-cn.com/problems/two-sum](https://leetcode-cn.com/problems/two-sum) + +暴力法: + +```plain +public int[] twoSum(int[] nums, int target) { + for (int i = 0; i < nums.length - 1; i++) { + for (int j = i + 1; j < nums.length; j++) { + int sum = nums[i] + nums[j]; + if (target == sum) { + return new int[]{i, j}; + } + } + } + return new int[0]; +} +``` +哈希映射法: +```plain +public int[] twoSum(int[] nums, int target) { +   Map map = new HashMap<>(); +    for(int i = 0; i< nums.length; i++) { +        if(map.containsKey(target - nums[i])) { +            return new int[] {map.get(target-nums[i]),i}; +        } +        map.put(nums[i], i); +    } +    throw new IllegalArgumentException("No two sum solution"); +} +``` +#### 15.三数之和 + +给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 + +注意:答案中不可以包含重复的三元组。 + +链接:[https://leetcode-cn.com/problems/3sum](https://leetcode-cn.com/problems/3sum) + +排序+双指针 + +```plain +public List> threeSum(int[] nums) { + if (nums == null || nums.length <= 2) { + return Collections.emptyList(); + } + Arrays.sort(nums); + List> res = new ArrayList<>(); + for (int k = 0; k < nums.length - 2; k++) { + if (nums[k] > 0) { + break; + } + if (k > 0 && nums[k] == nums[k - 1]) { + continue; + } + int i = k + 1; + int j = nums.length - 1; + while (i < j) { + int sum = nums[i] + nums[j] + nums[k]; + if (sum < 0) { + while (i < j && nums[i] == nums[++i]) ; + } + if (sum > 0) { + while (i < j && nums[j] == nums[--j]) ; + } + if (sum == 0) { + res.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[k]))); + while (i < j && nums[i] == nums[++i]) ; + while (i < j && nums[j] == nums[--j]) ; + } + } + } + return res; +} +``` +#### 20.有效的括号 + +给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 + +有效字符串需满足: + +左括号必须用相同类型的右括号闭合。 + +左括号必须以正确的顺序闭合。 + +注意空字符串可被认为是有效字符串。 + +来源:力扣(LeetCode) + +链接:[https://leetcode-cn.com/problems/valid-parentheses](https://leetcode-cn.com/problems/valid-parentheses) + +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + +```plain +public boolean isValid(String s) { +        if (s.isEmpty()) { +            return true; +        } +        // 长度奇数为无效 +        if ((s.length() % 2) != 0) { +            return false; +        } +        // 以右括号开头无效 +        if (s.charAt(0) == ')' || s.charAt(0) == '}' || s.charAt(0) == ']') { +            return false; +        } +        Stack stack = new Stack<>(); +        for (char c : s.toCharArray()) { +            if (c == '{') { +                stack.push('}'); +            } else if (c == '[') { +                stack.push(']'); +            } else if (c == '(') { +                stack.push(')'); +            } else if (stack.isEmpty() || c != stack.pop()) { +                return false; +            } +        } +        return stack.isEmpty(); +    } +``` +#### 155.最小栈 + +设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 + +push(x) —— 将元素 x 推入栈中。 + +pop() —— 删除栈顶的元素。 + +top() —— 获取栈顶元素。 + +getMin() —— 检索栈中的最小元素。 + +来源:力扣(LeetCode) + +链接:[https://leetcode-cn.com/problems/min-stack](https://leetcode-cn.com/problems/min-stack) + +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + +```plain +class MinStack { +    /** initialize your data structure here. */ +    private Stack data; +    private Stack minData; + /** +    * initialize your data structure here. +    */ +    public MinStack() { +        this.data = new Stack<>(); +        this.minData = new Stack<>(); +    } +    public void push(int x) { +        data.push(x); +        if (minData.isEmpty() || minData.peek() >= x) { +            minData.push(x); +        } +    } +    public void pop() { +        if (!data.isEmpty()) { +            int x = data.pop(); +            if (x == minData.peek()) { +                minData.pop(); +            } +        } +    } +    public int top() { +        if (!data.isEmpty()) { +            return data.peek(); +        } +        throw new RuntimeException("data error"); +    } +    public int getMin() { +        if (!minData.isEmpty()) { +            return minData.peek(); +        } +        throw new RuntimeException("data error"); +    } +} +``` +#### 242、有效的字母异位词 + +给定两个字符串*s*和*t*,编写一个函数来判断*t*是否是*s*的字母异位词。 + +[https://leetcode-cn.com/problems/valid-anagram/description/](https://leetcode-cn.com/problems/valid-anagram/description/) + +异位词:长度一样,包含的字母都一样,每个字符出现的频率也一样,只是顺序不同而已 + +哈希法: + +```plain +public boolean isAnagram(String s, String t) { + if (s.isEmpty() && t.isEmpty()) { + return true; + } + if (s.length() != t.length()) { + return false; + } + char[] sChars = s.toCharArray(); + char[] tChars = t.toCharArray(); + Map map = new HashMap<>(); + for (int i = 0; i < sChars.length; i++) { + if (map.containsKey(sChars[i])) { + map.put(sChars[i], map.get(sChars[i]) + 1); + } else { + map.put(sChars[i], 1); + } + } + //System.out.println(map.keySet().toString()); + for (int i = 0; i < tChars.length; i++) { + if (map.containsKey(tChars[i])) { + map.put(tChars[i], map.get(tChars[i]) - 1); + } else { + return false; + } + } + Iterator iterator = map.values().iterator(); + while (iterator.hasNext()) { + if (iterator.next() != 0) { + return false; + } + } + return true; +} +``` +排序法:时间复杂度 O(NlogN) = 排序时间复杂度 O(NlogN) + 判断是否相等时间复杂度 O(N) = O(NlogN + N) +```plain +public boolean isAnagram1(String s, String t) { + if (s.isEmpty() && t.isEmpty()) { + return true; + } + if (s.length() != t.length()) { + return false; + } + char[] sChars = s.toCharArray(); + char[] tChars = t.toCharArray(); + Arrays.sort(sChars); + Arrays.sort(tChars); + return Arrays.equals(sChars, tChars); +} +``` +哈希法优化: +```plain +public boolean isAnagram(String s, String t) { + if (s.isEmpty() && t.isEmpty()) { + return true; + } + if (s.length() != t.length()) { + return false; + } + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + map.put(c, map.getOrDefault(c, 0) + 1); + } + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + map.put(c, map.getOrDefault(c, 0) - 1); + if (map.get(c) < 0) { + return false; + } + } + return true; +} +``` +#### 49.字母异位词分组 + +给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 + +链接:[https://leetcode-cn.com/problems/group-anagrams/](https://leetcode-cn.com/problems/group-anagrams/) + +**说明:** + +* 所有输入均为小写字母。 +* 不考虑答案输出的顺序。 + +排序+哈希 + +思路: + +1、将每个字符串转换成字符数组然后排序 + +2、将排序后的字符串作为map中的key值存入map集合,value值是一个字符串list集合 + +3、对比每一个排序后的字符串,如果存在就取出原list集合存入此字符串,不存在就新建list集合存入 + +4、最后将map中所有values转化为list集合返回 + +```plain +public List> groupAnagrams(String[] strs) { + if (strs == null || strs.length == 0) { + return new ArrayList<>(); + } + Map> map = new HashMap<>(); + for (int i = 0; i < strs.length; i++) { + char[] chars = strs[i].toCharArray(); + Arrays.sort(chars); + String key = String.valueOf(chars); + if (!map.containsKey(key)) { + map.put(key, new ArrayList<>()); + } + map.get(key).add(strs[i]); + } + return new ArrayList<>(map.values()); +} +``` +#### 94.二叉树的中序遍历 + +给定一个二叉树的根节点`root`,返回它的**中序**遍历。 + +链接:[https://leetcode-cn.com/problems/binary-tree-inorder-traversal/](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) + +中序遍历:左-根-右 + +递归法: + +思路:递归三要素,递归体、结束条件、返回值 + +```plain +public List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + inorder(root.left, res); + res.add(root.val); + inorder(root.right, res); +} +``` +#### 144.二叉树的前序遍历 + +给你二叉树的根节点`root`,返回它节点值的**前序****遍历。 + +链接:[https://leetcode-cn.com/problems/binary-tree-preorder-traversal/](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) + +```plain +public List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; +} +private void inorder(TreeNode root, List res) { + if (root == null) { + return; + } + res.add(root.val); + inorder(root.left, res); + inorder(root.right, res); +} +``` +#### 590.N叉树的后序遍历 + +给定一个 N 叉树,返回其节点值的*后序遍历*。 + +链接:[https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/](https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/) + +递归法 + +```plain +public List postorder(Node root) { +List res = new ArrayList<>(); +order(root, res); +return res; +} +private void order(Node root, List res) { +if (root == null) { +return; +} +for (Node node : root.children) { +order(node, res); +} +res.add(root.val); +} +``` diff --git a/Week_03/homework-week03.md b/Week_03/homework-week03.md new file mode 100644 index 00000000..50fc2ba5 --- /dev/null +++ b/Week_03/homework-week03.md @@ -0,0 +1,151 @@ +#### [剑指 Offer 68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) + +思路:递归 + +三种情况 + +```plain +public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { +// 递归结束 +/** +* 1、root == null +* 2、root == p +* 3、root == q +*/ +if (root == null || root == p || root == q) { +return root; +} +// 根结点的左右子树中查找 +TreeNode left = lowestCommonAncestor(root.left, p, q); +TreeNode right = lowestCommonAncestor(root.right, p, q); +/** +* 1、如果p\q都存在且一个为左结点,一个为右结点,那么根结点root就是最近公共祖先 +* 2、如果p\q都存在且都是左结点,那么在根结点root的左子树中查找,返回的结点left即为最近公共祖先 +* 3、如果p\q都存在且都是右结点,那么在根结点root的右子树中查找,返回的结点right即为最近公共祖先 +* 4、如果p\q在左右子树中都查找不到,则不存在最近公共祖先 +*/ +return left == null ? right : right == null ? left : root; +} +``` +#### [105. 从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) + +#### [50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) + +```plain +// 分治法 +// n个x相乘,分两种情况计算 +// 如果 n 是奇数个则计算 ((n/2)*x)*((n/2)*x)*x +// 如果 n 是偶数个则计算 (n/2)*x)*((n/2)*x) +public double myPow(double x, int n) { +int power = n; +// 特殊情况 +if (n < 0) { +x = 1 / x; +power = -n; +} +return power(x, power); +} +private double power(double x, int power) { +//1.国际惯例,递归结束条件 +if (power == 0) { +return 1.0; +} +//2.递归体 +double result = power(x, power / 2); +//3.返回值 +/*if (power % 2 == 0) { +return result * result; +} else { +return result * result * x; +}*/ +// 简化 +return power % 2 == 0 ? result * result : result * result * x; +} +``` +#### [78. 子集](https://leetcode-cn.com/problems/subsets/) + +![图片](https://uploader.shimo.im/f/TBfnLQZW7k9BEKvU.png!thumbnail?fileGuid=VCQwtJjycJkyWHcD) + +```plain +public static List> subsets(int[] nums) { +List> res = new ArrayList<>(); +if (nums == null) { +return res; +} +dfs(0, res, nums, new ArrayList()); +return res; +} +private static void dfs(int i, List> res, int[] nums, List list) { +if (i == nums.length) { +res.add(new ArrayList<>(list)); +return; +} +// 不选 +dfs(i + 1, res, nums, list); +// 选 +list.add(nums[i]); +dfs(i + 1, res, nums, list); +// 重置 +list.remove(list.size() - 1); +} +``` +#### [77. 组合](https://leetcode-cn.com/problems/combinations/) + +```plain +public List> combine(int n, int k) { +List> res = new ArrayList<>(); +if (n <= 0 || k > n) { +return res; +} +Deque path = new ArrayDeque<>(); +dfs3(n, k, 1, path, res); +return res; +} +private void dfs3(int n, int k, int begin, Deque path, List> res) { +// 结束条件,path 大小等于k +if (k == path.size()) { +res.add(new ArrayList<>(path)); +return; +} +// +for (int i = begin; i <= n - (k - path.size()) + 1; i++) { +//选择一个数字 +path.addLast(i); +//进入下一层 , 起点 + 1 , 保证选过的数字不会被重复选择 +dfs3(n, k, i + 1, path, res); +//回溯到上一层需要清除当前层已选的路径 +path.removeLast(); +} +} +``` +#### [46. 全排列](https://leetcode-cn.com/problems/permutations/) + +```plain +public List> permute(int[] nums) { +List> res = new ArrayList<>(); +if (nums == null || nums.length == 0) { +return res; +} +Deque path = new ArrayDeque<>(); +boolean[] used = new boolean[nums.length]; +dfs4(nums, 0, path, used, res); +return res; +} +private void dfs4(int[] nums, int depth, Deque path, boolean[] used, List> res) { +if (depth == nums.length) { +res.add(new ArrayList<>(path)); +return; +} +for (int i = 0; i < nums.length; i++) { +if (!used[i]) { +path.addLast(nums[i]); +used[i] = true; +// 进入下层 +dfs4(nums, depth + 1, path, used, res); +// 回溯上层前,清除当先层选择的数字 +used[i] = false; +path.removeLast(); +} +} +} +``` diff --git "a/Week_03/\346\200\273\347\273\223-week03.md" "b/Week_03/\346\200\273\347\273\223-week03.md" new file mode 100644 index 00000000..42eed6ba --- /dev/null +++ "b/Week_03/\346\200\273\347\273\223-week03.md" @@ -0,0 +1,28 @@ +#### 递归: + +递归三部曲 + +1、找整个递归的终止条件:递归应该在什么时候结束? + +2、找返回值:应该给上一级返回什么信息? + +3、本级递归应该做什么:在这一级递归中,应该完成什么任务? + +参考博客:[https://lyl0724.github.io/2020/01/25/1/](https://lyl0724.github.io/2020/01/25/1/) + +#### 递归+分治+回溯: + +使用场景:如果一个问题有多个步骤,每一个步骤有多种方法,题目要求我们找出所有方法,可以使用回溯算法 + +回溯算法首先要学会画递归树 + +![图片](https://uploader.shimo.im/f/P9F4bpjZrLx5kI9f.png!thumbnail?fileGuid=9TWyXkg8r88jrgkK) + +课堂笔记: + +回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程 中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将 取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问 题的答案。 回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种 情况: + +• 找到一个可能存在的正确的答案; + +• 在尝试了所有可能的分步方法后宣告该问题没有答案。 在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。 + diff --git a/Week_04/homework-week04.md b/Week_04/homework-week04.md new file mode 100644 index 00000000..b0d67e3b --- /dev/null +++ b/Week_04/homework-week04.md @@ -0,0 +1,176 @@ +#### [860. 柠檬水找零](https://leetcode-cn.com/problems/lemonade-change/) + +思路:贪心策咯 + +找零尽量使用大面值的零钱 + +```plain +public boolean lemonadeChange(int[] bills) { +        int f5 = 0, f10 = 0; +        for (int i = 0; i < bills.length; i++) { +            int money = bills[i]; +            if (money == 5) { +                f5++; +            } else if (money == 10) { +                if (f5 == 0) return false; +                f5--; +                f10++; +            } else { +                if (f5 > 0 && f10 > 0) { +                    f5--; +                    f10--; +                } else if (f5 >= 3) { +                    f5 -= 3; +                } else { +                    return false; +                } +            } +        } +        return true; +    } +``` +#### [122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) + +思路:贪心算法,股票赚钱就是低买高卖赚差价 + +```plain +class Solution { +    public int maxProfit(int[] prices) { +        int profit = 0; +        for (int i = 1; i < prices.length; i++) { +            int diff = prices[i] - prices[i - 1]; +            if (diff > 0) +                profit += diff; +        } +        return profit; +    } +} +``` +#### [455. 分发饼干](https://leetcode-cn.com/problems/assign-cookies/) + +思路:贪心算法,尽量用大尺寸的饼干先满足胃口大的孩子,充分利用资源 + +```plain +class Solution { +    public int findContentChildren(int[] g, int[] s) { +        Arrays.sort(g); +        Arrays.sort(s); +        int index = s.length - 1;// 饼干数组下标 +        int count = 0; +        for (int i = g.length - 1; i >= 0; i--) { +            if (index >= 0 && g[i] <= s[index]) { +                count++; +                index--; +            } +        } +        return count; +    } +} +``` +#### [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) + +![图片](https://uploader.shimo.im/f/WiBi0PdSUSLMa3NT.png!thumbnail?fileGuid=j9qhrPWt3QKhWkr9) + +思路: + +深度优先搜索DFS + +主循环:循环遍历每一个格子,判断grid[i][j]== '1'时说明此方格代表一个岛屿,count++,然后以此方格为基点做深度优先搜索将相连的方格 grid[i][j]== '1'的都删除,表示他们代表一个岛屿。 + +dfs方法:按左下右上顺序搜索相连的方格,并检查是否满足grid[i][j]== '1',满足则grid[i][j] = '0',表示删除此方格,避免重复搜索;不满足则结束本次搜索; + +终结条件: + +* 方格超出边界 +* 方格grid[i][j] = '0' +```plain +// dfs 深度优先搜索 +public int numIslands(char[][] grid) { + if (grid.length == 0 && grid[0].length == 0) + return 0; + int count = 0;// 岛屿数量 + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == '1') { + count++; + dfs5(grid, i, j); + } + } + } + return count; +} +private void dfs5(char[][] grid, int i, int j) { + // 终结条件 + if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') + return; + grid[i][j] = '0'; + dfs5(grid, i - 1, j);// 左 + dfs5(grid, i, j + 1); // 下 + dfs5(grid, i + 1, j);// 右 + dfs5(grid, i, j - 1);// 上 +} +``` +广度优先搜索 +```plain +// bfs 广度优先搜索 +public int numIslands(char[][] grid) { + if (grid.length == 0 && grid[0].length == 0) + return 0; + int count = 0;// 岛屿数量 + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == '1') { + count++; + dfs6(grid, i, j); + } + } + } + return count; +} +private void dfs6(char[][] grid, int i, int j) { + Queue queue = new LinkedList<>(); + queue.add(new int[]{i, j}); + while (!queue.isEmpty()) { + int[] cur = queue.remove(); + i = cur[0]; + j = cur[1]; + // 判断边界 + if (i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == '1') { + grid[i][j] = '0'; + queue.add(new int[]{i - 1, j}); + queue.add(new int[]{i + 1, j}); + queue.add(new int[]{i, j - 1}); + queue.add(new int[]{i, j + 1}); + } + } +} +``` +#### [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) + +```plain +public int search(int[] nums, int target) { +int left = 0, right = nums.length - 1, mid; +while (left <= right) { +mid = left + (right - left) / 2; +if (target == nums[mid]) { +return mid; +} +// 前半部分有序 +if (nums[left] <= nums[mid]) { +if (target >= nums[left] && target < nums[mid]) { +right = mid - 1; +} else { +left = mid + 1; +} +} else { +if (target > nums[mid] && target <= nums[right]) { +left = mid + 1; +} else { +right = mid - 1; +} +} +} +return -1; +} +``` +#### diff --git "a/Week_04/\346\200\273\347\273\223/\344\272\214\345\210\206\346\237\245\346\211\276.md" "b/Week_04/\346\200\273\347\273\223/\344\272\214\345\210\206\346\237\245\346\211\276.md" new file mode 100644 index 00000000..9c932e97 --- /dev/null +++ "b/Week_04/\346\200\273\347\273\223/\344\272\214\345\210\206\346\237\245\346\211\276.md" @@ -0,0 +1,27 @@ +二分查找的前提 + +1、目标函数单调性(单调递增或者递减) + +2、存在上下界 + +3、能够通过索引访问 + +代码模板 + +```plain +public int binarySearch(int[] array, int target) { + int left = 9, right = array.length - 1, mid; + while (left <= right) { + mid = (right -left) + left / 2; + if (target == array[mid]) { + // find the terget + return mid;// or return result + } else if (target > array[mid]) { + left = mid + 1; + } else { + right = mid - 1; + } + } +return -1; +} +``` diff --git "a/Week_04/\346\200\273\347\273\223/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\343\200\201\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242.md" "b/Week_04/\346\200\273\347\273\223/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\343\200\201\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242.md" new file mode 100644 index 00000000..5e555466 --- /dev/null +++ "b/Week_04/\346\200\273\347\273\223/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\343\200\201\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242.md" @@ -0,0 +1,10 @@ +推荐查阅《我的第一本算法书》作者:[日]宫崎修一,石田保辉 + +第四章 图的搜索 其中4-2、4-3分别讲了广度优先搜索与深度优先搜索 + +写的很棒,都是以图片的方式讲解,很容易理解。 + +[广度优先搜索.mp4](https://uploader.shimo.im/f/PjKm9vxXCgTXvyBd.mp4?fileGuid=9hkRCy6VypQ38wkr) + + + diff --git "a/Week_04/\346\200\273\347\273\223/\350\264\252\345\277\203\347\256\227\346\263\225.md" "b/Week_04/\346\200\273\347\273\223/\350\264\252\345\277\203\347\256\227\346\263\225.md" new file mode 100644 index 00000000..dd456b25 --- /dev/null +++ "b/Week_04/\346\200\273\347\273\223/\350\264\252\345\277\203\347\256\227\346\263\225.md" @@ -0,0 +1,8 @@ +贪心算法是一种在每一步选择中都采取在当下状态下最好或最优(最有利)的选择,从而希望导致结果是全局最好或最优的算法。 + +贪心算法与动态规划的不同在于它对每个字问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 + +适用场景:问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。这种子问题最优解称为最优子结构。 + +参考博客[https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) + diff --git a/Week_07/homework&summary-week07.md b/Week_07/homework&summary-week07.md new file mode 100644 index 00000000..4b7b0974 --- /dev/null +++ b/Week_07/homework&summary-week07.md @@ -0,0 +1,644 @@ +7. 字典树和并查集 +8. 字典树的数据结构 + +字典树,即Trie树,又称单词查找或键树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。 + +优点:最大限度地减少无谓的字符串比较,查询效率比哈希表高。 + +1. 字典树的核心思想 + +空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 + +1. 字典树的基本性质 + +1、结点本身不存完整单词; + +2、从根结点到某结点,路径上经过的字符连接起来,为该结点对应的字符串; + +3、每个结点的所有子结点路径代表的字符都不相同。 + +字典树代码模板: + +```java +class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if (word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for (int i = 0;i < words.length;i++) { +            int n = words[i] - 'a'; +            if (curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word) { +        Trie node = this; +        char[] words = word.toCharArray(); +        for (int i = 0;i < words.length;i++) { +            node = node.next[words[i] - 'a']; +            if (node == null) return null; +        } +        return node; +    } +} +``` +[208. 实现 Trie (前缀树)](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) +```java +class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if(word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++){ +            int n = words[i] - 'a'; +            if(curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word){ +        Trie node = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++) { +            node = node.next[words[i] - 'a']; +            if(node == null) return null; +        } +        return node; +    } +} +/** + * Your Trie object will be instantiated and called as such: + * Trie obj = new Trie(); + * obj.insert(word); + * boolean param_2 = obj.search(word); + * boolean param_3 = obj.startsWith(prefix); + */ +``` +[212. 单词搜索 II](https://leetcode-cn.com/problems/word-search-ii/) +经典题型,使用字典树+dfs,逻辑思维简单易懂 + +```java +class Solution { +    private static final int[] dx = new int[]{0,0,-1,1}; +    private static final int[] dy = new int[]{-1,1,0,0}; +    public List findWords(char[][] board, String[] words) { +        if(words.length == 0) return new ArrayList<>(); +        // 构建字典树 +         Trie root = new Trie(); +        for(String word : words){ +            root.insert(word); +        } +        // 遍历board + dfs递归 向四周扩撒构建字符串,并在字典树中查找是否存在此字符串 +        Set res = new HashSet<>(); +        int m = board.length,n = board[0].length; +        for(int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                dfs(board,root,i,j,"",res); +            } +        } +        return new ArrayList(res); +    } +    public void dfs(char[][] board, Trie root, int i, int j, String currStr, Set res){ +        // 递归结束条件,边界处理以及访问过的字符不能再次访问 +        if(i < 0 || j < 0 || i == board.length || j == board[0].length || board[i][j] == '@') return; +        // 递归结束条件 2 +        char currChar = board[i][j]; +        if(root.next[currChar - 'a'] == null) return; +        // 处理当前层 +        char temp = currChar; +        board[i][j] = '@'; +        // 构建字符串 +        currStr += currChar; +        root = root.next[currChar - 'a']; +        if(root.isEnd){ +            res.add(currStr); +        } +        // 向上下左右递归下探 +        for(int k = 0; k < dx.length; k++){ +            dfs(board,root,i + dx[k], j + dy[k],currStr,res); +        } +        // 将字符串置为空字符串 +        currStr = ""; +        // 恢复当前层字符 +        board[i][j] = temp; +    } +    class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if(word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++){ +            int n = words[i] - 'a'; +            if(curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word){ +        Trie node = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++) { +            node = node.next[words[i] - 'a']; +            if(node == null) return null; +        } +        return node; +    } +} +} +``` +1. 并查集数据结构:跟树有些类似,只不过和树是相反的。在树这个数据结构里面,每个结点会记录它的子结点。在并查集里,每个结点会记录它的父节点; + +![图片](https://uploader.shimo.im/f/qg8SH3yyfOwN2Lmx.png!thumbnail?fileGuid=6KwxkCvh9X9JXDjd) + +1. 并查集适用场景:组团、配对问题 +2. 基本操作 + * makeSet(s):建立一个新的并查集,其中包含s个单元素集合。 + * unionSet(x,y):把元素x和元素y所在的集合合并,要求x和y所在的集合不相交,如果相交则不合并。 + * find(x):找到元素x所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将他们各自的代表比较一下就可以了。 + +[参考链接](https://zhuanlan.zhihu.com/p/93647900/) + +代码模板: + +```java +class UnionFind { + private int count = 0; + private int[] parent; + public UnionFind(int n) { + count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + public int find(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + if (rootP == rootQ) return; + parent[rootP] = rootQ; + count--; + } +} +``` +[547. 省份数量](https://leetcode-cn.com/problems/number-of-provinces/) +思路:DFS 深度优先搜索 + +```java +class Solution { +    public int findCircleNum(int[][] isConnected) { +        if(isConnected.length == 0) return 0; +        boolean[] statistics = new boolean[isConnected.length]; +        int res = 0; +        for(int i = 0; i < isConnected.length; i++){ +            if(!statistics[i]){ +                dfs(isConnected,statistics,i); +                res++; +            } +        } +        return res; +    } +    public void dfs(int[][] isConnected,boolean[] statistics, int i){ +        for(int j = 0; j < isConnected.length; j++){ +            if(isConnected[i][j] == 1 && !statistics[j]){ +                statistics[j] = true; +                dfs(isConnected,statistics,j); +            } +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { + public int findCircleNum(int[][] isConnected) { + if(isConnected.length == 0) return 0; + boolean[] statistics = new boolean[isConnected.length]; + int res = 0; + Queue queue = new LinkedList<>(); + for(int i = 0; i < isConnected.length; i++){ + if(!statistics[i]){ + queue.offer(i); + res++; + while(!queue.isEmpty()){ + int m = queue.poll(); + for(int n = 0; n < isConnected.length; n++){ + if(isConnected[m][n] == 1 && !statistics[n]){ + queue.offer(n); + statistics[n] = true; + } + } + } + } + } + return res; + } +} +``` +思路:并查集 +```java +class Solution { +    public int findCircleNum(int[][] isConnected) { +        if(isConnected.length == 0) return 0; +        // 初始化并查集 +        UnionFind union = new UnionFind(isConnected.length); +        // 遍历isConnected中每个关联关系,有相连的城市合并为相同的省份 +        for(int i = 0; i < isConnected.length; i++){ +            for(int j = 0; j < isConnected.length; j++){ +                if(isConnected[i][j] == 1){ +                    union.union(i,j); +                } +            } +        } +        return union.count; +    } +    class UnionFind { +        private int count = 0; +        private int[] parent; +        public UnionFind(int n) { +            count = n; +            parent = new int[n]; +            for (int i = 0; i < n; i++) { +                parent[i] = i; +            } +        } +        public int find(int p) { +            while (p != parent[p]) { +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        public void union(int p, int q) { +            int rootP = find(p); +            int rootQ = find(q); +            if (rootP == rootQ) return; +            parent[rootP] = rootQ; +            count--; +        } +    } +} +``` +[200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) +思路:DFS 深度优先搜索算法 + +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        int count = 0; +        for (int i = 0; i < grid.length; i++) { +            for (int j = 0; j < grid[0].length; j++) { +                if (grid[i][j] == '1') { +                    count++; +                    dfs(grid, i, j); +                } +            } +        } +        return count; +    } +    public void dfs(char[][] grid, int i, int j){ +        if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return; +        grid[i][j] = '0'; +        for(int k = 0; k < dx.length; k++){ +            dfs(grid,i+dx[k],j+dy[k]); +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        int count = 0; +        for (int i = 0; i < grid.length; i++) { +            for (int j = 0; j < grid[0].length; j++) { +                if (grid[i][j] == '1') { +                    count++; +                    bfs(grid, i, j); +                } +            } +        } +        return count; +    } +    public void bfs(char[][] grid, int i,int j){ +        Queue queue = new LinkedList<>(); +        queue.offer(new int[]{i,j}); +        while(!queue.isEmpty()){ +            int[] curr = queue.poll(); +            i = curr[0]; +            j = curr[1]; +            if(i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == '1'){ +                grid[i][j] = '0'; +                queue.offer(new int[]{i,j-1}); +                queue.offer(new int[]{i,j+1}); +                queue.offer(new int[]{i-1,j}); +                queue.offer(new int[]{i+1,j}); +            } +        } +    } +} +``` +思路:并查集 +```java +class Solution { +    private int m,n; +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        m = grid.length; +        n = grid[0].length; +        UnionFind union = new UnionFind(m*n); +        int spaces = 0;// 空地的数量 +        int[][] direction = new int[][]{{1,0},{0,1}}; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                if (grid[i][j] == '0') { +                    spaces++; +                }else{ +                    for(int[] dire : direction){ +                        int newx = i + dire[0]; +                        int newy = j + dire[1]; +                        // 判断边界 +                        if(newx < m && newy < n && grid[newx][newy] == '1'){ +                            // 合并陆地 +                            union.union(getIndex(i,j),getIndex(newx,newy)); +                        } +                    } +                } +            } +        } +        return union.getCount() - spaces; +    } +    public int getIndex(int i,int j){ +        return i*n + j; +    } +    class UnionFind{ +        private int count; +        private int[] parent; +        UnionFind(int n){ +            count = n; +            parent = new int[n]; +            for(int i = 0; i < n; i++){ +                parent[i] = i; +            } +        } +        private int find(int p){ +            while(p != parent[p]){ +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        private void union(int p, int q){ +            int P = find(p); +            int Q = find(q); +            if(P == Q) return; +            parent[P] = Q; +            count--; +        } +        public int getCount(){ +            return count; +        } +    } +} +``` +[130. 被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/) +思路:DFS 深度优先搜索 + +1、遍历 board ,从边界为‘O’的开始递归,查找相联通的‘O’ + +2、将与边界相联通的‘O’转换为‘#’,剩余‘O’就是被‘X’包围的 + +3、再次遍历 board,将‘#’转换为原来的‘O’,将‘O’转换为‘X’ + +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public void solve(char[][] board) { +        if (board.length == 0) return; +        int m = board.length,n = board[0].length; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                // 首先从边界为‘O’的开始递归下探,将与边界‘O’联通的‘O’都替换为‘#’ +                boolean isEdge = i == 0 || j == 0 || i == m-1 || j == n-1; +                if(isEdge && board[i][j] == 'O'){ +                    dfs(board,i,j); +                } +            } +        } +        // 将递归后的结果进行转换,‘#’代表和边界联通的,剩余的‘O’代表和边界不连通,需要转换为‘X’ +        for (int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                if(board[i][j] == '#'){ +                    board[i][j] = 'O'; +                }else if(board[i][j] == 'O'){ +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public void dfs(char[][] board, int i, int j){ +        // 递归结束条件 +        if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') return; +        board[i][j] = '#'; +        // 左右上下,递归下探 +        for (int k = 0; k < dx.length; k++){ +            dfs(board,i+dx[k],j+dy[k]); +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public void solve(char[][] board) { +        if (board.length == 0) return; +        int m = board.length,n = board[0].length; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                // 首先从边界为‘O’的开始递归下探,将与边界‘O’联通的‘O’都替换为‘#’ +                boolean isEdge = i == 0 || j == 0 || i == m-1 || j == n-1; +                if(isEdge && board[i][j] == 'O'){ +                    bfs(board,i,j); +                } +            } +        } +        // 将递归后的结果进行转换,‘#’代表和边界联通的,剩余的‘O’代表和边界不连通,需要转换为‘X’ +        for (int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                if(board[i][j] == '#'){ +                    board[i][j] = 'O'; +                }else if(board[i][j] == 'O'){ +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public void bfs(char[][] board, int i,int j){ +        Queue queue = new LinkedList<>(); +        queue.offer(new int[]{i,j}); +        while(!queue.isEmpty()){ +            int[] curr = queue.poll(); +            i = curr[0]; +            j = curr[1]; +            if(i >= 0 && i < board.length && j >= 0 && j < board[0].length && board[i][j] == 'O'){ +                board[i][j] = '#'; +                for (int k = 0; k < dx.length; k++){ +                    queue.offer(new int[]{i+dx[k],j+dy[k]}); +                } +            } +        } +    } +} +``` +思路:并查集 +1、首先创建一个虚拟结点,将所有在边界上为‘O’的点都联通到虚拟结点上; + +2、然后再将非边界上的‘O’按是否与边界元素‘O’相连分组; + +3、按分组后的情况,将与边界‘O’相连的即就是与虚拟结点相连的都转换为‘O’,不与边界‘O’相连的说明就是被‘X’包围的,转换为‘X’; + +```java +class Solution { +    private int[] dx = new int[]{0,0,-1,1}; +    private int[] dy = new int[]{-1,1,0,0}; +    private int rows,clos; +    public void solve(char[][] board) { +        rows = board.length; +        if (rows == 0) return; +        clos = board[0].length; +        UnionFind union = new UnionFind(rows*clos+1); +        int dummyNode = rows*clos; +        for (int i = 0; i < rows; i++){ +            for(int j = 0; j < clos; j++){ +                if (board[i][j] == 'O'){ +                    if (i == 0 || j == 0 || i == rows - 1 || j == clos - 1){ +                        union.union(getIndex(i,j),dummyNode); +                    }else{ +                        for(int k = 0; k < dx.length; k++){ +                            int currI = i + dx[k]; +                            int currJ = j + dy[k]; +                            if (currI >= 0 && currI < rows && currJ >= 0 && currJ < clos && board[currI][currJ] == 'O'){ +                                union.union(getIndex(i,j),getIndex(currI,currJ)); +                            } +                        } +                    } +                } +            } +        } +        for (int i = 0; i < rows; i++){ +            for (int j = 0; j < clos; j++) { +                if (union.find(getIndex(i,j)) == union.find(dummyNode)){ +                    board[i][j] = 'O'; +                }else { +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public int getIndex (int i, int j){ +        return i * clos + j; +    } +    class UnionFind{ +        private int count; +        private int[] parent; +        UnionFind(int n){ +            count = n; +            parent = new int[n]; +            for (int i = 0; i < n; i++){ +                parent[i] = i; +            } +        } +        private int find(int p){ +            while(p != parent[p]){ +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        private void union(int p, int q){ +            int P = find(p); +            int Q = find(q); +            if (p == Q) return; +            parent[P] = Q; +            count--; +        } +    } +} +``` diff --git "a/Week_08/\346\216\222\345\272\217\347\256\227\346\263\225.md" "b/Week_08/\346\216\222\345\272\217\347\256\227\346\263\225.md" new file mode 100644 index 00000000..2c10483a --- /dev/null +++ "b/Week_08/\346\216\222\345\272\217\347\256\227\346\263\225.md" @@ -0,0 +1,168 @@ +[十大经典排序算法](https://www.cnblogs.com/onepixel/p/7674659.html) + +1. 分类 +![图片](https://uploader.shimo.im/f/Qy9FJ3sFCdCDEaKn.png!thumbnail?fileGuid=VDdrDXQcVpRTRDvc) +2. 复杂度 +|排序方法|时间复杂度|空间复杂度| +|:----:|:----|:----:|:----|:----:|:----| +|插入排序|O(n^2)|O(1)| +|希尔排序|O(n^1.3)|O(1)| +|选择排序|O(n^2)|O(1)| +|堆排序|O(nlogn)|O(1)| +|冒泡排序|O(n^2)|O(1)| +|快速排序|O(nlogn)|O(nlogn)| +|归并排序|O(nlogn)|O(n)| +| | | | +|计数排序|O(n+k)|O(n+k)| +|桶排序|O(n+k)|O(n+k)| +|基数排序|O(n*k)|O(n+k)| + +3. 代码实现 +#### 1.插入 + +```java +public int[] insertionSort(int[] arr) { + int len = arr.length; + int preIndex, curr; + for (int i = 1; i < len; i++) { + preIndex = i - 1; + curr = arr[i]; + while (preIndex >= 0 && arr[preIndex] > curr) { + arr[preIndex + 1] = arr[preIndex]; + preIndex--; + } + arr[preIndex + 1] = curr; + } + return arr; +} +``` +1. 希尔 +2. 选择 +```java +public int[] selectionSort(int[] arr) { + int len = arr.length; + int minIndex, temp; + for (int i = 0; i < len - 1; i++) { + minIndex = i; + for (int j = i + 1; j < len; j++) { + if (arr[minIndex] > arr[j]) minIndex = j; + } + temp = arr[i]; + arr[i] = arr[minIndex]; + arr[minIndex] = temp; + } + return arr; +} +``` +3. 堆排序 +4. 冒泡 +```java +public int[] bubbleSort(int[] arr) { + int len = arr.length; + int temp; + for (int i = 0; i < len - 1; i++) { + for (int j = 0; j < len - 1 - i; j++) { + if (arr[j] > arr[j + 1]) { + temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + return arr; +} +``` +5. 快速 +6. 归并 +7. 计数 +8. 桶排序 +9. 基数排序 +### 4.算法实战 + +[1122. 数组的相对排序](https://leetcode-cn.com/problems/relative-sort-array/) + +思路:计数排序 + +题目条件: 0 <= arr1[i] <= 1000; + +1、在arr1 中寻找一个最大值 maxNum 并创建一个长度为 maxNum + 1 的数组来记录 arr1 中元素出现的频次; + +2、遍历 arr2 中元素,再根据元素在 arr1 中出现的频次,将元素添加至新数组中,并将频次清零; + +3、将频次不为 0 的元素按升序添加至新数组; + +```java +class Solution { +public int[] relativeSortArray(int[] arr1, int[] arr2) { +        int maxNum  = 0; +        // 寻找arr1中最大的值 +        for(int num : arr1){ +            maxNum = Math.max(maxNum,num); +        } +        // 将最大值 +1 作为新数组长度,用来记录 arr1 中元素出现的次数 +        int[] frequency = new int[maxNum + 1]; +        // 记录元素出现的频次 +        for(int num : arr1){ +            ++frequency[num]; +        } +        // 循环遍历 arr2 依据每个元素出现的频次来添加数组 +        int index = 0; +        int[] res = new int[arr1.length]; +        for(int num : arr2){ +            for(int i = 0; i < frequency[num]; i++){ +                res[index++] = num; +            } +            frequency[num] = 0; +        } +        // 将不在 arr2 中的元素按升序添加至末尾 +        for(int i = 0; i <= maxNum; i++){ +            for(int j = 0; j < frequency[i]; j++){ +                res[index++] = i; +            } +        } +        return res; +    } +} +``` +[242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/) +```java +class Solution { +    public boolean isAnagram(String s, String t) { +        char[] sCharArr = s.toCharArray(); +        char[] tCharArr = t.toCharArray(); +        Arrays.sort(sCharArr); +        Arrays.sort(tCharArr); +        return new String(sCharArr).equals(new String(tCharArr)); +    } +} +``` +[56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/) +```java +class Solution { +     public int[][] merge(int[][] intervals) { +        // 对 intervals 按每个区间 intervals[i] 的起点starti排序 +        Arrays.sort(intervals, new Comparator() { +            @Override +            public int compare(int[] o1, int[] o2) { +                return o1[0] - o2[0]; +            } +        }); +        // 区间比对并合并组合 + // 先存入第一个区间,然后依次取集合中最后一个区间和新区间对比 + // 1、如果新区间的起点 start < 最后一个区间的终点,说明两个区间有重叠需要重组合并,然后对比新区间和已有区间的终点值 end ,取相对大的值为新区间 end 值 + // 2、不满足条件,说明没重叠,就新增一个区间 +        List merge = new ArrayList<>(); +        for (int i = 0; i < intervals.length; i++) { +            int start = intervals[i][0]; +            int end = intervals[i][1]; +            if (merge.size() == 0 || merge.get(merge.size() - 1)[1] < start) { +                merge.add(new int[] {start , end}); +            } else { +                merge.get(merge.size() - 1)[1] = Math.max(merge.get(merge.size() - 1)[1], end); +            } +        } +        return merge.toArray(new int[merge.size()][]); +    } +} +``` +[493. 翻转对](https://leetcode-cn.com/problems/reverse-pairs/) diff --git "a/Week_09/\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225.md" "b/Week_09/\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225.md" new file mode 100644 index 00000000..b8162623 --- /dev/null +++ "b/Week_09/\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225.md" @@ -0,0 +1,569 @@ +#### 1、字符串基础问题 + +[709. 转换成小写字母](https://leetcode-cn.com/problems/to-lower-case/) + +ASCII码表: + +a-z:97-122 + +A-Z:65-90 + +0-9:48-57 + +```java +class Solution { +    public String toLowerCase(String str) { +        int len = str.length(); +        StringBuffer sb = new StringBuffer(); +        for(int i = 0; i < len; i++){ +            int ascii = (int)str.charAt(i); +            if(ascii >= 65 && ascii <= 90){ +                char c = (char)(ascii+32); +                sb.append(c); +            }else{ +                sb.append(str.charAt(i)); +            } +        } +        return sb.toString(); +    } +} +``` +[58. 最后一个单词的长度](https://leetcode-cn.com/problems/length-of-last-word/) +调用分割函数 + +```java +class Solution { +    public int lengthOfLastWord(String s) { +        if(s == null || s.length() == 0) return 0; +        String[] arrays = s.split(" "); +        return arrays.length == 0 ? 0 : arrays[arrays.length - 1].length(); +    } +} +``` +从后往前遍历 +```java +class Solution { +    public int lengthOfLastWord(String s) { +        if(s == null || s.length() == 0) return 0; +        int end = s.length() - 1; +        while(end >= 0 && s.charAt(end) == ' ') end--; +        if(end < 0) return 0; +        int start = end; +        while(start >= 0 && s.charAt(start) != ' ') start--; +        return end - start; +    } +} +``` +[771. 宝石与石头](https://leetcode-cn.com/problems/jewels-and-stones/) +```java +class Solution { +    public int numJewelsInStones(String jewels, String stones) { +        if(jewels.length() == 0 || stones.length() == 0) return 0; +        int res = 0; +        for(char jewel : jewels.toCharArray()){ +            for(char stone : stones.toCharArray()){ +                if(jewel == stone) res++; +            } +        } +        return res; +    } +} +``` +[387. 字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) +```java +class Solution { +    public int firstUniqChar(String s) { +        if(s == null || s.length() == 0) return -1; +        Map map = new HashMap<>(); +        for(char c : s.toCharArray()){ +            if(!map.containsKey(c)){ +                map.put(c,1); +            }else{ +                map.put(c,map.get(c)+1); +            } +        } +        for(int i = 0; i < s.length(); i++){ +            int count = (int)map.get(s.charAt(i)); +            if(count == 1) return i; +        } +        return -1; +    } +} +``` +[8. 字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/) +```java +class Solution { +    public int myAtoi(String s) { +        int res = 0; +        int len = s.length(); +        char[] charArray = s.toCharArray(); +        // 定义一个下标指针 +        int index = 0; +        // 循环遍历,去掉前导空格 +        while(index < len && charArray[index] == ' '){ +            index++; +        } +        // 处理极端情况 +        if(index == len) return 0; +        // 检查是正数还是负数 +        int sign = 1; +        char first = charArray[index]; +        if(first == '+'){ +            index++; +        }else if(first == '-'){ +            index++; +            sign = -1; +        } +        // 循环遍历字符 +        while(index < len){ +            char c = charArray[index]; +            // 检查字符是否是数字字符 +            if(c > '9' || c < '0') break; +            // 检查数字是否越界 +            if(res > Integer.MAX_VALUE/10 || (res == Integer.MAX_VALUE/10 && (c - '0') > Integer.MAX_VALUE%10)) +                return Integer.MAX_VALUE; +            if(res < Integer.MIN_VALUE/10 || (res == Integer.MIN_VALUE/10 && (c - '0') > - (Integer.MIN_VALUE%10))) +                return Integer.MIN_VALUE; +            // 合法情况,转换字符为相应的数字 +            res = res*10 + sign * (c - '0'); +            index++; +        } +        return res; +    } +} +``` +#### 2、字符串操作问题 + +[14. 最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix/) + +```java +class Solution { +    public String longestCommonPrefix(String[] strs) { +        if(strs.length == 0) return ""; +        String prefix = strs[0]; +        for(int i = 1; i < strs.length; i++){ +            String currStr = strs[i]; +            int j = 0; +            for(; j < prefix.length() && j < currStr.length(); j++){ +                if(currStr.charAt(j) != prefix.charAt(j)) +                    break; +            } +            prefix = prefix.substring(0,j); +            if(prefix.equals("")) return ""; +        } +        return prefix; +    } +} +``` +[344. 反转字符串](https://leetcode-cn.com/problems/reverse-string/) +```java +class Solution { +    public void reverseString(char[] s) { +        int left = 0 ,right = s.length - 1; +        while(left < right){ +            if(s[left] != s[right]){ +                char temp = s[left]; +            s[left] = s[right]; +            s[right] = temp; +            } +            left++; +            right--; +        } +    } +} +``` +[541. 反转字符串 II](https://leetcode-cn.com/problems/reverse-string-ii/) +```java +class Solution { +    public String reverseStr(String s, int k) { +        char[] charArray = s.toCharArray(); +        for(int index = 0;index < charArray.length;index +=2*k){ +            int left = index,right = Math.min(index + k - 1,charArray.length - 1); +            while(left < right){ +                char temp = charArray[left]; +                charArray[left++] = charArray[right]; +                charArray[right--] = temp; +            } +        } +        return new String(charArray); +    } +} +``` +[151. 翻转字符串里的单词](https://leetcode-cn.com/problems/reverse-words-in-a-string/) +```java +class Solution { +    public String reverseWords(String s) { +        // 去除多余空格 +        s = s.trim(); +        List strList = Arrays.asList(s.split("\s+")); +        Collections.reverse(strList); +        return String.join(" ",strList); +    } +} +``` +[557. 反转字符串中的单词 III](https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/) +```java +class Solution { +    public String reverseWords(String s) { +        String[] strArray = s.split(" "); +        StringBuffer sb = new StringBuffer(); +        for(String currStr : strArray){ +            int right = currStr.length() - 1; +            int left = 0; +            char[] charArr = currStr.toCharArray(); +            while(left < right){ +                char temp = charArr[left]; +                charArr[left++] = charArr[right]; +                charArr[right--] = temp; +            } +            sb.append(charArr); +            sb.append(" "); +        } +        return sb.toString().trim(); +    } +} +``` +[917. 仅仅反转字母](https://leetcode-cn.com/problems/reverse-only-letters/) +思路:双指针,碰到非字母字符跳过循环 + +```java +class Solution { +    public String reverseOnlyLetters(String S) { +        int len = S.length(); +        if(len < 2) return S; +        // 熟悉的双指针 +        int left = 0,right = len - 1; +        char[] charArr = S.toCharArray(); +        while(left < right){ +            char leftChar = charArr[left]; +            char rightChar = charArr[right]; +            if(!isLetter(leftChar)) { +                left++; +                continue; +            } +            if(!isLetter(rightChar)) { +                right--; +                continue; +            } +            char temp = charArr[left]; +            charArr[left++] = charArr[right]; +            charArr[right--] = temp; +        } +        return new String(charArr); +    } +    public boolean isLetter(char c){ +        return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +    } +} +``` +#### 3、异位词问题 + +[242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/) + +```java +class Solution { +    public boolean isAnagram(String s, String t) { +        if (s.isEmpty() && t.isEmpty()) { +            return true; +        } +        if (s.length() != t.length()) { +            return false; +        } +        char[] sChars = s.toCharArray(); +        char[] tChars = t.toCharArray(); +        Arrays.sort(sChars); +        Arrays.sort(tChars); +        for (int i = 0; i < sChars.length; i++) { +            if (sChars[i] != tChars[i]) { +                return false; +            } +        } +        //return Arrays.equals(sChars, tChars); +        return true; +    } +} +``` +[49. 字母异位词分组](https://leetcode-cn.com/problems/group-anagrams/) +```java + class Solution { +    public List> groupAnagrams(String[] strs) { +        if (strs == null || strs.length == 0) { +            return new ArrayList<>(); +        } +        Map> map = new HashMap<>(); +        for (int i = 0; i < strs.length; i++) { +            char[] chars = strs[i].toCharArray(); +            Arrays.sort(chars); +            String key = String.valueOf(chars); +            if (!map.containsKey(key)) { +                map.put(key, new ArrayList<>()); +            } +            map.get(key).add(strs[i]); +        } +        return new ArrayList<>(map.values()); +    } +} +``` +[438. 找到字符串中所有字母异位词](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) +#### 4、回文串问题 + +[125. ](https://leetcode-cn.com/problems/valid-palindrome/)[验证回文串](https://leetcode-cn.com/problems/valid-palindrome/) + +思路:先筛选,再反转,再比较 + +```java +class Solution { +    public boolean isPalindrome(String s) { +        if(s.length() == 0) return true; +        StringBuilder sb = new StringBuilder(); +        for(char c : s.toCharArray()){ +            if((c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >='0')) +                sb.append(c); +        } +        String origStr = sb.toString(); +        String reverseStr = sb.reverse().toString(); +        return reverseStr.equalsIgnoreCase(origStr); +    } +} +``` +思路:双指针判断 +1、如果 start 指针所指字符不符合[^A-Za-z0-9] start++ ,跳过本次判断; + +2、如果 end 指针所指字符不符合[^A-Za-z0-9] end-- , 跳过本次判断; + +3、如果 start 、end 指针所指字符都不符合,start++,end--; + +4、如果 start 、end 指针所指字符忽略大小写情况下不相同,就代表不是回文串,返回 false; + +```java +class Solution { +    public boolean isPalindrome(String s) { +        if(s.length() == 0) return true; +        // 双指针 +        int start = 0,end = s.length() - 1; +        while(start < end){ +            char startC = s.charAt(start); +            char endC = s.charAt(end); +            if(!isMath(startC)) { +                start++; +                continue; +            } +            if(!isMath(endC)) { +                end--; +                continue; +            } +            if(Character.toLowerCase(startC) != Character.toLowerCase(endC)) { +                return false; +            }else{ +                start++; +                end--; +            } +        } +        return true; +    } +    public boolean isMath(char c){ +        return ((c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >='0')); +    } +} +``` +[680. 验证回文字符串 Ⅱ](https://leetcode-cn.com/problems/valid-palindrome-ii/) +思路:双指针 + +1、i 指针从头开始,j 指针从尾开始,依次比较是否相等; + +2、charArrsy[i] == charArray[j] , 则将这两字符算在回文串中,并 i++,j--; + +3、charArrsy[i] != charArray[j] , 说明这两个字符是构成回文串的“阻碍”,依据题目可以删除其中任意一个字符后再进行比较; + +4、比较删除其中任意字符后的字串是否构成回文串,删除左边字符后取(i+1,j)范围内字符比较,删除右边字符取(i,j-1)范围内字符比较; + +5、比较字串是否是回文串又是“双指针”的用法; + +为什么使用字符数组,这样虽然开辟了多余的内存,但是数组能提升查询效率 + +```java +class Solution { +    public boolean validPalindrome(String s) { +        char[] charArray = s.toCharArray(); +        for(int i = 0,j = charArray.length -1; i < j; i++,j--){ +            if(charArray[i] != charArray[j]){ +                return isPalindrome(charArray,i+1,j) || isPalindrome(charArray,i,j-1); +            } +        } +        return true; +    } +    public boolean isPalindrome(char[] charArray , int i,int j){ +        while(i < j){ +            if(charArray[i++] != charArray[j--]) return false; +        } +        return true; +    } +} +``` +#### 5、最长子串、子序列问题 + +最常规子串、子序列问题: + +[1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) + +思路:动态规划 + +题解:[动态规划图文解析(java)](https://leetcode-cn.com/problems/longest-common-subsequence/solution/dong-tai-gui-hua-tu-wen-jie-xi-by-yijiaoqian/) + +![图片](https://uploader.shimo.im/f/m7zmYkakns58XZy6.png!thumbnail?fileGuid=gXxtwJrTyhxPCvYD) + +key:找到动态方程 + +1、当前对比的两个字符不相同时,取text1倒退一格和text2倒退一格两者中的最大值,也就是 + +dp[i + 1][j + 1] = Math.max(dp[i+1][j], dp[i][j+1]); + +2、当前对比的两个字符相同时,取text1与text2各退一格的值+1,及 + +dp[i+1][j+1] = dp[i][j] + 1; + +```java +class Solution { +    public int longestCommonSubsequence(String text1, String text2) { +        int m = text1.length(), n = text2.length(); +        int[][] dp = new int[m + 1][n + 1]; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                // 获取两个串字符 +                char c1 = text1.charAt(i), c2 = text2.charAt(j); +                if (c1 == c2) { +                    // 去找它们前面各退一格的值加1即可 +                    dp[i + 1][j + 1] = dp[i][j] + 1; +                } else { +                    //要么是text1往前退一格,要么是text2往前退一格,两个的最大值 +                    dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]); +                } +            } +        } +        return dp[m][n]; +    } +} +``` +[72. 编辑距离](https://leetcode-cn.com/problems/edit-distance/) +思路:动态规划 + +![图片](https://uploader.shimo.im/f/mT6HHnsITnH9dam1.png!thumbnail?fileGuid=gXxtwJrTyhxPCvYD) + +dp方程. dp[i][j] 表示 word1 字符串前 i +1个字符组成的字符串转换为 word2 字符串前 j +1个字符组成的字符串的最少操作数,那么 + +1、当 word1.charAt(i) == word2.charAt(j) ,只有一种操作删除,dp[i][j] = dp[i-1][j-1]; + +2、当 word1.charAt(i) != word2.charAt(j) ,有三种操作替换dp[i-1][j-1],删除dp[i-1][j],插入dp[i][j-1],求三者的最小值 min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1]); + +3、当 word1 为空字符串时,只能通过插入操作来转换 ,也就是 dp[j][0] = dp[j-1][0] + 1; + +4、当 word2 为空字符串时,只能通过删除操作来转换 ,也就是 dp[0][i] = dp[0][i-1] + 1; + +```java +class Solution { +    public int minDistance(String word1, String word2) { +        int len1 = word1.length(); +        int len2 = word2.length(); +        int[][] dp = new int[len1+1][len2+1]; +        for(int i = 1; i <= len2; i++) dp[0][i] = dp[0][i-1] + 1; +        for(int j = 1; j <= len1; j++) dp[j][0] = dp[j-1][0] + 1; +        for(int i = 1; i <= len1; i++){ +            for(int j = 1; j <= len2; j++){ +                char c1 = word1.charAt(i-1); +                char c2 = word2.charAt(j-1); +                if(c1 == c2){ +                    dp[i][j] = dp[i-1][j-1]; +                }else{ +                    dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1; +                } +            } +        } +        return dp[len1][len2]; +    } +} +``` +[5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/) +思路:暴力+双指针 + +```java +class Solution { +    public String longestPalindrome(String s) { +        if(s.length() <= 1) return s; +        int maxLen = 1; +        int index = 0; +        char[] charArr = s.toCharArray(); +        for(int i = 0; i < s.length(); i++){ +            for(int j = i+1; j < s.length(); j++){ +                if(j-i+1 > maxLen && isPalindrime(charArr,i,j)){ +                    maxLen = j-i+1; +                    index = i; +                } +            } +        } +        return s.substring(index,index + maxLen); +    } +    public boolean isPalindrime(char[] charArr,int left ,int right){ +        while(left < right){ +            if(charArr[left++] != charArr[right--]) return false; +        } +        return true; +    } +} +``` +动态规划: +```java +class Solution { +    public String longestPalindrome(String s) { +        int len = s.length(); +        if(len <= 1) return s; +        char[] charArr = s.toCharArray(); +        // 记录子串是回文串的长度 +        int maxLen = 1; +        // 记录子串是回文串的头坐标 +        int index = 0; +        // dp[i][j] 代表[i,j]区间内子串是否式回文串 +        boolean [][] dp = new boolean[len][len]; +        // 子串长度为 1,一个字符时肯定是回文串 +        for(int i = 0;i < len; i++){ +            dp[i][i] = true; +        } +        // 状态转移方程 charArr[i] == charArr[j] 前提下 dp[i][j] = dp[i+1][j-1] , +        // 判断当前子串是否是回文串 == 当首尾字符相同的情况下取决于剩余子串是否是回文串 +        for(int j = 1; j < len; j++){ +            for(int i = 0; i < j; i++){ +                if(charArr[i] != charArr[j]){ +                    dp[i][j] = false; +                }else{ +                    // (i,j) 区间长度小于2的子串都是回文串 +                    if(j-i < 3){ +                        dp[i][j] = true; +                    }else{ +                        dp[i][j] = dp[i+1][j-1]; +                    } +                } +                if(j-i+1 > maxLen && dp[i][j]){ +                    maxLen = j-i+1; +                    index = i; +                } +            } +        } +        return s.substring(index,index + maxLen); +    }    +} +``` +#### 6、字符串+DP问题 + +[10. 正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching/) + +[44. 通配符匹配](https://leetcode-cn.com/problems/wildcard-matching/) + +[115. 不同的子序列](https://leetcode-cn.com/problems/distinct-subsequences/) + +#### 7、字符串匹配算法(太难了,暂时搁置) + +* [Boyer-Moore 算法](https://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html) +* [Sunday 算法](https://blog.csdn.net/u012505432/article/details/52210975) +* [字符串匹配暴力法代码示例](https://shimo.im/docs/8G0aJqNL86wWrPUE) +* [Rabin-Karp 代码示例](https://shimo.im/docs/1wnsM7eaZ6Ab9j9M) +* [KMP 字符串匹配算法视频](https://www.bilibili.com/video/av11866460?from=search&seid=17425875345653862171) +* [字符串匹配的 KMP 算法](http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html) diff --git "a/Week_\346\257\225\344\270\232\346\200\273\347\273\223/\347\256\227\346\263\225\345\255\246\344\271\240\346\200\273\347\273\223.md" "b/Week_\346\257\225\344\270\232\346\200\273\347\273\223/\347\256\227\346\263\225\345\255\246\344\271\240\346\200\273\347\273\223.md" new file mode 100644 index 00000000..199c1f49 --- /dev/null +++ "b/Week_\346\257\225\344\270\232\346\200\273\347\273\223/\347\256\227\346\263\225\345\255\246\344\271\240\346\200\273\347\273\223.md" @@ -0,0 +1,1808 @@ +### 1、什么是算法? + +算法就是计算或者解决问题的步骤。我们可以把它想象成食谱。要想做出特定的料理,就要遵循食谱上的步骤;同理,要想用计算机解决特定的问题,就要遵循算法。这里所说的特定问题多种多样,比如“将随意排列的数字按从小到大的顺序重新排列”“寻找出发点到目的地的最短路径”,等等。食谱和算法之间最大的区别就在于算法是严密的。食谱上经常会有描述得比较模糊的部分,而算法的步骤都是用数学方式来描述的,所以十分明确。算法和程序有些相似,区别在于程序是以计算机能够理解的编程语言编写而成的,可以在计算机上运行,而算法是以人类能够理解的方式描述的,用于编写程序之前。不过,在这个过程中到哪里为止是算法、从哪里开始是程序,并没有明确的界限。就算使用同一个算法,编程语言不同,写出来的程序也不同;即便使用相同的编程语言,写程序的人不同,那么写出来的程序也是不同的。 + +摘录自《我的第一本算法》,这本书特别适合新手学习,以图形的方式来讲解,易于理解。 + +### 2、数据结构 + +什么是数据结构? + +答:数据存储于内存中,决定了数据顺序和位置关系的便是“数据结构”。 + +1. 数组(Array) + +数据按顺序存储在内存的连续空间内。 + +2. 链表(Linked List) + +是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 + +3. 栈(Stack) + +栈也是一种数据呈线性排列的数据结构,遵循后进先出原则 + +4. 队列(Queue) + +普通队列:队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。 + +双端队列(Deque):入口和出口都可以入队和出队 + +优先队列(PriorityQueue):根据优先级出队 + +5. 哈希表(Hash table) + +哈希表(Hash table),也叫散列表,是根据关键码值(Key value)而直接进行访问的数据结构。 + +6. 树(Tree) + +一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 + +特点: + +* 每个节点都只有有限个子节点或无子节点 +* 没有父节点的节点称为根节点 +* 每一个非根节点有且仅有一个父节点 +* 除了根节点外,每个子节点可以分为多个不相交的子树 +* 树中没有环路 +7. 堆(Heap) + +可以迅速找到一堆数中的最大或者最小值的数据结构。将根节点最大的堆叫做大顶堆或大根堆,根节点最小的堆叫做小顶堆或小根堆。常见的堆有二叉堆、斐波那契堆等。 + +满足下列性质: + +* 堆中任意节点的值总是>=或<=其子节点的值 +* 堆总是一颗完全二叉树 +8. 图(Graph) + +图结构也是一种非线性数据结构,并且每个数据元素之间可以任意关联。 + +一个典型的图结构包括如下两个部分: + +* 顶点(Vertex):图中的数据元素 +* 边(Edge):图中连接这些顶点的线 + +存储方法:邻接矩阵、邻接表 + +![图片](https://uploader.shimo.im/f/0EWVCDjlTBmw1Ga5.png!thumbnail?fileGuid=jVD8jjdtYgqcrRHV) + +邻接矩阵 + +![图片](https://uploader.shimo.im/f/naE9X4fnVq7GtK9w.png!thumbnail?fileGuid=jVD8jjdtYgqcrRHV) + +邻接表 + +关于数据结构总结脑图附件 + +[数据结构和算法-思维导图.pdf](https://uploader.shimo.im/f/VNXXts3PE3uAMFMr.pdf?fileGuid=jVD8jjdtYgqcrRHV) + +### 3、算法 + +1. 递归(Recursion) + +递归也是一种循环,不过是通过函数体自己调用自己来进行的循环 + +简单示例:计算 n! + +```python +// n!= 1*2*3*...*n +def Factorial(n): + if n<=1: + return 1 + return n*Factorial(n-1) +``` +代码模板(Java) +```java +public void recur(int level , int param){ + // terminator + if (level > MAX_LEVEL){ + // process result + retutn; + } + // process current logic + process(level,param); + // drill down + recur(level+1,newParam); + // restore current status +} +``` +* [爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/)(阿里巴巴、腾讯、字节跳动在半年内面试常考) + +常规写法: + +```java +public int climbStairs(int n) { +    if(n <= 2){ +        return n; +    } +    return climbStairs(n-1) + climbStairs(n-2); +} +``` +升级版本: +```java +public int climbStairs(int n) { + int f1 = 1, f2 = 2, f3 = 0; + for (int i = 3; i <= n; i++) { + f3 = f1 + f2; + f1 = f2; + f2 = f3; + } + return f3; +} +``` +* [括号生成](https://leetcode-cn.com/problems/generate-parentheses/)(字节跳动在半年内面试中考过) +```java +public List generateParenthesis(int n) { + List res = new ArrayList<>(); + if (n < 1) { + return res; + } + generate(0, 0, "", res, n); + return res; +} +public void generate(int left, int right, String s, List res, int n) { + // 1.递归终结条件 + if (left == n && right == n) { + res.add(s); + return; + } + // 2.处理当前层 + // 右括号多余左括号,说明已经是无效括号组合 + if (left < right) { + return; + } + // 3.下探下一层 + // 如果左括号还能添加,右括号不变 + if (left < n) + generate(left + 1, right, s + "(", res, n); + // 如果右括号还能添加,左括号不变 + if (right < n) + generate(left, right + 1, s + ")", res, n); +} +``` +* [翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/description/)(谷歌、字节跳动、Facebook 在半年内面试中考过) +```java +public TreeNode invertTree(TreeNode root) { + if (root == null) + return null; + TreeNode left = root.left; + root.left = root.right; + root.right = left; + invertTree(root.left); + invertTree(root.right); + return root; +} +``` +* [验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree)(亚马逊、微软、Facebook 在半年内面试中考过) +```java +// 中序遍历访问,先遍历左子树,如果有当前节点值<=前节点值,不满足条件 +private TreeNode per; +public boolean isValidBST(TreeNode root) { + if (root == null) + return true; + if (!isValidBST(root.left)) return false; + if (per != null && root.val <= per.val) return false; + per = root; + if (!isValidBST(root.right)) return false; + return true; +} +``` +* [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree)(亚马逊、微软、字节跳动在半年内面试中考过) +```java +class Solution { +    public int maxDepth(TreeNode root) { +        if(root == null) return 0; +        return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; +    } +} +``` +* [二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree)(Facebook、字节跳动、谷歌在半年内面试中考过) +```java +class Solution { +    public int minDepth(TreeNode root) { +        if(root == null) return 0; +        if(root.left == null && root.right ==null) return 1; +        if(root.left == null || root.right == null) +        return minDepth(root.left) + minDepth(root.right) + 1; +        return Math.min(minDepth(root.left),minDepth(root.right)) + 1; +    } +} +``` +* [二叉树的序列化与反序列化](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/)(Facebook、亚马逊在半年内面试常考) +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + *     int val; + *     TreeNode left; + *     TreeNode right; + *     TreeNode(int x) { val = x; } + * } + */ +public class Codec { +    // Encodes a tree to a single string. +    public String serialize(TreeNode root) { +        StringBuilder str = new StringBuilder(); +        // BFS +        return traversTree(root,str); +    } +    // Decodes your encoded data to tree. +    public TreeNode deserialize(String data) { +        if(data.isEmpty() || data.equals("#")){ +            return null; +        } +        Queue queue = new LinkedList(); +        String[] nodes = data.split(","); +        TreeNode root = new TreeNode(Integer.valueOf(nodes[0])); +        queue.add(root); +        for(int i = 1; i < nodes.length ; i++){ +            TreeNode p = queue.poll(); +            if(!"#".equals(nodes[i])){ +                TreeNode left = new TreeNode(Integer.valueOf(nodes[i])); +                p.left = left; +                queue.add(left); +            } +            if(!"#".equals(nodes[++i])){ +                TreeNode right = new TreeNode(Integer.valueOf(nodes[i])); +                p.right = right; +                queue.add(right); +            } +        } +        return root; +    } +    public String traversTree(TreeNode root,StringBuilder str){ +        if(root == null) { +            return "#"; +        } +        Queue queue = new LinkedList(); +        queue.add(root); +        while(!queue.isEmpty()){ +            TreeNode node = queue.poll(); +            if(node == null){ +                str.append("#"+","); +                continue; +            } +            str.append(node.val+","); +            queue.add(node.left); +            queue.add(node.right); +        } +        return str.toString(); +    } +} +// Your Codec object will be instantiated and called as such: +// Codec ser = new Codec(); +// Codec deser = new Codec(); +// TreeNode ans = deser.deserialize(ser.serialize(root)); +``` +课后作业: +* [二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)(Facebook 在半年内面试常考) +```java +class Solution { +    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { +        if (root == null || root == p || root == q) { +            return root; +        } +        TreeNode left = lowestCommonAncestor(root.left, p, q); +        TreeNode right = lowestCommonAncestor(root.right, p, q); +        if(left == null) return right; +        if(right == null) return left; +        return root; +    } +} +``` +* [从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal)(字节跳动、亚马逊、微软在半年内面试中考过) +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + *     int val; + *     TreeNode left; + *     TreeNode right; + *     TreeNode(int x) { val = x; } + * } + */ +class Solution { +    private Map indexMap = new HashMap<>(); +    public TreeNode buildTree(int[] preorder, int[] inorder) { +        if (preorder.length == 0 || inorder.length == 0) { +            return null; +        } +        int n = preorder.length; +        // 将中序遍历的数组保存在map中,以便快速查询节点对应的下标 +        for (int i = 0; i < n; i++) { +            indexMap.put(inorder[i], i); +        } +        return buildTreeHelper(preorder, inorder, 0, n - 1, 0, n - 1); +    } +    private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int preLeft, int preRight, int inLeft, int inRight) { +        // 递归结束条件,数组切分完为止 +        if (preLeft > preRight) { +            return null; +        } +        // 前序遍历的第一个节点就是根节点 +        int perRoot = preLeft; +        // 查询根节点在中序遍历中的下标位置 +        int index_root = (int)indexMap.get(preorder[perRoot]); +        // 创建根节点 +        TreeNode root = new TreeNode(preorder[perRoot]); +        // 计算左子树中的节点数目,以index_root下标为中心将前序遍历数组inorder切分为左子树、右子树两部分 +        int left_size = index_root - inLeft; +        // 递归构造左子树 +        root.left = buildTreeHelper(preorder, inorder, preLeft + 1, preLeft + left_size, inLeft, index_root - 1); +        // 递归构造右子树 +        root.right = buildTreeHelper(preorder, inorder, preLeft + left_size + 1, preRight, index_root + 1, inRight); +        return root; +    } +} +``` +* [组合](https://leetcode-cn.com/problems/combinations/)(微软、亚马逊、谷歌在半年内面试中考过) +```java +class Solution { +     public List> combine(int n, int k) { +        List> res = new ArrayList<>(); +        dfs(n, k, 1, new ArrayList(), res); +        return res; +    } +    private void dfs(int n, int k, int start, ArrayList list, List> res) { +        if (k == 0) { +            res.add(new ArrayList<>(list)); +            return; +        } +        for (int i = start; i <= n - k + 1; i++) { +            list.add(i); +            dfs9(n, k - 1, i + 1, list, res); +            list.remove(list.size() - 1); +        } +    } +} +``` +* [全排列](https://leetcode-cn.com/problems/permutations/)(字节跳动在半年内面试常考) +```java +class Solution { +    public List> permute(int[] nums) { +        List> res = new ArrayList<>(); +        Deque path = new ArrayDeque<>(); +        boolean[] used = new boolean[nums.length]; +        dfs(nums, 0, path, used, res); +        return res; +    } +    private void dfs(int[] nums, int level, Deque path, boolean[] used, List> res) { +        if (level == nums.length) { +            res.add(new ArrayList<>(path)); +            return; +        } +        for (int j = 0; j < nums.length; j++) { +            if (!used[j]) { +                path.addLast(nums[j]); +                used[j] = true; +                dfs(nums, level + 1, path, used, res); +                used[j] = false; +                path.removeLast(); +            } +        } +    } +} +``` +* [全排列 II ](https://leetcode-cn.com/problems/permutations-ii/)(亚马逊、字节跳动、Facebook 在半年内面试中考过) +```java +class Solution { +    public List> permuteUnique(int[] nums) { +        List> res = new ArrayList<>(); +        Deque path = new ArrayDeque<>(); +        boolean[] used = new boolean[nums.length]; +        // 排序(升序或者降序都可以),排序是剪枝的前提 +        Arrays.sort(nums); +        dfs(nums, 0, path, used, res); +        return res; +    } +    private void dfs(int[] nums, int level, Deque path, boolean[] used, List> res) { +        if (level == nums.length) { +            res.add(new ArrayList<>(path)); +            return; +        } +        for (int j = 0; j < nums.length; j++) { +            if (used[j]) { +                continue; +            } +            // 剪枝,取掉重复的搜索结果 +            if (j > 0 && nums[j] == nums[j - 1] && !used[j - 1]) { +                continue; +            } +            path.addLast(nums[j]); +            used[j] = true; +            dfs(nums, level + 1, path, used, res); +            used[j] = false; +            path.removeLast(); +        } +    } +} +``` +2. 分治、回溯 +* 分治代码模板 +```java +private static int divide_conquer(Problem problem, ) { + if (problem == NULL) { + int res = process_last_result(); + return res; + } + subProblems = split_problem(problem) + res0 = divide_conquer(subProblems[0]) + res1 = divide_conquer(subProblems[1]) + result = process_result(res0, res1); + return result; +} +``` +* [50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) +```java +class Solution { +    public double myPow(double x, int n) { +        if(n == 0) return 1; +        if(n == 1) return x; +        if(n == -1) return 1/x; +        double helf = myPow(x,n/2); +        double rest = myPow(x,n%2); +        return rest*helf*helf; +    } +} +``` +* [78. 子集](https://leetcode-cn.com/problems/subsets/) +```java +class Solution { +    public List> subsets(int[] nums) { +        List> result = new ArrayList<>(); +        subsets(nums,0,new ArrayList(),result); +        return result; +    } +    public void subsets(int[] nums,int index ,List list,List> result){ +        result.add(new ArrayList<>(list)); +        for(int i = index;i n/2 ,则其余元素个数 <= n/2 + +这样“多数元素”的票数就能抵消掉所有其余元素的票数,最终剩余的就是票数最多的元素。 + +```java +class Solution { +    public int majorityElement(int[] nums) { +        // 默认选中第一个元素为候选人,票数默认10 +        int count = 1; +        int choose = nums[0]; +        for (int i = 1; i < nums.length; i++) { +            if (nums[i] == choose) { +                count++; +            } else { +                count--; +            } +            // 当票数为0时,需要更换候选人,票数也重置为1 ,开始重新投票 +            if (count == 0) { +                choose = nums[i]; +                count = 1; +            } +        } +        return choose; +    } +} +``` +* [17. 电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) + +思路:dfs+回溯 + +![图片](https://uploader.shimo.im/f/ooqRWHZQ6AFwllN7.png!thumbnail?fileGuid=jVD8jjdtYgqcrRHV) + +```java +class Solution { +    public List letterCombinations(String digits) { +        List res = new ArrayList(); +        if (digits.length() == 0) return res; +        Map keyboard = new HashMap<>(); +        keyboard.put('2', "abc"); +        keyboard.put('3', "def"); +        keyboard.put('4', "ghi"); +        keyboard.put('5', "jkl"); +        keyboard.put('6', "mno"); +        keyboard.put('7', "pqrs"); +        keyboard.put('8', "tuv"); +        keyboard.put('9', "wxyz"); +        search(digits, 0, "", res, keyboard); +        return res; +    } +    public void search(String digits, int level, String s, List res, Map keyboard) { +        if (level == digits.length()) { +            res.add(s); +            return; +        } +        String letter = keyboard.get(digits.charAt(level)); +        for (int i = 0; i < letter.length(); i++) { +            search(digits, level + 1, s + letter.charAt(i), res, keyboard); +        } +    } +} +``` +* [51. N 皇后](https://leetcode-cn.com/problems/n-queens/) +```plain +``` +3. 深度优先搜索、广度优先搜索 +* DFS代码模板 + +中序遍历 + +```java +//Java +public List> levelOrder(TreeNode root) { +    List> allResults = new ArrayList<>(); +    if(root==null){ +        return allResults; +    } +    travel(root,0,allResults); +    return allResults; +} +public void travel(TreeNode root,int level,List> results){ +    if(results.size()==level){ +        results.add(new ArrayList<>()); +    } +    results.get(level).add(root.val); +    if(root.left!=null){ +        travel(root.left,level+1,results); +    } +    if(root.right!=null){ +        travel(root.right,level+1,results); +    } +} +``` +* BFS代码模板 + +层序遍历 + +```java +public List> levelOrder(TreeNode root) { + List> allResults = new ArrayList<>(); + if (root == null) { + return allResults; + } + Queue nodes = new LinkedList<>(); + nodes.add(root); + while (!nodes.isEmpty()) { + int size = nodes.size(); + List results = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = nodes.poll(); + results.add(node.val); + if (node.left != null) { + nodes.add(node.left); + } + if (node.right != null) { + nodes.add(node.right); + } + } + allResults.add(results); + } + return allResults; +} +``` +* [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + *     int val; + *     TreeNode left; + *     TreeNode right; + *     TreeNode(int x) { val = x; } + * } + */ +class Solution { +    public List> levelOrder(TreeNode root) { +        List> res = new ArrayList<>(); +        if(root == null) return res; +        Queue queue = new LinkedList<>(); +        queue.add(root); +        while(!queue.isEmpty()){ +            int size = queue.size(); +            List list = new ArrayList<>(); +            for(int i = 0 ; i < size; i++){ +                TreeNode node = queue.poll(); +                list.add(node.val); +                if(node.left != null) queue.add(node.left); +                if(node.right != null) queue.add(node.right); +            } +            res.add(list); +        } +        return res; +    } +} +``` +* [515. 在每个树行中找最大值](https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/) +```java +/** + * 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 largestValues(TreeNode root) { +        List res = new ArrayList<>(); +        if(root == null) return res; +        Queue queue = new LinkedList<>(); +        queue.add(root); +        while(!queue.isEmpty()){ +            int size = queue.size(); +            int max = Integer.MIN_VALUE; +            for(int i = 0;i < size;i++){ +                TreeNode node = queue.poll(); +                max = Math.max(max,node.val); +                if(node.left != null) queue.add(node.left); +                if(node.right != null) queue.add(node.right); +            } +            res.add(max); +        } +        return res; +    } +} +``` +* [589. N叉树的前序遍历](https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/) +```java +/* +// 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 preorder(Node root) { +        List res = new ArrayList<>(); +        if(root == null) return res; +        traverse(root,res); +        return res; +    } +    private void traverse(Node node,List res){ +        if(node == null){ +            return; +        } +        res.add(node.val); +        List children = node.children; +        for(int i = 0 ; i < children.size(); i++){ +            traverse(children.get(i),res); +        } +    } +} +``` +* [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) +```java +class Solution { +    public int numIslands(char[][] grid) { +        if(grid.length == 0 && grid[0].length == 0) return 0; +        int count = 0; +        for(int i = 0 ; i < grid.length; i++){ +            for(int j = 0; j < grid[i].length;j++){ +                if(grid[i][j] == '1'){ +                    count++; +                    dfs(grid,i,j); +                } +            } +        } +        return count; +    } +    private void dfs(char[][] grid , int i , int j){ +        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0'){ +            return; +        } +        grid[i][j] = '0'; +        dfs(grid,i+1,j);// rigth +        dfs(grid,i-1,j);// left +        dfs(grid,i,j+1);// top +        dfs(grid,i,j-1);// bottom +    } +} +``` +* [529. 扫雷游戏](https://leetcode-cn.com/problems/minesweeper/) +```plain +``` +4. 贪心算法 + +贪心算法是一种在每一步选择中都采取在当下状态下最好或最优(最有利)的选择,从而希望导致结果是全局最好或最优的算法。 + +关于贪心算法推荐博客[贪心算法](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) + +适用场景:问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。这种子问题最优解称为最优子结构。 + +[322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/) + +贪心策略:每次兑换都从最大的零钱开始兑换,找最少硬币兑换个数,直至兑换完毕。 + +```java +class Solution { +    private int res = Integer.MAX_VALUE; +    public int coinChange(int[] coins, int amount) { +        if(amount == 0) return 0; +        Arrays.sort(coins); +        change(coins,amount,coins.length - 1,0); +        return res == Integer.MAX_VALUE ? -1 : res; +    } +    public void change(int[] coins, int amount , int coinIndex ,int count){ +        if(amount == 0){ +            res = Math.min(res,count); +            return; +        } +        if(coinIndex < 0) return; +        for(int k = amount/coins[coinIndex];k>=0 && k + count< res ; k--){ +            change(coins,amount-(k*coins[coinIndex]),coinIndex - 1,count + k); +        } +    } +} +``` +* [122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) + +贪心策略:低买高卖,所有上涨日都买卖,下跌日都不进行买卖,这样的收益是最大的 + +```java +class Solution { +    public int maxProfit(int[] prices) { +        int profit = 0; +        for (int i = 1; i < prices.length; i++) { +            int temp = prices[i] - prices[i - 1]; +            if (temp > 0) { +                profit += temp; +            } +        } +        return profit; +    } +} +``` +5. 二分查找 +* [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) +```java +class Solution { +    public int search(int[] nums, int target) { +        int left = 0,right = nums.length - 1,mid = 0; +        while(left <= right){ +            mid = left + (right - left)/2; +            if(target == nums[mid]) return mid; +            if(nums[left] <= nums[mid]){ +                if(target >= nums[left] && target < nums[mid]) { +                    right = mid - 1; +                }else { +                    left = mid + 1; +                } +            }else { +                if(target > nums[mid] && target <= nums[right]){ +                    left = mid + 1; +                }else { +                    right = mid - 1; +                } +            } +        } +        return -1; +    }        +} +``` +* [69. x 的平方根](https://leetcode-cn.com/problems/sqrtx/) +```java +class Solution { +    public int mySqrt(int x) { +        if (x == 0) { +            return x; +        } +        long left = 1, right = x/2, mid = 1; +        while(left < right){ +            mid = (left + right + 1) >>> 1; +            if(mid*mid > x){ +                right = mid - 1; +            }else{ +                left = mid; +            } +        } +        return (int)left; +    } +} +``` +* [74. 搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix/) + +思路:二分法 + +将二维数组转换为虚拟一维数组,长度= m*n,每次找中间值 matrix[mid / n][mid % n] 与 target 对比; + +```java +class Solution { +    public boolean searchMatrix(int[][] matrix, int target) { +        if (matrix.length == 0) return false; +        int m = matrix.length; +        int n = matrix[0].length; +        int left = 0, right = m * n - 1; +        int mid = 0, num = 0; +        while (left <= right) { +            mid = left + (right - left) / 2; +            num = matrix[mid / n][mid % n]; +            if (target == num) { +                return true; +            } +            if (target > num) { +                left = mid + 1; +            } else { +                right = mid - 1; +            } +        } +        return false; +    } +} +``` +6. 动态规划 + +关键点:动态规划和递归或者分治没有根本上的区别(关键看有无最优的子结构) + +共性:都是找到重复子问题 + +差异性:最优子结构、中途可以淘汰次优解 + +经典案例:[剑指 Offer 10- I. 斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/) + +* 1)、常规递归法: +```java +class Solution { +    public int fib(int n) { +        return n <= 1 ? n : fib(n-1) + fib(n-2); +    } +} +``` +* 2)、保存中间值递归法: +```java +class Solution { +    private int[] num; +    public int fib(int n) { +        num = new int[n+1]; +        return fibonacci(n); +    } +    public int fibonacci(int n){ +        if(n <= 1) return n; +        if(num[n] == 0){ +            num[n] = fibonacci(n-1) + fibonacci(n-2); +            num[n] %= 1000000007; +        } +        return num[n]; +    } +} +``` +* 3)、自底向上递推 +```java +class Solution { +    public int fib(int n) { +        if(n <= 1) return n; +        int[] num = new int[n+1]; +        num[0] = 0; +        num[1] = 1; +        for(int i = 2; i <= n; i++){ +            num[i] = num[i-1] + num[i-2]; +            num[i] %= 1000000007; +        } +        return num[n]; +    } +} +``` +* 4)、动态规划 +```java +class Solution { +    public int fib(int n) { +        if(n <= 1) return n; +        int f0 = 0,f1 = 1,f2 = 0; +        for(int i = 2; i <= n; i++){ +            f2 = (f0 + f1) %1000000007; +            f0 = f1; +            f1 = f2; +        } +        return f2; +    } +} +``` +* [6](https://leetcode-cn.com/problems/unique-paths/)[2](https://leetcode-cn.com/problems/unique-paths/)[. 不同路径](https://leetcode-cn.com/problems/unique-paths/) + +![图片](https://uploader.shimo.im/f/sYmsMgz8T20nxbon.png!thumbnail?fileGuid=jVD8jjdtYgqcrRHV) + +思路:使用二维数组表示机器人行走区域,(0,0)表示起点start,dp[i][j]表示机器人行走到(i,j)坐标的路径条数,那么最终答案就是求机器人行走至终点(m-1,n-1)对应的dp[m-1][n-1]的值;已知机器人只能向下或向右行走,那么当机器人行走至第一行或第一列任意位置的路径条数都为 1,也就是dp[0][j] == 1,dp[i][0] == 1;同样也可推导出机器人行走至(i,j)位置只能从(i-1,j)点或(i,j-1)点走过来,那么dp[i][j] == dp[i-1][j] + dp[i][j-1]; + +```java +class Solution { +    public int uniquePaths(int m, int n) { +        if(m == 0 || n == 0) return 0; +        int[][] dp = new int[m][n]; +        for(int i = 0; i < m; i++) dp[i][0] = 1; +        for(int j = 0; j < n; j++) dp[0][j] = 1; +        for(int i = 1; i < m;i++){ +            for(int j = 1; j < n; j++){ +                dp[i][j] = dp[i-1][j] + dp[i][j-1]; +            } +        } +        return dp[m-1][n-1]; +    } +} +``` +优化版: +```java +class Solution { +    public int uniquePaths(int m, int n) { +        if(m == 0 || n == 0) return 0; +        int[][] dp = new int[m][n]; +        for(int i = 0; i < m;i++){ +            for(int j = 0; j < n; j++){ +                if (i == 0 || j == 0){ +                    dp[i][j] = 1; +                }else{ +                    dp[i][j] = dp[i-1][j] + dp[i][j-1]; +                } +            } +        } +        return dp[m-1][n-1]; +    } +} +``` +* 进阶:[63. 不同路径 II](https://leetcode-cn.com/problems/unique-paths-ii/) + +思路:不同于62题,机器人在行走过程中会碰到障碍物阻挡。同样的思路我们只需要处理无障碍物的情况; + +```java +class Solution { +    public int uniquePathsWithObstacles(int[][] obstacleGrid) { +        int m = obstacleGrid.length , n = obstacleGrid[0].length; +        int[][] dp = new int[m][n]; +        for(int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1; +        for(int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1; +        for(int i = 1; i < m;i++){ +            for(int j = 1; j < n; j++){ +                if(obstacleGrid[i][j] == 0) +                dp[i][j] = dp[i-1][j] + dp[i][j-1]; +            } +        } +        return dp[m-1][n-1]; +    } +} +``` +内存优化版: +```java +class Solution { +    public int uniquePathsWithObstacles(int[][] obstacleGrid) { +        int m = obstacleGrid.length , n = obstacleGrid[0].length; +        int[] dp = new int[n+1]; +        dp[1] = 1; +        for(int i = 0; i < m;i++){ +            for(int j = 1; j <= n; j++){ +                if(obstacleGrid[i][j-1] == 1) +                    dp[j] = 0; +                else +                    dp[j] += dp[j-1]; +            } +        } +        return dp[n]; +    } +} +``` +* 高阶:[980. 不同路径 III](https://leetcode-cn.com/problems/unique-paths-iii/) +```java +class Solution { +    private int res = 0; +    public int uniquePathsIII(int[][] grid) { +        // 初始化起点坐标和总步数,grid[i][j] == 2 也算步数,所以初始化为 1 +        int startX = 0, startY = 0, stepNum = 1; +        for(int i = 0; i < grid.length; i++){ +            for(int j = 0; j < grid[0].length; j++){ +                if(grid[i][j] == 1){ +                    startX = i; +                    startY = j; +                    continue; +                } +                if(grid[i][j] == 0) stepNum++; +            } +        } +        dfs(startX,startY,stepNum,grid); +        return res; +    } +    public void dfs(int startX,int startY,int stepNum,int[][] grid){ +        // 边界处理 +        if(startX < 0 || startX >= grid.length || startY < 0 || startY >= grid[0].length || grid[startX][startY] == -1) return; +        // 结束条件 +        if(grid[startX][startY] == 2) { +            if(stepNum == 0)res++; +            return; +        }; +        // 将当前位置标记为已经过,不可重复 +        grid[startX][startY] = -1; +        // 依次统计上下左右方向走的步数 +        dfs(startX,startY-1,stepNum -1,grid); +        dfs(startX,startY+1,stepNum -1,grid); +        dfs(startX-1,startY,stepNum -1,grid); +        dfs(startX+1,startY,stepNum -1,grid); +        // 结束本次遍历后将当前位置还原为可经过路线,以便下次统计 +        grid[startX][startY] = 0; +    } +} +``` +* [1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) +```java +class Solution { +    public int longestCommonSubsequence(String text1, String text2) { +        int l1 = text1.length(),l2 = text2.length(); +        int[][] dp = new int[l1+1][l2+1]; +        for(int i = 0; i < l1; i++){ +            for(int j = 0; j < l2; j++){ +                if(text1.charAt(i) == text2.charAt(j)){ +                        dp[i+1][j+1] = dp[i][j] + 1; +                    }else{ +                        dp[i+1][j+1] = Math.max(dp[i+1][j],dp[i][j+1]); +                    } +            } +        } +        return dp[l1][l2]; +    } +} +``` +* [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) + +保存中间值: + +```java +class Solution { +    public int climbStairs(int n) { +       if(n<=2) return n; +       int[] sum = new int[n+1]; +       sum[1] = 1; +       sum[2] = 2; +       for(int i = 3; i <= n;i++){ +           sum[i] = sum[i-1] + sum[i-2]; +       } +       return sum[n]; +    } +} +``` +动态规划:dp方程:f(n) = f(n-1) + f(n-2); +```java +class Solution { +    public int climbStairs(int n) { +        if(n <= 2) return n; +        int f1 = 1,f2 = 2,f3 = 0; +        for(int i = 3;i <= n; i++){ +            f3= f1 + f2; +            f1 = f2; +            f2 = f3; +        } +        return f3; +    } +} +``` +* [120. 三角形最小路径和](https://leetcode-cn.com/problems/triangle/) + +思路: + +```java +class Solution { +    public int minimumTotal(List> triangle) { +        int[] path = new int[triangle.size() + 1]; +        for(int i = triangle.size() -1; i >= 0;--i){ +            for(int j = 0; j < triangle.get(i).size();++j){ +                path[j] = Math.min(path[j],path[j+1]) + triangle.get(i).get(j); +            } +        } +        return path[0]; +    } +} +``` +* [53. 最大子序和](https://leetcode-cn.com/problems/maximum-subarray/) +```java +class Solution { +    public int maxSubArray(int[] nums) { +        int res = nums[0]; +        int sum = 0; +        for(int i = 0; i < nums.length; i++){ +            if(sum > 0){ +                sum += nums[i]; +            }else{ +                sum = nums[i]; +            } +            res = Math.max(res,sum); +        } +        return res; +    } +} +``` +* [152. 乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray/) + +思路:当值为负数时,会令最大乘积变为最小乘积或最小乘积变为最大乘积 + +```java +class Solution { +    public int maxProduct(int[] nums) { +        int proMin = 1 ,proMax = 1 ,max = Integer.MIN_VALUE; +        for(int i = 0; i < nums.length; i++){ +            if(nums[i] < 0){ +                int temp = proMax; +                proMax = proMin; +                proMin = temp; +            } +            proMin = Math.min(proMin*nums[i],nums[i]); +            proMax = Math.max(proMax*nums[i],nums[i]); +            max = Math.max(max,proMax); +        } +        return max; +    } +} +``` +7. 字典树和并查集 +1. 字典树的数据结构 + +字典树,即Trie树,又称单词查找或键树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。 + +优点:最大限度地减少无谓的字符串比较,查询效率比哈希表高。 + +2. 字典树的核心思想 + +空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 + +3. 字典树的基本性质 + +1、结点本身不存完整单词; + +2、从根结点到某结点,路径上经过的字符连接起来,为该结点对应的字符串; + +3、每个结点的所有子结点路径代表的字符都不相同。 + +字典树代码模板: + +```java +class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if (word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for (int i = 0;i < words.length;i++) { +            int n = words[i] - 'a'; +            if (curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word) { +        Trie node = this; +        char[] words = word.toCharArray(); +        for (int i = 0;i < words.length;i++) { +            node = node.next[words[i] - 'a']; +            if (node == null) return null; +        } +        return node; +    } +} +``` +[208. 实现 Trie (前缀树)](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) +```java +class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if(word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++){ +            int n = words[i] - 'a'; +            if(curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word){ +        Trie node = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++) { +            node = node.next[words[i] - 'a']; +            if(node == null) return null; +        } +        return node; +    } +} +/** + * Your Trie object will be instantiated and called as such: + * Trie obj = new Trie(); + * obj.insert(word); + * boolean param_2 = obj.search(word); + * boolean param_3 = obj.startsWith(prefix); + */ +``` +[212. 单词搜索 II](https://leetcode-cn.com/problems/word-search-ii/) +经典题型,使用字典树+dfs,逻辑思维简单易懂 + +```java +class Solution { +    private static final int[] dx = new int[]{0,0,-1,1}; +    private static final int[] dy = new int[]{-1,1,0,0}; +    public List findWords(char[][] board, String[] words) { +        if(words.length == 0) return new ArrayList<>(); +        // 构建字典树 +         Trie root = new Trie(); +        for(String word : words){ +            root.insert(word); +        } +        // 遍历board + dfs递归 向四周扩撒构建字符串,并在字典树中查找是否存在此字符串 +        Set res = new HashSet<>(); +        int m = board.length,n = board[0].length; +        for(int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                dfs(board,root,i,j,"",res); +            } +        } +        return new ArrayList(res); +    } +    public void dfs(char[][] board, Trie root, int i, int j, String currStr, Set res){ +        // 递归结束条件,边界处理以及访问过的字符不能再次访问 +        if(i < 0 || j < 0 || i == board.length || j == board[0].length || board[i][j] == '@') return; +        // 递归结束条件 2 +        char currChar = board[i][j]; +        if(root.next[currChar - 'a'] == null) return; +        // 处理当前层 +        char temp = currChar; +        board[i][j] = '@'; +        // 构建字符串 +        currStr += currChar; +        root = root.next[currChar - 'a']; +        if(root.isEnd){ +            res.add(currStr); +        } +        // 向上下左右递归下探 +        for(int k = 0; k < dx.length; k++){ +            dfs(board,root,i + dx[k], j + dy[k],currStr,res); +        } +        // 将字符串置为空字符串 +        currStr = ""; +        // 恢复当前层字符 +        board[i][j] = temp; +    } +    class Trie { +    private boolean isEnd; +    private Trie[] next; +    /** Initialize your data structure here. */ +    public Trie() { +        isEnd = false; +        next = new Trie[26]; +    } +     +    /** Inserts a word into the trie. */ +    public void insert(String word) { +        if(word == null || word.length() == 0) return; +        Trie curr = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++){ +            int n = words[i] - 'a'; +            if(curr.next[n] == null) curr.next[n] = new Trie(); +            curr = curr.next[n]; +        } +        curr.isEnd = true; +    } +     +    /** Returns if the word is in the trie. */ +    public boolean search(String word) { +        Trie node = searchPrefix(word); +        return node != null && node.isEnd; +    } +     +    /** Returns if there is any word in the trie that starts with the given prefix. */ +    public boolean startsWith(String prefix) { +        Trie node = searchPrefix(prefix); +        return node != null; +    } +    private Trie searchPrefix(String word){ +        Trie node = this; +        char[] words = word.toCharArray(); +        for(int i = 0; i < words.length; i++) { +            node = node.next[words[i] - 'a']; +            if(node == null) return null; +        } +        return node; +    } +} +} +``` +1. 并查集数据结构:跟树有些类似,只不过和树是相反的。在树这个数据结构里面,每个结点会记录它的子结点。在并查集里,每个结点会记录它的父节点; + +![图片](https://uploader.shimo.im/f/qg8SH3yyfOwN2Lmx.png!thumbnail?fileGuid=jVD8jjdtYgqcrRHV) + +2. 并查集适用场景:组团、配对问题 +3. 基本操作 + * makeSet(s):建立一个新的并查集,其中包含s个单元素集合。 + * unionSet(x,y):把元素x和元素y所在的集合合并,要求x和y所在的集合不相交,如果相交则不合并。 + * find(x):找到元素x所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将他们各自的代表比较一下就可以了。 + +[参考链接](https://zhuanlan.zhihu.com/p/93647900/) + +代码模板: + +```java +class UnionFind { + private int count = 0; + private int[] parent; + public UnionFind(int n) { + count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + public int find(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + if (rootP == rootQ) return; + parent[rootP] = rootQ; + count--; + } +} +``` +[547. 省份数量](https://leetcode-cn.com/problems/number-of-provinces/) +思路:DFS 深度优先搜索 + +```java +class Solution { +    public int findCircleNum(int[][] isConnected) { +        if(isConnected.length == 0) return 0; +        boolean[] statistics = new boolean[isConnected.length]; +        int res = 0; +        for(int i = 0; i < isConnected.length; i++){ +            if(!statistics[i]){ +                dfs(isConnected,statistics,i); +                res++; +            } +        } +        return res; +    } +    public void dfs(int[][] isConnected,boolean[] statistics, int i){ +        for(int j = 0; j < isConnected.length; j++){ +            if(isConnected[i][j] == 1 && !statistics[j]){ +                statistics[j] = true; +                dfs(isConnected,statistics,j); +            } +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { + public int findCircleNum(int[][] isConnected) { + if(isConnected.length == 0) return 0; + boolean[] statistics = new boolean[isConnected.length]; + int res = 0; + Queue queue = new LinkedList<>(); + for(int i = 0; i < isConnected.length; i++){ + if(!statistics[i]){ + queue.offer(i); + res++; + while(!queue.isEmpty()){ + int m = queue.poll(); + for(int n = 0; n < isConnected.length; n++){ + if(isConnected[m][n] == 1 && !statistics[n]){ + queue.offer(n); + statistics[n] = true; + } + } + } + } + } + return res; + } +} +``` +思路:并查集 +```java +class Solution { +    public int findCircleNum(int[][] isConnected) { +        if(isConnected.length == 0) return 0; +        // 初始化并查集 +        UnionFind union = new UnionFind(isConnected.length); +        // 遍历isConnected中每个关联关系,有相连的城市合并为相同的省份 +        for(int i = 0; i < isConnected.length; i++){ +            for(int j = 0; j < isConnected.length; j++){ +                if(isConnected[i][j] == 1){ +                    union.union(i,j); +                } +            } +        } +        return union.count; +    } +    class UnionFind { +        private int count = 0; +        private int[] parent; +        public UnionFind(int n) { +            count = n; +            parent = new int[n]; +            for (int i = 0; i < n; i++) { +                parent[i] = i; +            } +        } +        public int find(int p) { +            while (p != parent[p]) { +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        public void union(int p, int q) { +            int rootP = find(p); +            int rootQ = find(q); +            if (rootP == rootQ) return; +            parent[rootP] = rootQ; +            count--; +        } +    } +} +``` +[200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) +思路:DFS 深度优先搜索算法 + +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        int count = 0; +        for (int i = 0; i < grid.length; i++) { +            for (int j = 0; j < grid[0].length; j++) { +                if (grid[i][j] == '1') { +                    count++; +                    dfs(grid, i, j); +                } +            } +        } +        return count; +    } +    public void dfs(char[][] grid, int i, int j){ +        if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return; +        grid[i][j] = '0'; +        for(int k = 0; k < dx.length; k++){ +            dfs(grid,i+dx[k],j+dy[k]); +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        int count = 0; +        for (int i = 0; i < grid.length; i++) { +            for (int j = 0; j < grid[0].length; j++) { +                if (grid[i][j] == '1') { +                    count++; +                    bfs(grid, i, j); +                } +            } +        } +        return count; +    } +    public void bfs(char[][] grid, int i,int j){ +        Queue queue = new LinkedList<>(); +        queue.offer(new int[]{i,j}); +        while(!queue.isEmpty()){ +            int[] curr = queue.poll(); +            i = curr[0]; +            j = curr[1]; +            if(i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == '1'){ +                grid[i][j] = '0'; +                queue.offer(new int[]{i,j-1}); +                queue.offer(new int[]{i,j+1}); +                queue.offer(new int[]{i-1,j}); +                queue.offer(new int[]{i+1,j}); +            } +        } +    } +} +``` +思路:并查集 +```java +class Solution { +    private int m,n; +    public int numIslands(char[][] grid) { +        if (grid.length == 0) return 0; +        m = grid.length; +        n = grid[0].length; +        UnionFind union = new UnionFind(m*n); +        int spaces = 0;// 空地的数量 +        int[][] direction = new int[][]{{1,0},{0,1}}; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                if (grid[i][j] == '0') { +                    spaces++; +                }else{ +                    for(int[] dire : direction){ +                        int newx = i + dire[0]; +                        int newy = j + dire[1]; +                        // 判断边界 +                        if(newx < m && newy < n && grid[newx][newy] == '1'){ +                            // 合并陆地 +                            union.union(getIndex(i,j),getIndex(newx,newy)); +                        } +                    } +                } +            } +        } +        return union.getCount() - spaces; +    } +    public int getIndex(int i,int j){ +        return i*n + j; +    } +    class UnionFind{ +        private int count; +        private int[] parent; +        UnionFind(int n){ +            count = n; +            parent = new int[n]; +            for(int i = 0; i < n; i++){ +                parent[i] = i; +            } +        } +        private int find(int p){ +            while(p != parent[p]){ +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        private void union(int p, int q){ +            int P = find(p); +            int Q = find(q); +            if(P == Q) return; +            parent[P] = Q; +            count--; +        } +        public int getCount(){ +            return count; +        } +    } +} +``` +[130. 被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/) +思路:DFS 深度优先搜索 + +1、遍历 board ,从边界为‘O’的开始递归,查找相联通的‘O’ + +2、将与边界相联通的‘O’转换为‘#’,剩余‘O’就是被‘X’包围的 + +3、再次遍历 board,将‘#’转换为原来的‘O’,将‘O’转换为‘X’ + +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public void solve(char[][] board) { +        if (board.length == 0) return; +        int m = board.length,n = board[0].length; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                // 首先从边界为‘O’的开始递归下探,将与边界‘O’联通的‘O’都替换为‘#’ +                boolean isEdge = i == 0 || j == 0 || i == m-1 || j == n-1; +                if(isEdge && board[i][j] == 'O'){ +                    dfs(board,i,j); +                } +            } +        } +        // 将递归后的结果进行转换,‘#’代表和边界联通的,剩余的‘O’代表和边界不连通,需要转换为‘X’ +        for (int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                if(board[i][j] == '#'){ +                    board[i][j] = 'O'; +                }else if(board[i][j] == 'O'){ +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public void dfs(char[][] board, int i, int j){ +        // 递归结束条件 +        if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') return; +        board[i][j] = '#'; +        // 左右上下,递归下探 +        for (int k = 0; k < dx.length; k++){ +            dfs(board,i+dx[k],j+dy[k]); +        } +    } +} +``` +思路:BFS 广度优先搜索 +```java +class Solution { +    private static int[] dx = new int[]{0,0,-1,1}; +    private static int[] dy = new int[]{-1,1,0,0}; +    public void solve(char[][] board) { +        if (board.length == 0) return; +        int m = board.length,n = board[0].length; +        for (int i = 0; i < m; i++) { +            for (int j = 0; j < n; j++) { +                // 首先从边界为‘O’的开始递归下探,将与边界‘O’联通的‘O’都替换为‘#’ +                boolean isEdge = i == 0 || j == 0 || i == m-1 || j == n-1; +                if(isEdge && board[i][j] == 'O'){ +                    bfs(board,i,j); +                } +            } +        } +        // 将递归后的结果进行转换,‘#’代表和边界联通的,剩余的‘O’代表和边界不连通,需要转换为‘X’ +        for (int i = 0; i < m; i++){ +            for(int j = 0; j < n; j++){ +                if(board[i][j] == '#'){ +                    board[i][j] = 'O'; +                }else if(board[i][j] == 'O'){ +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public void bfs(char[][] board, int i,int j){ +        Queue queue = new LinkedList<>(); +        queue.offer(new int[]{i,j}); +        while(!queue.isEmpty()){ +            int[] curr = queue.poll(); +            i = curr[0]; +            j = curr[1]; +            if(i >= 0 && i < board.length && j >= 0 && j < board[0].length && board[i][j] == 'O'){ +                board[i][j] = '#'; +                for (int k = 0; k < dx.length; k++){ +                    queue.offer(new int[]{i+dx[k],j+dy[k]}); +                } +            } +        } +    } +} +``` +思路:并查集 +1、首先创建一个虚拟结点,将所有在边界上为‘O’的点都联通到虚拟结点上; + +2、然后再将非边界上的‘O’按是否与边界元素‘O’相连分组; + +3、按分组后的情况,将与边界‘O’相连的即就是与虚拟结点相连的都转换为‘O’,不与边界‘O’相连的说明就是被‘X’包围的,转换为‘X’; + +```java +class Solution { +    private int[] dx = new int[]{0,0,-1,1}; +    private int[] dy = new int[]{-1,1,0,0}; +    private int rows,clos; +    public void solve(char[][] board) { +        rows = board.length; +        if (rows == 0) return; +        clos = board[0].length; +        UnionFind union = new UnionFind(rows*clos+1); +        int dummyNode = rows*clos; +        for (int i = 0; i < rows; i++){ +            for(int j = 0; j < clos; j++){ +                if (board[i][j] == 'O'){ +                    if (i == 0 || j == 0 || i == rows - 1 || j == clos - 1){ +                        union.union(getIndex(i,j),dummyNode); +                    }else{ +                        for(int k = 0; k < dx.length; k++){ +                            int currI = i + dx[k]; +                            int currJ = j + dy[k]; +                            if (currI >= 0 && currI < rows && currJ >= 0 && currJ < clos && board[currI][currJ] == 'O'){ +                                union.union(getIndex(i,j),getIndex(currI,currJ)); +                            } +                        } +                    } +                } +            } +        } +        for (int i = 0; i < rows; i++){ +            for (int j = 0; j < clos; j++) { +                if (union.find(getIndex(i,j)) == union.find(dummyNode)){ +                    board[i][j] = 'O'; +                }else { +                    board[i][j] = 'X'; +                } +            } +        } +    } +    public int getIndex (int i, int j){ +        return i * clos + j; +    } +    class UnionFind{ +        private int count; +        private int[] parent; +        UnionFind(int n){ +            count = n; +            parent = new int[n]; +            for (int i = 0; i < n; i++){ +                parent[i] = i; +            } +        } +        private int find(int p){ +            while(p != parent[p]){ +                parent[p] = parent[parent[p]]; +                p = parent[p]; +            } +            return p; +        } +        private void union(int p, int q){ +            int P = find(p); +            int Q = find(q); +            if (p == Q) return; +            parent[P] = Q; +            count--; +        } +    } +} +```