diff --git a/Week01/NOTE.md b/Week01/NOTE.md index 50de30414..3ead73a99 100644 --- a/Week01/NOTE.md +++ b/Week01/NOTE.md @@ -1 +1,41 @@ -学习笔记 \ No newline at end of file +## 数据结构时间复杂度 +
+ +| 类型 | add | remove | query | modify | peculiarity | +| :----: | :----: | :----: | :----: | :----: | :----: | +| Array | O(n) | O(n) | O(1) | O(1) | 随机加速访问 | +| Linked List | O(1) | O(1) | O(n) | O(1) | next指针 | +| Double Linked List | O(1) | O(1) | O(n) | O(1) | pre,next指针 | +| Skip List | O(logn) | O(logn) | O(logn) | -- | 元素有序 | +| Stack | O(1) | O(1) | O(n) | -- | 后进先出 | +| Queue | O(1) | O(1) | O(n) | -- | 先进先出 | +| Deque | O(1) | O(1) | O(n) | -- | 两端都可进出的Quue | +| Priority Queue | O(1) | -- | O(logn) | -- | 两端都可进出的Quue | +
+ +## PriorityQueue分析 +``` +balanced binary heap: + 父结点的键值总是小于或等于任何一个子节点的键值 + 基于数组实现的二叉堆,对于数组中任意位置的n上元素,其左孩子在[2n+1]位置上,右孩子[2(n+1)]位置,它的父亲则在[n-1/2]上,而根的位置则是[0] +fields: + comparator--比较器,无此参数元素使用自然排序 + queue--数组(存数据) + size--元素个数 +method: + #grow -- 数组扩容 + #hugeCapacity -- 容量:0-Integer.MAX_VALUE + #add -- 调用offer(E e) + #offer -- size >= queue.length (扩容),size == 0直接添加为根,否则从队尾开始上移 --> siftUp + #siftUp + #siftUpComparable -- 一直上移至找到父节点 + #siftUpUsingComparator -- 操作类似,自然排序 + #peek -- 查看队列首元素,空时返回null + #indexOf -- 查看元素对应下标 + #remove -- 获取要移除元素下标i,--size,提取e = queue[size],queue[size] = null, i < size / 2将queue[i]不断下移,否则上移 + #poll -- 移除根, --size,提取e = queue[size],queue[size] = null,queue[i]不断下移,直到queue[i]的最小孩子比e 大为止 + #siftDown + #siftDownComparable -- 以k为父节点找子节点(left, right),c = queue[left], + 如果left < right && queue[left] > queue[right] { c = queue[right] },交换父节点和c;直至队尾元素比c的子节点都要小 + #siftDownUsingComparator -- 操作类似,自然排序 +``` \ No newline at end of file diff --git a/Week01/homework.md b/Week01/homework.md new file mode 100644 index 000000000..ee0b074c7 --- /dev/null +++ b/Week01/homework.md @@ -0,0 +1,129 @@ +## Week01 homework +
+ +### 删除排序数组中的重复项 +```java +public int removeDuplicates(int[] nums) { + if (nums == null && nums.length == 0) { + return 0; + } + int left = 0; + for (int i = 1; i < nums.length; i++) { + if (nums[i] != nums[left]) { + nums[++left] = nums[i]; + } + } + return ++left; +} +``` +
+ +### 旋转数组 +```java +public void rotate(int[] nums, int k) { + // 使用环状替代,k %= nums.length + k %= nums.length; + int count = 0; + for (int start = 0; count < nums.length; start++) { + int current = start; + int pre = nums[start]; + do { + int next = (current + k) % nums.length; + int temp = nums[next]; + nums[next] = pre; + pre = temp; + current = next; + count++; + } while (start != current); + } +} +``` +
+ +### 合并两个有序链表 +```java +public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + ListNode preHead = new ListNode(-1); + ListNode pre = preHead; + while (l1 != null && l2 != null) { + if (l1.val < l2.val) { + pre.next = l1; + l1 = l1.next; + } else { + pre.next = l2; + l2 = l2.next; + } + pre = pre.next; + } + pre.next = l1 == null ? l2 : l1; + return preHead.next; +} +``` +
+ +### 合并两个有序数组 +```java +public void merge(int[] nums1, int m, int[] nums2, int n) { + while (m > 0 && n > 0) { + nums1[m + n - 1] = nums1[m - 1] > nums2[n - 1] ? nums1[--m] : nums2[--n]; + } + for (int i = 0; i < n; i++) { + nums1[i] = nums2[i]; + } +} +``` +
+ +### 两数之和 +```java +public int[] twoSum(int[] nums, int target) { + // 循环遍历数组,num[i]存在于map中,直接返回map中的value和i + // 否则,将key:target - nums[i] value:i 存入map中 + int length = nums.length; + Map map = new HashMap<>(length); + for (int i = 0; i < length; i++) { + if (map.containsKey(nums[i])) { + return new int[]{map.get(nums[i]), i}; + } + map.put(target - nums[i], i); + } + return null; +} +``` +
+ +### 移动零 +```java +public void moveZeroes(int[] nums) { + if (nums == null) { + return; + } + int index = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + if (index != i) { + nums[index] = nums[i]; + nums[i] = 0; + } + index++; + } + } +} +``` + +### 加一 +```java +public int[] plusOne(int[] digits) { + // digits数组中所有位全进1,则新new一个digits.length + 1长度的数组,并把首位置为1 + for (int i = digits.length - 1; i >= 0; i--) { + digits[i] += 1; + if (digits[i] % 10 != 0) { + return digits; + } + digits[i] = 0; + } + digits = new int[digits.length + 1]; + digits[0] = 1; + return digits; +} +``` \ No newline at end of file diff --git a/Week02/NOTE.md b/Week02/NOTE.md index 50de30414..1dadffeba 100644 --- a/Week02/NOTE.md +++ b/Week02/NOTE.md @@ -1 +1,95 @@ -学习笔记 \ No newline at end of file +### 树 +``` +父节点中有所有子节点的指针 + 前序遍历 + 后序遍历 + 层序遍历 +``` + +### 二叉树 +``` +父节点中有左右子节点的指针 + 前序遍历 -- 根-左-右 + 中序遍历 -- 左-根-右 + 后续遍历 -- 左-右-根 + 层序遍历 +``` + +### 二叉搜索树 +``` +1.左节点小于根节点 +2.根节点小于右节点 +中序遍历为升序排列 +``` + +### 堆 +``` +常见的有二叉堆(完全二叉树),斐波那契堆等 + 一般用于找最值,PriorityQueue有二叉堆实现 + 1.根为最大值:大顶堆 + 父节点 >= 子节点 + 2.根为最小值:小顶堆 + 父节点 <= 子节点 +若父节点索引为i: + 左节点索引:2 * i + 1 or i << 1 + 1 + 右节点索引:2 * i + 1 or i << 1 + 2 +若子节点序索引i: + 父节点索引:(i - 1) / 2 or (i - 1) >> 1 +``` + +### HashMap +#### 常量 +``` +链表+数组(红黑树) +``` + +```java +// 默认的初始容量是16 +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; +// 最大容量 +static final int MAXIMUM_CAPACITY = 1 << 30; +// 默认加载充因子 +static final float DEFAULT_LOAD_FACTOR = 0.75f; +// 当桶(bucket)上的结点数大于这个值时会转成红黑树 +static final int TREEIFY_THRESHOLD = 8; +// 当桶(bucket)上的结点数小于这个值时树转链表 +static final int UNTREEIFY_THRESHOLD = 6; +// 桶中结构转化为红黑树对应的table的最小大小 +static final int MIN_TREEIFY_CAPACITY = 64; +// 存储元素的数组,总是2的幂次倍 +transient Node[] table; +// 存放具体元素的集 +transient Set> entrySet; +// 存放元素的个数,注意这个不等于数组的长度。 +transient int size; +// 每次扩容和更改map结构的计数器 +transient int modCount; +// 临界值 当实际大小(容量*加载因子)超过临界值时,会进行扩容 +int threshold; +// 加载因子 +final float loadFactor; +``` + +#### putVal +``` +1.计算hash值,确定在table中的位置 +2.将元素插入指定位置 + 1) 如果没有元素,直接插入 + a.插入后,数组元素超过扩容阈值(threshold),对table进行扩容 + b.插入后,小于threshold,结束 + 2) 如果有元素,挂在已有元素后 + a.如果之前已形成树,直接插入树对应的位置 + b.如果之前元素组成链表 + a) 加入元素后链表长度 > 8,将链表转为红黑树插入 + b) 加入元素后链表长度 <= 8, 直接插入 +``` + +#### get +``` +1.根据key,hash计算出存放在数组中的位置 +2.取出指定位置的元素 + 1) key完全相同,取出对应的value + 2) 若key不相同,判断挂载的是链表还是红黑树 + a. 若为树,按照树的方法查找 + b. 若为链表,按照链表的方法查找 +``` \ No newline at end of file diff --git a/Week02/homework.md b/Week02/homework.md new file mode 100644 index 000000000..221abb12d --- /dev/null +++ b/Week02/homework.md @@ -0,0 +1,322 @@ +### 有效的字母异位词 +``` +异位词: + 两个字符串s与t,s和t相同字母出现次数相同 +``` + +```java +/** + * 暴力 + */ +public boolean isAnagram(String s, String t) { + if (s == null || t == null || s.length() != t.length()) { + return false; + } + // 字符串排序,比较字符串是否相等 + char[] ss = s.toCharArray(); + char[] tt = t.toCharArray(); + Arrays.sort(ss); + Arrays.sort(tt); + return Arrays.equals(ss, tt); +} +``` +
+ +```java +public boolean isAnagram(String s, String t) { + if (s == null || t == null || s.length() != t.length()) { + return false; + } + /* + * 只有小写字母(a-z) 26位,数组result存字母出现次数的计数器 + * 1.当s中出现该字母,则对应下标值+1 + * 2.当t中出现该字母,则对应下标值-1 + */ + int[] result = new int[26]; + for (int i = 0; i < s.length(); i++) { + result[s.charAt(i) - 'a']++; + } + for (int i = 0; i < t.length(); i++) { + result[t.charAt(i) - 'a']--; + if (result[t.charAt(i) - 'a'] < 0) { + return false; + } + } + return true; +} +``` +
+ +### 字母异位词分组 + +```java +public List> groupAnagrams(String[] strs) { + if (strs == null || strs.length == 0) { + return new ArrayList<>(); + } + Map> map = new HashMap<>(); + for (int i = 0; i < strs.length; i++) { + // 字符串排序 + char[] arr = new char[26]; + for (char ch : strs[i].toCharArray()) { + arr[ch - 'a']++; + } + String key = String.valueOf(arr); + // map中不存在key,添加空集合 + if (!map.containsKey(key)) { + map.put(key, new ArrayList<>()); + } + map.get(key).add(strs[i]); + } + return new ArrayList<>(map.values()); +} +``` +
+ +### 二叉树的前序遍历 +```java +public List preorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + preOrder(root, result); + return result; +} + +public void preOrder(TreeNode node, List list) { + if (node == null) { + return; + } + // 前序遍历:根-左-右 + list.add(node.val); + preOrder(node.left, list); + preOrder(node.right, list); +} +``` +
+ +### 二叉树的中序遍历 +```java +public List inorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + inOrder(root, result); + return result; +} + +public void inOrder(TreeNode node, List list) { + if (node == null) { + return; + } + // 中序遍历:左-根-右 + inOrder(node.left, list); + list.add(node.val); + inOrder(node.right, list); +} +``` +
+ +### N叉树的前序遍历 +```java +public List preorder(Node root) { + List result = new ArrayList<>(); + preOrder(root, result); + return result; +} +public void preOrder(Node node, List list){ + if (node == null) { + return; + } + // N叉树的前序遍历:根-从左到右子节点遍历 + list.add(node.val); + if (node.children != null) { + for (Node childNode : node.children) { + preOrder(childNode, list); + } + } +} +``` +
+ +### N叉树的后序遍历 +```java +public List postorder(Node root) { + List result = new ArrayList<>(); + postOrder(root, result); + return result; +} + +public void postOrder(Node node, List list) { + if (node == null) { + return; + } + // N叉树后序遍历:从左到右子节点遍历-根 + if (node.children != null) { + for (Node childNode : node.children) { + postOrder(childNode, list); + } + } + list.add(node.val); +} +``` +
+ +### N叉树的层序遍历 +```java +public List> levelOrder(Node root) { + if (root == null) { + return new ArrayList<>(); + } + // 双端队列,存子节点 + Deque deque = new LinkedList<>(); + deque.add(root); + List> result = new ArrayList<>(); + while(!deque.isEmpty()){ + int size = deque.size(); + List list = new ArrayList<>(); + for (int i = 0; i < size; i++) { + Node node = deque.poll(); + list.add(node.val); + deque.addAll(node.children); + } + result.add(list); + } + return result; +} +``` +
+ +### 最小的k个数 + +```java +/** + * 暴力 + */ +public int[] getLeastNumbers(int[] arr, int k) { + if(arr == null || k < 1 || arr.length < k){ + return new int[]{}; + } + int[] result = new int[k]; + Arrays.sort(arr); + for(int i = 0; i < k; i++){ + result[i] = arr[i]; + } + return result; +} +``` + +```java +/** + * 最小堆 + */ +public int[] getLeastNumbers(int[] arr, int k) { + if(arr == null || k < 1 || arr.length < k){ + return new int[]{}; + } + PriorityQueue queue = new PriorityQueue<>(k, (i1, i2) -> Integer.compare(i2, i1)); + for (int i = 0; i < arr.length; i++) { + if (queue.size() > k - 1) { + if (queue.peek() > arr[i]) { + queue.poll(); + queue.offer(arr[i]); + } + } else { + queue.offer(arr[i]); + } + } + int[] result = new int[k]; + int index = 0; + for (Integer num : queue) { + result[index++] = num; + } + return result; +} +``` +
+ +### 滑动窗口最大值 +```java +public int[] maxSlidingWindow(int[] nums, int k) { + if (nums == null || k < 1 || nums.length < k) return new int[0]; + if (k == 1) return nums; + // max:移动框中的最大值 count:移动框中的最大值数量 + int max = 0, count = 0; + int[] result = new int[nums.length - k + 1]; + Deque queue = new LinkedList<>(); + for (int i = 0; i < k; i++) { + queue.offer(nums[i]); + max = Math.max(max, nums[i]); + count = max == nums[i] ? ++count : 1; + } + result[0] = max; + for (int i = k; i < nums.length; i++) { + int removeNum = queue.poll(); + queue.offer(nums[i]); + max = Math.max(max, nums[i]); + count = max == nums[i] ? ++count : 1; + // 添加新元素后,移除元素值为max且框中无重复max时,重新计算框中的max和count + if (max == removeNum && --count < 1) { + max = queue.peek(); + for (Integer num : queue) { + if (max <= num) { + count = max == num ? ++count : 1; + max = num; + } + } + } + result[i - k + 1] = max; + } + return result; +} +``` +
+ +### 丑数 II +```java +public int nthUglyNumber(int n) { + int[] ugly = new int[n]; + ugly[0] = 1; + int index2 = 0, index3 = 0, index5 = 0; + int factor2 = 2, factor3 = 3, factor5 = 5; + for (int i = 1; i < n; i++) { + int min = Math.min(Math.min(factor2, factor3), factor5); + ugly[i] = min; + if (min == factor2) { + factor2 = 2 * ugly[++index2]; + } + if (min == factor3) { + factor3 = 3 * ugly[++index3]; + } + if (min == factor5) { + factor5 = 5 * ugly[++index5]; + } + } + return ugly[n - 1]; +} +``` +
+ +### 前 K 个高频元素 +```java +public int[] topKFrequent(int[] nums, int k) { + // key:元素 value:元素出现次数 + Map map = new HashMap<>(); + for (Integer num : nums) { + map.put(num, map.containsKey(num) ? map.get(num) + 1 : 1); + } + // map中前k高频率元素 + PriorityQueue heap = new PriorityQueue<>((n1, n2) -> map.get(n1) - map.get(n2)); + for (Integer key : map.keySet()) { + if (heap.size() < k) { + heap.offer(key); + }else if (map.get(heap.peek()) < map.get(key)) { + heap.poll(); + heap.offer(key); + } + } + // 结果 + int[] result = new int[k]; + int index = k - 1; + while(!heap.isEmpty()){ + result[index--] = heap.poll(); + } + return result; +} +``` \ No newline at end of file diff --git a/Week03/NOTE.md b/Week03/NOTE.md index 50de30414..6b6f67005 100644 --- a/Week03/NOTE.md +++ b/Week03/NOTE.md @@ -1 +1,39 @@ -学习笔记 \ No newline at end of file +### 泛型递归模板 +```java +public void recurse(int level, int param) { + // terminator + if (level > MAX_VALUE) { + // process result + return; + } + // process logic + process(level, param); + // drill down + recurse(level + 1, new Param); + // restore current status if needed +} +``` +
+ +### 分治代码模板 +```java +public void divideConquer(int problem, int param) { + // terminator + if (level > MAX_VALUE) { + // process result + return; + } + // prepare data + int data = prepareData(problem); + Integer[] splitProblem(problem, data); + // conquer subProblem + int subResult1 = divideConquer(splitProblem[0], newparam); + int subResult2 = divideConquer(splitProblem[1], newparam); + ... + + // process and generate the final result + int result = processResult(subResult1, subResult2, ...); + + // revert the current level status if needed +} +``` \ No newline at end of file diff --git a/Week03/homework.md b/Week03/homework.md new file mode 100644 index 000000000..2f3070a69 --- /dev/null +++ b/Week03/homework.md @@ -0,0 +1,330 @@ +### 爬楼梯 +```java +/** + * 递归 + */ +public int climbStairs(int n) { + return fibonacci(n, 1, 1); +} + +public int fibonacci(int n, int a, int b) { + return n <= 1 ? b : fibonacci(n - 1, b, a + b); +} + +/** + * 循环累加 f(n) = f(n - 1) + f(n - 2); + */ +public int climbStairs(int n) { + int first = 1, second = 1; + for (int i = 1; i < n; i++) { + int temp = second; + second += first; + first = temp; + } + return second; +} +``` +
+ +### 括号生成 +```java +List result = new ArrayList<>(); +public List generateParenthesis(int n) { + recurse(n, n, ""); + return result; +} + +public void recurse(int left, int right, String str) { + if (left == 0 && right == 0) { + result.add(str); + return; + } + // add "(" + if (left > 0) recurse(left - 1, right, str + "("); + // add ")" + if (right > left) recurse(left, right - 1, str + ")"); +} +``` +
+ +### 翻转二叉树 +```java +public TreeNode invertTree(TreeNode root) { + recurse(root); + return root; +} +public void recurse(TreeNode node) { + if (node == null) return; + // 翻转左右子节点 + TreeNode left = node.left; + node.left = node.right; + node.right = left; + // 遍历左子树 + recurse(node.left); + // 遍历右子树 + recurse(node.right); +} +``` +
+ +### 验证二叉搜索树 +```java +public boolean isValidBST(TreeNode root) { + return recurse(root, null, null); +} + +public boolean recurse(TreeNode node, Integer lowwer, Integer upper) { + if (node == null) return true; + int val = node.val; + /* + * 根节点的左子树中: + * 右子节点大于父节点且小于最近节点是父节点左节点的值 + * 左子节点小于父节点 + * 根节点的右子树中: + * 左子节点小于父节点且大于最近节点是父节点右节点的值 + * 右子节点大于父节点 + */ + if (lowwer != null && val <= lowwer) return false; + if (upper != null && val >= upper) return false; + return recurse(node.left, lowwer, val) && recurse(node.right, val, upper); +} +``` +
+ +### 二叉树的最大深度 +```java +int max; + +public int maxDepth(TreeNode root) { + recurse(root, 0); + return max; +} + +public void recurse(TreeNode node, int curDepth) { + if (node == null) { + max = Math.max(max, curDepth); + return; + } + // left child + recurse(node.left, curDepth + 1); + // right child + recurse(node.right, curDepth + 1); +} +``` +
+ +### 二叉树的最小深度 +```java +public int minDepth(TreeNode root) { + if (root == null) return 0; + // left child + int s1 = minDepth(root.left); + // right child + int s2 = minDepth(root.right); + /* + * 根节点到最小子节点的距离 + * 无左节点时,从右节点中找,反之亦然 + */ + return root.left == null || root.right == null ? s1 + s2 + 1 : Math.min(s1, s2) + 1; +} +``` +
+ +### Pow(x, n) +```java +public double myPow(double x, int n) { + return n > 0 ? recurse(x, n) : 1 / recurse(x, -n); +} +public double recurse(double x, int n) { + if (n == 0) return 1; + double result = recurse(x, n / 2); + return n % 2 == 0 ? result * result : result * result * x; +} +``` +
+ +### 子集 +```java +/** + * 前序遍历 + */ +List> result = new ArrayList<>(); +public List> subsets(int[] nums) { + result.add(new ArrayList<>()); + recurse(nums, 0, new ArrayList<>()); + return result; +} + +public void recurse(int[] nums, int index, List subSet) { + if (index >= nums.length) { + return; + } + subSet = new ArrayList<>(subSet); + result.add(subSet); + recurse(nums, index + 1, subSet); + subSet.add(nums[index]); + recurse(nums, index + 1, subSet); +} + +/** + * 回溯 + */ +List> result = new ArrayList<>(); +public List> subsets(int[] nums) { + result.add(new ArrayList<>()); + recurse(nums, 0, new ArrayList<>()); + return result; +} + +public void recurse(int[] nums, int index, List subSet) { + if (index >= nums.length) { + return; + } + subSet.add(nums[index]); + result.add(new ArrayList<>(subSet)); + recurse(nums, index + 1, subSet); + subSet.remove(subSet.size() - 1); + recurse(nums, index + 1, subSet); +} +``` +
+ +### 多数元素 +```java +public int majorityElement(int[] nums) { + int flag = nums[0], count = 1; + for (int i = 1; i < nums.length; i++) { + if (count == 0) flag = nums[i]; + count += nums[i] == flag ? 1 : -1; + } + return flag; +} +``` +
+ +### 电话号码的字母组合 +```java +List result = new ArrayList<>(); +Map map = new HashMap<>(); +{ + map.put('2', new String[]{"a", "b", "c"}); + map.put('3', new String[]{"d", "e", "f"}); + map.put('4', new String[]{"g", "h", "i"}); + map.put('5', new String[]{"j", "k", "l"}); + map.put('6', new String[]{"m", "n", "o"}); + map.put('7', new String[]{"p", "q", "r", "s"}); + map.put('8', new String[]{"t", "u", "v"}); + map.put('9', new String[]{"w", "x", "y", "z"}); +} + +public List letterCombinations(String digits) { + if (digits == null || digits.length() == 0) return result; + recurse(digits, 0, ""); + return result; +} + +public void recurse(String digits, int index, String str) { + if (index >= digits.length()) { + result.add(str); + return; + } + for (String s : map.get(digits.charAt(index))) { + recurse(digits, index + 1, str + s); + } +} +``` +
+ +### 二叉树的最近公共祖先 +```java +public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || root == p || root == q) return root; + TreeNode leftNode = lowestCommonAncestor(root.left, p, q); + TreeNode rightNode = lowestCommonAncestor(root.right, p, q); + return leftNode != null && rightNode != null ? root : (leftNode == null ? rightNode : leftNode); +} +``` +
+ +### 从前序与中序遍历序列构造二叉树 +```java +Map indexMap = new HashMap<>(); +public TreeNode buildTree(int[] preorder, int[] inorder) { + for (int i = 0; i < inorder.length; i++) { + indexMap.put(inorder[i], i); + } + return recurse(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); +} + +public TreeNode recurse(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { + if (preStart > preEnd) return null; + // 获取根节点在inorder中的位置 + int rootVal = preorder[preStart]; + // 创建根节点 + TreeNode root = new TreeNode(rootVal); + int pIndex = indexMap.get(rootVal); + // 左子树 + root.left = recurse(preorder, preStart + 1, pIndex - inStart + preStart, inorder, inStart, pIndex - 1); + // 右子树 + root.right = recurse(preorder, preEnd + pIndex - inEnd + 1, preEnd, inorder, pIndex + 1, inEnd); + return root; +} +``` +
+ +### 组合 +```java +List> result = new ArrayList<>(); +public List> combine(int n, int k) { + if (n < k) throw new RuntimeException("Incorrect input data."); + recurse(n, 1, k, new ArrayList<>()); + return result; +} + +public void recurse(int n, int cur, int k, List list) { + if (list.size() == k) { + result.add(list); + return; + } + list.add(cur); + if (cur <= n) recurse(n, cur + 1, k, new ArrayList<>(list)); + if (n - cur + list.size() - 1 >= k) { + // 回溯 + list.remove(list.size() - 1); + recurse(n, cur + 1, k, new ArrayList<>(list)); + } +} +``` +
+ +### 全排列 +```java +List> result = new ArrayList<>(); +public List> permute(int[] nums) { + recurse(nums, 0); + return result; +} + +public void recurse(int[] nums, int index) { + if (index == nums.length - 1) { + List list = new ArrayList<>(); + for (Integer num : nums) { + list.add(num); + } + result.add(list); + return; + } + for (int i = index; i< nums.length; i++) { + swap(nums, i, index); + recurse(nums, index + 1); + swap(nums, i, index); + } +} + +private void swap(int[] nums, int i, int j) { + // 交换数值 + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; +} +``` \ No newline at end of file diff --git a/Week04/NOTE.md b/Week04/NOTE.md index 50de30414..6f18f9afe 100644 --- a/Week04/NOTE.md +++ b/Week04/NOTE.md @@ -1 +1,9 @@ -学习笔记 \ No newline at end of file +#### 二分查找一个半有序数组中间无序的地方 +```java +/** + * 思路: + * mid = (left + right) / 2; + * mid < nums[right], right = mid; + * mid > nums[left], left = mid + */ +``` \ No newline at end of file diff --git a/Week04/homework.md b/Week04/homework.md new file mode 100644 index 000000000..90dbe79ae --- /dev/null +++ b/Week04/homework.md @@ -0,0 +1,359 @@ +### 二叉树的层序遍历 +```java +/** + * bfs (广度优先) + */ +public List> levelOrder(TreeNode root) { + List> result = new ArrayList<>(); + if (root == null) return result; + // 存每层元素 + Deque deque = new LinkedList<>(); + deque.offer(root); + while (!deque.isEmpty()) { + int size = deque.size(); + List levelList = new ArrayList<>(); + result.add(levelList); + // 处理树的当前层元素 + for (int i = 0; i < size; i++) { + TreeNode node = deque.pop(); + levelList.add(node.val); + // 向队列中添加下一层的元素 + if (node.left != null) deque.offer(node.left); + if (node.right != null) deque.offer(node.right); + } + } + return result; +} + +/** + * dfs (深度优先) + */ +List> result = new ArrayList<>(); +public List> levelOrder(TreeNode root) { + dfs(root, 0); + return result; +} +private void dfs(TreeNode node, int level) { + if (node == null) return; + if (level == result.size()) { + // 当前深度的元素还未存过元素,添加新集合 + List list = new ArrayList<>(); + list.add(node.val); + result.add(list); + } else { + result.get(level).add(node.val); + } + // 左右子节点继续下探 + dfs(node.left, level + 1); + dfs(node.right, level + 1); +} +``` +
+ +### 在每个树行中找最大值 +```java +/** + * bfs (广度优先) + */ +public List largestValues(TreeNode root) { + List result = new ArrayList<>(); + if (root == null) return result; + Deque deque = new LinkedList<>(); + deque.offer(root); + while (!deque.isEmpty()) { + int size = deque.size(), levelMax = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + TreeNode node = deque.pop(); + if (levelMax < node.val) levelMax = node.val; + // 向队列中添加下一层元素 + if (node.left != null) deque.offer(node.left); + if (node.right != null) deque.offer(node.right); + } + result.add(levelMax); + } + return result; +} + +/** + * dfs (深度优先) + */ +List result = new ArrayList<>(); +public List largestValues(TreeNode root) { + dfs(root, 0); + return result; +} +private void dfs(TreeNode node, int level) { + if (node == null) return; + if (level == result.size()) { + result.add(node.val); + } else if (result.get(level) < node.val) { + result.set(level, node.val); + } + // 左右子节点下探 + dfs(node.left, level + 1); + dfs(node.right, level + 1); +} +``` +
+ +### 柠檬水找零 +```java +public boolean lemonadeChange(int[] bills) { + /* + * 排队找零 + * 1. 5美元, five++ + * 2. 10美元, ten++, five-- + * 3. 20美元, (ten-- and five--) or (five - 3) + */ + int five = 0, ten = 0; + for (int bill : bills) { + if (bill == 5) { + five++; + } else if (bill == 10) { + ten++; + five--; + } else if (ten > 0) { + ten--; + five--; + } else { + five -=3; + } + if (five < 0 || ten < 0) return false; + } + return true; +} +``` +
+ +### 买卖股票的最佳时机 II +```java +public int maxProfit(int[] prices) { + /* + * 当天股票价格如果小于后一天的股票价格 + * 则买入当天并在后一天卖出 + */ + if (prices == null || prices.length < 2) return 0; + int count = 0; + for (int i = 1; i < prices.length; i++) { + int temp = prices[i] - prices[i - 1]; + if (temp > 0) count += temp; + } + return count; +} +``` +
+ +### 分发饼干 +```java +public int findContentChildren(int[] g, int[] s) { + /* + * 对g,s进行排序 + * 饼干尺寸大的满足胃口大的孩子, 分发饼干数量就是满足数量 + */ + if (g == null || s == null) return 0; + Arrays.sort(g); + Arrays.sort(s); + int gIndex = g.length - 1, sIndex = s.length - 1; + while (gIndex >= 0 && sIndex >= 0) { + if (g[gIndex--] <= s[sIndex]) { + sIndex--; + } + } + return s.length - sIndex - 1; +} +``` +
+ +### 模拟行走机器人 +```java +public int robotSim(int[] commands, int[][] obstacles) { + /* + * 1. 机器人出发方向(转向指令确定机器人行走方向): + * y轴正方向(0, 1), x轴正方向(1,0),y轴负方向(0,-1),x轴负方向(-1,0) + * 2. 机器人从(0, 0)向y轴正方向(北方)出发 + * 3. 判断当前移动路线(一格一格移动判断)中是否存在障碍物 + * 存在: 停在障碍物前一格, 继续下一条命令 + * 不存在: 在x轴或y轴上行走 + * 4. 当前移动命令结束后计算距离, 与之前的最大距离值作比较 + */ + // 存障碍点 + Set set = new HashSet<>(); + for (int[] obs : obstacles) { + // 计算原理? 位运算学习后需要明白, 存String类型耗时是位运算的5倍左右 + long ox = (long) obs[0] + 30000; + long oy = (long) obs[1] + 30000; + set.add((ox << 16) + oy); + } + // 方向对应的x、y轴的前进值 + int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1 ,0}}; + int d = 0, x = 0, y = 0, result = 0; + for (int cmd : commands) { + if (cmd == -1) { + d = (d + 1) % 4; + } else if (cmd == -2) { + d = (d + 3) % 4; + } else { + while (cmd-- > 0 && !set.contains((((long) x + dirs[d][0] + 30000) << 16) + ((long) y + dirs[d][1] + 30000))) { + x += dirs[d][0]; + y += dirs[d][1]; + } + result = Math.max(result, x * x + y * y); + } + } + return result; +} +``` +
+ +### 跳跃游戏 +```java +public boolean canJump(int[] nums) { + /* + * 从后往前计算 + * 需要到达位置下标targetIndex, targetIndex - curIndex <= nums[curIndex]时 + * targetIndex = curIndex, curIndex--, 否则 curIndex-- + * targetIndex == 0时, 可以到达 + */ + int targetIndex = nums.length - 1, curIndex = nums.length - 2; + while (curIndex >= 0) { + if (targetIndex - curIndex <= nums[curIndex]) + targetIndex = curIndex; + curIndex--; + } + return targetIndex == 0; +} +``` +
+ +### x 的平方根 +```java +public int mySqrt(int x) { + // 二分法 x的平方根整数部分 < x / 2 (1除外) + long left = 0, right = x / 2 + 1; + while (left < right) { + // 取右中位数,防止死循环,例: x = 9, left = 3, right = 4时死循环 + long mid = (left + right + 1) / 2; + long square = mid * mid; + if (square > x) { + right = mid - 1; + } else { + left = mid; + } + } + return (int)left; +} + +public int mySqrt(int x) { + // 牛顿迭代法 + long curNum = x; + while (curNum * curNum > x) { + curNum = (curNum + x / curNum) / 2; + } + return (int)curNum; +} + +public int mySqrt(int x) { + // 数学公式换底计算 + if ( x == 0) return 0; + int ans = (int) Math.exp(0.5 * Math.log(x)); + return (long)(ans + 1) * (ans + 1) <= x ? ans + 1 : ans; +} +``` +
+ +### 有效的完全平方数 +```java +public boolean isPerfectSquare(int num) { + /* + * num < 2, 返回true + * 左边界left = 2, 右边界 right = num / 2 + * left <= right时 + * mid = (left + right) >> 1, square = mid * mid, square与square比较 + * square == num返回true + * square > num, right = mid - 1 + * square < num, left = mid + 1 + */ + if (num < 2) return true; + long left = 2, right = num >> 1; + while (left <= right) { + long mid = (left + right) >> 1; + long square = mid * mid; + if (square == num) { + return true; + }else if (square > num) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return false; +} + +public boolean isPerfectSquare(int num) { + // 牛顿迭代公式 + long curNum = num; + while (curNum * curNum > num) { + curNum = (curNum + num / curNum) / 2; + } + return curNum * curNum == num; +} + +public boolean isPerfectSquare(int num) { + // 数学公式换底 + int ans = (int)Math.exp(0.5 * Math.log(num)); + ans = (long)(ans + 1) * (ans + 1) <= num ? ans + 1 : ans; + return ans * ans == num; +} +``` +
+ +### 搜索旋转排序数组 +```java +public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1, index = 0; + while (left <= right) { + index = (left + right + 1) / 2; + int curNum = nums[index]; + if (curNum == target) { + return index; + } else if (curNum > nums[left]) { + // index左边数据有序 + if (target >= nums[left] && target < curNum) { + right = index - 1; + } else { + left = index + 1; + } + } else { + // index 右边数据有序 + if (target <= nums[right] && target > curNum) { + left = index + 1; + } else { + right = index - 1; + } + } + } + return -1; +} +``` +
+ +### 搜索二维矩阵 +```java +public boolean searchMatrix(int[][] matrix, int target) { + if ( matrix.length == 0) return false; + int col = matrix.length, row = matrix[0].length; + int left = 0, right = col * row - 1; + while (left <= right) { + int mid = (left + right) / 2; + if (matrix[mid / row][mid % row] == target) { + return true; + } else if (matrix[mid / row][mid % row] < target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return false; +} +``` +
\ No newline at end of file diff --git a/Week06/NOTE.md b/Week06/NOTE.md index 50de30414..6684dc9cd 100644 --- a/Week06/NOTE.md +++ b/Week06/NOTE.md @@ -1 +1,12 @@ -学习笔记 \ No newline at end of file +### 动态规划 +``` +定义 + 分治 + 最优子结构 +解题步骤 + 1. 重复问题 (数学归纳法思想) + 2. 定义状态数组 + 3. dp方程 +动态规划 与 递归或分治没有本质的区别(关键看有无最优子结构) + 共性:找重复子问题 + 差异:dp--最优子结构, 中途介意淘汰次优解(dp大多数时候复杂度更低) +``` \ No newline at end of file diff --git a/Week06/homework.md b/Week06/homework.md new file mode 100644 index 000000000..59642dc53 --- /dev/null +++ b/Week06/homework.md @@ -0,0 +1,479 @@ +#### 地下城游戏 +```java +public int calculateMinimumHP(int[][] dungeon) { + int row = dungeon.length; + if (row == 0) return 0; + int col = dungeon[0].length; + if (col == 0) return 0; + int[][] dp = new int[row + 1][col + 1]; + // 赋值 O(M * N) + for (int i = 0; i < row + 1; i++) { + Arrays.fill(dp[i], Integer.MAX_VALUE); + } + dp[row][col - 1] = 1; + dp[row - 1][col] = 1; + // O(M * N) + for (int i = row - 1; i >= 0; i--) { + for (int j = col - 1; j >= 0; j--) { + // dp方程 f(i,j) = max(min(f(i,j+1), f(i+1,j)-dungeon(i,j)), 1); + int min = Math.min(dp[i][j + 1], dp[i + 1][j]); + dp[i][j] = Math.max(min - dungeon[i][j], 1); + } + } + return dp[0][0]; +} +``` + +
+ +#### 不同路径 +```java +public int uniquePaths(int m, int n) { + /* + * 分析: 从start到当前网格的走法是只有两种(从上或者从左到达当前网格) + * 重复性: 到达当前网格的走法 = 上一个网格的走法 + 左一格网格的走法 + * 状态数组: int[][] dp = new int[m][n]; + * DP 方程: f(i, j) = f(i - 1, j) + f(i, j - 1); + */ + int[][] dp = new int[m][n]; + // 第一排和第一列只有一种走法能到达,全设为1 + for (int i = 1; i < m; i++) { + dp[i][0] = 1; + } + Arrays.fill(dp[0], 1); + // 时间:O(M * N) 空间: O(M * N) + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; +} +``` + +
+ +#### 不同路径 II +```java +public int uniquePathsWithObstacles(int[][] obstacleGrid) { + int row = obstacleGrid.length, col = obstacleGrid[0].length; + // 状态数组 + int[][] dp = new int[row][col]; + // dp 第一行赋值,遇到障碍后面位置不用赋值 O(M) + for (int i = 0; i < col; i++) { + if (obstacleGrid[0][i] == 1) break; + dp[0][i] = 1; + } + // dp 第一列赋值,遇到障碍后面的位置不用赋值 O(N) + for (int i = 0; i < row; i++) { + if (obstacleGrid[i][0] == 1) break; + dp[i][0] = 1; + } + /* + * 重复问题: + * 当前位置有障碍物: dp[i][j] = 0 + * 当前位置无障碍物: dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + * 时间 O(M * N), 空间 O(M * N) + */ + for (int i = 1; i < row; i++) { + for (int j = 1; j < col; j++) { + /* + * dp 方程: + * f(i, j) = 0 obstacleGrid[i][j] = 1; + * f(i, j) = f(i - 1, j) + f(i, j - 1) other + */ + if (obstacleGrid[i][j] == 0) + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[row - 1][col - 1]; +} +``` + +
+ +#### 最长公共子序列 +```java +public int longestCommonSubsequence(String text1, String text2) { + /* + * 重复问题: + * 当前位置两字符串的最大重复子序列长度 + * 1. 上一个位置和左一个位置的最大长度 text1.charAt(i) != text2.charAt(j) + * 2. 左上角位置长度值 + 1 text1.charAt(i) == text2.charAt(j) + */ + int row = text1.length(), col = text2.length(); + // 状态数组 + int[][] dp = new int[row + 1][col + 1]; + char[] rowChar = text1.toCharArray(); + char[] colChar = text2.toCharArray(); + // 时间: O(M * N), 空间: O(M * N) + for (int i = 1; i < row + 1; i++) { + for (int j = 1; j < col + 1; j++) { + /* + * dp 方程 + * f(i, j) = f(i - 1, j - 1) + 1 rowChar[i - 1] == colChar[j - 1] + * f(i, j) = max(f(i - 1, j), f(i - 1, j)) rowChar[i - 1] != colChar[j - 1] + */ + if (rowChar[i - 1] == colChar[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[row][col]; +} +``` + +
+ +#### 三角形最小路径和 +```java +public int minimumTotal(List> triangle) { + /* + * 重复问题 + * 当前位置值等于下一层i位置值和i+1位置值得最小值加当前位置值 + * 状态数组 + * int[] dp = new int[triangle.get(triangle.size() - 1).size()] + * dp 方程 + * f(i, j) = min(f(i + 1, j), f(i + 1, j + 1)) + triangle(i, j) + * time:O(n) space:O(n) + */ + int[] dp = new int[triangle.get(triangle.size() - 1).size() + 1]; + for (int i = triangle.size() - 1; i >= 0; i--) { + List current = triangle.get(i); + for (int j = 0; j < current.size(); j++) { + dp[j] = Math.min(dp[j], dp[j + 1]) + current.get(j); + } + } + return dp[0]; +} +``` + +
+ +#### 最大子序和 +```java +public int maxSubArray(int[] nums) { + /* + * 重复问题: + * 当前位置之前连续子数组和sum大于0时,sum += nums[i]; + * 否则,sum = nums[i]; + * 最后sum跟ans比较,取最大值 + * 状态数组 + * 简化为变量 + * dp 方程 + * f(i) = max(ans, f(i - 1) + nums[i]) f(i - 1) > 0 + * f(i) = max(ans, nums[i]) f(i - 1) <= 0 + */ + int sum = 0, ans = nums[0]; + for (int i = 0; i < nums.length; i++) { + if (sum > 0) { + sum += nums[i]; + } else { + sum = nums[i]; + } + ans = Math.max(ans, sum); + } + return ans; +} +``` + +
+ +#### 乘积最大子数组 +```java +public int maxProduct(int[] nums) { + /* + * ans 当前位置最大连续子数组乘积 + * maxPre 当前位置前一个位置的最大连续子数组乘积 + * minPre 当前位置前一个位置的最大连续子数组乘积的绝对值,当前位置为0时重新计算 + * + * 重复问题 + * crrentMax = max(maxPre * current, minPre * current, current) + * ans = max(ans, currentMax) + * 状态数组 变量 + * dp 方程 + * swap(maxPre, minPre) nums[i] < 0 + * f(i) = max(maxPre * nums[i], nums[i]) + */ + int ans = nums[0], maxPre = nums[0], minPre = nums[0]; + for (int i = 1; i < nums.length; i++) { + if (nums[i] < 0) { + int temp = maxPre; + maxPre = minPre; + minPre = temp; + } + maxPre = Math.max(maxPre * nums[i], nums[i]); + minPre = Math.min(minPre * nums[i], nums[i]); + ans = Math.max(ans, maxPre); + } + return ans; +} +``` + +
+ +#### 零钱兑换 +```java +public int coinChange(int[] coins, int amount) { + // 重复问题:当前金额, 循环coins数组, 获取当前金额所需最小硬币个数 + int max = amount + 1; + // 状态数组 + int[] dp = new int[max]; + Arrays.fill(dp, max); + dp[0] = 0; + // time: O(S * N) space: O(S) + for (int i = 1; i < max; i++) { + for (int j = 0; j < coins.length; j++) { + // dp方程: f(i) = min(f(i), f(i - coins[j]) + 1) + if (coins[j] <= i) { + dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); + } + } + } + return dp[amount] > amount ? -1 : dp[amount]; +} +``` + +
+ +#### 打家劫舍 +```java +public int rob(int[] nums) { + /* + * 重复问题: + * 偷 i 房子的金额最大值取一下情况的最大值 + * 偷 i - 2房子金额加 i 房子的金额 + * 偷 i - 1房子金额且不偷i房子金额 + */ + // 状态数组 + int[] dp = new int[nums.length + 2]; + // time: O(n) space: O(n) + for (int i = 0; i < nums.length; i++) { + // dp方程 f(i) = max(f(i - 1), f(i - 2) + nums[i]) + dp[i + 2] = Math.max(dp[i] + nums[i], dp[i + 1]); + } + return dp[nums.length + 1]; +} +``` + +
+ +#### 打家劫舍 II +```java +public int rob(int[] nums) { + /* + * 重复问题: + * 分两种情况 + * 偷nums[0], 不偷nums[nums.length - 1] + * 偷nums[nums.length - 1], 不偷nums[0] + * 偷 i 房子的金额最大值取一下情况的最大值 + * 偷 i - 2房子金额加 i 房子的金额 + * 偷 i - 1房子金额且不偷i房子金额 + */ + if (nums == null || nums.length == 0) return 0; + if (nums.length == 1) return nums[0]; + // 状态数组 + int[] dp = new int[nums.length + 2]; + // time: O(n) space: O(n) + for (int i = 1; i < nums.length; i++) { + // dp方程 f(i) = max(f(i - 1), f(i - 2) + nums[i]) + dp[i + 2] = Math.max(dp[i] + nums[i], dp[i + 1]); + } + for (int i = 0; i < nums.length - 1; i++) { + dp[i + 2] = Math.max(dp[i] + nums[i], dp[i + 1]); + } + return Math.max(dp[nums.length], dp[nums.length + 1]); +} +``` + +
+ +#### 卖股票的最佳时机 +```java +public int maxProfit(int[] prices) { + /* + * 重复问题: + * 第i天前买入股票, 计算第i天的股票价格卖出, 获利最大值 + * 与第i - 1天的获利最大值比较, 保存最大值 + */ + if (prices == null || prices.length == 0) return 0; + // 状态数组 + int[] dp = new int[prices.length]; + int lowwer = prices[0]; + // time: O(n) space: O(n) + for (int i = 1; i < prices.length; i++) { + // f(i) = max(prices[i] - lowwer, f(i - 1)) + dp[i] = Math.max(prices[i] - lowwer, dp[i - 1]); + if (prices[i] < lowwer) { + lowwer = prices[i]; + } + } + return dp[prices.length - 1]; +} + +/** + * 状态数组优化 + */ +public int maxProfit(int[] prices) { + /* + * 重复问题: + * 第i天前买入股票, 计算第i天的股票价格卖出, 获利最大值 + * 与第i - 1天的获利最大值比较, 保存最大值 + */ + if (prices == null || prices.length == 0) return 0; + int max = 0, lowwer = prices[0]; + // time: O(n) space: O(1) + for (int i = 1; i < prices.length; i++) { + // f(i) = max(prices[i] - lowwer, f(i - 1)) + max = Math.max(prices[i] - lowwer, max); + if (prices[i] < lowwer) { + lowwer = prices[i]; + } + } + return max; +} +``` + +
+ +#### 最小路径和 +```java +public int minPathSum(int[][] grid) { + /* + * 重复问题 + * 当前位置到右下角的最小路径 = min(下一格路径, 右一格路径) + grid[i][j] + */ + int row = grid.length; + if (row == 0) return 0; + int col = grid[0].length; + // 状态数组 + int[][] dp = new int[row + 1][col + 1]; + for (int i = 0; i < row; i++) { + dp[i][col] = Integer.MAX_VALUE; + } + Arrays.fill(dp[row], Integer.MAX_VALUE); + dp[row - 1][col] = 0; + dp[row][col - 1] = 0; + // time: O(M * N) space: O(M * N) + for (int i = row - 1; i >= 0; i--) { + for (int j = col - 1; j >= 0; j--) { + // dp方程: f(i, j) = min(f(i, j + 1), f(i + 1, j)) + grid[i][j] + dp[i][j] = Math.min(dp[i][j + 1], dp[i + 1][j]) + grid[i][j]; + } + } + return dp[0][0]; +} +``` + +
+ +#### 解码方法 +```java +public int numDecodings(String s) { + /* + * 重复问题: + * 当前位置解码种数 = 前一位之前的解码种数 + 前两位之前的解码种数 + */ + int len = s.length(); + if (len == 0) return 0; + // 状态数组 + int[] dp = new int[len]; + char[] charArr = s.toCharArray(); + if (charArr[0] == '0') return 0; + dp[0] = 1; + // time: O(n) space: O(n) + for (int i = 1; i < len; i++) { + /* + * dp方程 + * f(i) = 0 s(i) == 0 + * f(i) = f(i - 1) + f(i - 2) 10 < s(i - 1, i) < 27 && s(i) != '0' + * f(i) = f(i - 1) !(10 < s(i - 1, i) < 27) && s(i) != '0' + */ + if (charArr[i] != '0') dp[i] = dp[i - 1]; + int num = 10 * (charArr[i - 1] - '0') + charArr[i] - '0'; + if (num > 9 && num < 27) { + if (i == 1) dp[i]++; + else dp[i] += dp[i - 2]; + } + } + return dp[len - 1]; +} +``` + +
+ +#### 最大正方形 +```java +public int maximalSquare(char[][] matrix) { + int row = matrix.length; + if (row == 0) return 0; + int col = matrix[0].length, maxSide = 0; + // 状态数组 + int[][] dp = new int[row + 1][col + 1]; + // time: o(M * N) space: O(M * N) + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (matrix[i][j] == '1') { + /* + * dp方程: + * f(i, j) = min(min(f(i - 1, j), f(i, j - 1)), f(i - 1, j - 1)) + 1 matrix[i][j] == '1' + * f(i, j) = 0 others + */ + dp[i + 1][j + 1] = Math.min(Math.min(dp[i + 1][j], dp[i][j + 1]), dp[i][j]) + 1; + maxSide = Math.max(maxSide, dp[i + 1][j + 1]); + } + } + } + return maxSide * maxSide; +} +``` + +
+ +#### 回文子串 +```java +public int countSubstrings(String s) { + if (s == null || s.length() == 0) return 0; + int len = s.length(); + char[] charArr = s.toCharArray(); + // 状态数组 + boolean[][] dp = new boolean[len][len]; + int result = len; + for (int i = 0; i < len; i++) { + dp[i][i] = true; + } + // time: O(n ^ 2) space: O(n ^ 2) + for (int i = len - 1; i >= 0; i--) { + for (int j = i + 1; j < len; j++) { + // dp方程: f(i, j) = (j - i == 1) ? true : f(i + 1, j - 1) + if (charArr[i] == charArr[j]) { + dp[i][j] = j - i == 1 ? true : dp[i + 1][j - 1]; + if (dp[i][j]) result++; + } + } + } + return result; +} +``` + +
+ +#### 任务调度器 +```java +public int leastInterval(char[] tasks, int n) { + int[] nums = new int[26]; + for (int i = 0; i < tasks.length; i++) { + nums[tasks[i] - 'A']++; + } + Arrays.sort(nums); + int maxVal = nums[25] - 1, idelSlot = maxVal * n; + for (int i = 24; i >= 0 && nums[i] > 0; i--) { + idelSlot -= Math.min(nums[i], maxVal); + } + return idelSlot > 0 ? idelSlot + tasks.length : tasks.length; +} +``` + +
\ No newline at end of file diff --git a/Week07/NOTE.md b/Week07/NOTE.md deleted file mode 100644 index 50de30414..000000000 --- a/Week07/NOTE.md +++ /dev/null @@ -1 +0,0 @@ -学习笔记 \ No newline at end of file diff --git a/Week07/week07-homework.md b/Week07/week07-homework.md new file mode 100644 index 000000000..69b6f60b0 --- /dev/null +++ b/Week07/week07-homework.md @@ -0,0 +1,155 @@ +### 实现 Trie (前缀树) +```java +class Trie { + private boolean isEnd; + private Trie[] next; + /** Initialize your data structure here. */ + public Trie() { + isEnd = false; + next = new Trie[26]; + } + + /** Inserts a word into the trie. */ + public void insert(String word) { + if (word == null || word.length() == 0) return; + Trie cur = this; + char[] words = word.toCharArray(); + for (int i = 0; i < words.length; i++) { + int n = words[i] - 'a'; + if (cur.next[n] == null) cur.next[n] = new Trie(); + cur = cur.next[n]; + } + cur.isEnd = true; + } + + /** Returns if the word is in the trie. */ + public boolean search(String word) { + Trie node = searchPrefix(word); + return node != null && node.isEnd; + } + + /** Returns if there is any word in the trie that starts with the given prefix. */ + public boolean startsWith(String prefix) { + Trie node = searchPrefix(prefix); + return node != null; + } + + private Trie searchPrefix(String word) { + if (word == null) return null; + Trie cur = this; + char[] words = word.toCharArray(); + for (int i = 0; i < words.length; i++) { + cur = cur.next[words[i] - 'a']; + if (cur == null) return null; + } + return cur; + } +} +``` +
+ +### 朋友圈 +```java +class Solution { + private int find(int[] parent, int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + private void union(int[] parent, int p, int q) { + int rootP = find(parent, p); + int rootQ = find(parent, q); + if (rootP == rootQ) return; + parent[rootP] = rootQ; + } + + public int findCircleNum(int[][] M) { + int[] parent = new int[M.length]; + for (int i = 0; i < M.length; i++) { + parent[i] = i; + } + for (int i = 0; i < M.length - 1; i++) { + for (int j = i + 1; j < M.length; j++) { + if (M[i][j] == 1) union(parent, i, j); + } + } + int count = 0; + for (int i = 0; i < parent.length; i++) { + if (parent[i] == i) count++; + } + return count; + } +} +``` +
+ +### 岛屿数量 +```java +class Solution { + public int numIslands(char[][] grid) { + if (grid == null || grid[0].length == 0) return 0; + int row = grid.length, col = grid[0].length, count = 0; + // O(M * N) + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (grid[i][j] == '1') { + count++; + dfs(grid, i, j); + } + } + } + return count; + } + + private void dfs(char[][] grid, int i, int j) { + if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return; + grid[i][j] = '0'; + int[][] dirs = {{0, 1}, {1,0}, {-1, 0}, {0, -1}}; + for (int[] dir : dirs) { + dfs(grid, i + dir[0], j + dir[1]); + } + } +} +``` +
+ +### 被围绕的区域 +```java +class Solution { + public void solve(char[][] board) { + if (board == null || board.length == 0) return; + int row = board.length, col = board[0].length; + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + boolean isBoard = i == 0 || j == 0 || i == row - 1 || j == col - 1; + if (isBoard && board[i][j] == 'O') { + dfs(board, i, j); + } + } + } + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (board[i][j] == 'O') { + board[i][j] = 'X'; + } + if (board[i][j] == '#') { + board[i][j] = 'O'; + } + } + } + } + + private void dfs(char[][] board, int i, int j) { + if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') return; + board[i][j] = '#'; + int[][] dirs = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; + for (int[] dir : dirs) { + dfs(board, i + dir[0], j + dir[1]); + } + } +} +``` +
diff --git a/Week07/week07-note.md b/Week07/week07-note.md new file mode 100644 index 000000000..18b58020c --- /dev/null +++ b/Week07/week07-note.md @@ -0,0 +1,228 @@ +### 并查集 +#### 模板 +```java +/** + * 1.find: 确定元素子集 + * 2.union: 合并两个子集 + * 3.makeSet: 简历单元素集合 + * 路径压缩优化: 认老大, 减少深度 + */ +class unionFind { + private int count = 0; + private int[] parent; + public unionFind(int n) { + count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public int find(int p) { + while (p != parent[p]) { + // 路径压缩 + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + if (rootP == rootQ) return; + parent[rootP] = rootQ; + count--; + } +} +``` +
+ +### A* +#### 模板 +```java +/** + * BFS + 优先级 + */ +public class AStar { + public final static int BAR = 1; // 障碍值 + public final static int PATH = 2; // 路径 + public final static int DIRECT_VALUE = 10; // 横竖移动代价 + public final static int OBLIQUE_VALUE = 14; // 斜移动代价 + + Queue openList = new PriorityQueue(); // 优先队列(升序) + List closeList = new ArrayList(); + + /** + * 开始算法 + */ + public void start(MapInfo mapInfo) { + if(mapInfo==null) return; + // clean + openList.clear(); + closeList.clear(); + // 开始搜索 + openList.add(mapInfo.start); + moveNodes(mapInfo); + } + + + /** + * 移动当前结点 + */ + private void moveNodes(MapInfo mapInfo) { + while (!openList.isEmpty()) { + Node current = openList.poll(); + closeList.add(current); + addNeighborNodeInOpen(mapInfo,current); + if (isCoordInClose(mapInfo.end.coord)) { + drawPath(mapInfo.maps, mapInfo.end); + break; + } + } + } + + /** + * 在二维数组中绘制路径 + */ + private void drawPath(int[][] maps, Node end) { + if(end==null||maps==null) return; + System.out.println("总代价:" + end.G); + while (end != null) { + Coord c = end.coord; + maps[c.y][c.x] = PATH; + end = end.parent; + } + } + + + /** + * 添加所有邻结点到open表 + */ + private void addNeighborNodeInOpen(MapInfo mapInfo,Node current) { + int x = current.coord.x; + int y = current.coord.y; + // 左 + addNeighborNodeInOpen(mapInfo,current, x - 1, y, DIRECT_VALUE); + // 上 + addNeighborNodeInOpen(mapInfo,current, x, y - 1, DIRECT_VALUE); + // 右 + addNeighborNodeInOpen(mapInfo,current, x + 1, y, DIRECT_VALUE); + // 下 + addNeighborNodeInOpen(mapInfo,current, x, y + 1, DIRECT_VALUE); + // 左上 + addNeighborNodeInOpen(mapInfo,current, x - 1, y - 1, OBLIQUE_VALUE); + // 右上 + addNeighborNodeInOpen(mapInfo,current, x + 1, y - 1, OBLIQUE_VALUE); + // 右下 + addNeighborNodeInOpen(mapInfo,current, x + 1, y + 1, OBLIQUE_VALUE); + // 左下 + addNeighborNodeInOpen(mapInfo,current, x - 1, y + 1, OBLIQUE_VALUE); + } + + + /** + * 添加一个邻结点到open表 + */ + private void addNeighborNodeInOpen(MapInfo mapInfo,Node current, int x, int y, int value) { + if (canAddNodeToOpen(mapInfo,x, y)) { + Node end=mapInfo.end; + Coord coord = new Coord(x, y); + int G = current.G + value; // 计算邻结点的G值 + Node child = findNodeInOpen(coord); + if (child == null) { + int H=calcH(end.coord,coord); // 计算H值 + if(isEndNode(end.coord,coord)) { + child=end; + child.parent=current; + child.G=G; + child.H=H; + } else { + child = new Node(coord, current, G, H); + } + openList.add(child); + } else if (child.G > G) { + child.G = G; + child.parent = current; + openList.add(child); + } + } + } + + + /** + * 从Open列表中查找结点 + */ + private Node findNodeInOpen(Coord coord) { + if (coord == null || openList.isEmpty()) return null; + for (Node node : openList) { + if (node.coord.equals(coord)) { + return node; + } + } + return null; + } + + /** + * 计算H的估值:“曼哈顿”法,坐标分别取差值相加 + */ + private int calcH(Coord end,Coord coord) { + return Math.abs(end.x - coord.x) + + Math.abs(end.y - coord.y); + } + + /** + * 判断结点是否是最终结点 + */ + private boolean isEndNode(Coord end,Coord coord) { + return coord != null && end.equals(coord); + } + + + /** + * 判断结点能否放入Open列表 + */ + private boolean canAddNodeToOpen(MapInfo mapInfo,int x, int y) { + // 是否在地图中 + if (x < 0 || x >= mapInfo.width || y < 0 || y >= mapInfo.hight) return false; + // 判断是否是不可通过的结点 + if (mapInfo.maps[y][x] == BAR) return false; + // 判断结点是否存在close表 + if (isCoordInClose(x, y)) return false; + + return true; + } + + + /** + * 判断坐标是否在close表中 + */ + private boolean isCoordInClose(Coord coord) { + return coord!=null&&isCoordInClose(coord.x, coord.y); + } + + + /** + * 判断坐标是否在close表中 + */ + private boolean isCoordInClose(int x, int y) { + if (closeList.isEmpty()) return false; + for (Node node : closeList) { + if (node.coord.x == x && node.coord.y == y) { + return true; + } + } + return false; + } +} + +``` + +### 红黑树 AVL +``` +旋转 + 左旋 + 右旋 + 左右旋 + 右左旋 +``` diff --git a/Week08/NOTE.md b/Week08/NOTE.md index 50de30414..137db8d30 100644 --- a/Week08/NOTE.md +++ b/Week08/NOTE.md @@ -1 +1,375 @@ -学习笔记 \ No newline at end of file +#### XOR异或 ^ +``` +x ^ 0 = x +# 1s = ~0 +x ^ 1s = ~x +x ^ (~x) = 1s +x ^ x = 0 +c = a ^ b --> a ^ c = b --> b ^ c = a +a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c +``` +
+ +### 指定位置的位运算 +``` +# 将x最右边的n位清零 +x & (~0 << n) +# 获取x第n位的值(0 or 1) +x >> n & 1 +# 获取第n位的幂值 +x & (1 << n) +# 仅将第n位置为 1 +x | (1 << n) +# 仅将第n位置为 0 +x & (~(1 << n) +# 将x最高位至n位(含n)清零 +x & ((1 << n) - 1) +# 将x第n位至0位(含n)清零 +x & (~((1 << n + 1) - 1)) +``` +
+ +### 实战运用 +``` +# 清零最低位 1 +X = X & (X - 1) +# 得到最低位 1 +X = X & -X +``` +
+ +### 布隆过滤器 +``` +# 核心: + 超大位数组 + hash函数 + +# 添加元素 + 1. 将添加元素给k个hash函数 + 2. 得到对应位数组的k个位置 + 3. 将对应位置设为 1 +# 查询元素 + 1. 将下旬元素给k个hash函数 + 2. 得到对应位数组的k个位置 + 3. 只要有一个位置值为 0, 则元素不存在 + 3. 如果k个位置值全为 1, 则元素可能存在(存在误判, 用于最外层过滤) +``` + +``` +/** + * 示例代码 + */ +public class BloomFilter { + private static final int DEFAULT_SIZE = 2 << 24; + private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 }; + private BitSet bits = new BitSet(DEFAULT_SIZE); + private SimpleHash[] func = new SimpleHash[seeds.length]; + public BloomFilter() { + for (int i = 0; i < seeds.length; i++) { + func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); + } + } + public void add(String value) { + for (SimpleHash f : func) { + bits.set(f.hash(value), true); + } + } + public boolean contains(String value) { + if (value == null) { + return false; + } + boolean ret = true; + for (SimpleHash f : func) { + ret = ret && bits.get(f.hash(value)); + } + return ret; + } + // 内部类,simpleHash + public static class SimpleHash { + private int cap; + private int seed; + public SimpleHash(int cap, int seed) { + this.cap = cap; + this.seed = seed; + } + public int hash(String value) { + int result = 0; + int len = value.length(); + for (int i = 0; i < len; i++) { + result = seed * result + value.charAt(i); + } + return (cap - 1) & result; + } + } +} +``` +
+ +### LRU Cache +```java +/** + * 示例代码 + */ +class LRUCache { + /** + * 缓存映射表 + */ + private Map cache = new HashMap<>(); + /** + * 缓存大小 + */ + private int size; + /** + * 缓存容量 + */ + private int capacity; + /** + * 链表头部和尾部 + */ + private DLinkNode head, tail; + + public LRUCache(int capacity) { + //初始化缓存大小,容量和头尾节点 + this.size = 0; + this.capacity = capacity; + head = new DLinkNode(); + tail = new DLinkNode(); + head.next = tail; + tail.prev = head; + } + + /** + * 获取节点 + * @param key 节点的键 + * @return 返回节点的值 + */ + public int get(int key) { + DLinkNode node = cache.get(key); + if (node == null) { + return -1; + } + //移动到链表头部 + (node); + return node.value; + } + + /** + * 添加节点 + * @param key 节点的键 + * @param value 节点的值 + */ + public void put(int key, int value) { + DLinkNode node = cache.get(key); + if (node == null) { + DLinkNode newNode = new DLinkNode(key, value); + cache.put(key, newNode); + //添加到链表头部 + addToHead(newNode); + ++size; + //如果缓存已满,需要清理尾部节点 + if (size > capacity) { + DLinkNode tail = removeTail(); + cache.remove(tail.key); + --size; + } + } else { + node.value = value; + //移动到链表头部 + moveToHead(node); + } + } + + /** + * 删除尾结点 + * + * @return 返回删除的节点 + */ + private DLinkNode removeTail() { + DLinkNode node = tail.prev; + removeNode(node); + return node; + } + + /** + * 删除节点 + * @param node 需要删除的节点 + */ + private void removeNode(DLinkNode node) { + node.next.prev = node.prev; + node.prev.next = node.next; + } + + /** + * 把节点添加到链表头部 + * + * @param node 要添加的节点 + */ + private void addToHead(DLinkNode node) { + node.prev = head; + node.next = head.next; + head.next.prev = node; + head.next = node; + } + + /** + * 把节点移动到头部 + * @param node 需要移动的节点 + */ + private void moveToHead(DLinkNode node) { + removeNode(node); + addToHead(node); + } + + /** + * 链表节点类 + */ + private static class DLinkNode { + Integer key; + Integer value; + DLinkNode prev; + DLinkNode next; + + DLinkNode() { + } + + DLinkNode(Integer key, Integer value) { + this.key = key; + this.value = value; + } + } +} +``` +
+ +### 排序算法 + +#### 冒泡排序 +```java +public void bubbleSort(int[] nums) { + int len = nums.length; + // 相邻两个数比较, 顺序错误则交换位置 + // time:O(n ^ 2) space:O(1) + for (int i = 0; i < len; i++) { + for (int j = 0; j < len - 1 - i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } + } +} +``` +
+ +#### 选择排序 +```java +public void selectionSort(int[] nums) { + int len = nums.length; + // 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 + // time:O(n ^ 2) space:O(1) + for (int i = 0; i < len; i++) { + int minIndex = i; + for (int j = i + 1; j < len; j++) { + // 找从i开始后的最小数的索引 + if (nums[j] < nums[minIndex]) minIndex = j; + } + // 交换位置 + int temp = nums[i]; + nums[i] = nums[minIndex]; + nums[minIndex] = temp; + } +} +``` +
+ +#### 插入排序 +```java +public void selectionSort(int[] nums) { + int len = nums.length; + // 构建有序序列, 对于未排序数据, 在已排序序列中从后向前扫描, 找到相应位置并插入 + // time:O(n ^ 2) space:O(1) + for (int i = 0; i < len; i++) { + int curNum = nums[i], curIndex = i - 1; + while (curIndex >= 0 && nums[curIndex] > curNum) { + nums[curIndex + 1] = nums[curIndex]; + curIndex--; + } + nums[curIndex + 1] = curNum; + } +} +``` +
+ +#### 快速排序 +```java +/** + * 通过一趟排序将待排记录分隔成独立的两部分, + * 其中一部分记录的关键字均比另一部分的关键字小, + * 则可分别对这两部分记录继续进行排序,以达到整个序列有序 + */ +public void quickSort(int[] nums) { + // time:O(nlog n) space:O(nlog n) + quickSort(nums, 0, nums.length - 1); +} + +private void quickSort(int[] nums, int begin, int end) { + if (begin <= end) return; + // 寻找标杆位置 + int pivot = partition(nums, begin, end); + // pivot左边元素下探 + quickSort(nums, begin, pivot - 1); + // pivot右边元素下探 + quickSort(nums, pivot + 1, end); +} + +private int partition(int[] nums, int begin, int end) { + // pivot: 标杆位置, counter: 小于pivot的元素的个数 + int pivot = end, counter = begin; + for (int i = begin; i < end; i++) { + if (nums[i] < nums[pivot]) { + int temp = nums[i]; nums[i] = nums[counter]; nums[counter] = temp; + counter++; + } + } + int temp = nums[pivot]; nums[pivot] = nums[counter]; nums[counter] = temp; + return counter; +} +``` +
+ +#### 归并排序 +```java +/** + * 采用分治法(Divide and Conquer)的一个非常典型的应用。 + * 将已有序的子序列合并,得到完全有序的序列; + * 即先使每个子序列有序,再使子序列段间有序 + */ +public void mergeSort(int[] nums) { + // time:O(nlog n) space:O(n) + mergeSort(nums, 0, nums.length - 1); +} + +private void mergeSort(int[] nums, int left, int right) { + if (left <= right) return; + int mid = ((right - left) >> 1) + left; + // 左半部分下探 + mergeSort(nums, left, mid); + // 右半部分下探 + mergeSort(nums, mid + 1, right); + merge(nums, left, mid, right); +} + +/** + * 合并两个有序数组 + */ +private int merge(int[] nums, int left, int mid, int right) { + int[] temp = new int[right - left + 1]; + int i = left, j = mid + 1, k = 0; + while (i <= mid && j <= right) { + temp[k++] = nums[i] < nums[j] ? nums[i++] : nums[j++]; + } + while (i <= left) temp[k++] = nums[i++]; + while (j <= right) temp[k++] = nums[j++]; + System.arraycopy(temp, 0, nums, left, right - left + 1); +} +``` \ No newline at end of file diff --git a/Week08/homework.md b/Week08/homework.md new file mode 100644 index 000000000..fd4e5913e --- /dev/null +++ b/Week08/homework.md @@ -0,0 +1,241 @@ +### 位1的个数 +```java +public class Solution { + // you need to treat n as an unsigned value + public int hammingWeight(int n) { + int count = 0; + // time:O(num(1)) space:O(1) + while (n != 0) { + count++; + n &= n - 1; + } + return count; + } +} +``` +
+ +###
2的幂 +```java +class Solution { + public boolean isPowerOfTwo(int n) { + // time:O(1) space:O(1) + return n > 0 && (n & (n - 1)) == 0; + } +} +``` +
+ +###
颠倒二进制位 +```java +public class Solution { + // you need treat n as an unsigned value + public int reverseBits(int n) { + int ans = 0; + // time:O(1) space:O(1) + for (int i = 0; i < 32; i++) { + ans = (ans << 1) + (n & 1); + n >>= 1; + } + return ans; + } +} +``` +
+ +###
N皇后 +```java +/** + * hash表记录 "列状态" 、 "主对角线状态" 、 "副对角线状态" + */ +class Solution { + List> result = new ArrayList<>(); + Set cols = new HashSet<>(); + Set pies = new HashSet<>(); + Set nas = new HashSet<>(); + Deque stack = new LinkedList<>(); + public List> solveNQueens(int n) { + // time:O(N!) space:O(N) + dfs(n, 0); + return result; + } + private void dfs(int n, int row) { + if (row == n) { + List board = convertToBoard(n); + result.add(board); + return; + } + // 查找row行n列是否可以放皇后 + for (int i = 0; i < n; i++) { + if (cols.contains(i) || pies.contains(row + i) || nas.contains(row - i)) continue; + stack.push(i); + // 列 撇 捺 + cols.add(i); pies.add(row + i); nas.add(row - i); + dfs(n, row + 1); + // 状态还原 + cols.remove(i); pies.remove(row + i); nas.remove(row - i); + stack.pop(); + } + } + private List convertToBoard(int n) { + List board = new ArrayList<>(); + for (Integer num : stack) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < n; i++) { + builder.append("."); + } + builder.replace(num, num + 1, "Q"); + board.add(builder.toString()); + } + return board; + } +} + +/** + * 数组记录 "列状态" 、 "主对角线状态" 、 "副对角线状态" + */ +class Solution { + char[][] chars; + boolean[] cols, pies, nas; + List> result; + + public List> solveNQueens(int n) { + result = new ArrayList<>(); + if (n <= 0) return result; + chars = new char[n][n]; + for (int i = 0; i < n; i++) { + Arrays.fill(chars[i], '.'); + } + // 列 撇 捺 + cols = new boolean[n]; pies = new boolean[2 * n - 1]; nas = new boolean[2 * n - 1]; + // time:O(N!) space:O(N ^ 2) + dfs(n, 0); + return result; + } + private void dfs(int n, int row) { + if (row == n) { + List board = convertToBoard(n); + result.add(board); + return; + } + for (int i = 0; i < n; i++) { + if (cols[i] || pies[row + i] || nas[row - i + n - 1]) continue; + chars[row][i] = 'Q'; + cols[i] = pies[row + i] = nas[row - i + n - 1] = true; + dfs(n, row + 1); + // 状态还原 + cols[i] = pies[row + i] = nas[row - i + n - 1] = false; + chars[row][i] = '.'; + } + } + private List convertToBoard(int n) { + List board = new ArrayList<>(); + for (int i = 0; i < n; i++) { + board.add(new String(chars[i])); + } + return board; + } +} +``` +
+ +###
比特位计数 +```java +class Solution { + public int[] countBits(int num) { + int[] ans = new int[num + 1]; + ans[0] = 0; + // time:O(n) space:O(1) + for (int i = 1; i < ans.length; i++) { + int lowestBit = i & 1; + if (lowestBit == 1) { + ans[i] = ans[i - 1] + 1; + } else { + int temp = i; + while (temp != 0) { + ans[i]++; + temp &= temp - 1; + } + } + } + return ans; + } +} + +/** + * 优化代码 + */ +class Solution { + public int[] countBits(int num) { + int[] ans = new int[num + 1]; + // time:O(n) space:O(1) + for (int i = 0; i < ans.length; i++) { + ans[i] = ans[i >> 1] + (i & 1); + } + return ans; + } +} +``` +
+ +###
N皇后 II +```java +class Solution { + int count = 0; + boolean[] cols, pies, nas; + public int totalNQueens(int n) { + if (n < 1) return 0; + cols = new boolean[n]; + pies = new boolean[2 * n - 1]; + nas = new boolean[2 * n - 1]; + // time:O(N!) space:O(N) + dfs(n, 0); + return count; + } + private void dfs(int n, int row) { + if (row == n) { + count++; + return; + } + for (int i = 0; i < n; i++) { + if (cols[i] || pies[row + i] || nas[row - i + n - 1]) continue; + // 列 撇 捺 + cols[i] = pies[row + i] = nas[row - i + n - 1] = true; + dfs(n, row + 1); + // 状态还原 + cols[i] = pies[row + i] = nas[row - i + n - 1] = false; + } + } +} +``` +
+ +###
数组的相对排序 +```java +class Solution { + public int[] relativeSortArray(int[] arr1, int[] arr2) { + int[] count = new int[1001]; + // 计数 + for (int num1 : arr1) { + count[num1]++; + } + int index = 0; + // 处理arr2中的数 + for (int num2 : arr2) { + while (count[num2] > 0) { + arr1[index++] = num2; + count[num2]--; + } + } + // 处理剩余的数 + for (int i = 0; i < count.length; i++) { + while (count[i] > 0) { + arr1[index++] = i; + count[i]--; + } + } + return arr1; + } +} +``` +
\ No newline at end of file diff --git a/summary.md b/summary.md new file mode 100644 index 000000000..5ea3fdfed --- /dev/null +++ b/summary.md @@ -0,0 +1,19 @@ +### 期末总结 + +#### 刷题量 +  截止目前,刷题量有130+
+ +#### 谨记 +  过遍数
+  做笔记画脑图 -- 多看NB代码
+  模板烂熟于心
+  坚持刷题(至少 1 / day) -- 由量变到质变
+ +#### 收获 +  通过学习,巩固基础,完善知识体系。做新题时,有一定的思路,不再是白纸一张 + +#### 改变 +  70天的集中学习,仿佛回到了高中时代。这种授课方式很适合我这种想要努力提高自己,但还未养成自学习惯和学习方法的人。开课后,明显感觉自己有了要学习的想法,不再是只想不动。月底复报了‘开课吧’的架构师课程,为了自己的‘钱途’,继续加油!!! + +#### 结语 +  感谢老师、助教在学习上的引导和经验分享,也很荣幸和大家一起共同营造的学习氛围。祝大家‘钱途’光明,节节高升 \ No newline at end of file