diff --git a/week01/NOTE.md b/week01/NOTE.md index 50de3041..f7a5a218 100644 --- a/week01/NOTE.md +++ b/week01/NOTE.md @@ -1 +1,46 @@ -学习笔记 \ No newline at end of file +学习笔记 + +总结: + +五步刷题法 + +```test +第一遍: + 五分钟读题,思考 + 直接看解法,比较各解法优劣 + 背解法 +第二遍: + 马上自己写,提交 + 比较多种方法,优化 +第三遍: + 一天后再重复做 + 练习不同解法 +第四遍: + 一周后反复练习 +第五遍: + 面试前一周恢复训练 +``` + +去leetcode国际站,查看most votes答案 + + + +跳表: + +```text +链表中元素必须有序 +插入/删除/搜索时间复杂度都是O(lon n) +空间换时间 +``` + +PriorityQueue + +```text +优先级队列 +队列中数据按关键词有序排列,插入新数据时,会自动插入到合适的位置保证队列有序 +优先队列中的元素可以默认自然排序或者通过自定义Comparator在队列实例化的时排序 +``` + + + +即便工作日较忙,后续作业也应尽量每天争取完成一部分,周末再做可能导致时间不太够用 \ No newline at end of file diff --git a/week01/mergeSortedArray88/MergeSortedArray.java b/week01/mergeSortedArray88/MergeSortedArray.java new file mode 100644 index 00000000..1430cf38 --- /dev/null +++ b/week01/mergeSortedArray88/MergeSortedArray.java @@ -0,0 +1,75 @@ +package com.wsj.prictise.chap1_ArrayAndList.mergeSortedArray88; + +/** + * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 + */ +public class MergeSortedArray { + /** + * 双指针从前往后 + * 时间复杂度O(m + n) + * 空间复杂度O(m) + * 需要将num1的数组元素先复制一份,再比较大小,从num1的最前端开始赋值 + */ + public void merge1(int[] nums1, int m, int[] nums2, int n) { + int[] tmpNums = new int[m]; + System.arraycopy(nums1, 0, tmpNums, 0, m); + + int p1 = 0; + int p2 = 0; + int p = 0; + + while (p1 < m && p2 < n) { +// if (tmpNums[p1] < nums2[p2]) { +// nums1[p] = tmpNums[p1]; +// p1++; +// p++; +// } else { +// nums1[p] = nums2[p2]; +// p2++; +// p++; +// } + + // 三元简洁写法 + nums1[p++] = (tmpNums[p1] < nums2[p2]) ? tmpNums[p1++] : nums2[p2++]; + } + + if (p1 < m) { + System.arraycopy(tmpNums, p1, nums1, p, m + n - p1 - p2); + } + if (p2 < n) { + System.arraycopy(nums2, p2, nums1, p, m + n - p1 - p2); + } + } + + /** + * 双指针从后往前 + * 时间复杂度O(m + n) + * 空间复杂度O(1) + * 因为num1数组大小为m+n,num1的前m个元素才是有效位,m之后不参与比较 + * 所以从num1最尾端开始,把较大的元素放num1的尾端开始放,就不需要另外拷贝num1的元素 + */ + public void merge2(int[] nums1, int m, int[] nums2, int n) { + int p1 = m - 1; + int p2 = n - 1; + int p = m + n - 1; + + while (p1 >= 0 && p2 >= 0) { +// if (nums2[p2] > nums1[p1]) { +// nums1[p] = nums2[p2]; +// p2--; +// p--; +// } else { +// nums1[p] = nums1[p1]; +// p1--; +// p--; +// } + + //三元 + nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--]; + } + + if (p2 >= 0) { + System.arraycopy(nums2, 0, nums1, 0, p2 + 1); + } + } +} diff --git a/week01/mergeTwoSortedLists21/MergeSortedLists.java b/week01/mergeTwoSortedLists21/MergeSortedLists.java new file mode 100644 index 00000000..51ebe7ba --- /dev/null +++ b/week01/mergeTwoSortedLists21/MergeSortedLists.java @@ -0,0 +1,68 @@ +package com.wsj.prictise.chap1_ArrayAndList.mergeTwoSortedLists21; + +/** + * 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 + */ +public class MergeSortedLists { + public class ListNode { + int val; + ListNode next; + + ListNode() {} + + public ListNode(int val) { + this.val = val; + } + + public ListNode(int val, ListNode next) { + this.val = val; + this.next = next; + } + } + + /** + * 递归 + */ + public ListNode mergeTwoLists1(ListNode l1, ListNode l2) { + if (null == l1) { + return l2; + } else if (null == l2) { + return l1; + } else if (l1.val < l2.val) { + l1.next = mergeTwoLists1(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists1(l1, l2.next); + return l2; + } + } + + /** + * 迭代 + */ + public ListNode mergeTwoLists2(ListNode l1, ListNode l2) { + ListNode prehead = new ListNode(-1); + ListNode prev = prehead; + + 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; + } + + if (l1 != null) { + prev.next = l1; + } + if (l2 != null) { + prev.next = l2; + } + + return prehead.next; + } +} diff --git a/week01/moveZero283/moveZero.java b/week01/moveZero283/moveZero.java new file mode 100644 index 00000000..7ecf2473 --- /dev/null +++ b/week01/moveZero283/moveZero.java @@ -0,0 +1,23 @@ +package com.wsj.prictise.chap1_ArrayAndList.moveZero283; + +/** + * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 + * 必须在原数组上操作,不能拷贝额外的数组。 + * 尽量减少操作次数。 + */ +public class moveZero { + public void moveZeroes(int[] nums) { + int i = 0; + + for (int j = 0; j < nums.length; j++) { + int tmp; + if (nums[j] != 0) { + tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + + i++; + } + } + } +} diff --git a/week01/removeDuplicatesFromSortedArray26/removeDupli.java b/week01/removeDuplicatesFromSortedArray26/removeDupli.java new file mode 100644 index 00000000..dc53dc5b --- /dev/null +++ b/week01/removeDuplicatesFromSortedArray26/removeDupli.java @@ -0,0 +1,32 @@ +package com.wsj.prictise.chap1_ArrayAndList.removeDuplicatesFromSortedArray26; + +/** + * 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + * + * 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成 + * + */ +public class removeDupli { + public int removeDuplicates(int[] nums) { + if(nums == null){ + return 0; + } + if(nums.length < 2){ + return nums.length; + } + + int i = 0; + int j = 1; + while(j < nums.length){ + if(nums[i] != nums[j]){ + if(i + 1 < j){ + nums[i + 1] = nums[j]; + } + i++; + } + j++; + } + + return i + 1; + } +} diff --git a/week01/rotateArray189/RotateArray.java b/week01/rotateArray189/RotateArray.java new file mode 100644 index 00000000..93cfee08 --- /dev/null +++ b/week01/rotateArray189/RotateArray.java @@ -0,0 +1,53 @@ +package com.wsj.prictise.chap1_ArrayAndList.rotateArray189; + + +public class RotateArray { + + /** + * 旋转数组,暴力法求解 + * 时间复杂度: O(n * k) + */ + public void rotate1(int[] nums, int k) { + for (int i = 0; i < k; i++) { + int tmp = nums[nums.length - 1]; + for (int j = nums.length - 2; j >= 0; j--) { + nums[j + 1] = nums[j]; + } + nums[0] = tmp; + } + } + + /** + * 反转数组法 + * 旋转数组 k 次, k%n个尾部元素会被移动到头部,剩下的元素会被向后移动 + * 1.所有元素反转 + * 2.反转前k个元素 + * 3.再反转后面n - k个元素 + * + * 原始数组 : 1 2 3 4 5 6 7 + * 反转所有数字后 : 7 6 5 4 3 2 1 + * 反转前 k 个数字后 : 5 6 7 4 3 2 1 + * 反转后 n-k 个数字后 : 5 6 7 1 2 3 4 --> 结果 + * + * 时间复杂度:O(n) + */ + public void rotate2(int[] nums, int k) { + // 防止k比nums长度大 + k = 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[end]; + nums[end] = nums[start]; + nums[start] = temp; + + start++; + end--; + } + } +} diff --git a/week02/BinaryTree/No144_preorderTraversal/PreorderTraversal.java b/week02/BinaryTree/No144_preorderTraversal/PreorderTraversal.java new file mode 100644 index 00000000..c7d33bc2 --- /dev/null +++ b/week02/BinaryTree/No144_preorderTraversal/PreorderTraversal.java @@ -0,0 +1,26 @@ +package com.wsj.algorithm.week2.BinaryTree.No144_preorderTraversal; + +import com.wsj.algorithm.week2.BinaryTree.TreeNode; + +import java.util.ArrayList; +import java.util.List; + +/** + * 二叉树前序遍历 + * 时间复杂度:O(n), n为节点个数 + * 空间复杂度:平均情况为O(logn), 最坏情况为O(n), 即树为为单链表形式 + */ +public class PreorderTraversal { + + public List traversal(TreeNode root) { + List list = new ArrayList<>(); + + if (null != root) { + list.add(root.val); + list.addAll(traversal(root.left)); + list.addAll(traversal(root.right)); + } + + return list; + } +} diff --git a/week02/BinaryTree/No94_inorderTraversal/InorderTraversal.java b/week02/BinaryTree/No94_inorderTraversal/InorderTraversal.java new file mode 100644 index 00000000..1c00545d --- /dev/null +++ b/week02/BinaryTree/No94_inorderTraversal/InorderTraversal.java @@ -0,0 +1,26 @@ +package com.wsj.algorithm.week2.BinaryTree.No94_inorderTraversal; + +import com.wsj.algorithm.week2.BinaryTree.TreeNode; + +import java.util.ArrayList; +import java.util.List; + +/** + * 二叉树中序遍历 + * 时间复杂度:O(n), n为节点个数 + * 空间复杂度:平均情况为O(logn), 最坏情况为O(n), 即树为为单链表形式 + */ +public class InorderTraversal { + + public List traversal(TreeNode root) { + List list = new ArrayList<>(); + + if (null != root) { + list.addAll(traversal(root.left)); + list.add(root.val); + list.addAll(traversal(root.right)); + } + + return list; + } +} diff --git a/week02/BinaryTree/TreeNode.java b/week02/BinaryTree/TreeNode.java new file mode 100644 index 00000000..27827c69 --- /dev/null +++ b/week02/BinaryTree/TreeNode.java @@ -0,0 +1,17 @@ +package com.wsj.algorithm.week2.BinaryTree; + +public class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode() {} + public TreeNode(int val) { + this.val = val; + } + public TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } +} diff --git a/week02/NOTE.md b/week02/NOTE.md index 50de3041..b9177478 100644 --- a/week02/NOTE.md +++ b/week02/NOTE.md @@ -1 +1,126 @@ -学习笔记 \ No newline at end of file +学习笔记 + +HashMap + +```text +* 允许使用null键和null值,不保证顺序 +* 所有key构成的集合是Set:无序、不重复,所以key所在的类要重写equals()和hashCode()方法 +* 所有的value构成的集合是Collection:无序、可重复,所以value所在的类要重写equals() +* 所有entry构成的集合是Set +* HashMap判断两个key相等的标准:两个key通过equals()方法返回true,hashCode值也相等 +* HashMap判断两个value相等的标准:两个value通过equals()方法返回true +``` + +put方法: + +```text +put方法内部调用方法putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) +hash:key的hash值 +key:要put的key +value:要put的value +onlyIfAbsent:如果是true,put时不改变已有的值,此处传入false + +如果table为空或长度为0,则调用resize扩容,初始化默认大小16位 +然后根据key值计算hash值得到插入的数组索引i +如果该索引处为空,则创建一个新Node直接插入 +如果索引处不为空,判断key是否存在, +如果key存在,直接覆盖value +如果key不存在,判断该索引处是否为TreeNode, +如果是,则为红黑树调用putTreeVal插入 +如果不是,则为链表,判断链表是否大于8 +如果不大于8则链表插入,key存在则直接覆盖 +如果大于8则调用treeifyBin转换红黑树插入 + +键值对插入HashMap后判断插入后大小是否超过最大容量threshold +如果超过则调用resize进行扩容 +``` + + + + + +Hash Table + +```text +哈希表(散列表)是根据关键码值(key/value)直接进行访问的数据结构 +通过把key value映射到表中的一个位置来访问纪录,以加快查找的速度 +通过Hash函数把值映射到某一个位置,即index + +哈希表的访问、插入、删除的平均时间复杂度都是O(1), 最坏时间复杂度是O(n) +``` + +树 + +二叉树的遍历: + +```text +前序遍历:根-左-右 +中序遍历:左-根-右 +后序遍历:左-右-根 +``` + +代码模板: + +```text +// 前序 +def preorder(self, root): + if root: + self.traverse_path.append(root.val) + self.preorder(root.left) + self.preorder(root.right) + +// 中序 +def inorder(self, root): + if root: + self.inorder(root.left) + self.traverse_path.append(root.val) + self.inorder(root.right) + +// 后序 +def postorder(self, root): + if root: + self.postorder(root.left) + self.postorder(root.right) + self.traverse_path.append(root.val) +``` + + + +二叉搜索树 + +```text +是指一颗空树或者具有下列性质的二叉树: +1.左子树上所有节点的值均小于它的根节点的值 +2.右子树上所有节点的值均大于它的根节点的值 +3.左右子树也分别为二叉搜索树 +``` + +```text +查询和插入的时间复杂度都是O(logn) +``` + + + +堆 + +```text +Heap: 可以迅速找到一堆数中的最大或者最小值的数据结构 +根节点最大的是大顶堆,根节点最小的是小顶堆 +常见的堆有二叉堆、斐波那契堆 +``` + + + +```text +如果二叉堆通过数组实现 +二叉堆第一个元素在数组中的索引为0,则 +索引为i的左孩子的索引是(2*i+1) +索引为i的右孩子的索引是(2*i+2) +索引为i的父节点的索引是floor((i-1)/2) +``` + +```text +二叉堆插入元素最坏情况的时间复杂度就是堆的深度,也就是二叉树的深度log2n +删除的时间复杂度是logn +``` + diff --git a/week02/No1_twoSum/TwoSum.java b/week02/No1_twoSum/TwoSum.java new file mode 100644 index 00000000..6f42f7b6 --- /dev/null +++ b/week02/No1_twoSum/TwoSum.java @@ -0,0 +1,48 @@ +package com.wsj.algorithm.week2.HashMap.No1_twoSum; + +import java.util.HashMap; +import java.util.Map; + +/** + * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + * + * 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + */ +public class TwoSum { + /** + * 实际上就是寻找nums[]中是否存在x和target-x,并返回他们的下标 + * 将数组中元素存入到hash表中,就可以利用hash表搜索的复杂度为O(1)来降低查找复杂度 + * 时间复杂度:O(n) + * 空间复杂度:O(n) + */ + + // 两遍hash + public int[] twoSumHash2(int[] nums, int target) { + Map map = new HashMap<>(); + + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); + } + + for (int i = 0; i< nums.length; i++) { + if (map.containsKey(target - nums[i]) && map.get(target - nums[i]) != i) { + return new int[]{i, map.get(target - nums[i])}; + } + } + return new int[0]; + } + + // 一遍Hash + public int[] twoSumHash1(int[] nums, int target) { + Map map = new HashMap<>(); + + for (int i = 0; i< nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[]{i, map.get(target - nums[i])}; + } + map.put(nums[i], i); + } + + return new int[0]; + } +} diff --git a/week02/No242_validAnagram/ValidAnagram.java b/week02/No242_validAnagram/ValidAnagram.java new file mode 100644 index 00000000..0353ff9c --- /dev/null +++ b/week02/No242_validAnagram/ValidAnagram.java @@ -0,0 +1,89 @@ +package com.wsj.algorithm.week2.HashMap.No242_validAnagram; + +import java.util.Arrays; + +/** + * 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + */ +public class ValidAnagram { + + /** + * 构建Hash表方法 + * s和t都只包含a-z字母,所以只需要定义一个26位大小的数组 + * 遍历字符串s中的字符,当前位的字符出现一次,arr中对应位置数值++ + * t中字符出现一次,arr中对应位置数值-- + * 如果最后arr中所有位都是0,则s和t中的字母相同 + * 时间复杂度:O(n) + * 空间复杂度:O(1) + */ + public static boolean isAnagram1(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']++; + arr[t.charAt(i) - 'a']--; + } + + for (int i : arr) { + if (i != 0) { + return false; + } + } + + return true; + } + + /** + * 可先将s中出现的字母统计好 + */ + public static boolean isAnagram2(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 j = 0; j < t.length(); j++) { + arr[t.charAt(j) - 'a']--; + + if (arr[t.charAt(j) - 'a'] < 0) { + return false; + } + } + + return true; + } + + /** + * 通过对字符串中字符排序判断字符串是否相等 + * 时间复杂度:O(nlogn), 主体为排序的复杂度O(nlogn) + * 空间复杂度:O(1) + */ + public static boolean isAnagram3(String s, String t) { + if (s.length() != t.length()) { + return false; + } + + char[] charss = s.toCharArray(); + char[] charst = t.toCharArray(); + Arrays.sort(charss); + Arrays.sort(charst); + + return Arrays.equals(charss, charst); + } + + public static void main(String[] args) { + String a = "a"; + String b = "b"; + + System.out.println(isAnagram1(a, b)); + System.out.println(isAnagram2(a, b)); + } +} diff --git a/week03/NOTE.md b/week03/NOTE.md index 50de3041..cc8fc8f9 100644 --- a/week03/NOTE.md +++ b/week03/NOTE.md @@ -1 +1,48 @@ -学习笔记 \ 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: level + 1, newParam); + // restore current status 清理当前层 + +``` + +```text +找最近最简方法,拆分成重复子问题 +``` + +分治代码模板 + +```java +private static int divide_conquer(Problem problem, ) { + // terminator 没有子问题可分了 + 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); + // revert + + return result; +} +``` + + + +本周较忙,且递归问题需要较多时间理解消化,作业做的题较少,后续要在空闲时间补上,递归的题本身也需要多看多练 \ No newline at end of file diff --git a/week03/No236_lowestCommonAncestorOfBinarytree/lowestCommonAncestorOfBinaryTree.java b/week03/No236_lowestCommonAncestorOfBinarytree/lowestCommonAncestorOfBinaryTree.java new file mode 100644 index 00000000..715593d3 --- /dev/null +++ b/week03/No236_lowestCommonAncestorOfBinarytree/lowestCommonAncestorOfBinaryTree.java @@ -0,0 +1,45 @@ +package com.wsj.algorithm.week3.No236_lowestCommonAncestorOfBinarytree; + +/** + * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + */ +public class lowestCommonAncestorOfBinaryTree { + /** + * 时间复杂度:O(n),所有节点只访问一次 + * 空间复杂度:O(n),二叉树最坏情况为链式结构,高度为n + */ + public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode (int x) { + val = x; + } + } + + private TreeNode ancestor = null; + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + dfs(root, p, q); + return ancestor; + } + + private boolean dfs(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) { + return false; + } + // 遍历整个二叉树 + boolean lson = dfs(root.left, p, q); + boolean rson = dfs(root.right, p, q); + + /** + * 两种情况: + * 1.p,q都不是根节点,则左右子树必须包含p和q + * 2.p或q有一个是根节点,则另一个只要在左子树或右子树即可 + */ + if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) { + ancestor = root; + } + return lson || rson || (root.val == p.val || root.val == q.val); + } +} diff --git a/week03/No77_combinations/combinations.java b/week03/No77_combinations/combinations.java new file mode 100644 index 00000000..e2bb2980 --- /dev/null +++ b/week03/No77_combinations/combinations.java @@ -0,0 +1,67 @@ +package com.wsj.algorithm.week3.No77_combinations; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +/** + * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 + */ +public class combinations { + + public List> combine(int n, int k) { + List> result = new ArrayList<>(); + // 不满足条件,结果为空集合 + if (k <= 0 || n < k) { + return result; + } + // n==k, 就是1-k + if (n == k) { + ArrayList tmpList = new ArrayList<>(); + for (int i = 1; i <= k; i++) { + tmpList.add(i); + } + result.add(tmpList); + return result; + } + + Deque path = new ArrayDeque<>(); + dfs(n, k, 1, path, result); + return result; + } + + private void dfs(int n, int k, int begin, Deque path, List> res) { + // 递归终结 + if (path.size() == k) { + res.add(new ArrayList<>(path)); + return; + } + + for (int i = begin; i <= n; i++) { + // 先添加第一个数 + path.add(i); + // 向下递归 + dfs(n, k, i + 1, path, res); + + path.removeLast(); + } + } + + private void dfs2(int n, int k, int begin, Deque path, List> res) { + // 递归终结 + if (path.size() == k) { + res.add(new ArrayList<>(path)); + return; + } + + for (int i = begin; i <= n - (k - path.size()) + 1; i++) { + // 先添加第一个数 + path.add(i); + // 向下递归 + dfs2(n, k, i + 1, path, res); + + path.removeLast(); + } + } +} diff --git a/week04/NOTE.md b/week04/NOTE.md index 50de3041..e7cb910f 100644 --- a/week04/NOTE.md +++ b/week04/NOTE.md @@ -1 +1,155 @@ -学习笔记 \ No newline at end of file +学习笔记 + +深度优先遍历 + +代码模板 + +递归 + +```text +visited = set() +def dfs(node, visited): + if node in visited: + #已访问 + return; +visited.add(node) + +#处理当前节点 +... +#向下递归 +for next_node in node.children(): + if not next_node in visited: + dfs(next_node, visited) +``` + +非递归 + +```text +visited = set() +def dfs(node, visited): + visited.add(node) + +#处理当前节点 +... +#向下递归 +for next_node in node.children(): + if not next_node in visited: + dfs(next node, visited) +``` + +二叉树深度递归 + +```java +// 二叉树的深度优先遍历 +public List> levelOrder(TreeNode root) { + List> allResults = new ArrayList<>(); + // 递归终止 + if(root == null) { + retutn allResults; + } + + travel(root, 0, allResults); + return allResults; +} + +private void travel(TreeNode root, int level, List> results) { + // 当前层添加一个新List + if (results.size() == level) { + results.add(new ArrayList<>()); + } + results.get(level).add(root.val); + if (root.left != null) { + travel(root.left, level + 1, results); + } + if (root.right != null) { + travel(root.right, level + 1, results); + } +} +``` + + + +广度优先遍历 + +无向图中两个顶点之间的最短路径长度,可以通过广度优先遍历得到 + +需要一个队列来保存访问过的节点的顺序,以便按这个顺序来访问这些节点的邻接节点 + +代码模板 + +```text +def bfs(graph, start, end): + queue = [] + queue.append([start]) + visited.add(start) + + while queue: + node = queue.pop() + visited.add(node) + + process(node) + nodes = generate_related_nodes(node) + queue.push(nodes) +``` + +二叉树广度优先遍历 + +```java +// 二叉树的广度优先遍历 +public List> levelOrder(TreeNode root) { + List> allResults = new ArrayList<>(); + if (root == null) { + return allResults; + } + // 双端队列 + Queue nodes = new LinkedList<>(); + nodes.add(root); + while(!nodes.isEmpty()) { + // 先保存当前队列的长度 + int size = nodes.size(); + // 未访问过的节点 + List results = new ArrayList<>(); + // 处理 + for (int i = 0; i < size; i++) { + TreeNode node = nodes.poll(); + results.add(node.val); + if(node.left != null) { + nodes.add(node.left); + } + if(node.right!= null) { + nodes.add(node.right); + } + } + allResults.add(results); + } + return allResults; +} +``` + + + +二分查找 + +前提: + +1.目标函数是单调递增或递减的 + +2.存在上下界 + +3.能通过索引访问 + +代码模板 + +```text +left = 0, right = len(array) - 1 + +while left <= right + mid = (left + right)/2 + if array[mid] == target + break or return result + else if array[mid] < target + left = mid + 1 + else + right = mid -1 +``` + diff --git a/week04/No127_wordLadder/WordLadder.java b/week04/No127_wordLadder/WordLadder.java new file mode 100644 index 00000000..6ff6dde1 --- /dev/null +++ b/week04/No127_wordLadder/WordLadder.java @@ -0,0 +1,102 @@ +package com.wsj.algorithm.week4.No127_wordLadder; + +import java.util.*; + +/** + * 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则: + * + * 每次转换只能改变一个字母。 + * 转换过程中的中间单词必须是字典中的单词。 + * 说明: + * + * 如果不存在这样的转换序列,返回 0。 + * 所有单词具有相同的长度。 + * 所有单词只由小写字母组成。 + * 字典中不存在重复的单词。 + * 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 + * + */ +public class WordLadder { + /** + * 两个定点之间最短路径长度 --> 广度优先遍历 + * 构建图 + */ + public static int ladderLength(String beginWord, String endWord, List wordList) { + Set wordSet = new HashSet<>(wordList); + if (wordSet.size() == 0 || !wordSet.contains(endWord)) { + return 0; + } + + // bfs + Queue queue = new LinkedList<>(); + queue.add(beginWord); + Set visitedSet = new HashSet<>(); + visitedSet.add(beginWord); + + // beginWord本身算一个边 + int length = 1; + // 代码模板 + while (!queue.isEmpty()) { + int currentSize = queue.size(); + for (int i = 0; i < currentSize; i++) { + String currentWord = queue.poll(); + + if (visitWord(currentWord, endWord, queue, visitedSet, wordSet)) { + return length + 1; + } + } + + length++; + } + + return 0; + } + + private static boolean visitWord(String currentWord, String endWord, Queue queue, Set visitedWord, Set wordSet) { + char[] chars = currentWord.toCharArray(); + + for (int i = 0; i < currentWord.length(); i++) { + // 先保存原位置上的字母 + char originChar = chars[i]; + + // 在该位置上遍历26个字母替换当前位 + for (char c = 'a'; c < 'z'; c++) { + if (c == originChar) { + continue; + } + chars[i] = c; + String nextWord = String.valueOf(chars); + + // 字典里必须包含替换后这个单词 + if (wordSet.contains(nextWord)) { + if (nextWord.equals(endWord)) { + return true; + } + // 如果包含且未被访问 + if (!visitedWord.contains(nextWord)) { + queue.add(nextWord); + visitedWord.add(nextWord); + } + } + } + + chars[i] = originChar; + } + return false; + } + + public static void main(String[] args) { + String beginWord = "hit"; + String endWord = "cog"; + List wordList = new ArrayList<>(); + wordList.add("hot"); + wordList.add("dot"); + wordList.add("dog"); + wordList.add("lot"); + wordList.add("log"); + wordList.add("cog"); + + int length = ladderLength(beginWord, endWord, wordList); + System.out.println(length); + } +} diff --git a/week04/No127_wordLadder/WordLadder2.java b/week04/No127_wordLadder/WordLadder2.java new file mode 100644 index 00000000..57836296 --- /dev/null +++ b/week04/No127_wordLadder/WordLadder2.java @@ -0,0 +1,79 @@ +package com.wsj.algorithm.week4.No127_wordLadder; + +import java.util.*; + +public class WordLadder2 { + public static int ladderLength(String beginWord, String endWord, List wordList) { + Set wordSet = new HashSet<>(wordList); + if (wordSet.size() == 0 || !wordSet.contains(endWord)) { + return 0; + } + wordSet.remove(beginWord); + + // 第 2 步:图的广度优先遍历,必须使用队列和表示是否访问过的 visited 哈希表 + Queue queue = new LinkedList<>(); + queue.offer(beginWord); + Set visited = new HashSet<>(); + visited.add(beginWord); + + // 第 3 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1 + int step = 1; + while (!queue.isEmpty()) { + int currentSize = queue.size(); + for (int i = 0; i < currentSize; i++) { + // 依次遍历当前队列中的单词 + String currentWord = queue.poll(); + // 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1 + if (changeWordEveryOneLetter(currentWord, endWord, queue, visited, wordSet)) { + return step + 1; + } + } + step++; + } + return 0; + } + + private static boolean changeWordEveryOneLetter(String currentWord, String endWord, + Queue queue, Set visited, Set wordSet) { + char[] charArray = currentWord.toCharArray(); + for (int i = 0; i < endWord.length(); i++) { + // 先保存,然后恢复 + char originChar = charArray[i]; + for (char k = 'a'; k <= 'z'; k++) { + if (k == originChar) { + continue; + } + charArray[i] = k; + String nextWord = String.valueOf(charArray); + if (wordSet.contains(nextWord)) { + if (nextWord.equals(endWord)) { + return true; + } + if (!visited.contains(nextWord)) { + queue.add(nextWord); + // 注意:添加到队列以后,必须马上标记为已经访问 + visited.add(nextWord); + } + } + } + // 恢复 + charArray[i] = originChar; + } + return false; + } + + public static void main(String[] args) { + String beginWord = "hit"; + String endWord = "cog"; + List wordList = new ArrayList<>(); + wordList.add("hot"); + wordList.add("dot"); + wordList.add("dog"); + wordList.add("lot"); + wordList.add("log"); + wordList.add("cog"); + + int length = ladderLength(beginWord, endWord, wordList); + System.out.println(length); + } +} diff --git a/week04/No33_searchInRotatedSortedArray/searchRotatedSortedArray.java b/week04/No33_searchInRotatedSortedArray/searchRotatedSortedArray.java new file mode 100644 index 00000000..bf39ffa6 --- /dev/null +++ b/week04/No33_searchInRotatedSortedArray/searchRotatedSortedArray.java @@ -0,0 +1,53 @@ +package com.wsj.algorithm.week4.No_searchInRotatedSortedArray; + +/** + * 给你一个整数数组 nums ,和一个整数 target 。 + * 该整数数组原本是按升序排列,但输入时在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 + * 请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 + */ +public class searchRotatedSortedArray { + /** 二分查找,考虑4种情况 + * 1.索引0-mid升序,且target在0-mid之间 + * 2.索引mid到最后也是升序,target在mid之后 + * 3.num[mid] < mid[0],0-mid之间发生了旋转,target在旋转位置到mid之间 + * 4.num[mid] < mid[0],0-mid之间发生了旋转,target在0到旋转位置之间 + */ + + public int search(int[] nums, int target) { + int len = nums.length; + if (len == 0) { + return -1; + } + if (len == 1) { + return target == nums[0]? 0 : -1; + } + + int left = 0; + int right = nums.length - 1; + + while (left <= right) { + int mid = (left + right)/2; + + if (target == nums[mid]) { + return mid; + } + + // 0-mid升序 + if (nums[0] <= nums[mid]) { + if (nums[0] <= target && target < nums[mid]) { + right = mid - 1; + } else { + left = mid + 1; + } + } else { + // 0-mid之间有旋转 + if (nums[mid] < target && target <= nums[len - 1]) { + left = mid + 1; + } else { + right = mid - 1; + } + } + } + return -1; + } +} diff --git a/week04/No55_jumpGame/JumpGame.java b/week04/No55_jumpGame/JumpGame.java new file mode 100644 index 00000000..3d494426 --- /dev/null +++ b/week04/No55_jumpGame/JumpGame.java @@ -0,0 +1,32 @@ +package com.wsj.algorithm.week4.No55_jumpGame; + +/** + * 给定一个非负整数数组,你最初位于数组的第一个位置。 + * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 + * 判断你是否能够到达最后一个位置。 + */ +public class JumpGame { + /** + * 使用贪心算法 + */ + public static boolean canJump(int[] nums) { + int n = nums.length; + // 当前最远可以跳到的位置 + int jumpMax = 0; + for (int i = 0; i < n; ++i) { + if (i <= jumpMax) { + jumpMax = Math.max(jumpMax, i + nums[i]); + // 如果最远可以跳到的位置超过了数组长度,则返回true + if (jumpMax >= n - 1) { + return true; + } + } + } + return false; + } + + public static void main(String[] args) { + int[] nums = new int[]{2,3,1,1,4}; + System.out.println(canJump(nums)); + } +} diff --git a/week06/NOTE.md b/week06/NOTE.md index 50de3041..ed43e79e 100644 --- a/week06/NOTE.md +++ b/week06/NOTE.md @@ -1 +1,97 @@ -学习笔记 \ No newline at end of file +学习笔记 + +动态规划和递归或者分治没有根本上的区别 + +关键看有无最优子结构 + +如果没有最优子结构,说明所有的子问题都需要计算一遍,同时合并最后的结果(分治) + +共性:找到重复子问题 + +差异性:最优子结构,中途可以淘汰次优解 + + + +斐波那契数列: + +```text +dp方程:F[n] = F[n-1] + F[n-2] + +a[0] = 0; +a[1] = 1; +for(int i = 2; i <= n; ++i) { + a[i] = a[i - 1] + a[i - 2]; +} +a[n] +``` + + + +不同路径和: + +对于任意一个点的走法数,就等于该点右边点的走法数+该点下边点的走法数,如果该点是一个障碍物,那它的走法数为0 + +```text +第一列的格子只有从其上边格子走过去这一种走法,因此初始化dp[i][0]值为1 +第一行的格子只有从其左边格子走过去这一种走法,因此初始化dp[0][j]值为1 +dp方程:dp[i][j] = dp[i - 1][j] + dp[i][j - 1] +``` + + + +最长公共子序列: + +1.假设两个字符串都是空:最长子序列就是"" + +2.假设一个字符串是空,一个是任意字符串:最长子序列也是"" + +3.假设一个字符串是A,只要另一个字符串包含A,最长公共子序列长度就是1 + +4.S1 = ".....A", S2 = ".....A"从最后字符看起——>S1 A前子字符串和S2 A前子字符串的最长公共子序列数+1 + +两种情况: + +```java +// 如果s1和s2的最后一个字符不相同的话,那么他们之间的最长公共子序列的长度就等于s1去一个字符和s2来比,或者s2去一个字符和s1来比, +// 这两者之间的最长公共子序列的较大值 +// lcs: longest common subsequance +if s1[n - 1] != s2[n - 1]: + lcs[s1, s2] = Max(lcs[s1 - 1, s2], lcs[s1, s2 -1]) +``` + +```java +// 如果s1和s2的最后一个字符相同,那么他们之间的最长公共子序列长度等于s1减去最后一个字符的子串和s2减去最后一个字符的子串 +// 的最长公共子序列的长度 + 1 +if s1[n - 1] == s2[n - 1]: + lcs[s1, s2] = lcs[s1 -1, s2 -1] + 1 +``` + +```text +最终结果就是dp[s1.length - 1][s2.length - 1] +``` + + + +三角形最小路径和 : + +求顶点到最底层的最小路径和就是求从第二层的两个点到最底层的最小路径和的较小值 + 顶点的值 + +```text +1.分治: problem(i, j) = min(sub(i + 1, j + 1), sub(i + 1, j + 1)) + a[i , j] +2.定义状态数组:f[i, j] +3.dp方程:f[i, j] = min(f[i + 1, j], f[i + 1, j + 1]) + a[i, j] +``` + + + +最大子序列和: + +最大子序和 = 当前元素自身最大(只包含当前元素),或者包含之前元素,再加自身后最大 + +```text +从最后一个元素开始,针对每一个元素,包含取这个元素和不取这个元素两种情况,取这两种情况时子序列和的最大值 +1.分治:max_sum(i) = Max(max_sum(i - 1) + a[i]) +2.状态数组定义:f[i] +3.dp方程:f[i] = Max(f[i - 1], 0) + a[i] +``` + diff --git a/week06/No221_maximalSquare/maximalSquare.java b/week06/No221_maximalSquare/maximalSquare.java new file mode 100644 index 00000000..5cca823b --- /dev/null +++ b/week06/No221_maximalSquare/maximalSquare.java @@ -0,0 +1,36 @@ +package com.wsj.algorithm.week6.No221_maximalSquare; + +/** + * 在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。 + */ +public class maximalSquare { + /** + * 找出矩阵中只只包含1的正方形的边长最大值 + * dp方程:dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1 + */ + public int maximalSquare(char[][] matrix) { + int maxSide = 0; + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return maxSide; + } + int m = matrix.length; + int n = matrix[0].length; + + int[][] dp = new int[m][n]; + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == '1') { + if (i == 0 || j == 0) { + dp[i][j] = 1; + } else { + dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1; + } + maxSide = Math.max(maxSide, dp[i][j]); + } + } + } + + return maxSide * maxSide; + } +} diff --git a/week06/No64_minimumPathSum/minimumPathSum.java b/week06/No64_minimumPathSum/minimumPathSum.java new file mode 100644 index 00000000..53df065d --- /dev/null +++ b/week06/No64_minimumPathSum/minimumPathSum.java @@ -0,0 +1,45 @@ +package com.wsj.algorithm.week6.No64_minimumPathSum; + +/** + * 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 + * + * 说明:每次只能向下或者向右移动一步。 + */ +public class minimumPathSum { + /** + * 类似不同路径问题,网格上每个点都只有从左边过来或从上边过来两条路径, + * 所以当前点(i, j)开始的最小路径和 = 左边点(i, j - 1)和上边点(i - 1, j)过来的两个路径和的较小者 + 当前点的值 + * DP方程:f[i][j] = min(f[i][j - 1], f[i - 1][j]) + grid[i][j] + * 又只能向右和向下走,所以第一行的点只考虑从左边过来的路径(i - 1),第一列上的点只考虑从上过来的路径(j - 1) + * 且可以直接修改原矩阵,用当前点的最小路径和替换当前点的值,这样最终矩阵的最小路径和就是最右下的点的值 + * 时间复杂度O(m * n), 空间复杂度O(1) + */ + public int minPathSum(int[][] grid) { + if (grid == null || grid.length == 0) { + return 0; + } + + int m = grid.length; + int n = grid[0].length; + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + // 第一个点的路径和就是它原本的值 + if (i == 0 && j == 0) { + continue; + } else if (i == 0) { + // 第一行上的点,只能从左边过来 + grid[i][j] = grid[i][j - 1] + grid[i][j]; + } else if (j == 0) { + // 第一列上的点,只能从上边过来 + grid[i][j] = grid[i - 1][j] + grid[i][j]; + } else { + // 从左过来的路径和 和 从上过来的路径和的最小值 + grid[i][j] = Math.min(grid[i][j - 1], grid[i - 1][j]) + grid[i][j]; + } + } + } + + return grid[m - 1][n - 1]; + } +}