diff --git a/Week_01/DequeDemo.java b/Week_01/DequeDemo.java new file mode 100644 index 00000000..8ba276f2 --- /dev/null +++ b/Week_01/DequeDemo.java @@ -0,0 +1,40 @@ +package com.freezing.leetcode.jike; + +import java.util.Deque; +import java.util.LinkedList; + +public class DequeDemo { + public static void main(String[] args){ + Deque deque = new LinkedList<>(); + deque.addFirst("a"); + deque.addFirst("b"); + deque.addFirst("c"); + System.out.println(deque); + + String str = deque.peekFirst(); + System.out.println(str); + System.out.println(deque); + + while(!deque.isEmpty()){ + System.out.println(deque.pollFirst()); + } + System.out.println(deque); + + System.out.println("-----------------------------------"); + + Deque deque1 = new LinkedList<>(); + deque1.push("a"); + deque1.push("b"); + deque1.push("c"); + System.out.println(deque1); + + String str1 = deque1.peek(); + System.out.println(str1); + System.out.println(deque1); + + while(deque1.size() > 0){ + System.out.println(deque1.pop()); + } + System.out.println(deque1); + } +} diff --git a/Week_01/MyCircularDeque.java b/Week_01/MyCircularDeque.java new file mode 100644 index 00000000..2d009b80 --- /dev/null +++ b/Week_01/MyCircularDeque.java @@ -0,0 +1,85 @@ +package com.freezing.leetcode.jike; + +public class MyCircularDeque { + + private int capacity = 0; + private int arr[]; + private int front = 0; + private int last = 0; + + /** + * Initialize your data structure here. Set the size of the deque to be k. + */ + public MyCircularDeque(int k) { + capacity = k + 1; + arr = new int[capacity]; + } + + /** + * Adds an item at the front of Deque. Return true if the operation is successful. + */ + public boolean insertFront(int value) { + if (isFull()) return false; + front = (front - 1 + capacity) % capacity; + arr[front] = value; + return true; + } + + /** + * Adds an item at the rear of Deque. Return true if the operation is successful. + */ + public boolean insertLast(int value) { + if (isFull()) return false; + arr[last] = value; + last = (last + 1) % capacity; + return true; + } + + /** + * Deletes an item from the front of Deque. Return true if the operation is successful. + */ + public boolean deleteFront() { + if (isEmpty()) return false; + front = (front + 1) % capacity; + return true; + } + + /** + * Deletes an item from the rear of Deque. Return true if the operation is successful. + */ + public boolean deleteLast() { + if (isEmpty()) return false; + last = (last - 1 + capacity) % capacity; + return true; + } + + /** + * Get the front item from the deque. + */ + public int getFront() { + if (isEmpty()) return -1; + return arr[front]; + } + + /** + * Get the last item from the deque. + */ + public int getRear() { + if (isEmpty()) return -1; + return arr[(last - 1 + capacity) % capacity]; + } + + /** + * Checks whether the circular deque is empty or not. + */ + public boolean isEmpty() { + return front == last; + } + + /** + * Checks whether the circular deque is full or not. + */ + public boolean isFull() { + return (last + 1) % capacity == front; + } +} diff --git a/Week_01/README.md b/Week_01/README.md index 50de3041..ef9dc160 100644 --- a/Week_01/README.md +++ b/Week_01/README.md @@ -1 +1,183 @@ -学习笔记 \ No newline at end of file +学习笔记 + +1,解题的时候要注意好边界问题 + +2,用自己的思路解题后,需要看看是否有多余无效的步骤可以省略掉,例如『加一』这道题,末位加一没有进位就可以直接返回了,不用再去遍历前面的位数了 + +3,要关注输入的值是否会使数组溢出,例如『旋转数组』这道题的k就会大于数组的长度 + +4、对于不熟悉的系统函数,可以用自己的方式去实现,避免使用出错,还难以找出原因,例如『合并两个有序数组』,题解中使用了System.arraycopy()的函数来合并剩下的数组,由于对参数位置弄错了,导致结果一直不对,最后还是用for循环来解决了这个问题 + +5、链表的操作一般都要新建一个节点来记录头结点 + +## Queue源码分析 + +Queue是一个接口,继承自Collection,包含的Collection的所有方法,以及以下额外的6个方法,从中可以看出对队列的操作基本上包括有三种:添加、移除、访问,而每种操作失败后又有两种返回的方式,一种是直接返回false或者null,一种是直接抛出异常,具体使用哪一种,则需要看具体使用的场景 + +### 1 boolean add(E e); + +若队列的容量还没满,则添加一个元素到队列中,并返回true; +若队列的容量已经满了,则抛出一个异常IllegalStateException。 + +### 2 boolean offer(E e); + +若队列的容量还没满,则添加一个元素到队列中,并返回true; +若队列的容量已经满了,则不添加元素到队列中,并返回false。 + +### 3 E remove(); + +若队列不空,则将队列的头返回,并从队列中移除; +若队列为空,则抛出一个异常NoSuchElementException。 + +### 4 E poll(); + +若队列不空,则将队列的头返回,并从队列中移除; +若队列为空,则返回null。 + +### 5 E element(); + +若队列不空,将队列的头返回,但不从队列中移除; +若队列为空,则抛出一个异常NoSuchElementException。 + +### 6 E peek(); + +若队列不空,将队列的头返回,但不从队列中移除; +若队列为空,则返回null。 + + +## PriorityQueue源码分析 + +继承自AbstractQueue类,而AbstractQueue类实现了Queue的接口,其中的元素是有顺序的; +如果传入的类型是基本类型的话,则默认是按照升序排列的; +如果传入的类型是自定义类型的话,则需要自定义排序器; +默认的容量是11,也可以自己传入容量,但最小容量为1,否则就会抛出IllegalArgumentException异常; +内部主要是维护一个数组来存放传入的数据。 + +主要的方法有以下几个: + +peek()//返回队首元素 +poll()//返回队首元素,队首元素出队列 +add()//添加元素 +size()//返回队列元素个数 +isEmpty()//判断队列是否为空,为空返回true,不空返回false + +主要需要分析的是添加元素和移除元素对队列的排序操作 + +### 1 boolean add(E e) (同 boolean add(E e)); + +往队列中添加元素,并将元素按照排序规则插入到对应的位置; +``` + public boolean offer(E e) { + if (e == null) + throw new NullPointerException(); + modCount++; + int i = size; + if (i >= queue.length) + grow(i + 1); + size = i + 1; + if (i == 0) + queue[0] = e; + else + siftUp(i, e); + return true; + } +``` +1)if和else,分别执行对象判空和容量判断 +2)执行siftUp(i, e),i是原有队列长度,e是要入队的元素。 + +siftUp是堆中调整元素位置的一种方法,可以看出这里的优先队列是使用最大/最小堆实现的。接着看siftUp: +``` + private void siftUp(int k, E x) { + if (comparator != null) + siftUpUsingComparator(k, x); + else + siftUpComparable(k, x); + } +``` +看看使用了comparator的方法,k是原有队列长度,x是入队元素,queue是队列,comparator是比较器: +``` + private void siftUpUsingComparator(int k, E x) { + while (k > 0) { + int parent = (k - 1) >>> 1; + Object e = queue[parent]; + if (comparator.compare(x, (E) e) >= 0) + break; + queue[k] = e; + k = parent; + } + queue[k] = x; + } +``` +1)k>0,队列长度大于0 +2)parent = k - 1 >>> 1; 即(k-1)/2,表示最后一个非叶子节点的位置 +3)e为父节点,x是入队元素,x可以看做放在最后一个位置。如果compare(x, e) < 0,则执行元素往上走的方法。 +注:在siftUp中,如果是最小堆,那么应该是较小的元素往上走,如果是最大堆,则应该是较大的元素往上走。 + +由于源码中新入队元素x是在第1个参数的位置,因此最大/最小优先队列主要根据第1个参数的大小关系来判断。 +``` +//对于最大堆,当x>e时,让x上升,则 x>e时返回负数,即 +int compare(Integer x, Integer e){ + return x > e ? -1 : 1; +} +//对于最小堆,当x queue = new PriorityQueue(new Comparator(){ + @Override + public int compare(Integer o1, Integer o2){ + return o1 < o2 ? -1 : 1; + /* e.g., return o1.compare(o2); */ + } +}); +// 最大优先队列,则反过来 return o2.compareTo(o1); +``` + +### 2 E poll() + +将队首元素返回,并从队列中移除 + +``` + public E poll() { + if (size == 0) + return null; + int s = --size; + modCount++; + E result = (E) queue[0]; + E x = (E) queue[s]; + queue[s] = null; + if (s != 0) + siftDown(0, x); + return result; + } +``` +将队列长度-1,返回队首元素,并将元素从队列中移除,将剩下的元素重新进行排序 +``` + private void siftDown(int k, E x) { + if (comparator != null) + siftDownUsingComparator(k, x); + else + siftDownComparable(k, x); + } + private void siftDownUsingComparator(int k, E x) { + int half = size >>> 1; + while (k < half) { + int child = (k << 1) + 1; + Object c = queue[child]; + int right = child + 1; + if (right < size && + comparator.compare((E) c, (E) queue[right]) > 0) + c = queue[child = right]; + if (comparator.compare(x, (E) c) <= 0) + break; + queue[k] = c; + k = child; + } + queue[k] = x; + } +``` + diff --git a/Week_01/Week1.java b/Week_01/Week1.java new file mode 100644 index 00000000..b5d0c852 --- /dev/null +++ b/Week_01/Week1.java @@ -0,0 +1,176 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +public class Week1 { + /** + * 283.移动零 + * https://leetcode-cn.com/problems/move-zeroes/ + * + * @param nums + */ + public void moveZeroes(int[] nums) { + if (nums.length <= 1) return; + int j = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + nums[j] = nums[i]; + if (i != j) nums[i] = 0; + j++; + } + } + } + + /** + * 1.两数之和 + * https://leetcode-cn.com/problems/two-sum/ + * + * @param nums + * @param target + * @return + */ + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + int value = target - nums[i]; + if (map.containsKey(value)) { + return new int[]{map.get(value), i}; + } + map.put(nums[i], i); + } + return null; + } + + /** + * 26.删除排序数组中的重复项 + * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ + * + * @param nums + * @return + */ + public int removeDuplicates(int[] nums) { + if (nums.length <= 1) return nums.length; + int i = 0; + for (int j = 1; j < nums.length; j++) { + if (nums[i] != nums[j]) { + nums[++i] = nums[j]; + } + } + return i + 1; + } + + /** + * 66. 加一 + * https://leetcode-cn.com/problems/plus-one/ + * + * @param digits + * @return + */ + public int[] plusOne(int[] digits) { + for (int i = digits.length - 1; i >= 0; i--) { + digits[i] = ++digits[i] % 10; + if (digits[i] != 0) return digits; + } + digits = new int[digits.length + 1]; + digits[0] = 1; + return digits; + } + + /** + * 189. 旋转数组 + * https://leetcode-cn.com/problems/rotate-array/ + * + * @param nums + * @param k + */ + public void rotate(int[] nums, int k) { + if (nums.length < 2) return; + k %= nums.length; + reverse(nums, 0, nums.length - 1); + reverse(nums, 0, k - 1); + reverse(nums, k, nums.length - 1); + } + + public void reverse(int[] nums, int start, int end) { + while (start < end) { + int temp = nums[start]; + nums[start++] = nums[end]; + nums[end--] = temp; + } + } + + /** + * 88. 合并两个有序数组 + * https://leetcode-cn.com/problems/merge-sorted-array/ + * + * @param nums1 + * @param m + * @param nums2 + * @param n + */ + public void merge(int[] nums1, int m, int[] nums2, int n) { + int len1 = m - 1; + int len2 = n - 1; + int len = m + n - 1; + while (len1 >= 0 && len2 >= 0) { + nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--]; + } + for (int i = 0; i < len2 + 1; i++) { + nums1[i] = nums2[i]; + } + } + + /** + * 21. 合并两个有序链表 + * https://leetcode-cn.com/problems/merge-two-sorted-lists/ + * + * @param l1 + * @param l2 + * @return + */ + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + ListNode head = new ListNode(-1); + ListNode prev = head; + while (l1 != null && l2 != null) { + if (l1.val < l2.val) { + prev.next = l1; + l1 = l1.next; + } else { + prev.next = l2; + l2 = l2.next; + } + prev = prev.next; + } + prev.next = l1 == null ? l2 : l1; + return head.next; + } + + /** + * 42. 接雨水 + * https://leetcode-cn.com/problems/trapping-rain-water/ + * + * @param height + * @return + */ + public int trap(int[] height) { + int sum = 0; + Deque stack = new ArrayDeque<>(); + int current = 0; + while (current < height.length) { + while (!stack.isEmpty() && height[stack.peekLast()] < height[current]) { + int h = height[stack.pollLast()]; + if (stack.isEmpty()) break; + int w = current - stack.peekLast() - 1; + int min = Math.min(height[current], height[stack.peekLast()]); + sum += w * (min - h); + } + stack.addLast(current); + current++; + } + + return sum; + } +} diff --git a/Week_02/README.md b/Week_02/README.md index 50de3041..288e8e41 100644 --- a/Week_02/README.md +++ b/Week_02/README.md @@ -1 +1,70 @@ -学习笔记 \ No newline at end of file +学习笔记 + +1、map主要是要确定key和value的对应关系,set是无序的集合 + +2、树的面试题解法一般都是递归,主要是因为树本身的定义,子节点也是树结构,就是有重复性,符合递归的特性。 + +3、二叉树指的是整棵树中,每个结点都最多只有两个子节点; + +4、需要熟练掌握树的前序,中序,后序,层序遍历的写法: + +前序:根-左-右 + +```java +void preorder(TreeNode node){ + if(node != null){ + println(node.val); + preorder(node.left); + preorder(node.right); + } +} + +``` + +中序:左-根-右 + +```java +void inorder(TreeNode node){ + if(node != null){ + inorder(node.left); + println(node.val); + inorder(node.right); + } +} +``` + +后序:左-右-根 + +```java +void postorder(TreeNode node){ + if(node != null){ + postorder(node.left); + postorder(node.right); + println(node.val); + } +} +``` + +层序:从上到下,从左到右(用队列来实现,先进先出) + +```java +List> levelOrder(TreeNode root) { + List> res = new ArrayList<>(); + Deque deque = new ArrayDeque<>(); + if(root != null) deque.add(root); + while(!deque.isEmpty()){ + int size = deque.size(); + List list = new ArrayList<>(); + for(int i = 0; i < size; i++){ + Node node = deque.pollFirst(); + list.add(node.val); + if(node.left != null) deque.addLast(node.left); + if(node.right != null) deque.addLast(node.right); + } + res.add(list); + } + return res; +} +``` + +5、堆是一个接口,分为大顶堆和小顶堆,即堆顶的元素始终都是最大/最小的,最常用的是二叉堆(不是最好的实现方式),获取堆顶元素的时间复杂度为O(1),添加和删除元素的时间复杂度为O(logn),在java中使用的是PriorityQueue来实现二叉堆,可以通过传入自定义的Comparator来实现堆的排序方式。 \ No newline at end of file diff --git a/Week_02/Week2.java b/Week_02/Week2.java new file mode 100644 index 00000000..be7b04aa --- /dev/null +++ b/Week_02/Week2.java @@ -0,0 +1,259 @@ +package com.freezing.leetcode.jike; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; + +public class Week2 { + /** + * 242. 有效的字母异位词 + * https://leetcode-cn.com/problems/valid-anagram/ + * + * @param s + * @param t + * @return + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean isAnagram(String s, String t) { + if (s.length() != t.length()) return false; + int[] arr = new int[26]; + for (int i = 0; i < s.length(); i++) { + arr[s.charAt(i) - 'a']++; + } + for (int i = 0; i < t.length(); i++) { + arr[t.charAt(i) - 'a']--; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] != 0) return false; + } + return true; + } + + /** + * 1. 两数之和 + * https://leetcode-cn.com/problems/two-sum/description/ + * + * @param nums + * @param target + * @return + */ + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + int result = target - nums[i]; + if (map.containsKey(result)) { + return new int[]{map.get(result), i}; + } + map.put(nums[i], i); + } + return null; + } + + /** + * 49. 字母异位词分组 + * https://leetcode-cn.com/problems/group-anagrams/ + * + * @param strs + * @return + */ + public List> groupAnagrams(String[] strs) { + Map> map = new HashMap<>(); + for (int i = 0; i < strs.length; i++) { + char[] chars = strs[i].toCharArray(); + Arrays.sort(chars); + List list; + String str = new String(chars); + if (map.containsKey(str)) { + list = map.get(str); + } else { + list = new ArrayList<>(); + } + list.add(strs[i]); + map.put(str, list); + } + return new ArrayList<>(map.values()); + } + + /** + * 144. 二叉树的前序遍历 + * https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ + * + * @param root + * @return + */ + public List preorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + preorderTraversal(root, result); + return result; + } + + private void preorderTraversal(TreeNode node, List result) { + if (node == null) return; + result.add(node.val); + preorderTraversal(node.left, result); + preorderTraversal(node.right, result); + } + + /** + * 94. 二叉树的中序遍历 + * https://leetcode-cn.com/problems/binary-tree-inorder-traversal/ + * + * @param root + * @return + */ + public List inorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + inorderTraversal(root, result); + return result; + } + + private void inorderTraversal(TreeNode node, List result) { + if (node == null) return; + inorderTraversal(node.left, result); + result.add(node.val); + inorderTraversal(node.right, result); + } + + /** + * 589. N叉树的前序遍历 + * https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/description/ + * + * @param root + * @return + */ + public List preorder(Node root) { + List result = new ArrayList<>(); + preorder(root, result); + return result; + } + + private void preorder(Node node, List result) { + if (node == null) return; + result.add(node.val); + if (node.children.isEmpty()) return; + for (int i = 0; i < node.children.size(); i++) { + preorder(node.children.get(i), result); + } + } + + /** + * 429. N 叉树的层序遍历 + * https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ + * + * @param root + * @return + */ + public List> levelOrder(Node root) { + List> result = new ArrayList<>(); + Deque deque = new LinkedList<>(); + if (root != null) deque.addLast(root); + while (!deque.isEmpty()) { + int size = deque.size(); + Node node = deque.pollFirst(); + List list = new ArrayList<>(); + for (int i = 0; i < size; i++) { + list.add(node.val); + if (node.children == null || node.children.isEmpty()) continue; + for (int j = 0; j < node.children.size(); j++) { + deque.addLast(node.children.get(j)); + } + } + result.add(list); + } + + return result; + } + + /** + * 590. N叉树的后序遍历 + * https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/ + * + * @param root + * @return + */ + public List postorder(Node root) { + List result = new ArrayList<>(); + postorder(root, result); + return result; + } + + private void postorder(Node node, List result) { + if (node == null) return; + if (node.children != null) { + for (int i = 0; i < node.children.size(); i++) { + postorder(node.children.get(i), result); + } + } + result.add(node.val); + } + + /** + * 剑指 Offer 49. 丑数 + * https://leetcode-cn.com/problems/chou-shu-lcof/ + * + * @param n + * @return + */ + public int nthUglyNumber(int n) { + int[] uglyNumbers = {2, 3, 5}; + Queue queue = new PriorityQueue<>(); + queue.add(1L); + int count = 0; + while (!queue.isEmpty()) { + long num = queue.poll(); + if (++count >= n) { + return (int) num; + } + for (int i = 0; i < uglyNumbers.length; i++) { + long newUgly = num * uglyNumbers[i]; + if (!queue.contains(newUgly)) { + queue.add(newUgly); + } + } + } + return -1; + } + + /** + * 347. 前 K 个高频元素 + * https://leetcode-cn.com/problems/top-k-frequent-elements/ + * + * @param nums + * @param k + * @return + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public int[] topKFrequent(int[] nums, int k) { + final Map map = new HashMap<>(); + int index = 0; + int[] res = new int[k]; + Queue queue = new PriorityQueue<>(new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return map.get(o2) - map.get(o1); + } + }); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], map.getOrDefault(nums[i], 0) + 1); + } + for(int key:map.keySet()){ + queue.add(key); + } + while(index < k){ + res[index++] = queue.poll(); + } + return res; + } + +} diff --git a/Week_03/README.md b/Week_03/README.md index 50de3041..99f272bf 100644 --- a/Week_03/README.md +++ b/Week_03/README.md @@ -1 +1,422 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 一、递归 + +这周的学习内容是递归,递归的代码去理解还是比较晦涩的,一开始总是不得其解,后来想起超哥视频里面说的,要拒绝人肉递归,只需要记住递归的模板,找到最近重复子问题,然后每一层的递归只处理当前逻辑即可,递归的模板如下: + +```java +public void recur(int level, int param){ + //terminator 递归结束条件 + if(level > MAX_LEVEL){ + // process result 处理结果 + return; + } + + // process current logic 处理当前层级的逻辑 + process(level,param); + + // drill down 下探到下一层去 + recur(level + 1, newParam); + + // restore current status 重置当前的状态 +} +``` + +### 习题笔记 + +1、[验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)这道题中,可以利用二叉搜索树的特性,就是中序遍历的结果是单调递增的即可,默写出中序遍历的递归模板(左-根-右),需要满足的条件有:左子树为二叉搜索树,根值小于上一个值,右子树为二叉搜索树,代码如下: + +```java + // 这里初始化用Long.MIN_VALUE,是因为如果使用Integer.MIN_VALUE,那么如果测试用例中有结点为Integer.MIN_VALUE的话,会导致root.val <= preValue成立直接返回false + long preValue = Long.MIN_VALUE; + public boolean isValidBST(TreeNode root) { + if(root == null) return true; + if(!isValidBST(root.left)) return false; + if(root.val <= preValue) return false; + preValue = root.val; + return isValidBST(root.right); + } +``` + +2、[二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)这道题的主要思想是,对于当前结点来说,它的最大深度为左子树和右子树的深度的最大值,然后加上根节点的深度1,就是最大深度了,代码如下: + +```java + public int maxDepth(TreeNode root) { + if(root == null) return 0; + return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; + } +``` + +3、[ 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/)这道题的主要思想是,对于当前结点来说,如果左子树是空的,就直接返回右子树的深度加1,如果右子树是空的,就直接返回左子树的深度加1,如果左右子树都不为空,那么就返回两棵子树深度的最小值加1,代码如下: + +```java + public int minDepth(TreeNode root) { + if(root == null) return 0; + if(root.left == null) return minDepth(root.right) + 1; + if(root.right == null) return minDepth(root.left) + 1; + return Math.min(minDepth(root.left),minDepth(root.right)) + 1; + } +``` + +4、[二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)这道题的主要思想是: + +(1)如果根节点是p或者q,那么他们的最近公共祖先就是p或者q; + +(2)如果在某个结点t的左子节点找到了p或者q,则返回该结点; + +(3)如果在t的右子树中找到了q或者p,则t为结果; + +(4)如果在t的右子树中没找到了q或者p,则返回t的父节点,往t的父结点的右子树继续遍历,直到找到q或者p,以此类推 + +(5)如果在t的左子树跟右子树都没有找到p或者q,则说明t不是他们的公共祖先,直接返回null + +代码如下: + +```java + 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 && right == null) return null; + if(left == null) return right; + if(right == null) return left; + return root; + } +``` + +5、[从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)这道题的主要思想是,二叉树的前序遍历是(根-左-右),中序遍历是(左-根-右),说明取前序遍历的一个值root为根,那么在中序遍历中root左边的值就是其左子树,右边就是其右子树,以此类推,代码如下: + +```java + // 这里用map来存储前序中的值在中序里的位置,方便查找 + Map map = new HashMap<>(); + public TreeNode buildTree(int[] preorder, int[] inorder) { + int n = inorder.length; + for(int i = 0; i < n; i++){ + map.put(inorder[i],i); + } + return build(preorder,0,n-1,inorder,0,n-1); + } + + private TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd){ + if(preStart > preEnd) return null; + TreeNode root = new TreeNode(preorder[preStart]); + int inRoot = map.get(root.val); + // 根节点在中序里左边结点的数量 + int leftCount = inRoot - inStart; + root.left = build(preorder,preStart + 1,preStart + leftCount,inorder,inStart,inRoot -1); + root.right = build(preorder,preStart + leftCount + 1, preEnd, inorder, inRoot + 1, inEnd); + return root; + } +``` + +## 二、分治 + +分治的主要思想是想一个大问题拆分为几个小问题,小问题可以在再拆分为更小的问题,直到不能再拆分,然后解决好每一个小问题,再将其组合起来返回,代码模板如下: + +```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; +} +``` + +#### 习题笔记 + +1、[Pow(x, n)](https://leetcode-cn.com/problems/powx-n/)这道题的主要思想就是求x的n次方,拆解为求两个x的n/2次方相乘,直到n等于0,这里需要注意的问题就是: + +(1)n可能为负数,那么结果可以转换为1/(x^-n)进行处理,否则二分的时候会把结果越变越大 + +(2)如果n为奇数,则结果需要乘多一次本身,否则直接返回两个子结果相乘的结果即可 + +代码如下: + +```java + public double myPow(double x, int n) { + return n > 0 ? helper(x,n) : 1 / helper(x,-n); + } + + private double helper(double x, int n){ + if(n == 0) return 1; + double half = helper(x,n/2); + return n % 2 == 0 ? half * half : half * half * x; + } +``` + +2、[多数元素](https://leetcode-cn.com/problems/majority-element/)这道题有几种解法: + +(1)将数组排序,取中间的数即可 + +(2)用哈希表存储每个数字出现的个数,然后存到优先队列(大顶堆)中,取第一个元素 + +(3)用分治的方法,将数组分为两个子数组,求两个子数组的众数。如果两个子数组的众数相等,即为结果;如果不相等,则判断两个众数在数组中出现的次数,出现多的为众数;如果子数组的长度为1,则该元素为众数,代码如下: + +```java + // 计算某个数在数组中出现的次数 + private int countNum(int[] nums, int num, int start, int end){ + int count = 0; + for(int i = start ; i <= end; i ++){ + if(nums[i] == num) count++; + } + return count; + } + + private int majorityElement(int[] nums,int start,int end){ + // 子数组长度为1,直接返回该元素 + if(start == end){ + return nums[start]; + } + // 分治判断子数组众数 + int mid = (start + end) / 2; + int majorLeft = majorityElement(nums,start,mid); + int majorRight = majorityElement(nums,mid+1,end); + // 子数组众数相同,直接返回该众数 + if(majorLeft == majorRight) return majorLeft; + // 子数组众数不同,判断各子数组众数在当前数组出现次数 + int countMajorLeft = countNum(nums,majorLeft,start,end); + int countMajorRight = countNum(nums,majorRight,start,end); + // 出现次数多的为真正的众数 + return countMajorLeft > countMajorRight ? majorLeft : majorRight; + } + + public int majorityElement(int[] nums) { + return majorityElement(nums,0,nums.length - 1); + } +``` + +## 三、回溯 + +回溯的主要思想是试错,通过尝试分步去解决一个问题,在解决问题的时候,发现得不到最终的结果时,会取消前面几步的计算结果,然后尝试另外的分步去解决,其主要实现方式是递归,最重要的一步是在于取消前面的计算结果,算法中可以优化的地方在于如何剪枝,去除一些我们能知道的错误的分步,减少试错的次数。模板如下: + +```java +public void backtrack(int level, int param){ + //terminator 递归结束条件 + if(level > MAX_LEVEL){ + // process result 处理结果 + return; + } + + // process current logic 处理当前层级的逻辑 + process(level,param); + + // drill down 下探到下一层去 + recur(level + 1, newParam); + + // restore current status 重置当前的状态,重点 + restore() +} +``` + +#### 习题笔记 + +1、[电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)这道题的主要思想是遍历字符串中的数字,再遍历每个数字代表的英文字母进行组合,利用暴力法的话,则有多少的数字就得嵌套多少层循环,而用回溯法的话,就是先取一个数字的代表的一个英文字母,添加到字符串中,然后下探到下一层与其它数字的所有英文字符进行组合;组合完了以后,删除该字符,取该数字的另一个英文字母,继续刚才的操作,以此类推,代码如下: + +```java + String[] letters = new String[]{"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; + List res; + public List letterCombinations(String digits) { + res = new ArrayList<>(); + // 注意判空 + if(digits != null && digits.length() > 0){ + dfs(0,digits,new StringBuilder()); + } + return res; + } + + private void dfs(int index, String digits,StringBuilder sb){ + // 最后一层,保存组装的结果,返回 + if(index >= digits.length()){ + res.add(sb.toString()); + return; + } + // 获取当前层级的数字 + char c = digits.charAt(index); + // 获取当前层级数字代表的英文字母字符串 + // 由于0和1不参与,故这里是用数字的ASCII-2的ASCII即数字所代表的的字母字符串所在的位置 + String letter = letters[c - '2']; + // 遍历当前层级数字所代表的英文字母字符串 + for(int i = 0; i < letter.length(); i++){ + // 记录字符 + sb.append(letter.charAt(i)); + // 下探一层 + dfs(index+1,digits,sb); + // 删除记录 + sb.deleteCharAt(sb.length() -1); + } + } +``` + +2、[N 皇后](https://leetcode-cn.com/problems/n-queens/)这道题的主要思想在于遍历每一层中每一列的位置是否合法,不会受到上一层皇后的攻击,而皇后的攻击范围是横、竖、撇、捺,故同一行只能有一个皇后,同一列只能有一个皇后,当前行当前列(row,column)的上一行的前一列(row-1,column-1),上一行的后一列(row,column+1)不能有皇后。基于这个思想,则需要有columnSet,pieSet,naSet三个集合来辅助记录被占用的列,撇,捺,代码如下: + +```java + List> res; + public List> solveNQueens(int n) { + res = new ArrayList<>(); + // 记录被占用的列,撇,捺 + Set columnSet = new HashSet<>(); + Set pieSet = new HashSet<>(); + Set naSet = new HashSet<>(); + // 记录可以存放N皇后的结果 + int[] queens = new int[n]; + solveNQueens(0,n,queens,columnSet,pieSet,naSet); + return res; + } + + private void solveNQueens(int row,int n,int[] queens,Set columnSet,Set pieSet, Set naSet){ + // 最后一行记录完了,将结果打印后放入结果集并返回 + if(row == n) { + res.add(genarateBroad(queens,n)); + return; + } + // 遍历当前行每一列的位置 + for(int i = 0; i < n; i++){ + // 如果该列被占用了,进入下一列 + if(columnSet.contains(i)) continue; + int pie = row + i; + // 当前位置的撇已经被占用了,进入下一列 + if(pieSet.contains(pie)) continue; + int na = row - i; + // 当前位置的捺已经被占用了,进入下一列 + if(naSet.contains(na)) continue; + // 当前位置可以存放皇后,保存记录 + queens[row] = i; + columnSet.add(i); + pieSet.add(pie); + naSet.add(na); + // 下探到下一行进行记录 + solveNQueens(row + 1, n , queens,columnSet,pieSet,naSet); + // 清除当前列的记录,以便判断其它未判断的列是否符合记录 + queens[row] = -1; + columnSet.remove(i); + pieSet.remove(pie); + naSet.remove(na); + } + } + // 打印结果 + private List genarateBroad(int[] queens, int n){ + List list = new ArrayList<>(); + for(int i = 0; i < n; i++){ + char[] chars = new char[n]; + Arrays.fill(chars,'.'); + chars[queens[i]] = 'Q'; + list.add(new String(chars)); + } + return list; + } +``` + +3、[组合](https://leetcode-cn.com/problems/combinations/)这道题的主要思想是记录n个不同的数中,k个数组合在一起的种类,重点在于数是不同的,即第一个数只需与后面n-1个数进行组合,组合完了再与后面n-2的数进行组合,总的需要组合k层,代码如下: + +```java + List> res; + public List> combine(int n, int k) { + res = new ArrayList<>(); + // 注意判断边界 + if(n < 1 || k < 1||n < k) return res; + combine(1,n,k,new ArrayDeque()); + return res; + } + + // index表示当前层级 + // n表示整数的数量 + // k表示当前层需要组合的个数 + // deque这里采用队列方便添加删除 + private void combine(int index, int n, int k,Deque deque){ + // 最后一层已经组合完毕,将结果记录起来 + if(k == 0){ + // 切记这里需要重新new一个ArrayList,否则回溯会将已经添加的结果一并修改 + res.add(new ArrayList<>(deque)); + return; + } + // 每一层遍历的起始值都是从当前层开始,遍历到n-k+1,即后面的(k-1个)不用遍历到,因为后面没有数再与他们组合了 + for(int i = index; i <= n - k + 1; i++){ + // 添加记录到队列尾 + deque.addLast(i); + // 下探到下一层 + combine(i + 1, n, k - 1,deque); + // 回溯,删除队尾记录 + deque.pollLast(); + } + } +``` + +4、[全排列](https://leetcode-cn.com/problems/permutations/)这道题的主要思想是,数字没有重复,每个数字可以放在每个位置上,那么就是说每一个位置都遍历数字列表,用一个数组来记录某个数字有没有被使用了,即放到了位置上,没有被使用的话就把它放到位置上去,已经被使用了就判断下一个数字,以此类推,代码如下: + +```java + List> res; + public List> permute(int[] nums) { + res = new ArrayList<>(); + dfs(0,nums,new boolean[nums.length],new ArrayDeque()); + return res; + } + + private void dfs(int index, int[] nums,boolean[] used, Deque deque){ + // 最后一层已经处理完毕,添加结果到结果集 + if(index == nums.length) { + res.add(new ArrayList<>(deque)); + return; + } + // 每一层都要遍历所有数字 + for(int i = 0; i < nums.length; i++){ + // 数字已经被使用过了,判断下一个数字 + if(used[i]) continue; + // 数字没有被使用过,添加到队尾 + deque.addLast(nums[i]); + // 记录数字已经被使用了 + used[i] = true; + // 下探到下一层,即下一个位置 + dfs(index+1,nums,used,deque); + // 回溯,取消数字已经被使用的状态 + used[i] = false; + // 删除队尾的数字 + deque.pollLast(); + } + } +``` + +5、[全排列 II](https://leetcode-cn.com/problems/permutations-ii/)这道题与上一道题的区别在于,这里的数字是有重复的,所以这里就需要进行剪枝了,而剪枝的重点就在于排序,将数组进行排序后,方便我们判断当前数字是否与上一个数字重复。如果重复了且上一个数字还没被使用(回溯原因),那么就要跳过该数字,不然就会产生重复记录,代码如下: + +```java + List> res; + public List> permuteUnique(int[] nums) { + res = new ArrayList<>(); + // 切记要排序 + Arrays.sort(nums); + dfs(0,nums,new boolean[nums.length],new ArrayDeque()); + return res; + } + + private void dfs(int index, int[] nums, boolean[] used,Deque deque){ + if(index == nums.length){ + res.add(new ArrayList<>(deque)); + return; + } + for(int i = 0; i < nums.length; i++){ + // 当前数字是否与上一个数字重复且上一个数字还没被使用 + if(used[i] || (i > 0 && nums[i - 1] == nums[i] && !used[i-1])) continue; + used[i] = true; + deque.addLast(nums[i]); + dfs(index+1,nums,used,deque); + used[i] = false; + deque.pollLast(); + } + } +``` + +## 四、总结 + +递归这种类型的题目,还是有规矩可循的,首先还是要熟练递归的模板,分治和回溯也都是基于递归的,然后再去找最近重复子问题,只关心当前层级的逻辑,其它层级的逻辑递归自然会帮你解决,切忌人肉递归,回溯的话就要注意清除当前层记录的状态,以及进行适当的剪枝。其余的,就是刷遍数了,遍数多了自然通了。 + diff --git a/Week_03/Week3.java b/Week_03/Week3.java new file mode 100644 index 00000000..053aacb2 --- /dev/null +++ b/Week_03/Week3.java @@ -0,0 +1,154 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Week3 { + /** + * 236. 二叉树的最近公共祖先 + * https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ + * + * @param root + * @param p + * @param q + * @return + */ + 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 && right == null) return null; + if (left == null) return right; + if (right == null) return left; + return root; + } + + /** + * 105. 从前序与中序遍历序列构造二叉树 + * https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + * + * @param preorder + * @param inorder + * @return + */ + Map inorderRootIndexMap; + + public TreeNode buildTree(int[] preorder, int[] inorder) { + inorderRootIndexMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderRootIndexMap.put(inorder[i], i); + } + return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); + } + + private TreeNode build(int[] preorder, int pStart, int pEnd, int[] inorder, int iStart, int iEnd) { + if (pStart > pEnd) return null; + TreeNode root = new TreeNode(preorder[pStart]); + int rootIndex = inorderRootIndexMap.get(root.val); + int leftNum = rootIndex - iStart; + root.left = build(preorder, pStart + 1, pStart + leftNum, inorder, iStart, rootIndex - 1); + root.right = build(preorder, pStart + leftNum + 1, pEnd, inorder, rootIndex + 1, iEnd); + return root; + } + + /** + * 77. 组合 + * https://leetcode-cn.com/problems/combinations/ + * + * @param n + * @param k + * @return + */ + public List> combine(int n, int k) { + if (n < k || k < 1) return new ArrayList<>(); + List> res = new ArrayList<>(); + combine(1, k, n, res, new ArrayList()); + return res; + } + + private void combine(int index, int k, int n, List> res, List list) { + if (0 == k) { + res.add(new ArrayList<>(list)); + return; + } + for (int i = index; i <= n - k + 1; i++) { + list.add(i); + System.out.println(list.toString()); + combine(i + 1, k - 1, n, res, list); + list.remove(list.size() - 1); + } +// if(list.size() == k){ +// res.add(new ArrayList<>(list)); +// return; +// } +// for(int i = index; i <= n; i++){ +// list.add(i); +// System.out.println(list.toString()); +// combine(i+1,k,n,res,list); +// list.remove(list.size() -1); +// } + } + + /** + * 46. 全排列 + * https://leetcode-cn.com/problems/permutations/ + * + * @param nums + * @return + */ + public List> permute(int[] nums) { + List> res = new ArrayList<>(); + dfs(0, nums, new boolean[nums.length], res, new ArrayList()); + return res; + } + + private void dfs(int index, int[] nums, boolean[] used, List> res, List list) { + if (index == nums.length) { + res.add(new ArrayList<>(list)); + return; + } + for (int i = 0; i < nums.length; i++) { + if (used[i]) continue; + list.add(nums[i]); + used[i] = true; + dfs(index + 1, nums,used, res, list); + used[i] = false; + list.remove(list.size() - 1); + } + } + + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + // 切记要先排序 + Arrays.sort(nums); + Deque deque = new ArrayDeque<>(); + dfsUnique(0, nums, new boolean[nums.length], res, deque); + return res; + } + + private void dfsUnique(int index, int[] nums, boolean[] used, List> res, Deque deque) { + if (index == nums.length) { + res.add(new ArrayList<>(deque)); + return; + } + for (int i = 0; i < nums.length; i++) { + if (used[i]) continue; + if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; + deque.addLast(nums[i]); + used[i] = true; + dfsUnique(index + 1, nums, used, res, deque); + used[i] = false; + deque.pollLast(); + } + } + + public static void main(String[] args) { + Week3 week3 = new Week3(); + week3.combine(4, 2); + } +} diff --git a/Week_03/Week3Training.java b/Week_03/Week3Training.java new file mode 100644 index 00000000..a3d6842c --- /dev/null +++ b/Week_03/Week3Training.java @@ -0,0 +1,286 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Week3Training { + private List res; + + /** + * 22. 括号生成 + * https://leetcode-cn.com/problems/generate-parentheses/ + * + * @param n + * @return + */ + public List generateParenthesis(int n) { + res = new ArrayList<>(); + generateParenthesis(0, 0, n, ""); + return res; + } + + private void generateParenthesis(int left, int right, int max, String s) { + if (left == max && right == max) { + res.add(s); + return; + } + if (left < max) + generateParenthesis(left + 1, right, max, s + "("); + + if (right < left) + generateParenthesis(left, right + 1, max, s + ")"); + + } + + /** + * 226. 翻转二叉树 + * https://leetcode-cn.com/problems/invert-binary-tree/description/ + * + * @param root + * @return + */ + public TreeNode invertTree(TreeNode root) { + if (root == null) return null; + TreeNode tmp = root.left; + root.left = root.right; + root.right = tmp; + invertTree(root.left); + invertTree(root.right); + return root; + } + + + /** + * 98. 验证二叉搜索树 + * https://leetcode-cn.com/problems/validate-binary-search-tree/ + * + * @param root + * @return + */ + long preValue = Long.MIN_VALUE; + + public boolean isValidBST(TreeNode root) { + if (root == null) return true; + if (!isValidBST(root.left)) return false; + if (root.val <= preValue) return false; + preValue = root.val; + return isValidBST(root.right); + } + + + /** + * 104. 二叉树的最大深度 + * https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ + * + * @param root + * @return + */ + + public int maxDepth(TreeNode root) { + if (root == null) return 0; + return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; + } + + /** + * 111. 二叉树的最小深度 + * https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ + * + * @param root + * @return + */ + public int minDepth(TreeNode root) { + if (root == null) return 0; + if (root.left == null) return minDepth(root.right) + 1; + if (root.right == null) return minDepth(root.left) + 1; + return Math.min(minDepth(root.left), minDepth(root.right)) + 1; + } + + /** + * 50. Pow(x, n) + * https://leetcode-cn.com/problems/powx-n/ + * + * @param x + * @param n + * @return + */ + public double myPow(double x, int n) { + return n > 0 ? helper(x, n) : 1.0 / helper(x, -n); + } + + private double helper(double x, int n) { + if (n == 0) return 1; + double half = helper(x, n / 2); + return n % 2 == 0 ? half * half : half * half * x; + } + + + /** + * 78. 子集 + * https://leetcode-cn.com/problems/subsets/submissions/ + * + * @param nums + * @return + */ + public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + List list = new ArrayList<>(); + dfs(0, nums, res, list); + return res; + } + + private void dfs(int index, int[] nums, List> res, List list) { + for (int i = index; i < nums.length; i++) { + res.add(new ArrayList(list)); + dfs(i + 1, nums, res, list); + list.remove(list.size() - 1); + } + } + + /** + * 17. 电话号码的字母组合 + * https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ + * + * @param digits + * @return + */ + String[] letters = new String[]{"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; + + public List letterCombinations(String digits) { + List res = new ArrayList<>(); + if (digits.isEmpty()) return res; + dfs(0, digits, res, new StringBuilder()); + return res; + } + + private void dfs(int index, String digits, List res, StringBuilder sb) { + if (index == digits.length()) { + res.add(sb.toString()); + return; + } + char c = digits.charAt(index); + String letter = letters[c - '2']; + for (int i = index; i < letter.length(); i++) { + sb.append(letter.charAt(i)); + dfs(index + 1, digits, res, sb); + sb.deleteCharAt(sb.length() - 1); + } + } + + public boolean wordPattern(String pattern, String s) { + String[] words = s.split(" "); + if (words.length != pattern.length()) return false; + Map map = new HashMap<>(); + for (int i = 0; i < pattern.length(); i++) { + Character key = pattern.charAt(i); + if (map.containsKey(key)) { + if (!map.get(key).equals(words[i])) return false; + } else if (map.containsValue(words[i])) { + return false; + } + map.put(key, words[i]); + } + return true; + } + + /** + * 51. N 皇后 + * https://leetcode-cn.com/problems/n-queens/ + * + * @param n + * @return + */ + public List> solveNQueens(int n) { + List> res = new ArrayList<>(); + int[] queens = new int[n]; + Arrays.fill(queens, -1); + Set columnSet = new HashSet<>(); + Set pieSet = new HashSet<>(); + Set naSet = new HashSet<>(); + backtrack(res, queens, 0, n, columnSet, pieSet, naSet); + return res; + } + + private void backtrack(List> res, int[] queens, int row, int n, Set columnSet, Set pieSet, Set naSet) { + if (row == n) { + res.add(generateBroad(queens, n)); + } + for (int i = 0; i < n; i++) { + if (columnSet.contains(i)) continue; + int pie = row + i; + if (pieSet.contains(pie)) continue; + int na = row - i; + if (naSet.contains(na)) continue; + + queens[row] = i; + columnSet.add(i); + pieSet.add(pie); + naSet.add(na); + backtrack(res, queens, row + 1, n, columnSet, pieSet, naSet); + queens[row] = -1; + columnSet.remove(i); + pieSet.remove(pie); + naSet.remove(na); + } + } + + private List generateBroad(int[] queens, int n) { + List list = new ArrayList<>(); + for (int i = 0; i < n; i++) { + char[] chars = new char[n]; + Arrays.fill(chars, '.'); + chars[queens[i]] = 'Q'; + list.add(new String(chars)); + } + return list; + } + + + /** + * 297. 二叉树的序列化与反序列化 + * https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/ + */ + public class Codec { + + public String serialize(TreeNode node, String s) { + if (node == null) { + s += "None,"; + } else { + s += node.val + ","; + s = serialize(node.left, s); + s = serialize(node.right, s); + } + return s; + + } + + // Encodes a tree to a single string. + public String serialize(TreeNode root) { + return serialize(root, ""); + } + + public TreeNode deserialize(List list){ + if(list.get(0).equals("None")){ + list.remove(0); + return null; + } + TreeNode node = new TreeNode(Integer.valueOf(list.get(0))); + list.remove(0); + node.left = deserialize(list); + node.right = deserialize(list); + return node; + } + + // Decodes your encoded data to tree. + public TreeNode deserialize(String data) { + String[] strArr = data.split(","); + List list = new LinkedList<>(Arrays.asList(strArr)); + return deserialize(list); + } + } +} diff --git a/Week_04/README.md b/Week_04/README.md index 50de3041..b79e5a85 100644 --- a/Week_04/README.md +++ b/Week_04/README.md @@ -1 +1,624 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 一、广度优先搜索和深度优先搜索 + +### 知识点笔记 + +1、在之前学习的二叉树里面,广度优先搜索对应的是层序遍历,深度优先搜索对应的是前序遍历。而除了二叉树以外,其它的数据结构也可以进行这两种搜索,例如图和状态集。 + +2、所谓的搜索遍历,就是每个节点都要访问一次,且仅访问一次 + +3、深度优先搜索遍历的代码模板如下: + +```java +Set visited = new HashSet<>(); +public void dfs(Node root){ + // terminator + if(visited.contains(root)) return; + // process current node + visited.add(root); + // drill down + for(Node node : root.children){ + dfs(node); + } +} +``` + +4、广度优先搜索遍历的代码模板如下: + +```java +public void bfs(Node root){ + // 存放结点的队列 + Queue queue = new LinkedList<>(); + // 结点使用记录 + Set visited = new HashSet<>(); + if(root != null) { + queue.offer(node); + visited.add(root); + } + while(!queue.isEmpty()){ + // 每层遍历的次数 + int size = queue.size(); + for(int i = 0; i < size; i++){ + Node node = queue.poll(); + if(visited.contains(node)) continue; + // process current node + visited.add(node); + queue.offer(node); + } + } +} +``` + +### 习题笔记 + +1、[在每个树行中找最大值](https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/)这道题是可以用BFS做,也可以用DFS做。 + +用BFS做比较直观,就是层序遍历,然后记录每一层的最大值,基本上套上模板就可以了,代码如下: + +```java + public List largestValues(TreeNode root) { + List res = new ArrayList<>(); + Queue queue = new LinkedList<>(); + if(root != null) queue.offer(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.offer(node.left); + if(node.right != null) queue.offer(node.right); + } + res.add(max); + } + return res; + } +``` + +而DFS就相对绕一些,需要用一个map来记录遍历过的每一层的最大值,每遍历一个数,就要记录它属于哪一层,然后与map中保存的值进行比较,将较大的值存入map中,代码如下 + +```java + List res; + Map map; + public List largestValues(TreeNode root) { + res = new ArrayList<>(); + map = new HashMap<>(); + if(root != null) { + dfs(0,root); + } + for(int i = 0; i < map.size(); i++){ + res.add(map.get(i)); + } + return res; + } + + private void dfs(int index, TreeNode node){ + if(node == null){ + return; + } + int max = Math.max(node.val,map.getOrDefault(index,Integer.MIN_VALUE)); + map.put(index,max); + dfs(index + 1,node.left); + dfs(index + 1,node.right); + } +``` + +2、[岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)这道题的思想是相邻的1都算是同一个岛屿,即只要当前位置是1,其上下左右也是1则为相同的岛屿,利用DFS,遍历的时候将1置为2,表示已经遍历过了,然后遍历相邻的位置,如果是1也置为2,不为1就结束,这样遍历一个岛屿之后,其相邻的1就会都被置为2,减少遍历的次数,代码如下: + +```java + public int numIslands(char[][] grid) { + int res = 0; + for(int i = 0; i < grid.length; i++){ + for(int j = 0; j < grid[0].length; j++){ + //遍历到一个岛屿,res+1,并将处理相邻的岛屿 + if(grid[i][j] == '1'){ + res ++; + area(grid,i,j); + } + } + } + return res; + } + + private void area(char[][] grid, int r, int c){ + //当前位置不在范围内或者不为1,直接结束 + if(!inArea(grid,r,c) || grid[r][c] != '1') return; + //将当前位置置为2,表示遍历过了 + grid[r][c] = '2'; + // 遍历上下左右到位置 + area(grid,r-1,c); + area(grid,r+1,c); + area(grid,r,c-1); + area(grid,r,c+1); + } + // 判断位置是否合法 + private boolean inArea(char[][] grid, int r, int c){ + return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; + } +``` + +3、[扫雷游戏](https://leetcode-cn.com/problems/minesweeper/)这道题的思想与上一道相似,只不过这次不只上下左右四个方向,还有左上,左下,右上,右下一共八个方向,那么就可以用两个数组分别用来表示八个方位的x和y值的变化,然后在遍历之前,需要先尝试判断一下,相邻的方位是否有效,以及是否有雷,如果有雷,就需要将周围的雷数累计起来,将当前的值置为‘雷数’,否则直接置为‘B’,然后DFS相邻的还没有被遍历的位置,代码如下: + +```java + int[] dx = {-1, 0, 1, -1, 1, -1, 0, 1}; + int[] dy = {-1, -1, -1, 0, 0, 1, 1, 1}; + + public char[][] updateBoard(char[][] board, int[] click) { + int x = click[0], y = click[1]; + if (board[x][y] == 'M') { + board[x][y] = 'X'; + } else { + dfs(board, x, y); + } + return board; + } + + private void dfs(char[][] board, int r, int c) { + int count = 0; + for (int i = 0; i < 8; i++) { + int x = r + dx[i]; + int y = c + dy[i]; + if (!inBoard(board, x, y)) continue; + if (board[x][y] == 'M') { + count++; + } + } + if (count > 0) { + board[r][c] = (char) (count + '0'); + return; + } + board[r][c] = 'B'; + for (int i = 0; i < 8; i++) { + int x = r + dx[i]; + int y = c + dy[i]; + if (!inBoard(board, x, y) || board[x][y] != 'E') continue; + dfs(board, x, y); + } + } +``` + +还可以用BFS的方式进行遍历,只是这里需要手动维护一个集合来记录遍历过的位置,防止在下一次入队列的时候重复了,代码如下: + +```java + int[] dx = {-1, 0, 1, -1, 1, -1, 0, 1}; + int[] dy = {-1, -1, -1, 0, 0, 1, 1, 1}; + + public char[][] updateBoard(char[][] board, int[] click) { + int x = click[0], y = click[1]; + if (board[x][y] == 'M') { + board[x][y] = 'X'; + } else { + int row = board.length, column = board[0].length; + Queue queue = new LinkedList<>(); + boolean[][] visited = new boolean[row][column]; + queue.offer(click); + visited[x][y] = true; + while (!queue.isEmpty()) { + int[] point = queue.poll(); + int i = point[0], j = point[1]; + int count = 0; + for (int k = 0; k < 8; k++) { + int newX = i + dx[k]; + int newY = i + dy[k]; + if (!inBoard(board, newX, newY)) continue; + if (board[x][y] == 'M') count++; + } + if (count > 0) { + board[i][j] = (char) (count + '0'); + } else { + board[i][j] = 'B'; + for (int k = 0; k < 8; k++) { + int newX = i + dx[k]; + int newY = i + dy[k]; + if (!inBoard(board, newX, newY) || board[newX][newY] != 'E' || visited[newX][newY]) + continue; + visited[newX][newY] = true; + queue.offer(new int[]{newX, newY}); + } + } + } + } + return board; + } +``` + +4、[最小基因变化](https://leetcode-cn.com/problems/minimum-genetic-mutation/)和[单词接龙](https://leetcode-cn.com/problems/word-ladder/)、[单词接龙 II](https://leetcode-cn.com/problems/word-ladder-ii/)的思想类似,这里就以《单词接龙2》为例进行笔记记录,本质上来讲,都是将当前的字符串,在通过一次次的改变一个字符,且改变后的字符是在单词表中的,最终变为指定字符串,《最小基因变化》是只有4个字符进行变化,而《单词接龙》则有26个字符可以进行变化,这个获取变化的字符串倒是相差不大,都可以这么去获取: + +```java + private List getNeighbors(String s){ + List neighbors = new ArrayList<>(); + char[] chars = s.toCharArray(); + for(int i = 0; i < s.length(); i++){ + // 缓存当前位置的字符 + char c = chars[i]; + for(char j = 'a' ; j <= 'z'; j++){ + // 与当前字符一致的可以直接过滤 + if(c == j) continue; + // 将一个字符改为另一个字符 + chars[i] = j; + // 生成新的字符串 + String newStr = new String(chars); + // 判断新的字符串是否在字典中,是则添加到邻居列表中 + if(wordSet.contains(newStr)) neighbors.add(newStr); + } + // 记得遍历一个位置完后将其重置为原来的字符 + chars[i] = c; + } + return neighbors; + } +``` + +然后就是利用广度优先遍历,来遍历所有的可能,记录下来遍历的路径,需要注意的是,这里需要将遍历过的单词记录下来,因为这种遍历本质上是对图的遍历,如果不记录下遍历过的单词,容易进入死循环,导致超时,完整的代码如下: + +```java + Set wordSet; + public List> findLadders(String beginWord, String endWord, List wordList) { + List> res = new ArrayList<>(); + wordSet = new HashSet<>(wordList); + // 如果最终的单词不在单词表中,那么是绝对变不到的 + if(!wordSet.contains(endWord)) return res; + // 记录遍历过得单词 + Set visited = new HashSet<>(); + // 判断是否已经找到一条完整的队列 + boolean isFound = false; + // 队里这里是记录遍历的路径 + Queue> queue = new LinkedList<>(); + List path = new ArrayList<>(); + path.add(beginWord); + queue.offer(path); + // 单词表移除最初的单词,防止重复遍历 + wordSet.remove(beginWord); + visited.add(beginWord); + while(!queue.isEmpty()){ + int size = queue.size(); + // 用于记录当前层遍历过的单词 + Set subVisited = new HashSet<>(); + for(int i = 0; i < size; i++){ + // 取出队首的路径 + List p = queue.poll(); + // 获取路径的最后一个单词 + String s = p.get(p.size() - 1); + // 遍历单词的所有合法邻居单词 + for(String neighbor : getNeighbors(s)){ + // 已经遍历过的直接过滤 + if(visited.contains(neighbor)) continue; + // 路径添加邻居单词 + p.add(neighbor); + subVisited.add(neighbor); + // 若此邻居单词是最终结果,将完整路径记录下来,并将isFound置为true,表示已经找到最短路径 + if(neighbor.equals(endWord)){ + isFound = true; + res.add(new ArrayList<>(p)); + } + // 将新路径添加到队尾中 + queue.offer(new ArrayList<>(p)); + // 将当前邻居移除队列,进行下一个邻居的遍历 + p.remove(neighbor); + } + } + // 汇总当前层遍历过的单词到总的记录中 + visited.addAll(subVisited); + // 如果已经找到最短路径了,就不用再遍历下一层了 + if(isFound) break; + } + return res; + } + + private List getNeighbors(String s){ + List neighbors = new ArrayList<>(); + char[] chars = s.toCharArray(); + for(int i = 0; i < s.length(); i++){ + // 缓存当前位置的字符 + char c = chars[i]; + for(char j = 'a' ; j <= 'z'; j++){ + // 与当前字符一致的可以直接过滤 + if(c == j) continue; + // 将一个字符改为另一个字符 + chars[i] = j; + // 生成新的字符串 + String newStr = new String(chars); + // 判断新的字符串是否在字典中,是则添加到邻居列表中 + if(wordSet.contains(newStr)) neighbors.add(newStr); + } + // 记得遍历一个位置完后将其重置为原来的字符 + chars[i] = c; + } + return neighbors; + } +``` + +## 二、贪心算法 + +贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。但是实际上每一步选择都采取最优解,最终解也不一定就是最优解,所以需要视情况而定是否能是否贪心算法。 + +适用贪心算法的场景:问题能分解为子问题,子问题的最优解能递推到最终问题的最优解 + +### 习题笔记 + +1、[柠檬水找零](https://leetcode-cn.com/problems/lemonade-change/)这道题的思想是我们只能用5块钱和10块钱进行找零,那么当遇到5块钱的时候,不用找零,但要将当前拥有的5块钱的数量加1;当遇到10块钱的时候,就需要将10块钱的数量加1,5块钱的数量减1;当遇到20块钱的时候,就要看看当前有没有10块钱,有就优先用10块钱+5块钱进行找零,没有的话就只能用5块钱找零;每次找零后判断5块钱是否大于等于0,如果小于0,所以不够找,返回false;遍历到最后都没有返回false说明都能找零成功,返回true; + +```java + public boolean lemonadeChange(int[] bills) { + if(bills == null || bills.length == 0) return false; + int five = 0, ten = 0; + for(int i = 0; i < bills.length; i++){ + if(bills[i] == 5){ + five ++; + } else if(bills[i] == 10){ + five --; + ten ++; + } else if(ten > 0){ + ten --; + five --; + } else { + five-=3; + } + if(five < 0) return false; + } + return true; + } +``` + +2、[分发饼干](https://leetcode-cn.com/problems/assign-cookies/)这道题的思想是,将孩子的胃口数组和饼干数组都进行排序,那么可以选择先喂饱胃口小的孩子,如果当前饼干不满足小胃口的孩子,显然也不能满足大胃口的孩子,判断下一个饼干是否满足,以此类推,代码如下: + +```java + public int findContentChildren(int[] g, int[] s) { + Arrays.sort(g); + Arrays.sort(s); + int res = 0; + // 记录当前要喂饱的孩子的下标 + int j = 0; + for(int i = 0; i < s.length; i++){ + // 没有孩子可以遍历了 + if(j == g.length) break; + // 当前饼干可以满足这个孩子 + if(s[i] >= g[j]){ + res ++; + j++; + } + } + return res; + } +``` + +3、[模拟行走机器人](https://leetcode-cn.com/problems/walking-robot-simulation/)这道题的思想是,在一个坐标系中,以向北为基准0,东,南,西分别为1,2,3,那么当机器人收到向右转的指令时,即方向+1,当机器人收到向左转的时候,即方向+3,相当于向右转3次,可以以方向下标作为二维数组的行,列为相对当前位置即将走的位置: + +```java +int[][] xy = {{0,1},{1,0},{0,-1},{-1,0}};// 往北走一步,往东走一步,从南走一步,往西走一步 +``` + +遇到障碍物的时候,是不能走到障碍物上的,所以在走的时候,要先判断下一步是不是障碍物,没有才能继续走,是障碍物的话,这个指令就结束了,完整代码如下: + +```java + public int robotSim(int[] commands, int[][] obstacles) { + // x和y表示当前坐标,direction表示当前方向,res表示结果 + int x = 0, y = 0, direction = 0,res = 0; + // 每个方向代表的x值和y值的相对变化 + int[][] xy = {{0,1},{1,0},{0,-1},{-1,0}}; + // 将障碍物的坐标用set记录下来,方便判断 + Set obstacleSet = new HashSet<>(); + for(int[] obstacle : obstacles){ + obstacleSet.add(obstacle[0] + "," + obstacle[1]); + } + for(int com : commands){ + if(com == -2){ + // 向左转 + direction = (direction + 3) % 4; + } else if(com == -1){ + // 向右转 + direction = (direction + 1) % 4; + } else{ + // 在下一步没遇到障碍物的时候以及当前指令走的步数还没走完的时候 + while(com-- > 0 && !obstacleSet.contains(x+xy[direction][0] + "," + (y+xy[direction][1]))){ + // 记录每走一步xy值的变化 + x+= xy[direction][0]; + y+= xy[direction][1]; + } + res = Math.max(res,x*x+y*y); + } + } + return res; + } +``` + +4、[跳跃游戏](https://leetcode-cn.com/problems/jump-game/)这道题的主要思想是:取一个标识minStep,作为一个数能到达终点的最小步数,初始值为1 +(1)、从后面往前推,如果要到达最后一个位置,倒数第二个数的最小步数应该要大于等于minStep; +(2)、如果倒数第二个数小于minStep,那么说明倒数第二个数无法到达终点,倒数第三个数就必须大于等于minStep+1,才能跳到终点 +(3)、如果倒数第二个数大于等于minStep,那么就以倒数第二个为终点,重置minStep为1,判断倒数第三个数是否大于等于minStep; +(4)、如果遍历到最后,minStep等于1,那么就代表能到达终点,否则就意味着第一个数无法满足到达终点的最小步数。 +代码如下: + +```java +public boolean canJump(int[] nums) { + if(nums.length < 2) return true; + //初始化minStep + int minStep = 1; + //从倒数第二的数开始判断 + for (int i = nums.length - 2; i >= 0 ; i--) { + if(nums[i] >= minStep){ + // 如果当前数大于等于minStep,说明能到达后面一个数,重置minStep为1 + minStep = 1; + } else { + // 否则说明当前数无法直接到达后一个数,minStep加1,判断前面一个数能否到达当前数的后一位 + minStep++; + } + } + // minStep等于1说明前面的数能跳到终点 + return minStep == 1; +} +``` + +5、[跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/)这道题的主要思想是每次跳跃完之后所到达的位置y,与跳跃之前的位置x之间的那些位置(y-x),它们所能跳跃的最远的位置z,就是下一次跳跃的起跳位置,代码如下: + +```java + public int jump(int[] nums) { + // end表示当前一次跳跃结束的位置,maxPosition表示当前能跳到的最远位置,now表示当前的位置 + int end = 0, maxPosition = 0, now = 0,res = 0; + // 最后一个位置不用跳了 + while(end < nums.length - 1){ + // now + nums[now]表示当前位置能跳到的最远位置 + maxPosition = Math.max(maxPosition,now+nums[now]); + // 遍历到一次跳跃结束的位置,开始下一轮跳跃 + if(end == now++){ + end = maxPosition; + res ++; + } + } + return res; + } +``` + +## 三、二分查找 + +二分查找的前提是1.目标函数的单调性(单调递增或递减)2.存在上下界 3.能够通过索引访问 + +即每次查找,都能将搜索访问逐渐缩小 + +代码的模板如下: + +```java +public int binarySearch(int[] nums,int target){ + int left = 0, right = nums.length - 1; + while(left <= right){ + int mid == (left + right) >>> 1; + if(nums[mid] == target) return mid; + if(nums[mid] > target){ + right = mid - 1; + } else { + left = mid + 1; + } + } +} +``` + +这里的模板是不能够直接套用的,需要根据不同的题目来更改判断条件的,主要需要更改的条件有: + +1、left <= right和left < right + +当使用left <= right的时候,意味着当循环结束了的时候,right > left,所有的索引都遍历过了还是没有找到; + +当使用left < right的时候,意味着当循环结束了的时候,left == right,这里就需要判断最后一个索引对应的值是否是我们想要找的值; + +2、int mid == (left + right) >>> 1 和 int mid == (left + right + 1) >>> 1(这里使用>>>逻辑右移是可以保证运算后的值不溢出) + +当使用 (left + right) >>> 1的时候,表示取的是左中位数,即当数组长度为偶数的时候,取的时候偏左的索引,例如长度为2的左中位数为0 + +当使用(left + right + 1) >>> 1的时候,表示取的是有中位数,即当数组长度为偶数的时候,取的时候偏右的索引,例如2的右中位数为1 + +3、left = mid + 1 和 left = mid,right = mid - 1和 right = mid + +若中位数取的是左中位数,则对应的left必须是mid+1,否则的话left的值就一直不变,就会进入死循环 + +若中位数取的是右中位数,则对应的right必须是mid-1,否则的话right的值就一直不变,就会进入死循环 + +至于为什么会出现left=mid和right=mid的情况,就是有时候我们无法判断mid的值是否在我们判断的子区间中,那么就还需要将其作为边界,继续判断 + +### 习题笔记 + +1、[x 的平方根](https://leetcode-cn.com/problems/sqrtx/)这道题的主要思想是找到一个数,它的平方是最接近target的,且结果是要向下取整的,意味着它的平方是要小于等于target的,由于我们肯定能找到这个数,所以循环判断条件可以设置为while(left <= right),代码如下: + +```java + public int mySqrt(int x) { + // hi = x / 2 + 1是为了照顾x <= 1的情况,且由于x的平方根都是小于等于x/2的 + long lo = 0, hi = x / 2 + 1, res = 0; + while(lo <= hi){ + long mid = (lo + hi) >>> 1; + if(x >= mid*mid){ + res = mid; + lo = mid + 1; + } else { + hi = mid - 1; + } + } + return (int)res; + } +``` + +2、[搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix/)这道题的思想是可以将二维数组转为一维数组进行遍历,一维数组的总长度为row * column,那么初始边界可以这样设置left = 0,right = row * column,而二分之后每个元素的行可以转化为mid/column,列可以转化为mid%column,代码如下: + +```java + public boolean searchMatrix(int[][] matrix, int target) { + if(matrix == null || matrix.length == 0) return false; + int row = matrix.length, column = matrix[0].length; + int left = 0, right = row * column - 1; + while(left <= right){ + int mid = (left + right) >>> 1; + int value = matrix[mid/column][mid%column]; + if(target == value) return true; + if(target < value){ + right = mid - 1; + } else { + left = mid + 1; + } + } + return false; + } +``` + +3、[搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)这道题的思想是旋转数组的旋转点,左边是单调递增的,右边也是单调递增的,且右边的所有值都小于旋转点最左边的值。 + +(1)首先需要判断搜索的值target是否大于等于最左边的值,是则应该在旋转点的的左边进行查找,否则应该在旋转点的右边进行查找 + +(2)其次要判断mid是在旋转点的左边还是右边,如果target在旋转点的左边,而mid在旋转点的右边,为了方便判断区间从哪边缩小,可以将mid设置为Integer.MAX_VALUE,模拟单调递增;同理如果target在旋转点的右边,而mid在旋转点的左边,则可以将mid设置为Integer.MIN_VALUE,模拟单调递减,代码如下: + +```java + public int search(int[] nums, int target) { + int start = 0, end = nums.length - 1; + while(start <= end){ + // 取右中位数 + int mid = (start + end + 1) >>> 1; + if(nums[mid] == target) return mid; + if(target >= nums[0]){ + // 目标值大于等于最左边的值,说明在左半段 + // 若mid比最左边的值还小,说明mid在右半段,将mid的值置为Integer.MAX_VALUE + if(nums[mid] < nums[0]){ + nums[mid] = Integer.MAX_VALUE; + } + } else { + // 目标值小于最左边的值,说明在右半段 + // 如果mid比最左边的值还大,说明mid在左半段,将mid的值置位Integer.MIN_VALUE + if(nums[mid] >= nums[0]){ + nums[mid] = Integer.MIN_VALUE; + } + } + if(target > nums[mid]){ + start = mid + 1; + } else { + end = mid - 1; + } + + } + return -1; +``` + +4、[寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/)这道题的思想是,要找到旋转数组的最小值。 + +(1)如果数组没有旋转的话,最小值就是第一个元素了; + +(2)如果数组旋转了,那么旋转点右边的第一个值就是最小值 + +(3)所以要是mid大于等于搜索区间的最左边的值,意味着旋转点在mid的右边,left = mid + 1; + +(4)要是mid 小于搜索区间的最左边的值,意味着旋转点在mid的左边,并且我们不能排除mid就是旋转点右边的第一个元素,所以right = mid,所以mid就必须取的是左中位数,代码如下: + +```java + public int findMin(int[] nums) { + int lo = 0, hi = nums.length - 1; + while(lo < hi){ + if(nums[lo] <= nums[hi]) break; + int mid = (lo + hi) >>> 1; + if(nums[mid] >= nums[lo]){ + lo = mid + 1; + } else { + hi = mid; + } + } + return nums[lo]; + } +``` + + + + + diff --git a/Week_04/Week4.java b/Week_04/Week4.java new file mode 100644 index 00000000..d9516706 --- /dev/null +++ b/Week_04/Week4.java @@ -0,0 +1,476 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class Week4 { + /** + * 127. 单词接龙 + * https://leetcode-cn.com/problems/word-ladder/description/ + * + * @param beginWord + * @param endWord + * @param wordList + * @return + */ + private Set wordSet; + + public int ladderLength(String beginWord, String endWord, List wordList) { + wordSet = new HashSet<>(wordList); + if (!wordSet.contains(endWord)) return 0; + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + int count = 1; + queue.offer(beginWord); + visited.add(beginWord); + wordSet.remove(beginWord); + while (!queue.isEmpty()) { + count++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + String s = queue.poll(); + for (String neighbor : findNeighbors(s)) { + if (endWord.equals(neighbor)) return count; + if (!visited.contains(neighbor)) { + queue.offer(neighbor); + visited.add(neighbor); + } + } + } + } + return 0; + } + + private List findNeighbors(String s) { + char[] chars = s.toCharArray(); + List list = new ArrayList<>(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + for (char j = 'a'; j <= 'z'; j++) { + if (j == c) continue; + chars[i] = j; + String newString = new String(chars); + if (wordSet.contains(newString)) list.add(newString); + } + chars[i] = c; + } + return list; + } + + /** + * 126. 单词接龙 II + * https://leetcode-cn.com/problems/word-ladder-ii/ + * + * @param beginWord + * @param endWord + * @param wordList + * @return + */ + public List> findLadders(String beginWord, String endWord, List wordList) { + List> res = new ArrayList<>(); + Set wordSet = new HashSet<>(wordList); + if (!wordSet.contains(endWord)) return res; + Queue> queue = new LinkedList<>(); + List path = new ArrayList<>(); + boolean isFound = false; + Set visited = new HashSet<>(); + path.add(beginWord); + queue.offer(path); + wordSet.remove(beginWord); + visited.add(beginWord); + while (!queue.isEmpty()) { + int size = queue.size(); + Set subVisited = new HashSet<>(); + for (int i = 0; i < size; i++) { + List p = queue.poll(); + String s = p.get(p.size() - 1); + for (String neighbor : findNeighbors(s, wordSet)) { + if (visited.contains(neighbor)) continue; + p.add(neighbor); + if (endWord.equals(neighbor)) { + isFound = true; + res.add(new ArrayList<>(p)); + } + queue.offer(new ArrayList<>(p)); + p.remove(neighbor); + subVisited.add(neighbor); + } + } + visited.addAll(subVisited); + if (isFound) break; + } + return res; + } + + public Set findNeighbors(String s, Set wordSet) { + char[] chars = s.toCharArray(); + Set neighbor = new HashSet<>(); + for (int i = 0; i < s.length(); i++) { + char c = chars[i]; + for (char j = 'a'; j <= 'z'; j++) { + if (c == j) continue; + chars[i] = j; + String newStr = new String(chars); + if (wordSet.contains(newStr)) neighbor.add(newStr); + } + chars[i] = c; + } + return neighbor; + } + + /** + * 200. 岛屿数量 + * https://leetcode-cn.com/problems/number-of-islands/ + * + * @param grid + * @return + */ + public int numIslands(char[][] grid) { + 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 += area(grid, i, j); + } + } + } + return count; + } + + public int area(char[][] grid, int row, int column) { + if (!inArea(grid, row, column)) return 0; + if (grid[row][column] != '1') return 0; + grid[row][column] = '2'; + area(grid, row - 1, column); + area(grid, row + 1, column); + area(grid, row, column - 1); + area(grid, row, column + 1); + + return 1; + } + + /** + * 860. 柠檬水找零 + * https://leetcode-cn.com/problems/lemonade-change/ + * + * @param bills + * @return + */ + public boolean lemonadeChange(int[] bills) { + if (bills == null || bills.length == 0) return false; + int five = 0, ten = 0; + for (int i = 0; i < bills.length; i++) { + if (bills[i] == 5) { + five++; + } else if (bills[i] == 10) { + five--; + ten++; + } else { + if (ten > 0) { + ten--; + five--; + } else { + five = five - 3; + } + } + if (five < 0) { + return false; + } + } + return true; + } + + /** + * 122. 买卖股票的最佳时机 II + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/ + * + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + int count = 0; + for (int i = 1; i < prices.length; i++) { + int profit = prices[i] - prices[i - 1]; + if (profit > 0) count += profit; + } + return count; + } + + /** + * 455. 分发饼干 + * https://leetcode-cn.com/problems/assign-cookies/description/ + * + * @param g + * @param s + * @return + */ + public int findContentChildren(int[] g, int[] s) { + Arrays.sort(g); + Arrays.sort(s); + int j = 0; + int res = 0; + for (int i = 0; i < s.length; i++) { + if (j == g.length) break; + if (g[j] <= s[i]) { + res++; + j++; + } + } + return res; + } + + public boolean inArea(char[][] grid, int row, int column) { + return 0 <= row && row < grid.length + && 0 <= column && column < grid[0].length; + } + + /** + * 529. 扫雷游戏 + * https://leetcode-cn.com/problems/minesweeper/description/ + * + * @param board + * @param click + * @return + */ + int[] dx = {-1, 0, 1, -1, 1, -1, 0, 1}; + int[] dy = {-1, -1, -1, 0, 0, 1, 1, 1}; + + public char[][] updateBoard(char[][] board, int[] click) { + int x = click[0], y = click[1]; + if (board[x][y] == 'M') { + board[x][y] = 'X'; + } else { +// dfs(board, x, y); + int row = board.length, column = board[0].length; + Queue queue = new LinkedList<>(); + boolean[][] visited = new boolean[row][column]; + queue.offer(click); + visited[x][y] = true; + while (!queue.isEmpty()) { + int[] point = queue.poll(); + int i = point[0], j = point[1]; + int count = 0; + for (int k = 0; k < 8; k++) { + int newX = i + dx[k]; + int newY = i + dy[k]; + if (!inBoard(board, newX, newY)) continue; + if (board[x][y] == 'M') count++; + } + if (count > 0) { + board[i][j] = (char) (count + '0'); + } else { + board[i][j] = 'B'; + for (int k = 0; k < 8; k++) { + int newX = i + dx[k]; + int newY = i + dy[k]; + if (!inBoard(board, newX, newY) || board[newX][newY] != 'E' || visited[newX][newY]) + continue; + visited[newX][newY] = true; + queue.offer(new int[]{newX, newY}); + } + } + } + } + return board; + } + + private void dfs(char[][] board, int r, int c) { + int count = 0; + for (int i = 0; i < 8; i++) { + int x = r + dx[i]; + int y = c + dy[i]; + if (!inBoard(board, x, y)) continue; + if (board[x][y] == 'M') { + count++; + } + } + if (count > 0) { + board[r][c] = (char) (count + '0'); + return; + } + board[r][c] = 'B'; + for (int i = 0; i < 8; i++) { + int x = r + dx[i]; + int y = c + dy[i]; + if (!inBoard(board, x, y) || board[x][y] != 'E') continue; + dfs(board, x, y); + } + } + + private boolean inBoard(char[][] board, int r, int c) { + return 0 <= r && r < board.length + && 0 <= c && c < board[0].length; + } + + /** + * 874. 模拟行走机器人 + * https://leetcode-cn.com/problems/walking-robot-simulation/description/ + * + * @param commands + * @param obstacles + * @return + */ + public int robotSim(int[] commands, int[][] obstacles) { + int res = 0; + int x = 0, y = 0, direction = 0; + int[][] xy = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + Set obstacleSet = new HashSet<>(); + for (int[] obstacle : obstacles) { + obstacleSet.add(obstacle[0] + "," + obstacle[1]); + } + for (int com : commands) { + if (com == -2) { + direction = (direction + 3) % 4; + } else if (com == -1) { + direction = (direction + 1) % 4; + } else { + while (com-- > 0 && !obstacleSet.contains(x + xy[direction][0] + "," + y + xy[direction][1])) { + x += xy[direction][0]; + y += xy[direction][1]; + } + res = Math.max(res, x * x + y * y); + } + } + return res; + } + + /** + * 55. 跳跃游戏 + * https://leetcode-cn.com/problems/jump-game/ + * + * @param nums + * @return + */ + public boolean canJump(int[] nums) { + if (nums.length < 2) return true; + int minStep = 1; + for (int i = nums.length - 2; i >= 0; i--) { + if (nums[i] >= minStep) { + minStep = 1; + } else { + minStep++; + } + } + return minStep == 1; + } + + /** + * 45. 跳跃游戏 II + * https://leetcode-cn.com/problems/jump-game-ii/ + * + * @param nums + * @return + */ + public int jump(int[] nums) { + int end = 0, maxPosition = 0,res = 0,now = 0; + while(end < nums.length - 1){ + maxPosition = Math.max(maxPosition,now+nums[now]); + if(now == end){ + end = maxPosition; + res ++; + } + now ++; + } + return res; + } + + /** + * 33. 搜索旋转排序数组 + * https://leetcode-cn.com/problems/search-in-rotated-sorted-array/ + * @param nums + * @param target + * @return + */ + public int search(int[] nums, int target) { + int start = 0, end = nums.length - 1; + while(start <= end){ + int mid = (start + end + 1) >>> 1; + if(nums[mid] == target) return mid; + if(target >= nums[0]){ + // 目标值大于等于最左边的值,说明在左半段 + // 若mid比最左边的值还小,说明mid在右半段,将mid的值置为Integer.MAX_VALUE + if(nums[mid] < nums[0]){ + nums[mid] = Integer.MAX_VALUE; + } + } else { + // 目标值小于最左边的值,说明在右半段 + // 如果mid比最左边的值还大,说明mid在左半段,将mid的值置位Integer.MIN_VALUE + if(nums[mid] >= nums[0]){ + nums[mid] = Integer.MIN_VALUE; + } + } + if(target > nums[mid]){ + start = mid + 1; + } else { + end = mid - 1; + } + + } + return -1; + } + + /** + * 153. 寻找旋转排序数组中的最小值 + * https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/ + * @param nums + * @return + */ + public int findMin(int[] nums) { + int start = 0, end = nums.length - 1; + // 最小的值肯定在非单调递增的一端 + while(start <= end){ + if(nums[start] < nums[end]) return nums[start]; + int mid = (start + end) >>> 1; + if(nums[mid] > nums[start]){ + // mid的值大于最左边的值,说明左边单调递增,最小值在右边 + start = mid + 1; + } else { + // mid的值小于左边的值,说明右边单调递增,最小值在左边 + end = mid; + } + } + return -1; + } + + /** + * 74. 搜索二维矩阵 + * https://leetcode-cn.com/problems/search-a-2d-matrix/ + * @param matrix + * @param target + * @return + */ + public boolean searchMatrix(int[][] matrix, int target) { + if(matrix == null || matrix.length == 0) return false; + int row = matrix.length, column = matrix[0].length; + int start = 0, end = row * column - 1; + while(start <= end){ + int mid = (start + end) >>> 1; + int value = matrix[mid/column][mid%row]; + if(value == target) return true; + if(value < target){ + start = mid + 1; + } else { + end = mid - 1; + } + } + return false; + } + + + public static void main(String[] args) { + Week4 week4 = new Week4(); + int x = Integer.MAX_VALUE - 1; + int y = Integer.MAX_VALUE; + System.out.println("x = " + x); + System.out.println("y = " + y); + System.out.println("x + y = " + (x+y)); + System.out.println("(x+y)/2 = " + ((x+y)/2)); + System.out.println("(x+y)>>1 = " + ((x+y)>>1)); + System.out.println("(x+y)>>>1 = " + ((x+y)>>>1)); + } +} diff --git a/Week_04/Week4Training.java b/Week_04/Week4Training.java new file mode 100644 index 00000000..9020635a --- /dev/null +++ b/Week_04/Week4Training.java @@ -0,0 +1,150 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class Week4Training { + /** + * 102. 二叉树的层序遍历 + * https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ + * + * @param root + * @return + */ + public List> levelOrder(TreeNode root) { + List> res = new ArrayList<>(); + Deque deque = new ArrayDeque<>(); + if (root != null) deque.addLast(root); + while (!deque.isEmpty()) { + int size = deque.size(); + List list = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = deque.pollFirst(); + list.add(node.val); + if (node.left != null) deque.addLast(node.left); + if (node.right != null) deque.addLast(node.right); + } + res.add(list); + } + return res; + } + + /** + * 433. 最小基因变化 + * https://leetcode-cn.com/problems/minimum-genetic-mutation/#/description + * + * @param start + * @param end + * @param bank + * @return + */ + private char[] genes = {'A', 'T', 'C', 'G'}; + + public int minMutation(String start, String end, String[] bank) { + Set bankSet = new HashSet<>(Arrays.asList(bank)); + if (!bankSet.contains(end)) return -1; + if (start.equals(end)) return 0; + int count = 0; + Queue queue = new LinkedList<>(); + queue.offer(start); + bankSet.remove(start); + while (!queue.isEmpty()) { + count++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + String s = queue.poll(); + for (String neighbor : getNeighbors(s, bankSet)) { + if (end.equals(neighbor)) return count; + queue.offer(neighbor); + } + } + } + return -1; + } + + private List getNeighbors(String s, Set bankSet) { + List list = new ArrayList<>(); + char[] chars = s.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + for (int j = 0; j < genes.length; j++) { + if (c == genes[j]) continue; + chars[i] = genes[j]; + String newStr = new String(chars); + if (bankSet.contains(newStr)) list.add(newStr); + } + chars[i] = c; + } + return list; + } + + /** + * 515. 在每个树行中找最大值 + * https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/#/description + * + * @param root + * @return + */ + public List largestValues(TreeNode root) { + List res = new ArrayList<>(); + Queue queue = new LinkedList<>(); + if (root != null) queue.offer(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(node.val, max); + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + res.add(max); + } + return res; + } + + /** + * 69. x 的平方根 + * https://leetcode-cn.com/problems/sqrtx/ + * + * @param x + * @return + */ + public int mySqrt(int x) { + long left = 0, right = x / 2 + 1; + while (left < right) { + long mid = (left + right + 1) >>> 1; + long temp = mid * mid; + if (temp <= x) { + left = mid; + } else { + right = mid - 1; + } + } + return (int) left; + } + + /** + * 367. 有效的完全平方数 + * https://leetcode-cn.com/problems/valid-perfect-square/ + * + * @param num + * @return + */ + public boolean isPerfectSquare(int num) { + int res = mySqrt(num); + return res * res == num; + } + + public static void main(String[] args) { + Week4Training training = new Week4Training(); + System.out.println(training.isPerfectSquare(16)); + } +} diff --git a/Week_06/README.md b/Week_06/README.md index 50de3041..939f4673 100644 --- a/Week_06/README.md +++ b/Week_06/README.md @@ -1 +1,126 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 动态规划 + +​ 做了那么多动态规划的题之后,发现其实动态规划大部分情况下就是找到重复子问题后,将所有重复子问题的解穷举出来,存到数组中,然后再在数组中找到最优解。而在数组中,后一位存的基本上都跟前面几位相关,或前一位,或前两位,或上一位左一位等等。 + +​ 其次就是,动态规划一般是用空间来换时间,所以在做题目的时候,可以先采用多维数组进行存储中间状态值,进而得出最优解,然后再观察是否可以降维处理,例如在记录中间状态值时,该值若只与前一个值相关。这样逐渐降维优化下来,空间复杂度进一步降低,而不是一开始就考虑用最小维。 + +动态规划的题解一般分为三个步骤 + +1、找到重复子问题 + +2、定义dp数组,确定初始值 + +3、找到状态转移方程(最难) + +### 习题笔记 + +1、[64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/) + +(1)重复子问题:grid[i]\[j] 的路径可以从grid[i]\[j-1]走过来,也可以从grid[i-1]\[j]走过来,那么最小和就是从这二者其中找到最小的,加上grid[i]\[j]的值 + +(2)定义dp数组:dp[i]\[j]表示grid[i]\[j]的最小路径和, 初始值dp[0...i]\[0] 和dp[0]\[0...j]的值可以由dp[0...i-1]\[0] 和dp[0]\[0...j-1]来确定 + +(3)状态转移方程: + +dp[i]\[j] = Math.min(dp[i-1]\[j],dp[i]\[j-1]) + +代码如下: + +```java +public int minPathSum(int[][] grid) { + int m = grid.length, n = grid[0].length; + // 初始化base case,利用原数组省了创建新的空间 + for(int i = 1; i < m; i++) grid[i][0] += grid[i-1][0]; + for(int i = 1; i < n; i++) grid[0][i] += grid[0][i-1]; + for(int i = 1; i < m; i++){ + for(int j = 1; j < n; j++){ + grid[i][j] += Math.min(grid[i-1][j],grid[i][j-1]); + } + } + return grid[m-1][n-1]; + } +``` + +2、[91. 解码方法](https://leetcode-cn.com/problems/decode-ways/) + +(1)重复子问题:若s[i]为0,判断s[i-1]的值:若s[i-1]为1或者2,则可以组合成10或20,可以匹配到26的字母里面,否则就匹配不了;若s[i]不为0,则当s[i-1]为1的时候,可以组合到11-19,当s[i-1]为2且s[i]小于7时,可以组合21-26,否则的话只能是s[i]自己去匹配。 + +(2)定义dp数组:dp[i+1]表示前i个字符能解码的方法组合数量 + +(3)状态转移方程: + +if(s[i] == 0): + +​ if(s[i-1] == 1,2) dp[i+1] = dp[i-1]\(只有一种组合翻译) + +​ else return 0 + +else: + +​ if(s[i-1] == 1 || s[i-1] == 2 && s[i] < 7) dp[i+1] = dp[i] + dp[i-1] (单独翻译为dp[i],组合翻译为dp[i-1]) + +​ else dp[i+1] = dp[i] (只能单独翻译) + +代码如下: + +```java +public int numDecodings(String s) { + int n = s.length(); + int[] dp = new int[n + 1]; + dp[0] = 1; + dp[1] = s.charAt(0) == '0' ? 0 : 1; + for(int i = 1; i < n; i++){ + int cur = s.charAt(i) - '0'; + int pre = s.charAt(i-1) - '0'; + if(cur == 0){ + if(pre == 1 || pre == 2){ + dp[i + 1] = dp [i - 1]; + } else { + return 0; + } + } else if (pre == 1 || (pre == 2 && cur < 7)){ + dp[i + 1] = dp[i] + dp[i - 1]; + } else { + dp[i + 1] = dp[i]; + } + } + return dp[n]; + } +``` + +3、[221. 最大正方形](https://leetcode-cn.com/problems/maximal-square/) + +(1)重复子问题:若(i,j)为1,以(i,j)为右下角,最小边长即为1,若(i-1,j-1),(i-1,j),(i,j-1)都为1,则最小边长增加1,以此类推 + +(2)定义dp数组:dp[i]\[j]表示以(i,j)为右下角,能找到的最大正方形边长,首行和首列初始值为矩阵的首行首列值 + +(3)状态转移方程:dp[i]\[j] = min(dp[i-1]\[j-1],dp[i-1]\[j],dp[i]\[j-1])+ 1 + +```java +public int maximalSquare(char[][] matrix) { + int m = matrix.length, n = matrix[0].length; + int[][] dp = new int[m][n]; + int maxSide = 0; + for(int i = 0; i < m; i++){ + dp[i][0] = matrix[i][0] == '1' ? 1:0; + maxSide = Math.max(maxSide,dp[i][0]); + } + for(int i = 0; i < m; i++){ + for(int j = 1; j < n; j++){ + if(matrix[i][j] == '1'){ + if(i == 0){ + dp[i][j] = 1; + } else { + dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1])) + 1; + } + } + maxSide = Math.max(dp[i][j],maxSide); + } + } + return maxSide * maxSide; + } +``` + +未完待续。。。 \ No newline at end of file diff --git a/Week_06/Week6.java b/Week_06/Week6.java new file mode 100644 index 00000000..0d752e9f --- /dev/null +++ b/Week_06/Week6.java @@ -0,0 +1,199 @@ +package com.freezing.leetcode.jike; + +import java.util.Arrays; + +public class Week6 { + + /** + * 64. 最小路径和 + * https://leetcode-cn.com/problems/minimum-path-sum/ + * + * @param grid + * @return + */ + public int minPathSum(int[][] grid) { + int m = grid.length, n = grid[0].length; + int[][] dp = new int[m][n]; + dp[0][0] = grid[0][0]; + for (int i = 1; i < m; i++) dp[i][0] = dp[i - 1][0] + grid[i][0]; + for (int i = 1; i < n; i++) dp[0][i] = dp[0][i - 1] + grid[0][i]; + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; + } + } + return dp[m - 1][n - 1]; + } + + /** + * 91. 解码方法 + * https://leetcode-cn.com/problems/decode-ways/ + * + * @param s + * @return + */ + public int numDecodings(String s) { + if (s.length() == 0 || s.charAt(0) == '0') return 0; + int[] dp = new int[s.length() + 1]; + dp[0] = 1; + dp[1] = s.charAt(0) == '0' ? 0 : 1; + for (int i = 1; i < s.length(); i++) { + int cur = s.charAt(i) - '0'; + int pre = s.charAt(i - 1) - '0'; + if (cur == 0) { + if (pre == 1 || pre == 2) { + dp[i + 1] = dp[i - 1]; + } else { + return 0; + } + } else if (pre == 1 || (pre == 2 && cur <= 6)) { + dp[i + 1] = dp[i] + dp[i - 1]; + } else { + dp[i + 1] = dp[i]; + } + } + return dp[s.length()]; + } + + /** + * 221. 最大正方形 + * https://leetcode-cn.com/problems/maximal-square/ + * + * @param matrix + * @return + */ + public int maximalSquare(char[][] matrix) { + if (matrix == null || matrix.length < 1 || matrix[0].length < 1) return 0; + int m = matrix.length, n = matrix[0].length; + int[][] dp = new int[m + 1][n + 1]; + int side = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == '1') { + dp[i + 1][j + 1] = Math.min(dp[i][j], Math.min(dp[i + 1][j], dp[i][j + 1])) + 1; + side = Math.max(side, dp[i + 1][j + 1]); + } + } + } + return side * side; + } + + /** + * 621. 任务调度器 + * https://leetcode-cn.com/problems/task-scheduler/ + * + * @param tasks + * @param n + * @return + */ + public int leastInterval(char[] tasks, int n) { + int[] count = new int[26]; + for (int i = 0; i < tasks.length; i++) count[tasks[i] - 'A']++; + Arrays.sort(count); + int idle = count[25] - 1; + int maxCount = idle * n + count[25]; + for (int i = 24; i >= 0 && count[i] == count[25]; i--) maxCount++; + return Math.max(maxCount, tasks.length); + } + + /** + * 647. 回文子串 + * https://leetcode-cn.com/problems/palindromic-substrings/ + * + * @param s + * @return + */ + public int countSubstrings(String s) { + if (s == null || s.length() == 0) return 0; + int n = s.length(); + boolean[][] dp = new boolean[n][n]; + int res = s.length(); + for (int i = 0; i < n; i++) dp[i][i] = true; + for (int i = n - 1; i >= 0; i--) { + for (int j = i + 1; j < n; j++) { + if (s.charAt(i) == s.charAt(j)) { + dp[i][j] = j - i == 1 || dp[i + 1][j - 1]; + } + if (dp[i][j]) res++; + } + } + return res; + } + + /** + * 32. 最长有效括号 + * https://leetcode-cn.com/problems/longest-valid-parentheses/ + * + * @param s + * @return + */ + public int longestValidParentheses(String s) { + int n = s.length(); + int[] dp = new int[n]; + int max = 0; + for (int i = 1; i < n; i++) { + if (s.charAt(i) == ')') { + if (s.charAt(i - 1) == '(') { + if (i >= 2) { + dp[i] = dp[i - 2] + 2; + } else { + dp[i] = 2; + } + } else if (i - dp[i - 1] - 1 >= 0 && s.charAt(i - dp[i - 1] - 1) == '(') { + if (i - dp[i - 1] - 2 >= 0) { + dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]; + } else { + dp[i] = dp[i - 1] + 2; + } + } + } + max = Math.max(max, dp[i]); + } + return max; + } + + /** + * 363. 矩形区域不超过 K 的最大数值和 + * https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/ + * @param matrix + * @param k + * @return + */ + public int maxSumSubmatrix(int[][] matrix, int k) { + int m = matrix.length,n = matrix[0].length,max = Integer.MIN_VALUE; + for(int l = 0; l < n; l++){ + int[] rowSums = new int[m]; + for(int r = l; r < n; r++){ + for(int i = 0; i < m; i++){ + rowSums[i] += matrix[i][r]; + } + max = Math.max(dpmax(rowSums,k),max); + if(max == k) return max; + } + } + return max; + } + private int dpmax(int[] dp,int k){ + int max = dp[0],pre = 0; + for (int i = 0; i < dp.length; i++) { + pre = Math.max(dp[i],dp[i] + pre); + max = Math.max(pre,max); + } + if(max <= k) return max; + max = Integer.MIN_VALUE; + for (int i = 0; i < dp.length; i++) { + int sum = 0; + for (int j = i; j < dp.length; j++) { + sum += dp[j]; + if(sum == k) return sum; + if(sum > max && sum < k) max = sum; + } + } + return max; + } + + public static void main(String[] args){ + Week6 week6 = new Week6(); + week6.maxSumSubmatrix(new int[][]{{2,2,-1}},3); + } +} diff --git a/Week_06/Week6Training.java b/Week_06/Week6Training.java new file mode 100644 index 00000000..f570886a --- /dev/null +++ b/Week_06/Week6Training.java @@ -0,0 +1,351 @@ +package com.freezing.leetcode.jike; + +import java.util.Arrays; +import java.util.List; + +public class Week6Training { + + public int climbStairs(int n) { + System.out.println("climbStairs1"); + int[] steps = new int[]{1, 2}; + int[] dp = new int[n + 1]; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + for (int j = 0; j < 2; j++) { + int step = steps[j]; + if (i < step) continue; + dp[i] += dp[i - step]; + System.out.println("dp[" + i + "] = " + dp[i]); + } + } + return dp[n]; + } + + public int climbStairs2(int n) { + System.out.println("climbStairs2"); + int[] steps = new int[]{1, 2}; + int[] dp = new int[n + 1]; + dp[0] = 1; + for (int j = 0; j < 2; j++) { + for (int i = 1; i <= n; i++) { + int step = steps[j]; + if (i < step) continue; + dp[i] += dp[i - step]; + System.out.println("dp[" + i + "] = " + dp[i]); + } + } + return dp[n]; + } + + /** + * 1143. 最长公共子序列 + * https://leetcode-cn.com/problems/longest-common-subsequence/ + * + * @param text1 + * @param text2 + * @return + */ + 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 = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + dp[i][j] = text1.charAt(i - 1) == text2.charAt(j - 1) ? + dp[i - 1][j - 1] + 1 : + Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + return dp[m][n]; + } + + /** + * 120. 三角形最小路径和 + * https://leetcode-cn.com/problems/triangle/ + * + * @param triangle + * @return + */ + public int minimumTotal(List> triangle) { + int[] dp = new int[triangle.get(triangle.size() - 1).size()]; + for (int i = triangle.size() - 1; i >= 0; i--) { + for (int j = 0; j < triangle.get(i).size(); j++) { + List row = triangle.get(i); + if (i == triangle.size() - 1) { + dp[j] = row.get(j); + } else { + dp[j] = row.get(j) + Math.min(dp[j], dp[j + 1]); + } + } + } + return dp[0]; + } + + /** + * 53. 最大子序和 + * https://leetcode-cn.com/problems/maximum-subarray/ + * @param nums + * @return + */ + public int maxSubArray(int[] nums) { + int max = nums[0], pre = 0; + for(int num : nums){ + pre = Math.max(num,num+pre); + max = Math.max(pre,max); + } + return max; + } + + /** + * 152. 乘积最大子数组 + * https://leetcode-cn.com/problems/maximum-product-subarray/ + * + * @param nums + * @return + */ + public int maxProduct(int[] nums) { + int res = nums[0], max = nums[0], min = nums[0]; + for (int i = 1; i < nums.length; i++) { + int mx = max, mn = min; + max = Math.max(mx * nums[i], Math.max(mn * nums[i], nums[i])); + min = Math.min(mn * nums[i], Math.min(mx * nums[i], nums[i])); + res = Math.max(res, max); + } + return res; + } + + + /** + * 322. 零钱兑换 + * https://leetcode-cn.com/problems/coin-change/ + * + * @param coins + * @param amount + * @return + */ + public int coinChange(int[] coins, int amount) { + if (amount == 0) return 0; + if (coins == null || coins.length == 0) return -1; + int[] dp = new int[amount + 1]; + dp[0] = 0; + for (int i = 1; i < amount + 1; i++) { + int min = 0; + for (int j = 0; j < coins.length; j++) { + if (i < coins[j]) continue; + min = Math.min(min, dp[i - coins[j]]); + } + dp[i] = min + 1; + } + return dp[amount]; + } + + /** + * 518. 零钱兑换 II + * https://leetcode-cn.com/problems/coin-change-2/ + * @param amount + * @param coins + * @return + */ + public int change(int amount, int[] coins) { + int[] dp = new int[amount + 1]; + dp[0] = 1; + for(int i = 0; i < coins.length; i++){ + for(int j = 1; j <= amount; j++){ + if(j < coins[i]) continue; + dp[j] += dp[j-coins[i]]; + } + } + return dp[amount]; + } + + /** + * 198. 打家劫舍 + * https://leetcode-cn.com/problems/house-robber/ + * + * @param nums + * @return + */ + public int rob(int[] nums) { + int n = nums.length; + if (n == 0) return 0; + if (n == 1) return nums[0]; + int first = 0, second = 0; + for (int i = 0; i < n; i++) { + int tmp = second; + second = Math.max(first + nums[i], second); + first = tmp; + } + return second; + } + + /** + * 213. 打家劫舍 II + * https://leetcode-cn.com/problems/house-robber-ii/description/ + * + * @param nums + * @return + */ + public int rob2(int[] nums) { + int n = nums.length; + if (n == 0) return 0; + if (n == 1) return nums[0]; + return Math.max(myRob(Arrays.copyOfRange(nums, 0, n - 1)), + myRob(Arrays.copyOfRange(nums, 1, n))); + } + + private int myRob(int[] nums) { + int first = 0, second = 0, tmp; + for (int i = 0; i < nums.length; i++) { + tmp = second; + second = Math.max(first + nums[i], second); + first = tmp; + } + return second; + } + + /** + * 121. 买卖股票的最佳时机 + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/#/description + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + if(prices == null || prices.length < 2) return 0; + int n = prices.length; + int[][] dp = new int[n][2]; + dp[0][1] = -prices[0]; + for(int i = 1; i < n; i++){ + dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]); + dp[i][1] = Math.max(dp[i-1][1], - prices[i]); + } + return dp[n-1][0]; + } + + /** + * 123. 买卖股票的最佳时机 III + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/ + * + * @param prices + * @return + */ + public int maxProfit3(int[] prices) { + if (prices == null || prices.length < 2) return 0; + int n = prices.length; + int[][][] dp = new int[n][3][2]; + for (int i = 1; i <= 2; i++) { + dp[0][i][0] = 0; + dp[0][i][1] = -prices[0]; + } + for (int i = 1; i < n; i++) { + for (int j = 1; j <= 2; j++) { + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); + } + } + return dp[n - 1][2][0]; + } + + /** + * 188. 买卖股票的最佳时机 IV + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/ + * @param k + * @param prices + * @return + */ + public int maxProfit(int k, int[] prices) { + if(prices == null || prices.length < 2) return 0; + int n = prices.length; + if(k >= n/2){ + return myProfit(prices); + } + int[][][] dp = new int[n][k+1][2]; + for(int i = 1; i <= k; i++) dp[0][i][1] = -prices[0]; + for(int i = 1; i < n; i++){ + for(int j = 1; j <= k; j++){ + dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]); + } + } + return dp[n-1][k][0]; + } + + private int myProfit(int[] prices){ + int n = prices.length; + int[][] dp = new int[n][2]; + dp[0][1] = -prices[0]; + for(int i = 1; i < n; i++){ + dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]); + dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]); + } + return dp[n-1][0]; + } + + /** + * 309. 最佳买卖股票时机含冷冻期 + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/ + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + if(prices == null || prices.length < 2) return 0; + int n = prices.length; + int[][] dp = new int[n][2]; + dp[0][1] = -prices[0]; + for(int i = 1; i < n; i++){ + dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]); + dp[i][1] = Math.max(dp[i-1][1], (i>=2?dp[i-2][0]:0) - prices[i]); + } + return dp[n-1][0]; + } + + /** + * 714. 买卖股票的最佳时机含手续费 + * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ + * @param prices + * @param fee + * @return + */ + public int maxProfit(int[] prices, int fee) { + if(prices == null || prices.length < 2) return 0; + int n = prices.length; + int[][] dp = new int[n][2]; + dp[0][1] = -prices[0]; + for(int i = 1; i < n; i++){ + dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i] - fee); + dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]); + } + return dp[n-1][0]; + } + + /** + * 72. 编辑距离 + * https://leetcode-cn.com/problems/edit-distance/ + * + * @param word1 + * @param word2 + * @return + */ + public int minDistance(String word1, String word2) { + int m = word1.length(), n = word2.length(); + int[][] dp = new int[m + 1][n + 1]; + for (int i = 0; i <= m; i++) dp[i][0] = i; + for (int i = 0; i <= n; i++) dp[0][i] = i; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (word1.charAt(i - 1) == word2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; + } + } + } + return dp[m][n]; + } + + public static void main(String[] args) { + Week6Training training = new Week6Training(); +// training.maximalSquare(new char[][]{{'1', '0', '1', '0', '0'}, {'1', '0', '1', '1', '1'}, {'1', '1', '1', '1', '1'}, {'1', '0', '0', '1', '0'}}); +// training.climbStairs(3); +// training.climbStairs2(3); + training.maxProfit3(new int[]{1, 2, 3, 4, 5}); + } +} diff --git a/Week_07/README.md b/Week_07/README.md index 50de3041..73e0f4ec 100644 --- a/Week_07/README.md +++ b/Week_07/README.md @@ -1 +1,583 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 一、字典树 + +### 理论笔记 + +1、基本结构 + +字典树即Trie树,又称单词查找树或键树,是一种树形结构,主要应用于统计和排序大量的字符串,能最大限度地减少无谓的字符串比较,查询效率比哈希表高 + +2、基本性质 + +(1)结点本身不存完整的单词; + +(2)从根节点到某一节点,路径上经过的字符连接起来就是该结点对应的字符串; + +(3)每个节点的所有子结点路径代表的字符都不相同 + +(4)一次构建,多次查询 + +3、核心思想 + +空间换时间,利用字符串的公共前缀来降低查询时间的开销 + +4、代码模板 + +```java +public class Trie { + + class TrieNode { + public boolean isEnd = false; + public TrieNode[] next = new TrieNode[26]; + } + + private TrieNode root; + + /** + * 初始化 + */ + public Trie() { + root = new TrieNode(); + } + + /** + * 插入字符串,构建字典树 + */ + public void insert(String word) { + TrieNode node = root; + for (char c : word.toCharArray()) { + if (node.next[c - 'a'] == null) { + node.next[c - 'a'] = new TrieNode(); + } + node = node.next[c - 'a']; + } + node.isEnd = true; + } + + /** + * 判断字符串是否在该字典树中 + */ + public boolean search(String word) { + TrieNode node = root; + for (char c : word.toCharArray()) { + if (node.next[c - 'a'] == null) return false; + node = node.next[c - 'a']; + } + return node.isEnd; + } + + /** + * 判断前缀是否在该字典树中 + */ + public boolean startsWith(String prefix) { + TrieNode node = root; + for (char c : prefix.toCharArray()) { + if (node.next[c - 'a'] == null) return false; + node = node.next[c - 'a']; + } + return true; + } +} + +``` + +### 习题笔记 + +1、[212. 单词搜索 II](https://leetcode-cn.com/problems/word-search-ii/)这道题的思想是字典树+DFS,先用单词列表构建字典树,在DFS中向上下左右四个方向扩散遍历,判断遍历的结果是否在字典树中,代码如下: + +```java +class Solution { + Set res; + TrieNode root; + int[] dx = {0, 0, -1, 1}; + int[] dy = {-1, 1, 0, 0}; + + public List findWords(char[][] board, String[] words) { + int m = board.length, n = board[0].length; + res = new HashSet<>(); + root = new TrieNode(); + // 构建字典树 + for(String word : words){ + insert(word); + } + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + //遍历board中的字符,若该字符是单词表中某个单词的第一个字符,进入DFS + if(root.next[board[i][j] - 'a'] == null) continue; + dfs(board,root,new boolean[m][n],i,j,new StringBuilder()); + } + } + return new ArrayList<>(res); + } + + public void dfs(char[][] board, TrieNode node,boolean[][] visited,int r,int c,StringBuilder sb){ + sb.append(board[r][c]); + visited[r][c] = true; + node = node.next[board[r][c] - 'a']; + // 该结点是单词的结束点,记录该单词到结果集 + if(node.isEnd) res.add(sb.toString()); + // 向四个方向扩散查找下一个字符 + for(int i = 0; i < dx.length; i++){ + int x = r+dx[i], y = c + dy[i]; + // 超过边界或者该字符访问过了或者该字符不在后面的字典树中 + if(!inBoard(board,x,y) || visited[x][y] || node.next[board[x][y] - 'a'] == null) continue; + dfs(board,node,visited,x,y,sb); + } + // 回溯 + sb.deleteCharAt(sb.length() - 1); + visited[r][c] = false; + } + + public boolean inBoard(char[][] board, int r, int c){ + return 0 <= r && r < board.length && 0 <= c && c < board[0].length; + } + + public void insert(String word){ + if(root == null) return; + TrieNode node = root; + for(char c : word.toCharArray()){ + if(node.next[c-'a'] == null){ + node.next[c-'a'] = new TrieNode(); + } + node = node.next[c-'a']; + } + node.isEnd =true; + } + + public class TrieNode { + public boolean isEnd = false; + public TrieNode[] next = new TrieNode[26]; + } +} +``` + +## 二、并查集 + +构建一个数据结构,用于解决组团配对问题,属于同一个团体的,他们的代表是同一个,初始创建的时候,每个人都是自己的代表,直到合并后拥有共同的代表,主要的操作有: + +**makeSet(s)**:建立一个并查集,其中包含s个单元素集合 + +**unionSet(x,y)**:若x,y不在同一个集合,则把元素x和元素y所在的集合合并 + +**find(x)**:找到元素x所在集合的代表,可用于判断两个元素是否在同一个集合中,并且在查找的过程中可以进行路径压缩,即直接将该元素的上级直接改为集合的代表,减少下一次的判断次数。 + +代码模板如下: + +```java +public class UnionFind { + // 集合个数 + private int count; + // 用数组来表示集合的关系,下标表示元素,值表示元素的上级 + 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--; + } + // 返回该并查集的集合数 + public int getCount() { + return count; + } +} +``` + +### 习题笔记 + +1、[547. 省份数量](https://leetcode-cn.com/problems/number-of-provinces/)这道题的主要思想就是利用并查集,将相连的两个省份合并到一个集合中去,最后得出并查集的个数就是题解,代码如下: + +```java +class Solution { + public int findCircleNum(int[][] isConnected) { + int n = isConnected.length; + UnionFind uf = new UnionFind(n); + for(int i = 0; i < n; i++){ + for(int j = i+1; j < n; j++){ + if(isConnected[i][j] == 1) uf.union(i,j); + } + } + return uf.getCount(); + } +} + +class UnionFind{ + private int count; + 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 --; + } + + public int getCount(){ + return count; + } +} +``` + +2、[200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)这道题用并查集的方式来解决,就是将二维数组转化为一个并查集,然后将相邻的陆地1合并到一个集合中,并计算水的数量,最后用并查集的集合数减去水数,就是岛屿的数量了。这里二维数组转化为并查集一维数组之后,二维数组的位置(i,j)对应的一维数组的位置就是n * i + j,其中n是二维数组的列数,代码如下: + +```java +class Solution { + public int numIslands(char[][] grid) { + int m = grid.length, n = grid[0].length; + UnionFind uf = new UnionFind(m*n); + int spaces = 0; + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + if(grid[i][j] == '1'){ + // 当前位置为岛屿的话,判断相邻的右边和下边是否也是岛屿,是则合并 + int currentIndex = getIndex(n,i,j); + if(i + 1 < m && grid[i+1][j] == '1') uf.union(currentIndex,getIndex(n,i+1,j)); + if(j + 1 < n && grid[i][j+1] == '1') uf.union(currentIndex,getIndex(n,i,j+1)); + } else { + // 当前位置为水,统计水的双 + spaces++; + } + } + } + return uf.getCount() - spaces; + } + // 获取二维数组在一维数组对应的位置 + public int getIndex(int n, int i, int j){ + return n * i + j; + } +} + +class UnionFind{ + private int count; + 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 --; + } + + public int getCount(){ + return count; + } +} +``` + +3、[130. 被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/)这道题的思想跟上一道的岛屿数量相似,不过这里需要注意的,如果当前的O是与边界的O相连的,那么就不进行变换,所以我们需要判断当前的O是否与边界的O相连,用一个比较巧妙的方法就是使用一个虚拟的结点,用来合并所有与边界O相连的O,代码如下: + +```java +class Solution { + int[] dx = {-1,1,0,0}; + int[] dy = {0,0,-1,1}; + public void solve(char[][] board) { + if(board == null || board.length == 0) return; + int m = board.length, n = board[0].length; + // 这里用m*n+1,就是要用最后的m*n作为与边界O相连O的集合 + UnionFind uf = new UnionFind(m*n+1); + // 虚拟结点,用于记录与边界O相连的O + int dummy = m*n; + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + if(board[i][j] == 'O'){ + // 判断是否为边界 + boolean isEdge = i == 0 || i == m-1 || j == 0 || j == n-1; + if(isEdge){ + // 初始化合并边界的O + uf.union(dummy,getIndex(n,i,j)); + } else { + // 合并当前位置四周的O + for(int k = 0; k < dx.length; k++){ + int x = i + dx[k], y = j + dy[k]; + if(!inBoard(m,n,x,y) || board[x][y] != 'O') continue; + uf.union(getIndex(n,i,j),getIndex(n,x,y)); + } + } + } + } + } + // 遍历判断每个O是否与边界相连,不是则改为X + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + if(board[i][j] == 'O' && uf.find(getIndex(n,i,j)) != uf.find(dummy)){ + board[i][j] = 'X'; + } + } + } + } + + public int getIndex(int n, int i, int j){ + return n * i + j; + } + + public boolean inBoard(int m,int n, int i, int j){ + return 0 <= i && i < m && 0 <= j && j < n; + } +} + +public class UnionFind { + private int count; + 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--; + } + + public int getCount() { + return count; + } +} +``` + +## 三、剪枝 + +剪枝就是在DFS中,判断哪些遍历的结果是错误的,将这些错误的分支剪去,减少遍历的次数,提高遍历效率,最常用的方式就是用回溯法,采用试错的方式,当发现现有的分步答案得不到正确的结果时,取消前面几步的计算,再通过其它分支去寻找解决的答案,在最坏的情况下,回溯法会导致复杂度为指数时间的计算。 + +### 习题笔记 + +1、[36. 有效的数独](https://leetcode-cn.com/problems/valid-sudoku/)这道题的思想就是,判断当前行、列和3x3九宫格中是否有重复的数字,有则不是有效的数独,可以采用三个二维数组分别来表示每一行、列、九宫格中的数字是否已经被使用过了,代码如下: + +```java +class Solution { + public boolean isValidSudoku(char[][] board) { + // 第二维用10主要是方便存1-9而已 + boolean[][] rowUsed = new boolean[9][10]; + boolean[][] colUsed = new boolean[9][10]; + boolean[][] boxUsed = new boolean[9][10]; + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(board[i][j] == '.') continue; + int num = board[i][j] - '0'; + // 九宫格的位置计算 + int boxIndex = i / 3 * 3 + j / 3; + if(rowUsed[i][num] || colUsed[j][num] || boxUsed[boxIndex][num]) return false; + rowUsed[i][num] = true; + colUsed[j][num] = true; + boxUsed[boxIndex][num] = true; + } + } + return true; + } +} +``` + +2、[37. 解数独](https://leetcode-cn.com/problems/sudoku-solver/)这道题是上一道题的加强版,这里是需要将最终的结果给解出来,所以就需要用到DFS来遍历每一个格子,然后尝试填入每一个合法的数字,如果最终不能得出结果,那就要回溯前面填入的数字,重新填入其它合法的数字,代码如下: + +```java +class Solution { + public void solveSudoku(char[][] board) { + boolean[][] rowUsed = new boolean[9][10]; + boolean[][] colUsed = new boolean[9][10]; + boolean[][] boxUsed = new boolean[9][10]; + // 初始化三个数组的值,有填入数字的就将对应位置的值设为true + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(board[i][j] == '.') continue; + int num = board[i][j] - '0'; + int boxIndex = i / 3 * 3 + j / 3; + rowUsed[i][num] = true; + colUsed[j][num] = true; + boxUsed[boxIndex][num] = true; + } + } + // 开始尝试解决 + dfs(board,rowUsed,colUsed,boxUsed,0,0); + } + + public boolean dfs(char[][] board, boolean[][] rowUsed, boolean[][] colUsed, boolean[][] boxUsed, int row, int col){ + if(col == board[0].length){ + // 如果一列填完了,那么进行下一行从头开始填 + col = 0; + // 如果最后一行都填完了,那就代表找到结果叻 + if(++row == board.length) return true; + } + // 当前位置已经有数字了,直接进入下一个位置 + if(board[row][col] != '.') return dfs(board,rowUsed,colUsed,boxUsed,row,col+1); + for(int i = 1; i <= 9; i++){ + int boxIndex = row / 3 * 3 + col / 3; + // 判断当前数字是否可以填入当前位置 + boolean canUse = !rowUsed[row][i] && !colUsed[col][i] && !boxUsed[boxIndex][i]; + if(canUse){ + // 尝试填入数字 + board[row][col] = (char)(i + '0'); + rowUsed[row][i] = true; + colUsed[col][i] = true; + boxUsed[boxIndex][i] = true; + // 进入下一个位置的处理,如果接下来的位置都能处理成功,代表当前位置的填入数字是正确的 + if(dfs(board,rowUsed,colUsed,boxUsed,row,col+1)) return true; + // 如果接下来的位置有不能处理成功的,那么回溯重置当前位置填入的数字,尝试用另外的数字去填入到当前位置 + board[row][col] = '.'; + rowUsed[row][i] = false; + colUsed[col][i] = false; + boxUsed[boxIndex][i] = false; + } + } + return false; + } +} +``` + +## 四、双向BFS + +双向BFS是在BFS的基础上,在从起点开始往后遍历的时候,同时也尝试从终点开始往前遍历,若二者最终能够相遇,能遍历到相同的结点,则遍历结束,用这种方式可以减少扩散的结点数,提高遍历的效率,代码模板如下: + +```java +class Solution { + public int twoEndedBFS(String beginWord, String endWord, List wordList) { + Set wordSet = new HashSet<>(wordList); + if(!wordSet.contains(endWord)) return 0; + Set visited = new HashSet<>(); + // 用两个set来替换Queue + Set beginSet = new HashSet<>(); + Set endSet = new HashSet<>(); + int count = 1; + beginSet.add(beginWord); + endSet.add(endWord); + // 循环遍历两个set,直到两个set中有一个为空为止 + while(!beginSet.isEmpty() && !endSet.isEmpty()){ + count++; + // 每次都只处理结点少的那个set,减少后面遍历的次数 + if(beginSet.size() > endSet.size()){ + Set tmp = beginSet; + beginSet = endSet; + endSet = tmp; + } + Set newBeginSet = new HashSet<>(); + for(String word : beginSet){ + if(changeLetter(wordSet,word,visited,newBeginSet,endSet)) return count; + } + beginSet = newBeginSet; + } + return 0; + } + + public boolean changeLetter(Set wordSet, String s, Set visited, Set newBeginSet, Set endSet){ + char[] chars = s.toCharArray(); + for(int i = 0; i < s.length(); i++){ + char c = chars[i]; + for(char j = 'a'; j <= 'z'; j++){ + if(c == j) continue; + chars[i] = j; + String newWord = String.valueOf(chars); + // 变换的结果来endSet中,代表相遇了 + if(endSet.contains(newWord)) return true; + if(visited.contains(newWord) || !wordSet.contains(newWord)) continue; + newBeginSet.add(newWord); + visited.add(newWord); + } + chars[i] = c; + } + return false; + } +} +``` + +## 五、高级树 + +平衡二叉树是一种特殊的搜索二叉树,搜索二叉树的查找效率只与树的高度相关,而所谓平衡,即每个结点的左右子树高度差相差不大,那么意味着搜索效率就更高了。平衡二叉树是采用的平衡因子来判断左右子树的高度差,且一般在生成插入的时候就进行判断是否平衡,如果不平衡则需要通过旋转来进行平衡,主要的旋转操作有以下四种: + +**左旋**:右右子树,将当前结点A的右子结点B替换当前结点,A结点作为结点B的左结点,如果B结点有左子树的话,还应将其左子树作为A结点的右子树; + +**右旋**:左左子树,将当前结点A的左子结点B替换当前结点,A结点作为结点B的右结点,如果B结点有右子树的话,还应将其右子树作为A结点的左子树; + +**左右旋**:左右子树,先将A结点的左子节点B和B的右子节点C进行左旋,则C变成A的左子节点,B变成C的左子节点,这时该子树就变成左左子树了,再进行右旋操作即可; + +**右左旋**:右左子树,先将A结点的右子节点B和B的左子节点C进行右旋,则C变成A的右子节点,B变成C的右子节点,这时该子树就变成右右子树了,再进行左旋操作即可。 + +### 1、AVL树 + +AVL树是一种极端的平衡二叉树,它的平衡因子严格保持是-1,0,1,意味着虽然它的查找效率很高,但是在插入的时候可能需要频繁地进行旋转操作。 + +### 2、红黑树 + +红黑树是一种近似平衡的二叉搜索树,它能够确保任何一个结点的左右子树的高度差小于两倍。具体来说,红黑树是满足如下条件的二叉搜索树: + +• 每个结点要么是红色,要么是黑色 + +• 根结点是黑色 + +• 每个叶结点(NIL结点,空结点)是黑色的。 + +• 不能有相邻接的两个红色结点 + +• 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。 + +与AVL树相比,AVL树的搜索效率更高,但是红黑树生成和插入结点的效率更高。 + +所以如果读操作非常多,写操作非常少的情况下,用AVL树,比如数据库等,而如果写操作非常多,读操作非常少则用红黑树比如map,set等。 \ No newline at end of file diff --git a/Week_07/Trie.java b/Week_07/Trie.java new file mode 100644 index 00000000..82058515 --- /dev/null +++ b/Week_07/Trie.java @@ -0,0 +1,54 @@ +package com.freezing.leetcode.jike; + +/** + * 208. 实现 Trie (前缀树) + * https://leetcode-cn.com/problems/implement-trie-prefix-tree/#/description + */ +public class Trie { + private TrieNode root; + + /** + * Initialize your data structure here. + */ + public Trie() { + root = new TrieNode(); + } + + /** + * Inserts a word into the trie. + */ + public void insert(String word) { + TrieNode node = root; + for (char c : word.toCharArray()) { + if (node.next[c - 'a'] == null) { + node.next[c - 'a'] = new TrieNode(); + } + node = node.next[c - 'a']; + } + node.isEnd = true; + } + + /** + * Returns if the word is in the trie. + */ + public boolean search(String word) { + TrieNode node = root; + for (char c : word.toCharArray()) { + if (node.next[c - 'a'] == null) return false; + node = node.next[c - 'a']; + } + return node.isEnd; + } + + /** + * Returns if there is any word in the trie that starts with the given prefix. + */ + public boolean startsWith(String prefix) { + TrieNode node = root; + for (char c : prefix.toCharArray()) { + if (node.next[c - 'a'] == null) return false; + node = node.next[c - 'a']; + } + return true; + } +} diff --git a/Week_07/TrieNode.java b/Week_07/TrieNode.java new file mode 100644 index 00000000..fdab626b --- /dev/null +++ b/Week_07/TrieNode.java @@ -0,0 +1,6 @@ +package com.freezing.leetcode.jike; + +public class TrieNode { + public boolean isEnd = false; + public TrieNode[] next = new TrieNode[26]; +} diff --git a/Week_07/UnionFind.java b/Week_07/UnionFind.java new file mode 100644 index 00000000..06124e07 --- /dev/null +++ b/Week_07/UnionFind.java @@ -0,0 +1,32 @@ +package com.freezing.leetcode.jike; + +public class UnionFind { + private int count; + 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--; + } + + public int getCount() { + return count; + } +} diff --git a/Week_07/Week7.java b/Week_07/Week7.java new file mode 100644 index 00000000..35856da3 --- /dev/null +++ b/Week_07/Week7.java @@ -0,0 +1,508 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class Week7 { + + /** + * 212. 单词搜索 II + * https://leetcode-cn.com/problems/word-search-ii/ + * + * @param board + * @param words + * @return + */ + Set res; + TrieNode root; + int[] dx = {0, 0, -1, 1}; + int[] dy = {-1, 1, 0, 0}; + + public List findWords(char[][] board, String[] words) { + root = new TrieNode(); + for (String word : words) { + insert(word); + } + int m = board.length, n = board[0].length; + boolean[][] visited = new boolean[m][n]; + res = new HashSet<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (root.next[board[i][j] - 'a'] == null) continue; + dfs(board, visited, root, i, j, new StringBuilder()); + } + } + return new ArrayList<>(res); + } + + private void dfs(char[][] board, boolean[][] visited, TrieNode node, int x, int y, StringBuilder sb) { + sb.append(board[x][y]); + visited[x][y] = true; + node = node.next[board[x][y] - 'a']; + if (node.isEnd) res.add(sb.toString()); + + for (int i = 0; i < 4; i++) { + int newX = x + dx[i]; + int newY = y + dy[i]; + if (!inBoard(board, newX, newY) || visited[newX][newY] || node.next[board[newX][newY] - 'a'] == null) + continue; + dfs(board, visited, node, newX, newY, sb); + } + sb.deleteCharAt(sb.length() - 1); + visited[x][y] = false; + } + + public boolean inBoard(char[][] board, int x, int y) { + return 0 <= x && x < board.length && 0 <= y && y < board[0].length; + } + + public void insert(String word) { + TrieNode node = root; + for (char c : word.toCharArray()) { + TrieNode tmp = node.next[c - 'a']; + if (tmp == null) { + tmp = new TrieNode(); + node.next[c - 'a'] = tmp; + } + node = tmp; + } + node.isEnd = true; + } + + /** + * 547. 省份数量 + * https://leetcode-cn.com/problems/number-of-provinces/ + * + * @param isConnected + * @return + */ + public int findCircleNum(int[][] isConnected) { + int n = isConnected.length; + UnionFind unionFind = new UnionFind(n); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (isConnected[i][j] == 1) { + unionFind.union(i, j); + } + } + } + return unionFind.getCount(); + } + + /** + * 130. 被围绕的区域 + * https://leetcode-cn.com/problems/surrounded-regions/ + * + * @param board + */ + public void solve(char[][] board) { + if (board == null || board.length == 0) return; + int m = board.length, n = board[0].length; + UnionFind uf = new UnionFind(m * n + 1); + int dummyNode = m * n; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == 'O') { + boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; + if (isEdge) { + uf.union(getIndex(i, j, n), dummyNode); + } else { + for (int k = 0; k < dx.length; k++) { + int x = i + dx[k]; + int y = j + dy[k]; + if (inBoard(board, x, y) && board[x][y] == 'O') { + uf.union(getIndex(i, j, n), getIndex(x, y, n)); + } + } + } + } + } + } + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == 'O' && uf.find(getIndex(i, j, n)) != uf.find(dummyNode)) { + board[i][j] = 'X'; + } + } + } + } + + public int getIndex(int i, int j, int c) { + return i * c + j; + } + + /** + * 36. 有效的数独 + * https://leetcode-cn.com/problems/valid-sudoku/ + * + * @param board + * @return + */ + public boolean isValidSudoku(char[][] board) { + int[][] row = new int[9][9]; + int[][] col = new int[9][9]; + int[][] box = new int[9][9]; + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] != '.') { + int num = board[i][j] - '1'; + int boxIndex = i / 3 * 3 + j / 3; + if (row[i][num] == 1) return false; + row[i][num] = 1; + if (col[j][num] == 1) return false; + col[j][num] = 1; + if (box[boxIndex][num] == 1) return false; + box[boxIndex][num] = 1; + } + } + } + return true; + } + + /** + * 37. 解数独 + * https://leetcode-cn.com/problems/sudoku-solver/ + * + * @param board + */ + public void solveSudoku(char[][] board) { + boolean[][] rowUsed = new boolean[9][10]; + boolean[][] colUsed = new boolean[9][10]; + boolean[][][] boxUsed = new boolean[3][3][10]; + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + if (board[i][j] != '.') { + int num = board[i][j] - '0'; + rowUsed[i][num] = true; + colUsed[j][num] = true; + boxUsed[i / 3][j / 3][num] = true; + } + } + } + dfs(board, rowUsed, colUsed, boxUsed, 0, 0); + } + + public boolean dfs(char[][] board, boolean[][] rowUsed, boolean[][] colUsed, boolean[][][] boxUsed, int row, int col) { + if (col == board[0].length) { + col = 0; + row++; + if (row == board.length) return true; + } + if (board[row][col] == '.') { + for (int i = 1; i <= 9; i++) { + boolean canUse = !rowUsed[row][i] && !colUsed[col][i] && !boxUsed[row / 3][col / 3][i]; + if (canUse) { + board[row][col] = (char) (i + '0'); + rowUsed[row][i] = true; + colUsed[col][i] = true; + boxUsed[row / 3][col / 3][i] = true; + if (dfs(board, rowUsed, colUsed, boxUsed, row, col + 1)) return true; + board[row][col] = '.'; + rowUsed[row][i] = false; + colUsed[col][i] = false; + boxUsed[row / 3][col / 3][i] = false; + } + } + } else { + return dfs(board, rowUsed, colUsed, boxUsed, row, col + 1); + } + return false; + } + + /** + * 22. 括号生成 + * https://leetcode-cn.com/problems/generate-parentheses/ + * + * @param n + * @return + */ + public List generateParenthesis(int n) { + List res = new ArrayList<>(); + dfs(0, 0, n, "", res); + return res; + } + + public void dfs(int left, int right, int max, String s, List res) { + if (left == max && right == max) { + res.add(s); + return; + } + if (left < max) { + dfs(left + 1, right, max, s + "(", res); + } + if (right < left) { + dfs(left, right + 1, max, s + ")", res); + } + } + + + /** + * 51. N 皇后 + * https://leetcode-cn.com/problems/n-queens/ + * + * @param n + * @return + */ + List> solveNQueensRes; + + public List> solveNQueens(int n) { + solveNQueensRes = new ArrayList<>(); + dfs(new int[n], 0, n, new HashSet(), new HashSet(), new HashSet()); + return solveNQueensRes; + } + + public void dfs(int[] queens, int row, int n, Set colSet, Set pieSet, Set naSet) { + if (row == n) { + solveNQueensRes.add(generateBoard(queens, n)); + return; + } + for (int i = 0; i < n; i++) { + if (colSet.contains(i)) continue; + int pie = row + i; + if (pieSet.contains(pie)) continue; + int na = row - i; + if (naSet.contains(na)) continue; + + queens[row] = i; + colSet.add(i); + pieSet.add(pie); + naSet.add(na); + dfs(queens, row + 1, n, colSet, pieSet, naSet); + queens[row] = -1; + colSet.remove(i); + pieSet.remove(pie); + naSet.remove(na); + } + } + + public List generateBoard(int[] queens, int n) { + List list = new ArrayList<>(); + for (int i = 0; i < n; i++) { + char[] chars = new char[n]; + Arrays.fill(chars, '.'); + chars[queens[i]] = 'Q'; + list.add(new String(chars)); + } + return list; + } + + /** + * 127. 单词接龙 + * https://leetcode-cn.com/problems/word-ladder/ + * + * @param beginWord + * @param endWord + * @param wordList + * @return + */ + public int ladderLength(String beginWord, String endWord, List wordList) { + Set wordSet = new HashSet<>(wordList); + if (!wordSet.contains(endWord)) return 0; + Set visited = new HashSet<>(); + Set beginSet = new HashSet<>(); + Set endSet = new HashSet<>(); + int count = 1; + beginSet.add(beginWord); + endSet.add(endWord); + while (!beginSet.isEmpty() && !endSet.isEmpty()) { + count++; + if (beginSet.size() > endSet.size()) { + Set tmp = beginSet; + beginSet = endSet; + endSet = tmp; + } + Set newBeginSet = new HashSet<>(); + for (String word : beginSet) { + if (changeLetter(wordSet, word, visited, newBeginSet, endSet)) return count; + } + beginSet = newBeginSet; + } + return 0; + } + + public boolean changeLetter(Set wordSet, String s, Set visited, Set newBeginSet, Set endSet) { + char[] chars = s.toCharArray(); + for (int i = 0; i < s.length(); i++) { + char c = chars[i]; + for (char j = 'a'; j <= 'z'; j++) { + if (c == j) continue; + chars[i] = j; + String newWord = String.valueOf(chars); + if (endSet.contains(newWord)) return true; + if (visited.contains(newWord) || !wordSet.contains(newWord)) continue; + newBeginSet.add(newWord); + visited.add(newWord); + } + chars[i] = c; + } + return false; + } + + + /** + * 433. 最小基因变化 + * https://leetcode-cn.com/problems/minimum-genetic-mutation/ + * + * @param start + * @param end + * @param bank + * @return + */ + char[] genes = {'A', 'C', 'G', 'T'}; + + public int minMutation(String start, String end, String[] bank) { + Set bankSet = new HashSet<>(Arrays.asList(bank)); + if (!bankSet.contains(end)) return -1; + Set visited = new HashSet<>(); + Set startSet = new HashSet<>(); + Set endSet = new HashSet<>(); + int count = 0; + startSet.add(start); + endSet.add(end); + while (!startSet.isEmpty() && !endSet.isEmpty()) { + count++; + if (startSet.size() > endSet.size()) { + Set tmp = startSet; + startSet = endSet; + endSet = tmp; + } + Set newStartSet = new HashSet<>(); + for (String s : startSet) { + if (changeGene(bankSet, s, visited, newStartSet, endSet)) return count; + } + startSet = newStartSet; + } + return -1; + } + + public boolean changeGene(Set bankSet, String s, Set visited, Set newStartSet, Set endSet) { + char[] chars = s.toCharArray(); + for (int i = 0; i < s.length(); i++) { + char c = chars[i]; + for (char j : genes) { + if (c == j) continue; + chars[i] = j; + String newGene = String.valueOf(chars); + if (endSet.contains(newGene)) return true; + if (visited.contains(newGene) || !bankSet.contains(newGene)) continue; + visited.add(newGene); + newStartSet.add(newGene); + } + chars[i] = c; + } + return false; + } + + + /** + * 773. 滑动谜题 + * https://leetcode-cn.com/problems/sliding-puzzle/ + * + * @param board + * @return + */ + int[][] neighbor = { + {1, 3}, + {0, 2, 4}, + {1, 5}, + {0, 4}, + {1, 3, 5}, + {2, 4} + }; + + public int slidingPuzzle(int[][] board) { + String target = "123450"; + String start = boardToString(board); + if (start.equals(target)) return 0; + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + int count = 0; + queue.offer(start); + visited.add(start); + while (!queue.isEmpty()) { + count++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + String s = queue.poll(); + int zeroIndex = s.indexOf('0'); + for (int j = 0; j < neighbor[zeroIndex].length; j++) { + String newStr = swap(s, zeroIndex, neighbor[zeroIndex][j]); + if (newStr.equals(target)) return count; + if (visited.contains(newStr)) continue; + queue.offer(newStr); + visited.add(newStr); + } + } + } + return -1; + } + + public String swap(String s, int i, int j) { + char[] chars = s.toCharArray(); + char tmp = chars[i]; + chars[i] = chars[j]; + chars[j] = tmp; + return String.valueOf(chars); + } + + public String boardToString(int[][] board) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + sb.append(board[i][j]); + } + } + return sb.toString(); + } + + /** + * 1091. 二进制矩阵中的最短路径 + * https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/ + * + * @param grid + * @return + */ + public int shortestPathBinaryMatrix(int[][] grid) { + int m = grid.length, n = grid[0].length; + if (grid[0][0] == 1 || grid[m - 1][n - 1] == 1) return -1; + if (m == 1 && n == 1) return 1; + int[][] xy = {{0, 1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {-1, -1}, {-1, 0}, {-1, 1}}; + int count = 1; + Queue queue = new LinkedList<>(); + queue.offer(new int[]{0, 0}); + while (!queue.isEmpty()) { + count++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] arr = queue.poll(); + for (int j = 0; j < 8; j++) { + int x = arr[0] + xy[j][0], y = arr[1] + xy[j][1]; + if (!inGrid(grid, x, y) || grid[x][y] == 1) + continue; + if (x == m - 1 && y == n - 1) return count; + queue.offer(new int[]{x, y}); + grid[x][y] = 1; + } + } + } + return -1; + } + + public boolean inGrid(int[][] grid, int r, int c) { + return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; + } + + + public static void main(String[] args) { + Week7 week7 = new Week7(); +// week7.findWords(new char[][]{{'o', 'a', 'a', 'n'}, {'e', 't', 'a', 'e'}, {'i', 'h', 'k', 'r'}, {'i', 'f', 'l', 'v'}}, +// new String[]{"oath", "pea", "eat", "rain"}); +// week7.findCircleNum(new int[][]{{1, 1, 0}, {1, 1, 1}, {0, 1, 1}}); + week7.shortestPathBinaryMatrix(new int[][]{{0, 0, 0, 0, 1}, {1, 0, 0, 0, 0}, {0, 1, 0, 1, 0}, {0, 0, 0, 1, 1}, {0, 0, 0, 1, 0}}); + } +} diff --git a/Week_08/LRUCache.java b/Week_08/LRUCache.java new file mode 100644 index 00000000..f362e04a --- /dev/null +++ b/Week_08/LRUCache.java @@ -0,0 +1,80 @@ +package com.freezing.leetcode.jike; + +import java.util.HashMap; +import java.util.Map; + +public class LRUCache { + class DLinkedNode { + int key, value; + DLinkedNode prev, next; + + public DLinkedNode() { + } + + public DLinkedNode(int key, int value) { + this.key = key; + this.value = value; + } + } + + private Map cacheMap; + private int size, capacity; + private DLinkedNode head, tail; + + public LRUCache(int capacity) { + this.capacity = capacity; + this.size = 0; + cacheMap = new HashMap<>(); + head = new DLinkedNode(); + tail = new DLinkedNode(); + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + if (!cacheMap.containsKey(key)) return -1; + DLinkedNode node = cacheMap.get(key); + moveToHead(node); + return node.value; + } + + public void put(int key, int value) { + DLinkedNode node = cacheMap.get(key); + if (node == null) { + node = new DLinkedNode(key, value); + cacheMap.put(key, node); + addToHead(node); + if (++size > capacity) { + DLinkedNode removeNode = removeTail(); + cacheMap.remove(removeNode.key); + --size; + } + } else { + node.value = value; + moveToHead(node); + } + } + + public void addToHead(DLinkedNode node) { + node.prev = head; + node.next = head.next; + head.next.prev = node; + head.next = node; + } + + public void removeNode(DLinkedNode node) { + node.prev.next = node.next; + node.next.prev = node.prev; + } + + public void moveToHead(DLinkedNode node) { + removeNode(node); + addToHead(node); + } + + public DLinkedNode removeTail() { + DLinkedNode node = tail.prev; + removeNode(node); + return node; + } +} diff --git a/Week_08/README.md b/Week_08/README.md index 50de3041..efd9301f 100644 --- a/Week_08/README.md +++ b/Week_08/README.md @@ -1 +1,453 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 一、位运算 + +主要记住一些指定位置的位运算,类似公式这样子: + +\1. 将 x 最右边的 n 位清零:x & (~0 << n) + +\2. 获取 x 的第 n 位值(0 或者 1): (x >> n) & 1 + +\3. 获取 x 的第 n 位的幂值:x & (1 << n) + +\4. 仅将第 n 位置为 1:x | (1 << n) + +\5. 仅将第 n 位置为 0:x & (~ (1 << n)) + +\6. 将 x 最高位至第 n 位(含)清零:x & ((1 << n) - 1) + +\7. 将x最后一位1清零:x & (x - 1) + +\8. 获取x最后一位1的位置(将除最后一位1以外的所谓位置为0):x & -x + +\9. 判断奇偶:(x & 1)== 1(奇) (x & 1)== 0(偶) + +### 习题笔记 + +1、[190. 颠倒二进制位](https://leetcode-cn.com/problems/reverse-bits/)这道题的主要思想就是将原二进制位从右往左依次获取出来,然后填充到一个新的二进制的末尾去,代码如下: + +```java +public class Solution { + // you need treat n as an unsigned value + public int reverseBits(int n) { + int res = 0; + for(int i = 0; i < 32; i++){ + // 每次结果都往左移一位,腾出位置给n取出来的二进制位 + res <<= 1; + res |= (n >> i) & 1; + } + return res; + } +} +``` + +2、[338. 比特位计数](https://leetcode-cn.com/problems/counting-bits/)这道题采用的是dp+位运算,定义dp[i]表示i的二进制数中1的数量,而i&(i-1)表示将i的最后一位1置为0,即i&(i-1) 将比i少一个1,得出状态方程为dp[i] = dp[i&(i-1)] + 1,代码如下: + +```java +class Solution { + public int[] countBits(int num) { + int[] dp = new int[num+1]; + for(int i = 1; i <= num; i++){ + dp[i] = dp[i&(i-1)] + 1; + } + return dp; + } +} +``` + +3、[51. N 皇后](https://leetcode-cn.com/problems/n-queens/)这道题之前是用set来存储填过的位置,这里可以替换为用位运算来存储,代码如下: + +```java +class Solution { + List> res; + public List> solveNQueens(int n) { + res = new ArrayList<>(); + dfs(new int[n],0,n,0,0,0); + return res; + } + + public void dfs(int[] queens,int row, int n,int col,int pie,int na){ + if(row == n){ + res.add(generateBoard(queens)); + return; + } + // (1<>1); + } + } + + public List generateBoard(int[] queens){ + int n = queens.length; + List list = new ArrayList<>(); + for(int i = 0; i < n; i++){ + char[] chars = new char[n]; + Arrays.fill(chars,'.'); + chars[queens[i]] = 'Q'; + list.add(String.valueOf(chars)); + } + return list; + } +} +``` + +## 二、LRU缓存机制 + +LRU全称为Least Recently Used,即最近最少使用,意思是在这个缓存中,会缓存最近使用的数据,而里面最久未使用的会在缓存容量不足的时候被移除,实现方式是用一个map和一个双向链表实现,代码如下: + +```java +class LRUCache { + // 双向链表 + class DLinkNode { + int key,value; + DLinkNode prev,next; + public DLinkNode(){} + public DLinkNode(int key,int value){ + this.key = key; + this.value = value; + } + } + + // size表示当前的容量,capacity表示最大的容量 + int size,capacity; + // 用一个虚拟的头和尾结点,方便判断插入和删除的位置 + DLinkNode head,tail; + // 哈希表用来存储key对应的结点 + Map cacheMap; + + public LRUCache(int capacity) { + this.capacity = capacity; + size = 0; + cacheMap = new HashMap<>(); + head = new DLinkNode(); + tail = new DLinkNode(); + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + // 获取key在map里面对应的结点 + DLinkNode node = cacheMap.get(key); + // 结点不存在直接返回-1 + if(node == null) return -1; + // 结点存在则将结点移动到头部来 + moveToHead(node); + // 返回结点值 + return node.value; + } + + public void put(int key, int value) { + DLinkNode node = cacheMap.get(key); + if(node == null){ + // 结点不存在则创建一个结点,并将其添加到map中,且添加到头部去 + node = new DLinkNode(key,value); + cacheMap.put(key,node); + addToHead(node); + // 若添加新结点后超出容量,则将尾结点移除,并从map中移除 + if(++size > capacity){ + DLinkNode removeNode = removeTail(); + cacheMap.remove(removeNode.key); + size--; + } + } else { + // 结点存在直接更新结点值,并将结点移动到头部去 + node.value = value; + moveToHead(node); + } + } + + public void addToHead(DLinkNode node){ + node.prev = head; + node.next = head.next; + head.next.prev = node; + head.next = node; + } + + public void removeNode(DLinkNode node){ + node.prev.next = node.next; + node.next.prev = node.prev; + } + + public void moveToHead(DLinkNode node){ + removeNode(node); + addToHead(node); + } + + public DLinkNode removeTail(){ + DLinkNode node = tail.prev; + removeNode(node); + return node; + } +} + +/** + * Your LRUCache object will be instantiated and called as such: + * LRUCache obj = new LRUCache(capacity); + * int param_1 = obj.get(key); + * obj.put(key,value); + */ +``` + +## 三、排序 + +1、比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。 + +**初级排序(O(n^2)):** + +(1)选择排序:在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 + +```java + public void selectSort(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + int min = i; + for (int j = i + 1; j < a.length; j++) { + min = a[min] < a[j] ? min : j; + } + swap(a, i, min); + } + } + public void swap(int[] a, int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } +``` + +(2)插入排序:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 + +```java + public void insertSort(int[] a) { + for (int i = 1; i < a.length; i++) { + int num = a[i]; + int j = i - 1; + while (j >= 0 && num < a[j]) { + a[j + 1] = a[j]; + j--; + } + a[j + 1] = num; + } + } +``` + +(3)冒泡排序:重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来,这样每次遍历都会将最大/最小的元素浮到顶端 + +```java + public void bubbleSort(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + for (int j = 0; j < a.length - i - 1; j++) { + if (a[j] > a[j + 1]) swap(a, j, j + 1); + } + } + } + public void swap(int[] a, int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } +``` + +**高级排序(N*LogN):** + +(1)快速排序:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 + +```java + public void quickSort(int[] array, int begin, int end) { + if (end <= begin) return; + int pivot = partition(array, begin, end); + quickSort(array, begin, pivot - 1); + quickSort(array, pivot + 1, end); + } + + public int partition(int[] a, int begin, int end) { + int pivot = end, counter = begin; + for (int i = begin; i < end; i++) { + if (a[i] < a[pivot]) { + swap(a, i, counter); + counter++; + } + } + swap(a, pivot, counter); + return counter; + } + + public void swap(int[] a, int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } +``` + +(2)归并排序:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并 + +```java + public void mergeSort(int[] a) { + mergeSort(a, 0, a.length - 1); + } + + public void mergeSort(int[] a, int left, int right) { + if (right <= left) return; + int mid = (left + right) >>> 1; + mergeSort(a, left, mid); + mergeSort(a, mid + 1, right); + int i = left, j = mid + 1, k = 0; + int[] tmp = new int[right - left + 1]; + while (i <= mid && j <= right) tmp[k++] = a[i] < a[j] ? a[i++] : a[j++]; + while (i <= mid) tmp[k++] = a[i++]; + while (j <= right) tmp[k++] = a[j++]; + System.arraycopy(tmp, 0, a, left, tmp.length); + } +``` + +(3)堆排序:将数组元素依次建立小顶堆,然后依次取堆顶元素,并删除 + +```java + public void heapSort(int[] a) { + PriorityQueue priorityQueue = new PriorityQueue<>(); + for (int i = 0; i < a.length; i++) { + priorityQueue.add(a[i]); + } + for (int i = 0; i < a.length; i++) { + a[i] = priorityQueue.poll(); + } + } +``` + + + +2、非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。 + +(1)计数排序:计数排序要求输入的数据必须是有确定范围的整数。将输入的数据值转化为键存储在额外开辟的数组空间中;然后依次把计数大于 1 的填充回原数组 + +(2)桶排序:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。 + +(3)基数排序:按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。 + +### 习题笔记 + +1、[1122. 数组的相对排序](https://leetcode-cn.com/problems/relative-sort-array/)这道题的主要思想就是用计数排序,先开辟一个新数组tmp,先将arr1中的元素转化为key,出现的次数转化为value填充进tmp中,再根据arr2中的顺序从tmp取出填回arr1,最后将arr2没有的数也依次填回arr1中: + +```java +class Solution { + public int[] relativeSortArray(int[] arr1, int[] arr2) { + // 题目表示arr1和arr2最大长度不会大于1000,所以开辟的新数组长度为1001即可 + int[] tmp = new int[1001]; + int pos = 0; + // 记录arr1中的数字出现的次数 + for(int num : arr1){ + tmp[num]++; + } + // 根据arr2中的数字顺序,从tmp中取出对应的所有数字,填回arr1 + for(int num : arr2){ + while(tmp[num]-- > 0){ + arr1[pos++] = num; + } + } + // 将剩余没有出现在arr2中的数字也依次取出填回arr1 + for(int i = 0; i < tmp.length; i++){ + while(tmp[i]-- > 0){ + arr1[pos++] = i; + } + } + return arr1; + } +} +``` + +2、[242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/)这道题也是一样用计数排序的思想,开辟一个新数组tmp用于记录s中的每个字符出现的次数,再遍历t,将t中出现的字符在tmp中减去对应的次数,最后判断tmp中所有字符所代表的位置的值是否都为0,但凡有一个不为0则返回false,否则返回true: + +```java +class Solution { + public boolean isAnagram(String s, String t) { + if(s == null | t == null || s.length() != t.length()) return false; + int[] tmp = new int[26]; + for(int i = 0; i < s.length(); i++){ + tmp[s.charAt(i)-'a']++; + tmp[t.charAt(i)-'a']--; + } + for(int i = 0; i < tmp.length; i++){ + if(tmp[i] != 0) return false; + } + return true; + } +} +``` + +3、[56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/)这道题的思想是先将集合中的区间,按照左边界进行排序,然后依次判断当前区间与结果集最后一个区间是否重叠,即当前区间的右边界是否大于结果集最后一个区间的左边界,是则代表重叠,则合并,修改结果集最后一个区间的右边界为两个区间右边界的最大值,否则不合并,直接添加到结果集中去: + +```java +class Solution { + public int[][] merge(int[][] intervals) { + if(intervals == null || intervals.length == 0) return new int[0][2]; + List res = new ArrayList<>(); + Arrays.sort(intervals,new Comparator(){ + public int compare(int[] o1, int[] o2){ + return o1[0] - o2[0]; + } + }); + for(int i = 0; i < intervals.length; i++){ + int left = intervals[i][0], right = intervals[i][1]; + if(res.isEmpty() || res.get(res.size()-1)[1] < left){ + res.add(new int[]{left,right}); + } else { + int[] lastArr = res.get(res.size()-1); + lastArr[1] = Math.max(right,lastArr[1]); + } + } + return res.toArray(new int[res.size()][0]); + } +} +``` + +4、[493. 翻转对](https://leetcode-cn.com/problems/reverse-pairs/)这道题的思想是:利用归并排序的思想,假设对于数组 nums[l..r] 而言,我们已经分别求出了子数组 nums[l..m]与nums[m+1..r] 的翻转对数目,并已将两个子数组分别排好序,则 nums[l..r] 中的翻转对数目,就等于两个子数组的翻转对数目之和,加上左右端点分别位于两个子数组的翻转对数目。 + +```java +class Solution { + public int reversePairs(int[] nums) { + if(nums == null || nums.length < 2) return 0; + return mergeSort(nums,0,nums.length -1); + } + + public int mergeSort(int[] nums, int left,int right){ + if(left >= right) return 0; + int mid = (left + right) >> 1; + // 计算子区间的翻转对数量 + int count = mergeSort(nums,left,mid) + mergeSort(nums,mid+1,right); + // 合并子区间排序结果,并统计当前区间的翻转对数量 + // 此时nums[left,mid]和nums[mid+1,right]已经都是有序的了 + // i,j分别代表正常归并排序的两个子区间左边界,k是合并结果的index + // p是用于记录翻转对的位置 + int i = left, p = left, k = 0; + int[] tmp = new int[right - left + 1]; + for(int j = mid+1;j <= right; j++){ + // 记录对于nums[j],翻转对开始的位置 + while(p <= mid && nums[p] <= 2*(long)nums[j]) p++; + // 将nums[i]小于等于nums[j]的先填充到tmp中去 + while(i <= mid && nums[i] <= nums[j]) tmp[k++] = nums[i++]; + // 轮到nums[j]最小了,填充nums[j]到tmp + tmp[k++] = nums[j]; + // 统计对于nums[j],翻转对的数量,并将结果累加到count中 + count += mid - p + 1; + } + // 继续合并未合并完的数组 + while(i <= mid) tmp[k++] = nums[i++]; + // 将合并之后的有序数组tmp复制回nums中 + System.arraycopy(tmp,0,nums,left,tmp.length); + return count; + } +} +``` + diff --git a/Week_08/Sort.java b/Week_08/Sort.java new file mode 100644 index 00000000..4c760a92 --- /dev/null +++ b/Week_08/Sort.java @@ -0,0 +1,154 @@ +package com.freezing.leetcode.jike; + +import java.util.PriorityQueue; + +public class Sort { + /** + * 快速排序 + * + * @param array + * @param begin + * @param end + */ + public void quickSort(int[] array, int begin, int end) { + if (end <= begin) return; + int pivot = partition(array, begin, end); + quickSort(array, begin, pivot - 1); + quickSort(array, pivot + 1, end); + } + + public int partition(int[] a, int begin, int end) { + int pivot = end, counter = begin; + for (int i = begin; i < end; i++) { + if (a[i] < a[pivot]) { + swap(a, i, counter); + counter++; + } + } + swap(a, pivot, counter); + return counter; + } + + public void swap(int[] a, int i, int j) { + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + + /** + * 冒泡排序 + * + * @param a + */ + public void bubbleSort(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + for (int j = 0; j < a.length - i - 1; j++) { + if (a[j] > a[j + 1]) swap(a, j, j + 1); + } + } + } + + /** + * 选择排序 + * + * @param a + */ + public void selectSort(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + int min = i; + for (int j = i + 1; j < a.length; j++) { + min = a[min] < a[j] ? min : j; + } + swap(a, i, min); + } + } + + /** + * 插入排序 + * + * @param a + */ + public void insertSort(int[] a) { + for (int i = 1; i < a.length; i++) { + int num = a[i]; + int j = i - 1; + while (j >= 0 && num < a[j]) { + a[j + 1] = a[j]; + j--; + } + a[j + 1] = num; + } + } + + /** + * 希尔排序 + * + * @param a + */ + public void shellSort(int[] a) { + int len = a.length; + for (int gap = len / 2; gap > 0; gap /= 2) { + for (int i = gap; i < len; i++) { + int j = i, num = a[j]; + while (j - gap >= 0 && num < a[j - gap]) { + a[j] = a[j - gap]; + j -= gap; + } + a[j] = num; + } + } + } + + /** + * 归并排序 + * + * @param a + */ + public void mergeSort(int[] a) { + mergeSort(a, 0, a.length - 1); + } + + public void mergeSort(int[] a, int left, int right) { + if (right <= left) return; + int mid = (left + right) >>> 1; + mergeSort(a, left, mid); + mergeSort(a, mid + 1, right); + int i = left, j = mid + 1, k = 0; + int[] tmp = new int[right - left + 1]; + while (i <= mid && j <= right) tmp[k++] = a[i] < a[j] ? a[i++] : a[j++]; + while (i <= mid) tmp[k++] = a[i++]; + while (j <= right) tmp[k++] = a[j++]; + System.arraycopy(tmp, 0, a, left, tmp.length); + } + + /** + * 堆排序 + * + * @param a + */ + public void heapSort(int[] a) { + PriorityQueue priorityQueue = new PriorityQueue<>(); + for (int i = 0; i < a.length; i++) { + priorityQueue.add(a[i]); + } + for (int i = 0; i < a.length; i++) { + a[i] = priorityQueue.poll(); + } + } + + public static void main(String[] args) { + Sort sort = new Sort(); + int[] arr = new int[]{6, 3, 4, 2, 1, 5}; +// sort.quickSort(arr, 0, arr.length-1); +// sort.bubbleSort(arr); +// sort.selectSort(arr); +// sort.insertSort(arr); +// sort.shellSort(arr); +// sort.mergeSort(arr); + sort.heapSort(arr); + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i]); + } + + } +} diff --git a/Week_08/Week8.java b/Week_08/Week8.java new file mode 100644 index 00000000..971ae16c --- /dev/null +++ b/Week_08/Week8.java @@ -0,0 +1,252 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class Week8 { + /** + * 191. 位1的个数 + * https://leetcode-cn.com/problems/number-of-1-bits/ + * + * @param n + * @return + */ + public int hammingWeight(int n) { + int count = 0; + while (n != 0) { + count++; + n &= (n - 1); + } + return count; + } + + /** + * 231. 2的幂 + * https://leetcode-cn.com/problems/power-of-two/ + * + * @param n + * @return + */ + public boolean isPowerOfTwo(int n) { + return n > 0 && (n & (n - 1)) == 0; + } + + /** + * 190. 颠倒二进制位 + * https://leetcode-cn.com/problems/reverse-bits/ + * + * @param n + * @return + */ + public int reverseBits(int n) { + int res = 0; + for (int i = 0; i < 32; i++) { + res <<= 1; + res |= (n >> i) & 1; + } + return res; + } + + /** + * 338. 比特位计数 + * https://leetcode-cn.com/problems/counting-bits/description/ + * + * @param num + * @return + */ + public int[] countBits(int num) { + int[] res = new int[num + 1]; + for (int i = 0; i <= num; i++) { + int count = 0; + int current = i; + while (current > 0) { + count++; + current &= (current - 1); + } + res[i] = count; + } + return res; + } + + /** + * 51. N 皇后 + * https://leetcode-cn.com/problems/n-queens/description/ + * + * @param n + * @return + */ + public List> solveNQueens(int n) { + List> res = new ArrayList<>(); + dfs(new int[n], 0, n, 0, 0, 0, res); + return res; + } + + public void dfs(int[] queens, int row, int n, int column, int pie, int na, List> res) { + if (row == n) { + res.add(generateBoard(queens)); + return; + } + int availableLocations = ((1 << n) - 1) & (~(column | pie | na)); + while (availableLocations != 0) { + int position = availableLocations & -availableLocations; + availableLocations &= (availableLocations - 1); + int colNum = Integer.bitCount(position - 1); + queens[row] = colNum; + dfs(queens, row + 1, n, column | position, (pie | position) << 1, (na | position) >> 1, res); + } + } + + public List generateBoard(int[] queens) { + List list = new ArrayList<>(); + for (int i = 0; i < queens.length; i++) { + char[] chars = new char[queens.length]; + Arrays.fill(chars, '.'); + chars[queens[i]] = 'Q'; + list.add(String.valueOf(chars)); + } + return list; + } + + /** + * 52. N皇后 II + * https://leetcode-cn.com/problems/n-queens-ii/description/ + */ + int count; + + public int totalNQueens(int n) { + count = 0; + dfs(new int[n], 0, n, 0, 0, 0); + return count; + } + + public void dfs(int[] queens, int row, int n, int column, int pie, int na) { + if (row == n) { + count++; + return; + } + int availablePositions = ((1 << n) - 1) & ~(column | pie | na); + while (availablePositions != 0) { + int pos = availablePositions & -availablePositions; + availablePositions &= (availablePositions - 1); + int colNum = Integer.bitCount(pos - 1); + queens[row] = colNum; + dfs(queens, row + 1, n, column | pos, (pie | pos) << 1, (na | pos) >> 1); + } + } + + /** + * 1122. 数组的相对排序 + * https://leetcode-cn.com/problems/relative-sort-array/ + * + * @param arr1 + * @param arr2 + * @return + */ + public int[] relativeSortArray(int[] arr1, int[] arr2) { + int[] res = new int[1001]; + int pos = 0; + for (int num : arr1) { + res[num]++; + } + for (int num : arr2) { + while (res[num] > 0) { + arr1[pos++] = num; + res[num]--; + } + } + for (int i = 0; i < res.length; i++) { + while (res[i] > 0) { + arr1[pos++] = i; + res[i]--; + } + } + return arr1; + } + + /** + * 242. 有效的字母异位词 + * https://leetcode-cn.com/problems/valid-anagram/ + * + * @param s + * @param t + * @return + */ + public boolean isAnagram(String s, String t) { + if (s == null || t == null || s.length() != t.length()) return false; + int[] arr = new int[26]; + for (char c : s.toCharArray()) { + arr[c - 'a']++; + } + for (char c : t.toCharArray()) { + arr[c - 'a']--; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] > 0) return false; + } + return true; + } + + /** + * 56. 合并区间 + * https://leetcode-cn.com/problems/merge-intervals/ + * + * @param intervals + * @return + */ + public int[][] merge(int[][] intervals) { + if (intervals.length == 0) return new int[0][2]; + List res = new ArrayList<>(); + Arrays.sort(intervals, new Comparator() { + @Override + public int compare(int[] o1, int[] o2) { + return o1[0] - o2[0]; + } + }); + for (int i = 0; i < intervals.length; i++) { + int l = intervals[i][0], r = intervals[i][1]; + if (res.isEmpty() || res.get(res.size() - 1)[1] < l) { + res.add(new int[]{l, r}); + } else { + res.get(res.size() - 1)[1] = Math.max(res.get(res.size() - 1)[1], r); + } + } + return res.toArray(new int[res.size()][]); + } + + /** + * 493. 翻转对 + * https://leetcode-cn.com/problems/reverse-pairs/ + * + * @param nums + * @return + */ + public int reversePairs(int[] nums) { + if (nums == null || nums.length == 0) return 0; + return mergeSort(nums, 0, nums.length - 1); + } + + public int mergeSort(int[] nums, int left, int right) { + if (right <= left) return 0; + int mid = (left + right) >>> 1; + int count = mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right); + int[] tmp = new int[right - left + 1]; + int i = left, k = 0, l = left; + for (int j = mid + 1; j <= right; j++) { + while (l <= mid && nums[l] <= 2 * (long) nums[j]) l++; + while (i <= mid && nums[i] < nums[j]) tmp[k++] = nums[i++]; + tmp[k++] = nums[j]; + count += mid - l + 1; + } + while (i <= mid) tmp[k++] = nums[i++]; + System.arraycopy(tmp, 0, nums, left, tmp.length); + return count; + } + + public static void main(String[] args) { + Week8 week8 = new Week8(); +// week8.reverseBits(43261596); + System.out.println(65 & -65); + } +} diff --git a/Week_09/README.md b/Week_09/README.md index 50de3041..f6c57333 100644 --- a/Week_09/README.md +++ b/Week_09/README.md @@ -1 +1,191 @@ -学习笔记 \ No newline at end of file +学习笔记 + +## 高级动态规划 + +高级动态规划的复杂度来源,主要是 + +1. 状态拥有更多维度(二维、三维、或者更多、甚至需要压缩) +2. 状态方程更加复杂 + +在做题的时候可以将多个与结果相关的属性作为一维来处理它的状态,然后再看看是否可以进行状态压缩,减少维度 + +### 习题笔记 + +1、[10. 正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching/) + +最近重复子问题:判断s中的第i个字符s[i]与p中的第j个字符p[j]是否相同,如果相同或者p[j]=='.',则当前两个字符能匹配上,否则若p[j]=='*',则要判断有两种匹配方式,一种是 *匹配0个,即把p[j-1]也给去掉不匹配了,一种是匹配上s[i] + +状态数组dp:dp[i]\[j]表示s的前i个字符是否与p的前j个字符匹配上 + +状态转移方程:dp[i]\[j] = dp[i-1]\[j-1] (s[i-1] == p[j-1] || p[j-1] == '.') + +​ = dp[i]\[j-2] || (s[i-1] == p[j-2] || p[j-2] == '.')&& dp[i-1]\[j] (j >= 2 && p[j-1] == '*') + +代码如下: + +```java +class Solution { + public boolean isMatch(String s, String p) { + if(p.length() == 0) return s.length() == 0; + int m = s.length(), n = p.length(); + char[] chars = s.toCharArray(), charp = p.toCharArray(); + boolean[][] dp = new boolean[m+1][n+1]; + // 初始化数组 + dp[0][0] = true; + for(int i = 2; i <= n; i++){ + // 字符*的前面得有字符才能消除前一个字符 + dp[0][i] = charp[i-1] == '*' && dp[0][i-2]; + } + for(int i = 1; i <= m; i++){ + for(int j = 1; j <= n; j++){ + if(chars[i-1] == charp[j-1] || charp[j-1] == '.'){ + // 当前字符能匹配上 + dp[i][j] = dp[i-1][j-1]; + } else if(j >= 2 && charp[j-1] == '*'){ + // 当前字符是*,要么直接把*和前一个字符去掉,要么前一个字符能和s当前字符匹配上,则p保持不动,继续匹配上一个字符 + dp[i][j] = dp[i][j-2] || ((chars[i-1] == charp[j-2] || charp[j-2] == '.') && dp[i-1][j]); + } + } + } + return dp[m][n]; + } +} +``` + +2、[44. 通配符匹配](https://leetcode-cn.com/problems/wildcard-matching/)这道题与上一道题的思想是差不多的,主要的区别在于*变成可以匹配任意字符串,即不能讲前一个字符消除,代码如下: + +```java +public boolean isMatch(String s, String p) { + if(p.length() == 0) return s.length() == 0; + int m = s.length(), n = p.length(); + char[] chars = s.toCharArray(), charp = p.toCharArray(); + boolean[][] dp = new boolean[m+1][n+1]; + dp[0][0] = true; + // 只要当前字符是*,则表示是空字符串,可以匹配,否则就不能匹配空的s + for(int i = 1; i <= n && charp[i-1] == '*'; i++){ + dp[0][i] = true; + } + for(int i = 1; i <= m; i++){ + for(int j = 1; j <= n; j++){ + if(chars[i-1] == charp[j-1] || charp[j-1] == '?'){ + dp[i][j] = dp[i-1][j-1]; + } else if(charp[j-1] == '*'){ + // 若p当前的字符是*,那么可以将*匹配为空字符串,也可以将*匹配s的当前字符串 + dp[i][j] = dp[i][j-1] | dp[i-1][j]; + } + } + } + return dp[m][n]; + } +``` + +## 字符串算法 + +常见的字符串算法,就是翻转字符串,字母异位,回文子串,公共子序列等等; + +翻转字符串:将字符的位置替换,采用双指针的方式,用i和j分别指向字符串的首尾位置,然后i不断往前遍历,j不断往后遍历,在遍历的同时进行交换,直到i==j结束 + +字母异位:若两个字符串里面每个字母的个数都一样,则这两个字符串互为异位词,可以采用大小为256的数组来存储这些记录,从而进行判断 + +回文子串:若字符串的首尾字母是一样的,且往中间推也满足这样的性质,则这个字符串为回文子串,这类算法基本上也是采用双指针的方式来进行遍历判断; + +子序列:子序列的意思就是从字符串中依次取出字符来组成一个新的字符串,且这个字符的相对顺序不变 + +需要注意的是,在java中s.charAt(i) 的效率比char[i]的效率要低,所以在进行遍历的时候,最好还是采用char[i]的方式提高效率 + +### 习题笔记 + +1、[151. 翻转字符串里的单词](https://leetcode-cn.com/problems/reverse-words-in-a-string/)这道题的主要思想是从后面开始遍历,遍历到空格符或者第一个字符,则表示后面的是一个完整的单词,进行记录 + +```java +class Solution { + public String reverseWords(String s) { + StringBuilder sb = new StringBuilder(); + char[] a = s.toCharArray(); + int start = a.length - 1, end = a.length - 1; + for(int i = a.length - 1; i >= -1; i--){ + if(i == -1 || a[i] == ' '){ + if(start != end){ + sb.append(s.substring(start+1,end+1)).append(" "); + } + start = i-1; + end = i-1; + } else { + start--; + } + } + return sb.toString().trim(); + } +} +``` + +2、[8. 字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/)这道题的思想就是从左边开始遍历,先过滤空格,然后判断符号,接着再来计算转换数字的值,需要注意的是,如果转换为数字后溢出了,则需要处理为Integer.MIN_VALUE或Integer.MAX_VALUE + +```java +class Solution { + public int myAtoi(String s) { + boolean isNeg = false; + int i = 0, res = 0; + char[] a = s.toCharArray(); + // 过滤空格符 + while(i < a.length && a[i] == ' ') i++; + // 整个字符串都是空格符,直接返回0 + if(i == a.length) return res; + // 判断符号 + if(a[i] == '-'){ + i++; + isNeg = true; + } else if(a[i] == '+'){ + i++; + } + // 计算数字 + while(i < a.length && Character.isDigit(a[i])){ + int cur = a[i++] - '0'; + // 若这个数字加上之后溢出了,则直接返回最大值或最小值 + if(res > (Integer.MAX_VALUE - cur) / 10){ + return isNeg ? Integer.MIN_VALUE : Integer.MAX_VALUE; + } + // 否则将这个数字加到最后一位 + res = res * 10 + cur; + } + return isNeg ? -res : res; + } +} +``` + +3、[5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/) 这道题是dp和字符串相结合的典型题目,根据回文子串的特性,可以得出状态数组dp[i]\[j]表示在字符串s中,i到j组成的子串是否为回文子串,状态方程数组为: + +dp[i]\[j] = dp[i+1]\[j-1] (s[i] == s[j]) + +```java +class Solution { + public String longestPalindrome(String s) { + if(s.length() < 2) return s; + int n = s.length(),start = 0,maxLen = 1; + char[] a = s.toCharArray(); + boolean[][] dp = new boolean[n][n]; + // 每个字符自己本身是回文子串 + for(int i = 0; i < n; i++) dp[i][i] = true; + for(int i = n - 2; i >= 0; i--){ + for(int j = i+1; j < n; j++){ + if(a[i] == a[j]){ + // j-i<=2 表示字符串长度小于等于3,减去首尾两个字符,最多就剩1个字符,肯定是回文子串 + if(j - i <= 2){ + dp[i][j] = true; + } else { + // 若i,j长度大于3,则需要判断减去首尾两个字符的时候,该子串是否为回文子串 + dp[i][j] = dp[i+1][j-1]; + } + // 记录最长回文子串的长度和起始位置 + if(dp[i][j] && j - i + 1 > maxLen){ + start = i; + maxLen = j - i + 1; + } + } + } + } + return s.substring(start,start+maxLen); + } +} +``` + diff --git a/Week_09/Week9.java b/Week_09/Week9.java new file mode 100644 index 00000000..8f1fdd06 --- /dev/null +++ b/Week_09/Week9.java @@ -0,0 +1,374 @@ +package com.freezing.leetcode.jike; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Week9 { + /** + * 387. 字符串中的第一个唯一字符 + * https://leetcode-cn.com/problems/first-unique-character-in-a-string/ + * + * @param s + * @return + */ + public int firstUniqChar(String s) { + if (s == null || s.length() == 0) return -1; + int[] tmp = new int[256]; + for (char c : s.toCharArray()) { + tmp[c]++; + } + for (int i = 0; i < s.length(); i++) { + if (tmp[s.charAt(i)] == 1) return i; + } + return -1; + } + + /** + * 541. 反转字符串 II + * https://leetcode-cn.com/problems/reverse-string-ii/ + * + * @param s + * @param k + * @return + */ + public String reverseStr(String s, int k) { + char[] a = s.toCharArray(); + for (int start = 0; start < s.length(); start += 2 * k) { + int i = start, j = Math.min(start + k - 1, a.length - 1); + while (i < j) { + char tmp = a[i]; + a[i++] = a[j]; + a[j--] = tmp; + } + } + return String.valueOf(a); + } + + /** + * 151. 翻转字符串里的单词 + * https://leetcode-cn.com/problems/reverse-words-in-a-string/ + * + * @param s + * @return + */ + public String reverseWords(String s) { + String[] words = s.trim().split(" +"); + StringBuilder sb = new StringBuilder(); + for (int i = words.length - 1; i >= 0; i--) { + sb.append(words[i]).append(" "); + } + return sb.toString().trim(); + } + + /** + * 557. 反转字符串中的单词 III + * https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ + * + * @param s + * @return + */ + public String reverseWords3(String s) { + int start = 0, end = 0; + char[] a = s.toCharArray(); + for (int i = 0; i < a.length; i++) { + if (a[i] == ' ') { + end--; + swap(a, start, end); + start = i + 1; + end = i + 1; + } else { + end++; + } + } + swap(a, start, end - 1); + return String.valueOf(a); + } + + private void swap(char[] a, int start, int end) { + while (start < end) { + char tmp = a[start]; + a[start++] = a[end]; + a[end--] = tmp; + } + } + + /** + * 917. 仅仅反转字母 + * https://leetcode-cn.com/problems/reverse-only-letters/ + * + * @param S + * @return + */ + public String reverseOnlyLetters(String S) { + int i = 0, j = S.length() - 1; + char[] a = S.toCharArray(); + for (; i < j; i++, j--) { + while (i < j && !Character.isLetter(a[i])) i++; + while (i < j && !Character.isLetter(a[j])) j--; + char tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + return String.valueOf(a); + } + + /** + * 205. 同构字符串 + * https://leetcode-cn.com/problems/isomorphic-strings/ + * + * @param s + * @param t + * @return + */ + public boolean isIsomorphic(String s, String t) { + int[] preIndexOfs = new int[256]; + int[] preIndexOft = new int[256]; + for (int i = 0; i < s.length(); i++) { + if (preIndexOfs[s.charAt(i)] != preIndexOft[t.charAt(i)]) return false; + preIndexOfs[s.charAt(i)] = i + 1; + preIndexOft[t.charAt(i)] = i + 1; + } + return true; + } + + /** + * 8. 字符串转换整数 (atoi) + * https://leetcode-cn.com/problems/string-to-integer-atoi/ + * + * @param s + * @return + */ + public int myAtoi(String s) { + int i = 0, n = s.length(), res = 0; + boolean isNeg = false; + char[] chars = s.toCharArray(); + while (i < n && chars[i] == ' ') i++; + if (i == n) return 0; + if (chars[i] == '-') { + i++; + isNeg = true; + } else if (chars[i] == '+') { + i++; + } else if (!Character.isDigit(chars[i])) { + return 0; + } + while (i < n && Character.isDigit(chars[i])) { + int cur = chars[i++] - '0'; + if (res > (Integer.MAX_VALUE - cur) / 10) { + return isNeg ? Integer.MIN_VALUE : Integer.MAX_VALUE; + } + res = res * 10 + cur; + } + return isNeg ? -res : res; + } + + /** + * 438. 找到字符串中所有字母异位词 + * https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/ + * + * @param s + * @param p + * @return + */ + public List findAnagrams(String s, String p) { + List res = new ArrayList<>(); + if (s == null || s.length() < p.length()) return res; + int sLen = s.length(), pLen = p.length(); + int left = 0, right = 0; + int[] count = new int[256], window = new int[256]; + for (char c : p.toCharArray()) { + count[c]++; + } + while (right < sLen) { + char curR = s.charAt(right++); + window[curR]++; + while (window[curR] > count[curR]) { + window[s.charAt(left++)]--; + } + if (right - left == pLen) { + res.add(left); + } + } + return res; + } + + /** + * 5. 最长回文子串 + * https://leetcode-cn.com/problems/longest-palindromic-substring/ + * + * @param s + * @return + */ + public String longestPalindrome(String s) { + int n = s.length(); + if (n < 2) return s; + int maxLen = 1, start = 0; + boolean[][] dp = new boolean[n][n]; + for (int i = 0; i < n; i++) dp[i][i] = true; + for (int i = n - 2; i >= 0; i--) { + for (int j = i + 1; j < n; j++) { + if (s.charAt(i) == s.charAt(j)) { + if (j - i <= 2) { + dp[i][j] = true; + } else { + dp[i][j] = dp[i + 1][j - 1]; + } + if (dp[i][j] && j - i + 1 > maxLen) { + maxLen = j - i + 1; + start = i; + } + } + } + } + return s.substring(start, start + maxLen); + } + + /** + * 44. 通配符匹配 + * https://leetcode-cn.com/problems/wildcard-matching/ + * + * @param s + * @param p + * @return + */ + public boolean isMatch(String s, String p) { + if (p.length() == 0) return s.length() == 0; + int m = s.length(), n = p.length(); + char[] chars = s.toCharArray(), charp = p.toCharArray(); + boolean[][] dp = new boolean[m + 1][n + 1]; + dp[0][0] = true; + for (int i = 1; i <= n && charp[i - 1] == '*'; i++) { + dp[0][i] = true; + } + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (chars[i - 1] == charp[j - 1] || charp[j - 1] == '?') { + dp[i][j] = dp[i - 1][j - 1]; + } else if (charp[j - 1] == '*') { + dp[i][j] = dp[i][j - 1] | dp[i - 1][j]; + } + } + } + return dp[m][n]; + } + + /** + * 115. 不同的子序列 + * https://leetcode-cn.com/problems/distinct-subsequences/ + * + * @param s + * @param t + * @return + */ + public int numDistinct(String s, String t) { + int m = t.length(), n = s.length(); + int[][] dp = new int[m + 1][n + 1]; + char[] charS = s.toCharArray(), charT = t.toCharArray(); + 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++) { + if (charS[j - 1] == charT[i - 1]) { + dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1]; + } else { + dp[i][j] = dp[i][j - 1]; + } + } + } + return dp[m][n]; + } + + /** + * 91. 解码方法 + * https://leetcode-cn.com/problems/decode-ways/ + * + * @param s + * @return + */ + public int numDecodings(String s) { + int n = s.length(); + int[] dp = new int[n + 1]; + char[] a = s.toCharArray(); + dp[0] = 1; + dp[1] = a[0] == '0' ? 0 : 1; + for (int i = 1; i < n; i++) { + int cur = a[i] - '0'; + int pre = a[i - 1] - '0'; + if (cur == 0) { + if (pre == 1 || pre == 2) { + dp[i + 1] = dp[i - 1]; + } else { + return 0; + } + } else if (pre == 1 || (pre == 2 && cur < 7)) { + dp[i + 1] = dp[i] + dp[i - 1]; + } else { + dp[i + 1] = dp[i]; + } + } + return dp[n]; + } + + /** + * 300. 最长递增子序列 + * https://leetcode-cn.com/problems/longest-increasing-subsequence/ + * + * @param nums + * @return + */ + public int lengthOfLIS(int[] nums) { + int n = nums.length; + int[] dp = new int[n]; + Arrays.fill(dp, 1); + int res = 1; + for (int i = 1; i < n; i++) { + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + res = Math.max(dp[i], res); + } + return res; + } + + /** + * 32. 最长有效括号 + * https://leetcode-cn.com/problems/longest-valid-parentheses/ + * + * @param s + * @return + */ + public int longestValidParentheses(String s) { + if (s.length() < 2) return 0; + char[] a = s.toCharArray(); + int[] dp = new int[a.length]; + int res = 0; + for (int i = 1; i < a.length; i++) { + if (a[i] == ')') { + if (a[i - 1] == '(') { + if (i > 2) { + dp[i] = dp[i - 2] + 2; + } else { + dp[i] = 2; + } + } else if (i - dp[i - 1] - 1 >= 0 && a[i - dp[i - 1] - 1] == '(') { + if (i - dp[i - 1] - 2 >= 0) { + dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2; + } else { + dp[i] = dp[i - 1] + 2; + } + } + res = Math.max(dp[i], res); + } + } + return res; + } + + public static void main(String[] args) { + Week9 week9 = new Week9(); +// week9.reverseWords(" hello world "); +// week9.isIsomorphic("egg", "add"); + week9.myAtoi("9223372036854775808"); + } +} diff --git a/Week_09/Week9Training.java b/Week_09/Week9Training.java new file mode 100644 index 00000000..510fadac --- /dev/null +++ b/Week_09/Week9Training.java @@ -0,0 +1,278 @@ +package com.freezing.leetcode.jike; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Week9Training { + + /** + * 709. 转换成小写字母 + * https://leetcode-cn.com/problems/to-lower-case/ + * + * @param str + * @return + */ + public String toLowerCase(String str) { + if (str == null) return null; + char[] chars = str.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] >= 'A' && chars[i] <= 'Z') { + chars[i] += 32; + } + } + return String.valueOf(chars); + } + + /** + * 58. 最后一个单词的长度 + * https://leetcode-cn.com/problems/length-of-last-word/ + * + * @param s + * @return + */ + public int lengthOfLastWord(String s) { + if (s == null) return 0; + int start = s.length() - 1, end = s.length() - 1; + for (int i = s.length() - 1; i >= 0; i--) { + if (s.charAt(i) == ' ') { + if (start != end) { + return end - start; + } + start = i - 1; + end = i - 1; + } else { + start--; + } + } + return end - start; + } + + /** + * 771. 宝石与石头 + * https://leetcode-cn.com/problems/jewels-and-stones/ + * + * @param jewels + * @param stones + * @return + */ + public int numJewelsInStones(String jewels, String stones) { + int count = 0; + char[] chars = new char[256]; + for (char s : stones.toCharArray()) { + chars[s]++; + } + for (char j : jewels.toCharArray()) { + count += chars[j]; + } + return count; + } + + /** + * 14. 最长公共前缀 + * https://leetcode-cn.com/problems/longest-common-prefix/ + * + * @param strs + * @return + */ + public String longestCommonPrefix(String[] strs) { + if (strs.length == 0) return ""; + Arrays.sort(strs); + int len = strs[0].length(); + for (int i = 0; i < len; i++) { + char c = strs[0].charAt(i); + for (int j = 1; j < strs.length; j++) { + if (i == strs[j].length() || c != strs[j].charAt(i)) { + return strs[0].substring(0, i); + } + } + } + return strs[0]; + } + + /** + * 344. 反转字符串 + * https://leetcode-cn.com/problems/reverse-string/ + * + * @param s + */ + public void reverseString(char[] s) { + if (s == null || s.length == 0) return; + int len = s.length; + for (int i = 0; i < len / 2; i++) { + char tmp = s[i]; + s[i] = s[len - i - 1]; + s[len - i - 1] = tmp; + } + } + + /** + * 49. 字母异位词分组 + * https://leetcode-cn.com/problems/group-anagrams/ + * + * @param strs + * @return + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public List> groupAnagrams(String[] strs) { + Map> map = new HashMap<>(); + for (String str : strs) { + char[] keyArr = str.toCharArray(); + ; + Arrays.sort(keyArr); + String key = String.valueOf(keyArr); + List list = map.getOrDefault(key, new ArrayList()); + list.add(str); + map.put(key, list); + } + return new ArrayList<>(map.values()); + } + + /** + * 125. 验证回文串 + * https://leetcode-cn.com/problems/valid-palindrome/ + * + * @param s + * @return + */ + public boolean isPalindrome(String s) { + if (s == null || s.length() == 0) return true; + int i = 0, j = s.length() - 1; + char left, right; + while (i <= j) { + while (i < j && !Character.isLetterOrDigit(s.charAt(i))) { + i++; + } + left = s.charAt(i); + while (i < j && !Character.isLetterOrDigit(s.charAt(j))) { + j--; + } + right = s.charAt(j); + if (Character.toLowerCase(left) != Character.toLowerCase(right)) return false; + i++; + j--; + } + return true; + } + + /** + * 680. 验证回文字符串 Ⅱ + * https://leetcode-cn.com/problems/valid-palindrome-ii/ + * + * @param s + * @return + */ + public boolean validPalindrome(String s) { + int i = 0, j = s.length() - 1; + for (; i < j && s.charAt(i) == s.charAt(j); i++, j--) ; + return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1); + } + + private boolean isPalindrome(String s, int i, int j) { + for (; i < j && s.charAt(i) == s.charAt(j); i++, j--) ; + return i >= j; + } + + /** + * 1143. 最长公共子序列 + * https://leetcode-cn.com/problems/longest-common-subsequence/ + * + * @param text1 + * @param text2 + * @return + */ + 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 = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (text1.charAt(i - 1) == text2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + return dp[m][n]; + } + + /** + * 10. 正则表达式匹配 + * https://leetcode-cn.com/problems/regular-expression-matching/ + * + * @param s + * @param p + * @return + */ + public boolean isMatch(String s, String p) { + if (p.length() == 0) return s.length() == 0; + int m = s.length(), n = p.length(); + char[] chars = s.toCharArray(), charp = p.toCharArray(); + boolean[][] dp = new boolean[m + 1][n + 1]; + dp[0][0] = true; + for (int i = 2; i <= n; i++) { + dp[0][i] = charp[i - 1] == '*' && dp[0][i - 2]; + } + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (chars[i - 1] == charp[j - 1] || charp[j - 1] == '.') { + dp[i][j] = dp[i - 1][j - 1]; + } else if (j >= 2 && charp[j - 1] == '*') { + dp[i][j] = dp[i][j - 2] || ((chars[i - 1] == charp[j - 2] || charp[j - 2] == '.') && dp[i - 1][j]); + } + } + } + return dp[m][n]; + } + + /** + * 746. 使用最小花费爬楼梯 + * https://leetcode-cn.com/problems/min-cost-climbing-stairs/ + * + * @param cost + * @return + */ + public int minCostClimbingStairs(int[] cost) { + int n = cost.length; + int[] dp = new int[n + 1]; + dp[0] = cost[0]; + dp[1] = cost[1]; + for (int i = 2; i <= cost.length; i++) { + int currentCost = i == cost.length ? 0 : cost[i]; + dp[i] = Math.min(dp[i - 2], dp[i - 1]) + currentCost; + } + return dp[n]; + } + + /** + * 72. 编辑距离 + * https://leetcode-cn.com/problems/edit-distance/ + * + * @param word1 + * @param word2 + * @return + */ + public int minDistance(String word1, String word2) { + int m = word1.length(), n = word2.length(); + char[] chars1 = word1.toCharArray(), chars2 = word2.toCharArray(); + int[][] dp = new int[m + 1][n + 1]; + for (int i = 0; i <= m; i++) { + for (int j = 0; j <= n; j++) { + if (i == 0 || j == 0) { + dp[i][j] = i + j; + } else if (chars1[i - 1] == chars2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; + } + } + } + return dp[m][n]; + } +} diff --git "a/Week_\346\257\225\344\270\232\346\200\273\347\273\223/README.md" "b/Week_\346\257\225\344\270\232\346\200\273\347\273\223/README.md" index 50de3041..624c5e02 100644 --- "a/Week_\346\257\225\344\270\232\346\200\273\347\273\223/README.md" +++ "b/Week_\346\257\225\344\270\232\346\200\273\347\273\223/README.md" @@ -1 +1,23 @@ -学习笔记 \ No newline at end of file +学习笔记 + +毕业啦,撒花✿✿ヽ(°▽°)ノ✿,这是我第一次完完全全投入的网课,也许是因为那颗想学好算法的心,也许是因为在面试里有个好的表现,也许是因为心疼那小2000大洋的学费(毕竟没花过这么大的网课费),不管怎样,也是一步一步地完成了所有的课程,给自己一个大大的小红花。 + +在参加训练营之前,自己也曾试过去刷LeetCode,但总是间歇性踌躇满志,持续性混吃等死,没有方向,随机刷题,一道题死磕到底,死磕了还不出来的就直接放弃了,并加重了负能量,就算是死磕出来了,然后就放一边了,过了几天甚至几个月再看原来的题,又都忘光光了,所以也就一直坚持不下去。 + +在参加了超哥的试听课之后,超哥直接就点出了所有人刷题的误区,毫无疑问,枪枪打中了我的痛处: + +- **不要死磕一道题,勇于面对自己的菜** + +放下自己所谓的自尊心,站在巨人的肩膀上一点儿也不丢人,自己可以尝试去解决问题,但是10-15分钟后还没有头绪,就应该直接去看题解,看看别人的思路,别人优秀的代码,并将其融会贯通,变为自己的东西,厚积薄发,总有一天,自己也能下笔如有神,在此之前,要勇于面对自己的菜; + +- **一题不能只做一遍,贯彻五毒神掌** + +刷题刷的不是数量,而是遍数,刷100道题,跟一道题刷100遍,效果是差很多的。刷100道题后,再回到之前的题,容易的可能还做的出来,难度稍微大一点的,又得回忆好久才能做出来,或者直接就忘了原来的思路了。而一道题刷了100遍,当你再看到它的时候,凭借着肌肉记忆,想都不用想就能直接打出来了。当然一道题刷100遍是有点夸张了。所谓五毒神掌,核心就是**第一遍背,第二遍默,隔天默,隔周默,面试前默**,只要过了遍数,再看到同样的题或者类似的题,就再也不会一脸懵逼了。 + +- **你不是一个人,不是孤军奋战** + +自己一个人的学习和刷题,总是会觉得枯燥乏味,但是一旦有人陪着一起前行,一起激励,一起进步,那感觉就不一样了。向上的路总是充满坎坷与荆棘,每周看大家作业提交以及刷题情况,发现越到后面,人就越少了,但是总还是有那么几个人一直在那里,你不努力,总有人在努力,这也是我前进的动力,有老师在指导,有班主任在鞭策,有同学在并肩,就是这样让我抛弃了颓废的理由,一直坚持到现在。 + + + +毕业不是终点,而是另一个起点,路漫漫其修远兮,吾将上下而求索,刷题还要继续,五毒也要贯彻,脑图要时不时翻一翻,要开始自己去打怪升级了,加油! \ No newline at end of file