From 79bfc47b881a0edfecdc8bbd49c3d19d0875616b Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 31 Jan 2021 22:44:12 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E5=91=A8=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Week_01/DequeDemo.java | 33 ++++++ Week_01/MergeTwoSortedLists.java | 179 +++++++++++++++++++++++++++++++ Week_01/MoveZeroes.java | 79 ++++++++++++++ Week_01/PlusOne.java | 105 ++++++++++++++++++ Week_01/README.md | 34 +++++- Week_01/TwoSum.java | 97 +++++++++++++++++ 6 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 Week_01/DequeDemo.java create mode 100644 Week_01/MergeTwoSortedLists.java create mode 100644 Week_01/MoveZeroes.java create mode 100644 Week_01/PlusOne.java create mode 100644 Week_01/TwoSum.java diff --git a/Week_01/DequeDemo.java b/Week_01/DequeDemo.java new file mode 100644 index 00000000..9e2a5705 --- /dev/null +++ b/Week_01/DequeDemo.java @@ -0,0 +1,33 @@ +import java.util.Deque; +import java.util.LinkedList; +import java.util.PriorityQueue; + +/** + * @author lianght1 + * @date 2021/1/31 + */ +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.getFirst(); + System.out.println(str); + System.out.println(deque); + + while (deque.size() > 0) { + System.out.println(deque.removeFirst()); + } + + System.out.println(deque); + + } + + + +} diff --git a/Week_01/MergeTwoSortedLists.java b/Week_01/MergeTwoSortedLists.java new file mode 100644 index 00000000..2600cb0a --- /dev/null +++ b/Week_01/MergeTwoSortedLists.java @@ -0,0 +1,179 @@ +//题号:21 +//https://leetcode-cn.com/problems/merge-two-sorted-lists/ +//将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 +// +// +// +// 示例 1: +// +// +//输入:l1 = [1,2,4], l2 = [1,3,4] +//输出:[1,1,2,3,4,4] +// +// +// 示例 2: +// +// +//输入:l1 = [], l2 = [] +//输出:[] +// +// +// 示例 3: +// +// +//输入:l1 = [], l2 = [0] +//输出:[0] +// +// +// +// +// 提示: +// +// +// 两个链表的节点数目范围是 [0, 50] +// -100 <= Node.val <= 100 +// l1 和 l2 均按 非递减顺序 排列 +// +// Related Topics 递归 链表 +// 👍 1506 👎 0 + + + +public class MergeTwoSortedLists{ + public static void main(String[] args) { + Solution solution = new MergeTwoSortedLists().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) + /** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ + class Solution { + + /** + * 递归 + */ + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + +// if (l1 == null) { +// return l2; +// } +// +// if (l2 == null) { +// return l1; +// } +// +// //不要试图去人肉递归,只要记住,mergeTwoLists(x,y)这个函数给出的是排好序的 +// //合并链表,我们只需要将小的链表的下一个节点指向它即可。 +// if (l1.val <= l2.val) { +// l1.next = mergeTwoLists(l1.next, l2); +// return l1; +// } else { +// l2.next = mergeTwoLists(l1, l2.next); +// return l2; +// } + + /** + * 法一:递归法,时间复杂度O(n),空间复杂度O(n) + */ + +// if (l1 == null) { +// return l2; +// } +// +// if (l2 == null) { +// return l1; +// } +// +// //不要试图去人肉递归,只要记住,mergeTwoLists(x,y)这个函数给出的是排好序的 +// //合并链表,我们只需要将小的链表的下一个节点指向它即可。 +// if (l1.val < l2.val) { +// l1.next = mergeTwoLists(l1.next, l2); +// return l1; +// } else { +// l2.next = mergeTwoLists(l1, l2.next); +// return l2; +// } + + /** + * 非递归,迭代法, + */ + + + ListNode result = new ListNode(); + ListNode pre = result; + + 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 result.next; + + } + + /** + * 非递归 + */ +// public ListNode mergeTwoLists(ListNode l1, ListNode l2) { +// +// //这个类似哑结点 +// ListNode result = new ListNode(); +// +// //因为这里的pre会一直被替换为pre.next,因此需要个其他变量 +// //否則result会找不到一开始的位置 +// ListNode pre = result; +// +// while (l1 != null && l2 != null) { +// //比较大小,直接将pre指向小的那个链表,然后将相应链表指针后移 +// if (l1.val <= l2.val) { +// pre.next = l1; +// l1 = l1.next; +// } else { +// pre.next = l2; +// l2 = l2.next; +// } +// //pre里面增加了一个元素,因此要将指针后移 +// pre = pre.next; +// } +// +// //这是一边链表比另一边长的时候,直接将长的部分放到pre后面即可 +// pre.next = l1 == null ? l2 : l1; +// +// return result.next; +// } + } + //leetcode submit region end(Prohibit modification and deletion) + public class ListNode { + int val; + MergeTwoSortedLists.ListNode next; + + ListNode() { + } + + ListNode(int val) { + this.val = val; + } + + ListNode(int val, MergeTwoSortedLists.ListNode next) { + this.val = val; + this.next = next; + } + } +} \ No newline at end of file diff --git a/Week_01/MoveZeroes.java b/Week_01/MoveZeroes.java new file mode 100644 index 00000000..d04cf982 --- /dev/null +++ b/Week_01/MoveZeroes.java @@ -0,0 +1,79 @@ +//题号:283 +//https://leetcode-cn.com/problems/move-zeroes/ +//给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 +// +// 示例: +// +// 输入: [0,1,0,3,12] +//输出: [1,3,12,0,0] +// +// 说明: +// +// +// 必须在原数组上操作,不能拷贝额外的数组。 +// 尽量减少操作次数。 +// +// Related Topics 数组 双指针 +// 👍 935 👎 0 + + +public class MoveZeroes{ + public static void main(String[] args) { + Solution solution = new MoveZeroes().new Solution(); + solution.moveZeroes(new int[]{1,0, 3, 0, 12}); + } + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public void moveZeroes(int[] nums) { + + + /** + * 法一:两次循环 + */ + + +// if (nums == null) { +// return; +// } +// +// int j = 0; +// for (int i = 0; i < nums.length; i++) { +// if (nums[i]!=0) { +// nums[j++] = nums[i]; +// } +// } +// +// //前面有j个非0的,因为下标从0开始,因此其实位置刚好是j +// for (int i = j; i < nums.length; i++) { +// nums[i] = 0; +// } + + + /** + * 法二:双指针 + */ + + + if (nums == null) { + return; + } + + //因为非0数据要保持原先位置,因此需要用非0的与0替换 + int j = 0; + for (int i = 0; i < nums.length; i++) { + //因为i,j起点一样,当nums[i]不等于0,j会加1,因此j一定是在0的位置 + if (nums[i] != 0) { + //只有不同位置才需要交换 + if (i != j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + j++; + } + } + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_01/PlusOne.java b/Week_01/PlusOne.java new file mode 100644 index 00000000..bc40cb56 --- /dev/null +++ b/Week_01/PlusOne.java @@ -0,0 +1,105 @@ +//66 +//https://leetcode-cn.com/problems/plus-one/ +//给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 +// +// 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 +// +// 你可以假设除了整数 0 之外,这个整数不会以零开头。 +// +// +// +// 示例 1: +// +// +//输入:digits = [1,2,3] +//输出:[1,2,4] +//解释:输入数组表示数字 123。 +// +// +// 示例 2: +// +// +//输入:digits = [4,3,2,1] +//输出:[4,3,2,2] +//解释:输入数组表示数字 4321。 +// +// +// 示例 3: +// +// +//输入:digits = [0] +//输出:[1] +// +// +// +// +// 提示: +// +// +// 1 <= digits.length <= 100 +// 0 <= digits[i] <= 9 +// +// Related Topics 数组 +// 👍 617 👎 0 + +public class PlusOne { + public static void main(String[] args) { + Solution solution = new PlusOne().new Solution(); + solution.plusOne(new int[]{9, 9, 9}); + } + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + 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; +// } +// } +// +// //能走到这里只有一种可能,就是9999这种,需要多近一位,new一个全是0的数组即可,然后直接首位赋值1就行 +// digits = new int[digits.length + 1]; +// digits[0] = 1; +// +// return digits; + + for (int i = digits.length - 1; i >= 0; i--) { + + /** + * 这儿有两种写法,效果都差不多,但是第二种会优雅(elegant)很多 + */ + /** + * 法一:先计算再判断是否溢出 + */ + +// digits[i] %= 10; +// digits[i]++; +// if (digits[i] != 0) { +// return digits; +// } + + /** + * 法二:先判断是否溢出再计算 + */ + + if (digits[i] < 9) { + digits[i]++; + return digits; + } + + digits[i] = 0; + + } + + digits = new int[digits.length + 1]; + digits[0] = 1; + + return digits; + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_01/README.md b/Week_01/README.md index 50de3041..e59174c6 100644 --- a/Week_01/README.md +++ b/Week_01/README.md @@ -1 +1,33 @@ -学习笔记 \ No newline at end of file +学习笔记 +##### Queue + +Queue是一个接口,而Stack是一个类,由此可以看出Queue的实现和运用场景很多,因此抽象成了接口。 + +盲猜Stack在实际生活中运用的比较少,从Java Deque API建议用Deque而不是Stack也可以看出来。 + +Queue的实现多种多样,大致分为两类,一个是数组,一个是链表。 + +Queue多与并发一起使用,大部分操作都会涉及到锁的使用,名字里带有BlockingQueue的都是线程安全的实现。 + +add(e),offer(e)会将元素加入到数组/链表,在加入之前会判断数组是否达到最大值,如果达到,会进行动态扩容。(这个不同实现好像不一样,ArrayBlockingQueue好像不会扩容,ArrayDeque会扩容) + +remove(),poll()会将队列尾部元素取出,并将其从队列里删除。 + +element(),peek()会将队列尾部元素取出,但是不会从队列删除。 + +##### PriorityQueue + +PriorityQueue是一种特殊的队列,它插入的时候会按照一定的顺序排序,因此它的插入操作时间复杂度是O(logn)**(视频里说的是O(1)?)**,取出操作是O(logn)。 + +插入:add(E e),offer(E e),add()其实也是调用的offer(),插入先判断数组是否有空余位置,如果没有则进行扩容。插入时会根据传入的Comparator进行排序之后插入,因此我感觉是O(logn)而不是O(1),如果没有传Comparator则采用默认的排序方式进行排序。 + +取出:removeEq(Object o),remove(Object),removeAt(int i),这几个方法最后都会调用removeAt,因此我们只需要看removeAt即可。removeAt支持从指定位置取出元素,取出之后会对元素重新排序。 + + +#### 本周独白 + +本来想着把有空的话把栈和队列分别用数组和链表实现一遍,自己过一下这些内容的, +但是发现严重高估自己了,刷五毒神掌的题和做本周作业就没时间了,周末两天好像有点不够用的样子, +不知道是自己效率低还是没适应这种强度? + +希望下周进度会好点,加油!! diff --git a/Week_01/TwoSum.java b/Week_01/TwoSum.java new file mode 100644 index 00000000..f16eba3d --- /dev/null +++ b/Week_01/TwoSum.java @@ -0,0 +1,97 @@ +//题号:1 +//https://leetcode-cn.com/problems/two-sum/ +//给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。 +// +// 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 +// +// 你可以按任意顺序返回答案。 +// +// +// +// 示例 1: +// +// +//输入:nums = [2,7,11,15], target = 9 +//输出:[0,1] +//解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 +// +// +// 示例 2: +// +// +//输入:nums = [3,2,4], target = 6 +//输出:[1,2] +// +// +// 示例 3: +// +// +//输入:nums = [3,3], target = 6 +//输出:[0,1] +// +// +// +// +// 提示: +// +// +// 2 <= nums.length <= 103 +// -109 <= nums[i] <= 109 +// -109 <= target <= 109 +// 只会存在一个有效答案 +// +// Related Topics 数组 哈希表 +// 👍 10131 👎 0 + + +import java.util.HashMap; +import java.util.Map; + +public class TwoSum { + public static void main(String[] args) { + Solution solution = new TwoSum().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int[] twoSum(int[] nums, int target) { + + /** + * 法一,暴力解法,时间复杂度为0(n2),空间复杂度O(1) + */ + //这里i最大值只会到nums.length-2,注意:以后遇到多重循环嵌套且前后有比较关系,可以将第一层循环减少相应嵌套层数 +// for (int i = 0; i < nums.length - 1; i++) { +// for (int j = i + 1; j < nums.length; j++) { +// if (nums[i] + nums[j] == target) { +// return new int[]{i, j}; +// } +// } +// } +// +// return null; + + + /** + * 法二,引入hashMap,加上缓存,时间复杂度为O(n),空间复杂度为O(n) + */ + + + Map targetMap = new HashMap<>(nums.length); + for (int i = 0; i < nums.length; i++) { + if (targetMap.containsKey(nums[i])) { + return new int[]{targetMap.get(nums[i]), i}; + } + targetMap.putIfAbsent(target - nums[i], i); + } + + + + return null; + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file From ca4b1d16c474f0d24b7c7a1cf039d7a98dfe5936 Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 7 Feb 2021 22:17:43 +0800 Subject: [PATCH 02/15] the second week homework. --- Week_02/BinaryTreeInorderTraversal.java | 138 +++++++++++++++++++++ Week_02/BinaryTreePreorderTraversal.java | 145 +++++++++++++++++++++++ Week_02/GroupAnagrams.java | 96 +++++++++++++++ Week_02/NAryTreePreorderTraversal.java | 122 +++++++++++++++++++ Week_02/README.md | 24 +++- Week_02/ValidAnagram.java | 105 ++++++++++++++++ 6 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 Week_02/BinaryTreeInorderTraversal.java create mode 100644 Week_02/BinaryTreePreorderTraversal.java create mode 100644 Week_02/GroupAnagrams.java create mode 100644 Week_02/NAryTreePreorderTraversal.java create mode 100644 Week_02/ValidAnagram.java diff --git a/Week_02/BinaryTreeInorderTraversal.java b/Week_02/BinaryTreeInorderTraversal.java new file mode 100644 index 00000000..bd454d89 --- /dev/null +++ b/Week_02/BinaryTreeInorderTraversal.java @@ -0,0 +1,138 @@ +//题号:94 +//https://leetcode-cn.com/problems/binary-tree-inorder-traversal/ +//给定一个二叉树的根节点 root ,返回它的 中序 遍历。 +// +// +// +// 示例 1: +// +// +//输入:root = [1,null,2,3] +//输出:[1,3,2] +// +// +// 示例 2: +// +// +//输入:root = [] +//输出:[] +// +// +// 示例 3: +// +// +//输入:root = [1] +//输出:[1] +// +// +// 示例 4: +// +// +//输入:root = [1,2] +//输出:[2,1] +// +// +// 示例 5: +// +// +//输入:root = [1,null,2] +//输出:[1,2] +// +// +// +// +// 提示: +// +// +// 树中节点数目在范围 [0, 100] 内 +// -100 <= Node.val <= 100 +// +// +// +// +// 进阶: 递归算法很简单,你可以通过迭代算法完成吗? +// Related Topics 栈 树 哈希表 +// 👍 853 👎 0 + + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class BinaryTreeInorderTraversal { + public static void main(String[] args) { + Solution solution = new BinaryTreeInorderTraversal().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + + LinkedList result = new LinkedList<>(); + + public List inorderTraversal(TreeNode root) { + + /** + * 法一:递归 + */ + +// if (root == null) { +// return result; +// } +// +// inorderTraversal(root.left); +// result.add(root.val); +// inorderTraversal(root.right); +// +// return result; + + /** + * 法二:非递归 + */ + + LinkedList res = new LinkedList<>(); + Deque stack = new LinkedList<>(); + + while (root != null || !stack.isEmpty()) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + res.add(root.val); + root = root.right; + + } + + return res; + + + } +} +//leetcode submit region end(Prohibit modification and deletion) +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + } \ No newline at end of file diff --git a/Week_02/BinaryTreePreorderTraversal.java b/Week_02/BinaryTreePreorderTraversal.java new file mode 100644 index 00000000..ffa43753 --- /dev/null +++ b/Week_02/BinaryTreePreorderTraversal.java @@ -0,0 +1,145 @@ +//题号:144 +//https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ +//给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 +// +// +// +// 示例 1: +// +// +//输入:root = [1,null,2,3] +//输出:[1,2,3] +// +// +// 示例 2: +// +// +//输入:root = [] +//输出:[] +// +// +// 示例 3: +// +// +//输入:root = [1] +//输出:[1] +// +// +// 示例 4: +// +// +//输入:root = [1,2] +//输出:[1,2] +// +// +// 示例 5: +// +// +//输入:root = [1,null,2] +//输出:[1,2] +// +// +// +// +// 提示: +// +// +// 树中节点数目在范围 [0, 100] 内 +// -100 <= Node.val <= 100 +// +// +// +// +// 进阶:递归算法很简单,你可以通过迭代算法完成吗? +// Related Topics 栈 树 +// 👍 511 👎 0 + + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class BinaryTreePreorderTraversal { + public static void main(String[] args) { + Solution solution = new BinaryTreePreorderTraversal().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + + LinkedList res = new LinkedList<>(); + + public List preorderTraversal(TreeNode root) { + + /** + * 法一:递归 + */ + +// if (root == null) { +// return res; +// } +// +// res.add(root.val); +// preorderTraversal(root.left); +// preorderTraversal(root.right); +// +// return res; + + + /** + * 法二:迭代 + */ + + LinkedList res = new LinkedList<>(); + if (root == null) { + return res; + } + + Deque stack = new LinkedList<>(); + TreeNode node = root; + + while (!stack.isEmpty() || node != null) { + while (node != null) { + res.add(node.val); + stack.push(node); + node = node.left; + } + + node = stack.pop(); + node = node.right; + + } + + + return res; + + } +} +//leetcode submit region end(Prohibit modification and deletion) +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + } \ No newline at end of file diff --git a/Week_02/GroupAnagrams.java b/Week_02/GroupAnagrams.java new file mode 100644 index 00000000..1fd3f0cf --- /dev/null +++ b/Week_02/GroupAnagrams.java @@ -0,0 +1,96 @@ +//题号:49 +// +//给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 +// +// 示例: +// +// 输入: ["eat", "tea", "tan", "ate", "nat", "bat"] +//输出: +//[ +// ["ate","eat","tea"], +// ["nat","tan"], +// ["bat"] +//] +// +// 说明: +// +// +// 所有输入均为小写字母。 +// 不考虑答案输出的顺序。 +// +// Related Topics 哈希表 字符串 +// 👍 652 👎 0 + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GroupAnagrams { + public static void main(String[] args) { + Solution solution = new GroupAnagrams().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List> groupAnagrams(String[] strs) { + + /** + * 法一:排序+hashMap + * + * 时间复杂度O(nlogn),空间复杂度O(n) + * + */ + +// if (strs == null || strs.length == 0) { +// return new ArrayList<>(); +// } +// +// Map> map = new HashMap<>(); +// for (String s : strs) { +// char[] ca = s.toCharArray(); +// Arrays.sort(ca); +// String keyStr = String.valueOf(ca); +// if (!map.containsKey(keyStr)) { +// map.put(keyStr, new ArrayList<>()); +// } +// map.get(keyStr).add(s); +// } +// +// return new ArrayList<>(map.values()); + + + /** + * 法二:无排序,自建key值 + * + * 时间复杂度O(n*26),空间复杂度O(n) + * + */ + + if (strs == null || strs.length == 0) { + return new ArrayList<>(); + } + + Map> map = new HashMap<>(); + for (String s : strs) { + + char[] ca = new char[26]; + for (char c : s.toCharArray()) { + ca[c - 'a']++; + } + + String keyStr = String.valueOf(ca); + if (!map.containsKey(keyStr)) { + map.put(keyStr, new ArrayList<>()); + } + map.get(keyStr).add(s); + } + + return new ArrayList<>(map.values()); + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_02/NAryTreePreorderTraversal.java b/Week_02/NAryTreePreorderTraversal.java new file mode 100644 index 00000000..a3e78d27 --- /dev/null +++ b/Week_02/NAryTreePreorderTraversal.java @@ -0,0 +1,122 @@ +//题号:589 +//https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/ +//给定一个 N 叉树,返回其节点值的前序遍历。 +// +// 例如,给定一个 3叉树 : +// +// +// +// +// +// +// +// 返回其前序遍历: [1,3,5,6,2,4]。 +// +// +// +// 说明: 递归法很简单,你可以使用迭代法完成此题吗? Related Topics 树 +// 👍 137 👎 0 + + +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; + +public class NAryTreePreorderTraversal { + public static void main(String[] args) { + Solution solution = new NAryTreePreorderTraversal().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/* +// Definition for a Node. +class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } +}; +*/ + +class Solution { + + LinkedList res = new LinkedList<>(); + + + public List preorder(Node root) { + + + /** + * 法一,递归 时间复杂度O(n2),空间复杂度O(n) + * 树的递归怎么缓存? + */ + +// if (root == null) { +// return res; +// } +// //递归遍历整个树,将根的值放入结果中 +// res.add(root.val); +// for (Node children : root.children) { +// //每个孩子也是根,直接递归遍历 +// preorder(children); +// } +// return res; + + + /** + * 法二:非递归 + * + * 自己维护一个栈模拟递归过程 + * + * 时间复杂度O(n2),空间复杂度O(n) + * + * + */ + + if (root == null) { + return res; + } + + Stack temp = new Stack<>(); + temp.add(root); + while (!temp.isEmpty()) { + root = temp.pop(); + res.add(root.val); + for (int i = root.children.size() - 1; i >= 0; i--) { + if (root.children.get(i) != null) { + //利用栈先进后出的特性,减少一次反转操作 + temp.add(root.children.get(i)); + } + } + } + + return res; + + } + +} +//leetcode submit region end(Prohibit modification and deletion) +class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } +}; + } \ No newline at end of file diff --git a/Week_02/README.md b/Week_02/README.md index 50de3041..49a29515 100644 --- a/Week_02/README.md +++ b/Week_02/README.md @@ -1 +1,23 @@ -学习笔记 \ No newline at end of file +学习笔记 + +##### 课后作业: + +HashMap: + +get(Object key):哈希表为了解决哈希冲突,有如下解决办法 +(1)二次映射法。 +(2)哈希函数映射的同一个位置用其他数据结构存储,通常是链表,但是当冲突比较严重时,链表过长,这时候查询的时间 +复杂度会无线趋近于O(n),为了高效查询,会引入红黑树(时间复杂度O(logn))来存储。查的时候需要比对两个值,一个是hash值,当hash +值相等代表映射到了同一个内存地址,然后会比对对应的值是否相等,当两者都相等才判定是同一元素。 + +put(K key, V value) +java里的哈希表支持动态扩容,在++size > threshold时会调用resize方法将size变成原来的两倍, +当同一个哈希函数映射地址总节点大于等于8的时候会将链表转换成红黑树存储,以保证查询的高效性, +而当红黑树节点删减到小于6则会将红黑树转化成链表,节省空间。(java的源码真的不愧为工业级的,里面确实有很多细节性的优化啊) + +##### 本周总结 +树和图这两个数据结构当时大学的时候就学的不是很好,这周听超哥的课感觉清晰了很多,以前树的前中后序遍历都不怎么会, +主要是试图人肉递归,很容易把自己绕进去,现在直接背模板,然后让时间消化,一下过程简单了很多。虽然还是有部分理解知识点有疑问, +我理解是遍数过的不够导致的,后续题做多了应该就没啥问题了。 + +第二周比第一周适应点了,下周加油!! \ No newline at end of file diff --git a/Week_02/ValidAnagram.java b/Week_02/ValidAnagram.java new file mode 100644 index 00000000..4b9f2f85 --- /dev/null +++ b/Week_02/ValidAnagram.java @@ -0,0 +1,105 @@ +//题号:242 +//https://leetcode-cn.com/problems/valid-anagram/ +//给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 +// +// 示例 1: +// +// 输入: s = "anagram", t = "nagaram" +//输出: true +// +// +// 示例 2: +// +// 输入: s = "rat", t = "car" +//输出: false +// +// 说明: +//你可以假设字符串只包含小写字母。 +// +// 进阶: +//如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? +// Related Topics 排序 哈希表 +// 👍 336 👎 0 + + +import java.util.Arrays; + +public class ValidAnagram { + public static void main(String[] args) { + Solution solution = new ValidAnagram().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean isAnagram(String s, String t) { + + /** + * 法一:直接用封装的hashMap + * 时间复杂度O(n),空间复杂度O(n) + */ + +// if (s.length() != t.length()) { +// return false; +// } +// +// Map charMap = new HashMap<>(); +// for (char c : s.toCharArray()) { +// charMap.put(c, charMap.getOrDefault(c, 0) + 1); +// } +// +// for (char c : t.toCharArray()) { +// int count = charMap.getOrDefault(c, -1) - 1; +// if (count < 0) { +// return false; +// } +// charMap.put(c, count); +// } +// return true; + + + /** + * 法二:用数组代表哈希表 + * 哈希表主要是key-value映射能为我们省去大量查询时间,这种数据量很大的 + * 情况下确实很有用,但对于数据量和mapping关系已知(26个字母)的情况下,我们可 + * 以用数组替代,从而省去了mapping的操作 + * + * 时间复杂度O(n),空间复杂度O(1) 26 + * + */ + +// if (s.length() != t.length()) { +// return false; +// } +// +// int[] table = new int[26]; +// +// for (char c : s.toCharArray()) { +// table[c - 'a']++; +// } +// +// for (char c : t.toCharArray()) { +// if (--table[c - 'a'] < 0) { +// return false; +// } +// } +// +// return true; + + /** + * 法三,排序直接比较,复杂度太高,不推荐 + * 时间复杂度O(nlogn),空间复杂度O(logn) + */ + + if (s.length() != t.length()) { + return false; + } + char[] str1 = s.toCharArray(); + char[] str2 = t.toCharArray(); + Arrays.sort(str1); + Arrays.sort(str2); + return Arrays.equals(str1, str2); + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From e9ab3256823e72faebe0a674a248cab01205825b Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 21 Feb 2021 22:08:44 +0800 Subject: [PATCH 03/15] the third week homework. --- Week_03/Combinations.java | 124 +++++++++++++++ ...ryTreeFromPreorderAndInorderTraversal.java | 143 ++++++++++++++++++ .../ErChaShuDeZuiJinGongGongZuXianLcof.java | 105 +++++++++++++ Week_03/Permutations.java | 67 ++++++++ Week_03/PermutationsIi.java | 74 +++++++++ Week_03/README.md | 30 +++- 6 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 Week_03/Combinations.java create mode 100644 Week_03/ConstructBinaryTreeFromPreorderAndInorderTraversal.java create mode 100644 Week_03/ErChaShuDeZuiJinGongGongZuXianLcof.java create mode 100644 Week_03/Permutations.java create mode 100644 Week_03/PermutationsIi.java diff --git a/Week_03/Combinations.java b/Week_03/Combinations.java new file mode 100644 index 00000000..b795b230 --- /dev/null +++ b/Week_03/Combinations.java @@ -0,0 +1,124 @@ +//题号:77 +//https://leetcode-cn.com/problems/combinations/ +//给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 +// +// 示例: +// +// 输入: n = 4, k = 2 +//输出: +//[ +// [2,4], +// [3,4], +// [2,3], +// [1,2], +// [1,3], +// [1,4], +//] +// Related Topics 回溯算法 +// 👍 499 👎 0 + + +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +public class Combinations { + public static void main(String[] args) { + Solution solution = new Combinations().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List> combine(int n, int k) { + + /** + * 递归法一 + */ +// List> res = new LinkedList<>(); +// if (k <= 0 || n < k) { +// return res; +// } +// +// Deque path = new LinkedList<>(); +// +// +// recur(1,n, k, path,res); +// +// +// return res; + + /** + * 递归法二: + */ + + +// List> res = new LinkedList<>(); +// recur1(res, new ArrayList<>(), 1, n, k); +// +// return res; + + /** + * 法三 + */ + + List> result = new ArrayList>(); + if (k > n || k < 0) { + return result; + } + if (k == 0) { + result.add(new ArrayList<>()); + return result; + } + result = combine(n - 1, k - 1); + for (List list : result) { + list.add(n); + } + result.addAll(combine(n - 1, k)); + return result; + + + + + + + + + + + } + + private void recur1(List> res, ArrayList subRes, int deep, int n, int k) { + if (k == 0) { + res.add(new ArrayList<>(subRes)); + return; + } + + for (int i = deep; i <= n - k + 1; i++) { + subRes.add(i); + recur1(res, subRes, i + 1, n, k - 1); + subRes.remove(subRes.size() - 1); + } + } + + private void recur(int deep, int n, int k, Deque path, List> res) { + if (k == 0) { + res.add(new ArrayList<>(path)); + return; + } + if (deep > n - k + 1) { + return; + } + + // 不选当前考虑的数 begin,直接递归到下一层 + recur(deep + 1, n, k, path, res); + + // 不选当前考虑的数 begin,递归到下一层的时候 k - 1,这里 k 表示还需要选多少个数 + path.addLast(deep); + recur(deep + 1, n, k - 1, path, res); + + //清楚状态 + path.removeLast(); + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_03/ConstructBinaryTreeFromPreorderAndInorderTraversal.java b/Week_03/ConstructBinaryTreeFromPreorderAndInorderTraversal.java new file mode 100644 index 00000000..6feafc65 --- /dev/null +++ b/Week_03/ConstructBinaryTreeFromPreorderAndInorderTraversal.java @@ -0,0 +1,143 @@ +//题号:105 +//https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ +//根据一棵树的前序遍历与中序遍历构造二叉树。 +// +// 注意: +//你可以假设树中没有重复的元素。 +// +// 例如,给出 +// +// 前序遍历 preorder = [3,9,20,15,7] +//中序遍历 inorder = [9,3,15,20,7] +// +// 返回如下的二叉树: +// +// 3 +// / \ +// 9 20 +// / \ +// 15 7 +// Related Topics 树 深度优先搜索 数组 +// 👍 878 👎 0 + + +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +public class ConstructBinaryTreeFromPreorderAndInorderTraversal { + public static void main(String[] args) { + Solution solution = new ConstructBinaryTreeFromPreorderAndInorderTraversal().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + + private Map indexMap = new HashMap<>(); + public TreeNode buildTree(int[] preorder, int[] inorder) { + + /** + * 递归 + * 时间复杂度O(n),空间复杂度O(n) + */ + +// int n = preorder.length; +// //利用map存储节点位置,以方便我们快速查找 +// for (int i = 0; i < n; i++) { +// indexMap.put(inorder[i], i); +// } +// +// return recur(preorder, inorder, 0, n - 1, 0, n - 1); + + /** + * 迭代 + * 时间复杂度O(n),空间复杂度O(n) + */ + + if (preorder == null || preorder.length == 0) { + return null; + } + + TreeNode root = new TreeNode(preorder[0]); + Deque stack = new LinkedList<>(); + stack.push(root); + + int inOrderIndex = 0; + for (int i = 1; i < preorder.length; i++) { + + int preOrderVal = preorder[i]; + TreeNode node = stack.peek(); + if (node.val != inorder[inOrderIndex]) { + node.left = new TreeNode(preOrderVal); + stack.push(node.left); + } else { + while (!stack.isEmpty() && stack.peek().val == inorder[inOrderIndex]) { + node = stack.pop(); + inOrderIndex++; + } + + node.right = new TreeNode(preOrderVal); + stack.push(node.right); + } + } + + return root; + + + } + + private TreeNode recur(int[] preOrder, int[] inOrder, int preOrder_left, int preOrder_right, int inOrder_left, int inOrder_right) { + + // preOrder 为空,直接返回 null + if (preOrder_left > preOrder_right) { + return null; + } + + int preOrder_root = preOrder_left; + int inOrder_root = indexMap.get(preOrder[preOrder_root]); + + + //根节点 + TreeNode root = new TreeNode(preOrder[preOrder_root]); + //左子树中节点数目 + int subLeft = inOrder_root - inOrder_left; + // 递归地构造左子树,并连接到根节点 + // 先序遍历中「从 左边界+1 开始的 subLeft」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素 + root.left = recur(preOrder, inOrder, preOrder_left + 1, preOrder_left + subLeft, inOrder_left, inOrder_root - 1); + + // 递归地构造右子树,并连接到根节点 + // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素 + root.right = recur(preOrder, inOrder, preOrder_left + subLeft + 1, preOrder_right, inOrder_root + 1, inOrder_right); + + return root; + } +} +//leetcode submit region end(Prohibit modification and deletion) +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + } \ No newline at end of file diff --git a/Week_03/ErChaShuDeZuiJinGongGongZuXianLcof.java b/Week_03/ErChaShuDeZuiJinGongGongZuXianLcof.java new file mode 100644 index 00000000..966cb30e --- /dev/null +++ b/Week_03/ErChaShuDeZuiJinGongGongZuXianLcof.java @@ -0,0 +1,105 @@ +//题号:剑指 Offer 68 - II +//https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ +//给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 +// +// 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大( +//一个节点也可以是它自己的祖先)。” +// +// 例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4] +// +// +// +// +// +// 示例 1: +// +// 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +//输出: 3 +//解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 +// +// +// 示例 2: +// +// 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +//输出: 5 +//解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。 +// +// +// +// +// 说明: +// +// +// 所有节点的值都是唯一的。 +// p、q 为不同节点且均存在于给定的二叉树中。 +// +// +// 注意:本题与主站 236 题相同:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a +//-binary-tree/ +// Related Topics 树 +// 👍 191 👎 0 + + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ErChaShuDeZuiJinGongGongZuXianLcof { + public static void main(String[] args) { + Solution solution = new ErChaShuDeZuiJinGongGongZuXianLcof().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + + Map parent = new HashMap<>(); + Set visited = new HashSet<>(); + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + + /** + * 递归 + */ + if (root == null || root == p || root == q) { + return root; + } + + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + if (left == null) { + return right; + } + + if (right == null) { + return left; + } + + return root; + + + + + + } + + private void dfs(TreeNode root) { + + } +} +//leetcode submit region end(Prohibit modification and deletion) +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x) { val = x; } + } + } \ No newline at end of file diff --git a/Week_03/Permutations.java b/Week_03/Permutations.java new file mode 100644 index 00000000..f7ddce7b --- /dev/null +++ b/Week_03/Permutations.java @@ -0,0 +1,67 @@ +//题号:46 +//https://leetcode-cn.com/problems/permutations/ +//给定一个 没有重复 数字的序列,返回其所有可能的全排列。 +// +// 示例: +// +// 输入: [1,2,3] +//输出: +//[ +// [1,2,3], +// [1,3,2], +// [2,1,3], +// [2,3,1], +// [3,1,2], +// [3,2,1] +//] +// Related Topics 回溯算法 +// 👍 1142 👎 0 + + +import java.util.ArrayList; +import java.util.List; + +public class Permutations { + public static void main(String[] args) { + Solution solution = new Permutations().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List> permute(int[] nums) { + + List> res = new ArrayList<>(); + boolean[] visited = new boolean[nums.length]; + recur(res, new ArrayList<>(), visited, nums); + + return res; + + } + + private void recur(List> res, List subRes, boolean[] visited,int[] nums) { + + if (subRes.size() == nums.length) { + res.add(new ArrayList<>(subRes)); + return; + } + + for (int i = 0; i < nums.length; i++) { + //list.contains()这个是O(n)的时间复杂度,可以用数组或map优化为O(1) +// if (subRes.contains(nums[i])) continue;//存在元素则跳过 + if (visited[i]) continue; + visited[i] = true; + subRes.add(nums[i]); + recur(res, subRes,visited,nums); + subRes.remove(subRes.size() - 1); + visited[i] = false; + } + + + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_03/PermutationsIi.java b/Week_03/PermutationsIi.java new file mode 100644 index 00000000..7e3bc219 --- /dev/null +++ b/Week_03/PermutationsIi.java @@ -0,0 +1,74 @@ +//题号:47 +//https://leetcode-cn.com/problems/permutations-ii/ +//给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 +// +// +// +// 示例 1: +// +// +//输入:nums = [1,1,2] +//输出: +//[[1,1,2], +// [1,2,1], +// [2,1,1]] +// +// +// 示例 2: +// +// +//输入:nums = [1,2,3] +//输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +// +// +// +// +// 提示: +// +// +// 1 <= nums.length <= 8 +// -10 <= nums[i] <= 10 +// +// Related Topics 回溯算法 +// 👍 593 👎 0 + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PermutationsIi { + public static void main(String[] args) { + Solution solution = new PermutationsIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List> permuteUnique(int[] nums) { + + List> res = new ArrayList<>(); + Arrays.sort(nums); + boolean[] visited = new boolean[nums.length]; + recur(res, new ArrayList<>(), visited, nums); + + return res; + } + + private void recur(List> res, List subRes, boolean[] visited, int[] nums) { + if (subRes.size() == nums.length) { + res.add(new ArrayList<>(subRes)); + return; + } + + for (int i = 0; i < nums.length; i++) { + if (visited[i]||i>0&&nums[i]==nums[i-1]&&!visited[i-1]) continue; + visited[i] = true; + subRes.add(nums[i]); + recur(res, subRes, visited, nums); + subRes.remove(subRes.size() - 1); + visited[i] = false; + } + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_03/README.md b/Week_03/README.md index 50de3041..bbc6f82a 100644 --- a/Week_03/README.md +++ b/Week_03/README.md @@ -1 +1,29 @@ -学习笔记 \ No newline at end of file +学习笔记 + +##### 本周总结 + +这周学习了递归,了解了递归的模板方法如下: + +- 找到终止条件 +- 当前层逻辑 +- 进入下一层 +- 状态重置 + +套用这个模板递归看着没有以前那么难了,但是对于一个函数,是可以在本身函数 +直接递归还是需要新写一个函数递归,这个我还是感觉很疑惑。 + +另外递归写多了发现树这种结构,好像天生适合递归,前中后序遍历,DFS都可以用递归 +实现。 + +递归这种逻辑有两个算法实现:分治和回溯。 + +*分治*采用的是分而治之的思想,将一个大问题不断拆分成一个个子问题,直到子问题可以直接 +得出答案,然后再将这些子问题答案组装起来,得到最终答案。这种思想在现在的生活中 +很常见,比如著名的map-reduce就是这个思想。 + +*回溯*,我理解就是利用递归,不断的去尝试所有可能的解法,知道找到想要的解法,没有任何 +条件约束的傻回溯,时间复杂度是非常高的,好像是O(2^n),但是我们可以通过一些条件约束 +进行剪枝,这样其实效率会高很多,和正常迭代差不多。 + +春节两周完成一周课程,这周作业时间比较充裕,感觉比前两周好点。 + From 650c28867b2ee7497e4e3327d0a2f883492cc8e0 Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 28 Feb 2021 21:36:34 +0800 Subject: [PATCH 04/15] the fourth week homework. --- Week_04/AssignCookies.java | 75 +++ Week_04/BestTimeToBuyAndSellStockIi.java | 80 ++++ Week_04/BinaryTreeLevelOrderTraversal.java | 219 +++++++++ Week_04/FindLargestValueInEachTreeRow.java | 172 +++++++ Week_04/FindMinimumInRotatedSortedArray.java | 84 ++++ Week_04/GenerateParentheses.java | 281 +++++++++++ Week_04/JumpGame.java | 73 +++ Week_04/JumpGameIi.java | 67 +++ Week_04/LemonadeChange.java | 110 +++++ Week_04/Minesweeper.java | 204 ++++++++ Week_04/MinimumGeneticMutation.java | 307 ++++++++++++ Week_04/NumberOfIslands.java | 130 +++++ Week_04/README.md | 95 +++- Week_04/SearchA2dMatrix.java | 81 ++++ Week_04/SearchInRotatedSortedArray.java | 102 ++++ Week_04/Sqrtx.java | 110 +++++ Week_04/ValidPerfectSquare.java | 74 +++ Week_04/WalkingRobotSimulation.java | 130 +++++ Week_04/WordLadder.java | 471 +++++++++++++++++++ Week_04/WordLadderIi.java | 257 ++++++++++ 20 files changed, 3121 insertions(+), 1 deletion(-) create mode 100644 Week_04/AssignCookies.java create mode 100644 Week_04/BestTimeToBuyAndSellStockIi.java create mode 100644 Week_04/BinaryTreeLevelOrderTraversal.java create mode 100644 Week_04/FindLargestValueInEachTreeRow.java create mode 100644 Week_04/FindMinimumInRotatedSortedArray.java create mode 100644 Week_04/GenerateParentheses.java create mode 100644 Week_04/JumpGame.java create mode 100644 Week_04/JumpGameIi.java create mode 100644 Week_04/LemonadeChange.java create mode 100644 Week_04/Minesweeper.java create mode 100644 Week_04/MinimumGeneticMutation.java create mode 100644 Week_04/NumberOfIslands.java create mode 100644 Week_04/SearchA2dMatrix.java create mode 100644 Week_04/SearchInRotatedSortedArray.java create mode 100644 Week_04/Sqrtx.java create mode 100644 Week_04/ValidPerfectSquare.java create mode 100644 Week_04/WalkingRobotSimulation.java create mode 100644 Week_04/WordLadder.java create mode 100644 Week_04/WordLadderIi.java diff --git a/Week_04/AssignCookies.java b/Week_04/AssignCookies.java new file mode 100644 index 00000000..4b2f7611 --- /dev/null +++ b/Week_04/AssignCookies.java @@ -0,0 +1,75 @@ +//题号:455 +//https://leetcode-cn.com/problems/assign-cookies/description/ +//假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 +// +// 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i +//],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 +// +// +// 示例 1: +// +// +//输入: g = [1,2,3], s = [1,1] +//输出: 1 +//解释: +//你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 +//虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 +//所以你应该输出1。 +// +// +// 示例 2: +// +// +//输入: g = [1,2], s = [1,2,3] +//输出: 2 +//解释: +//你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 +//你拥有的饼干数量和尺寸都足以让所有孩子满足。 +//所以你应该输出2. +// +// +// +// +// 提示: +// +// +// 1 <= g.length <= 3 * 104 +// 0 <= s.length <= 3 * 104 +// 1 <= g[i], s[j] <= 231 - 1 +// +// Related Topics 贪心算法 +// 👍 306 👎 0 + + +import java.util.Arrays; + +public class AssignCookies { + public static void main(String[] args) { + Solution solution = new AssignCookies().new Solution(); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int findContentChildren(int[] g, int[] s) { + + + //g是孩子胃口,s是饼干,排序时间复杂度是nlogn + Arrays.sort(g); + Arrays.sort(s); + + int child = 0, cookie = 0; + + while (child < g.length && cookie < s.length) { + //一次遍历饼干值,如果大于孩子胃口则满足孩子+1 + if (s[cookie] >= g[child]) { + child++; + } + cookie++; + } + + return child; + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/BestTimeToBuyAndSellStockIi.java b/Week_04/BestTimeToBuyAndSellStockIi.java new file mode 100644 index 00000000..b5f67d1b --- /dev/null +++ b/Week_04/BestTimeToBuyAndSellStockIi.java @@ -0,0 +1,80 @@ +//题号:122 +//https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ +//给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 +// +// 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 +// +// 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 +// +// +// +// 示例 1: +// +// 输入: [7,1,5,3,6,4] +//输出: 7 +//解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 +//  随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 +// +// +// 示例 2: +// +// 输入: [1,2,3,4,5] +//输出: 4 +//解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 +//  注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 +//  因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 +// +// +// 示例 3: +// +// 输入: [7,6,4,3,1] +//输出: 0 +//解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 +// +// +// +// 提示: +// +// +// 1 <= prices.length <= 3 * 10 ^ 4 +// 0 <= prices[i] <= 10 ^ 4 +// +// Related Topics 贪心算法 数组 +// 👍 1101 👎 0 + + +public class BestTimeToBuyAndSellStockIi { + public static void main(String[] args) { + Solution solution = new BestTimeToBuyAndSellStockIi().new Solution(); + solution.maxProfit(new int[]{7, 1, 5, 3, 6, 4}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int maxProfit(int[] prices) { + int profit = 0; + //因为i-1,所以i从1开始 + for (int i = 1; i < prices.length; i++) { + int temp = prices[i] - prices[i - 1]; + if (temp > 0) { + profit += temp; + } + } + return profit; + + +// int profit = 0, i = 0; +// while (i < prices.length) { +// // find next local minimum +// while (i < prices.length-1 && prices[i+1] <= prices[i]) i++; +// int min = prices[i++]; // need increment to avoid infinite loop for "[1]" +// // find next local maximum +// while (i < prices.length-1 && prices[i+1] >= prices[i]) i++; +// profit += i < prices.length ? prices[i++] - min : 0; +// } +// return profit; + } + + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/BinaryTreeLevelOrderTraversal.java b/Week_04/BinaryTreeLevelOrderTraversal.java new file mode 100644 index 00000000..2402b470 --- /dev/null +++ b/Week_04/BinaryTreeLevelOrderTraversal.java @@ -0,0 +1,219 @@ +//题号:102 +//https://leetcode-cn.com/problems/binary-tree-level-order-traversal/#/description +//给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 +// +// +// +// 示例: +//二叉树:[3,9,20,null,null,15,7], +// +// +// 3 +// / \ +// 9 20 +// / \ +// 15 7 +// +// +// 返回其层序遍历结果: +// +// +//[ +// [3], +// [9,20], +// [15,7] +//] +// +// Related Topics 树 广度优先搜索 +// 👍 784 👎 0 + + +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class BinaryTreeLevelOrderTraversal { + public static void main(String[] args) { + Solution solution = new BinaryTreeLevelOrderTraversal().new Solution(); +// 0,2,4,1,null,3,-1,5,1,null,6,null,8 + solution.levelOrder(new TreeNode(0, new TreeNode(2, new TreeNode(1, new TreeNode(5), new TreeNode(1)), null), new TreeNode(4, new TreeNode(3, null, new TreeNode(6)), new TreeNode(-1, null, new TreeNode(8))))); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public List> levelOrder(TreeNode root) { + + + + + + + /** + * 法一:直接BFS,得到每层的数量在遍历 + * 时间复杂度O(n),空间复杂度O(n) + */ + +// List> res = new ArrayList<>(); +// //root为空直接返回 +// if (root == null) { +// return res; +// } +// +// //BFS是queue,DFS是stack +// Deque queue = new LinkedList<>(); +// +// queue.offer(root); +// +// while (!queue.isEmpty()) { +// +// int size = queue.size(); +// List subRes = new ArrayList<>(); +// +// //拿到size其实就是将树分层, +// //然后得到size对应的层级信息 +// for (int i = 0; i < size; i++) { +// +// TreeNode node = queue.poll(); +// subRes.add(node.val); +// +// if (node.left != null) { +// queue.offer(node.left); +// } +// +// if (node.right != null) { +// queue.offer(node.right); +// } +// +// } +// +// res.add(subRes); +// +// } +// +// return res; + + + /** + * 法二:BFS,不要size,新建一个队列装每层数据,遍历完之后再把队列赋值回去 + * 时间复杂度O(n),空间复杂度O(n) + */ + List> res = new ArrayList<>(); + + if (root == null) { + return res; + } + + //BFS是queue,DFS是stack + Deque queue = new LinkedList<>(); + + queue.offer(root); + + while (!queue.isEmpty()) { + + List subRes = new ArrayList<>(); + + Deque tempQueue = new LinkedList<>(); + + while(!queue.isEmpty()) { + + TreeNode node = queue.poll(); + subRes.add(node.val); + + if (node.left != null) { + tempQueue.offer(node.left); + } + + if (node.right != null) { + tempQueue.offer(node.right); + } + + } + + res.add(subRes); + queue = tempQueue; + + } + + return res; + + + /** + * dfs + * 时间复杂度O(n),空间复杂度O(n) + */ + +// List> res = new ArrayList<>(); +// +// if (root == null) { +// return res; +// } +// +// dfs(root, 0, res); +// +// +// return res; + + + + + } + + private void dfs(TreeNode node, int level, List> res) { + + + /** + * 这儿为啥没有出口啊?难道根据下探时的if判断可以直接找到出口? + * 出口是node.left和node.right为空 + */ + + //没遍历到新的一层,创建一个子list供装数据 + //为防止res.get(level)报空。因此必须要这一步 + if (res.size()==level) { + res.add(new ArrayList<>()); + } + + //当前层逻辑 + res.get(level).add(node.val); + + //下探到下一层 + if (node.left != null) { + dfs(node.left, level + 1, res); + } + + if (node.right != null) { + dfs(node.right, level + 1, res); + } + + //重置状态,这里没有状态改变,所以不用 + + } +} +//leetcode submit region end(Prohibit modification and deletion) +public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + } \ No newline at end of file diff --git a/Week_04/FindLargestValueInEachTreeRow.java b/Week_04/FindLargestValueInEachTreeRow.java new file mode 100644 index 00000000..1c1ddbae --- /dev/null +++ b/Week_04/FindLargestValueInEachTreeRow.java @@ -0,0 +1,172 @@ +//题号:515 +//https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/#/description +//您需要在二叉树的每一行中找到最大的值。 +// +// 示例: +// +// +//输入: +// +// 1 +// / \ +// 3 2 +// / \ \ +// 5 3 9 +// +//输出: [1, 3, 9] +// +// Related Topics 树 深度优先搜索 广度优先搜索 +// 👍 125 👎 0 + + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class FindLargestValueInEachTreeRow { + public static void main(String[] args) { + Solution solution = new FindLargestValueInEachTreeRow().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public List largestValues(TreeNode root) { + + /** + * 一开始想到的应该是BFS + * 时间复杂度O(n),空间复杂度O(n),树的深度 + */ + +// List res = new ArrayList<>(); +// if (root == null) { +// return res; +// } +// +// Deque queue = new LinkedList<>(); +// queue.offer(root); +// +// while (!queue.isEmpty()) { +// int max = Integer.MIN_VALUE; +// int size = queue.size(); +// 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 + */ + + + List res = new ArrayList<>(); + +// if (root == null) { +// return res; +// } + + //也是傻,没必要用个map去装,直接在res里面操作就行 +// Map levelMaxMap = new TreeMap<>(); +// dfs(1, res, root, levelMaxMap); +// +// for (Integer i : levelMaxMap.keySet()) { +// res.add(levelMaxMap.get(i)); +// } + + dfsOpt(0, res, root); + + + return res; + + + + } + + private void dfsOpt(int level, List res, TreeNode node) { + + if (node == null) { + return; + } + + //当前层逻辑 + //递归是从浅到深,因此第一条路遍历完会将res初始化完 + if (res.size() == level) { + res.add(node.val); + } else { + //习惯写成了add(index,element)这种,它会在index位置加入一个元素,而不是替代,找了好久原因 +// res.add(level, Math.max(res.get(level), node.val)); + res.set(level, Math.max(res.get(level), node.val)); + } + + //下探 + //这里的!=null其实也相当于终止条件了,正常模板下探前没有!=null判断, + // 然后是在递归开始判断if(node==null) return;这里其实没什么区别,就是不进下一层和进下一层直接退出而已 +// if (node.left != null) { + dfsOpt(level + 1, res, node.left); +// } + +// if (node.right != null) { + dfsOpt(level + 1, res, node.right); +// } + + + + } + + private void dfs(int level, List res, TreeNode node, Map levelMaxMap) { + + + + //当前层逻辑 + int maxLevel = levelMaxMap.getOrDefault(level, Integer.MIN_VALUE); + levelMaxMap.put(level, Math.max(node.val, maxLevel)); + + //这儿的!=null其实也是终止条件 + if (node.left != null) { + dfs(level + 1, res, node.left, levelMaxMap); + } + + if (node.right != null) { + dfs(level + 1, res, node.right, levelMaxMap); + } + + + } +} +//leetcode submit region end(Prohibit modification and deletion) +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + } \ No newline at end of file diff --git a/Week_04/FindMinimumInRotatedSortedArray.java b/Week_04/FindMinimumInRotatedSortedArray.java new file mode 100644 index 00000000..135b3041 --- /dev/null +++ b/Week_04/FindMinimumInRotatedSortedArray.java @@ -0,0 +1,84 @@ +//题号:153 +//假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。 +// +// 请找出其中最小的元素。 +// +// +// +// 示例 1: +// +// +//输入:nums = [3,4,5,1,2] +//输出:1 +// +// +// 示例 2: +// +// +//输入:nums = [4,5,6,7,0,1,2] +//输出:0 +// +// +// 示例 3: +// +// +//输入:nums = [1] +//输出:1 +// +// +// +// +// 提示: +// +// +// 1 <= nums.length <= 5000 +// -5000 <= nums[i] <= 5000 +// nums 中的所有整数都是 唯一 的 +// nums 原来是一个升序排序的数组,但在预先未知的某个点上进行了旋转 +// +// Related Topics 数组 二分查找 +// 👍 357 👎 0 + + +public class FindMinimumInRotatedSortedArray { + public static void main(String[] args) { + Solution solution = new FindMinimumInRotatedSortedArray().new Solution(); + solution.findMin(new int[]{11,13,15,17}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int findMin(int[] nums) { + + + /** + * 这个题的思路其实就是找到有序区间的最大值,然后最大值下一位就是旋转位,也即是最小值 + * 注意边界判断 + */ + + if (nums[nums.length - 1] > nums[0]) { + return nums[0]; + } + + int left = 0, right = nums.length - 1; + + while (left <= right) { + + int mid = left + (right - left) / 2; + + if (mid + 1 < nums.length && nums[mid] > nums[mid + 1]) { + return nums[mid + 1]; + } else if (nums[mid] >= nums[0]) { + left = mid + 1; + } else { + right = mid; + } + } + + + + return nums[right]; + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/GenerateParentheses.java b/Week_04/GenerateParentheses.java new file mode 100644 index 00000000..d3f008fc --- /dev/null +++ b/Week_04/GenerateParentheses.java @@ -0,0 +1,281 @@ +//题号:22 +//https://leetcode-cn.com/problems/generate-parentheses/ +//数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 +// +// +// +// 示例 1: +// +// +//输入:n = 3 +//输出:["((()))","(()())","(())()","()(())","()()()"] +// +// +// 示例 2: +// +// +//输入:n = 1 +//输出:["()"] +// +// +// +// +// 提示: +// +// +// 1 <= n <= 8 +// +// Related Topics 字符串 回溯算法 +// 👍 1565 👎 0 + + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class GenerateParentheses { + public static void main(String[] args) { + Solution solution = new GenerateParentheses().new Solution(); + solution.generateParenthesis(3); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List generateParenthesis(int n) { + +// List res = new ArrayList<>(); +// recur2(0, 0, n, res, ""); +// +// return res; + + /** + * dfs + */ + +// List res = new ArrayList<>(); +// dfs(0, 0, n, res, ""); +// +// return res; + + + /** + * bfs + * 有的题解说下面这种解法是BFS, + * 但是我看着不像BFS,因为它没有拿到当前queue的size去遍历的过程,也就是没有每层去 + * 横扫所有节点的过程,看着就像是dfs的迭代写法,只是stack用了queue, + * 这里取出顺序其实并没什么影响 + * + */ + +// List res = new ArrayList<>(); +// if (n == 0) { +// return res; +// } +// Queue queue = new LinkedList<>(); +// queue.offer(new Node("", 0, 0)); +// +// while (!queue.isEmpty()) { +// +// int size = queue.size(); +// Node curNode = queue.poll(); +//// for (int i = 0; i < size; i++) { +// if (curNode.left == n && curNode.right == n) { +// res.add(curNode.res); +// } +// if (curNode.left < n) { +// queue.offer(new Node(curNode.res + "(", curNode.left + 1, curNode.right)); +// } +// if (curNode.left > curNode.right) { +// queue.offer(new Node(curNode.res + ")", curNode.left, curNode.right + 1)); +// } +//// } +// } +// return res; + + + /** + * 这个应该才是真正的bfs + */ + Deque strQ = new LinkedList<>(); + Deque leftQ = new LinkedList<>(); + Deque rightQ = new LinkedList<>(); + + strQ.offer(""); + leftQ.offer(0); + rightQ.offer(0); + + //下面两种终止条件判断都可以 + while (leftQ.peek() + rightQ.peek() < 2 * n) { +// while (strQ.peek().length() < 2 * n) { + int size = strQ.size(); + while (size-- > 0) { + String cur = strQ.poll(); + int left = leftQ.poll(); + int right = rightQ.poll(); + if (left < n) { + + strQ.offer(cur + "("); + leftQ.offer(left + 1); + rightQ.offer(right); + } + + if (right < left) { + strQ.offer(cur + ")"); + leftQ.offer(left); + rightQ.offer(right + 1); + } + + } + } + /** + * 因为这种情况下会每层去加括号并且会把上一层的取出来, + * 最后得到的queue就是最后一层的括号值 + */ + return (List) strQ; + + + + + +// List result = new LinkedList<>(); +// +// //第一层初始应该是空串,因为出口判断条件不一样,因此max也不一样 +// recur1(0, 0, n, result, ""); +//// recur(0,2 * 3, result, ""); +// +// +// return result; + } + + private void dfs(int left, int right, int max, List res, String s) { + + if (s.length() == max * 2) { + res.add(s); + return; + } + + //当前层逻辑 + if (left < max) { + dfs(left + 1, right, max, res, s+"("); + } + + if (right < left) { + dfs(left, right + 1, max, res, s+")"); + } + + + + + } + + + + + + + + + + + + + + + + + private void recur2(int left, int right, int n, List res, String s) { + + //结束条件 + if (left == n && right == n) { + res.add(s); + return; + } + + //当前逻辑 + //进入下一层,加上条件剪枝 + if (left result,String str){ + + + if (deep == max) { + result.add(str); + System.out.println(str); + return; + } + + //current logic + + //drop down + /** + * 这儿对应递归两种可能,左分支和右分支 + */ + recur(deep + 1, max, result, str+ "("); + recur(deep + 1, max, result, str+ ")"); + + + } + + + /** + * 条件判断可以直接在递归过程中判断, + * 1、左括号可以随便加,只要不超过做大限制即可 + * 2、右括号必须在有左括号的时候才能加,右括号<左括号 + */ + public void recur1(int left, int right, int max, List result, String str) { + + if (left == max && right == max) { +// if (str.length() == max * 2) { + result.add(str); + System.out.println(str); + return; + } + + //current logic + + //drop down + /** + * 这儿对应递归两种可能,左分支和右分支 + * 可以加些条件进行剪枝 + */ + if (left < max) + recur1(left + 1, right, max, result, str + "("); + if (right < left) + recur1(left, right + 1, max, result, str + ")"); + + + } + + class Node { + /** + * 当前得到的字符串 + */ + private String res; + /** + * 剩余左括号数量 + */ + private int left; + /** + * 剩余右括号数量 + */ + private int right; + + public Node(String str, int left, int right) { + this.res = str; + this.left = left; + this.right = right; + } + } + + + + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/JumpGame.java b/Week_04/JumpGame.java new file mode 100644 index 00000000..5e8d08d5 --- /dev/null +++ b/Week_04/JumpGame.java @@ -0,0 +1,73 @@ +//题号:55 +//https://leetcode-cn.com/problems/jump-game/ +//给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 +// +// 数组中的每个元素代表你在该位置可以跳跃的最大长度。 +// +// 判断你是否能够到达最后一个下标。 +// +// +// +// 示例 1: +// +// +//输入:nums = [2,3,1,1,4] +//输出:true +//解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 +// +// +// 示例 2: +// +// +//输入:nums = [3,2,1,0,4] +//输出:false +//解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。 +// +// +// +// +// 提示: +// +// +// 1 <= nums.length <= 3 * 104 +// 0 <= nums[i] <= 105 +// +// Related Topics 贪心算法 数组 +// 👍 1063 👎 0 + + +public class JumpGame { + public static void main(String[] args) { + Solution solution = new JumpGame().new Solution(); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public boolean canJump(int[] nums) { + + /** + * 从后依次往前跳 + */ + if (nums == null) { + return false; + } + + int endReach = nums.length - 1; + for (int i = nums.length - 1; i >= 0; i--) { + if (nums[i] + i >= endReach) { + endReach = i; + } + } + + return endReach == 0; + + + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/JumpGameIi.java b/Week_04/JumpGameIi.java new file mode 100644 index 00000000..3f589d36 --- /dev/null +++ b/Week_04/JumpGameIi.java @@ -0,0 +1,67 @@ +//题号:45 +//给定一个非负整数数组,你最初位于数组的第一个位置。 +// +// 数组中的每个元素代表你在该位置可以跳跃的最大长度。 +// +// 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 +// +// 示例: +// +// 输入: [2,3,1,1,4] +//输出: 2 +//解释: 跳到最后一个位置的最小跳跃数是 2。 +//  从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 +// +// +// 说明: +// +// 假设你总是可以到达数组的最后一个位置。 +// Related Topics 贪心算法 数组 +// 👍 839 👎 0 + + +public class JumpGameIi{ + public static void main(String[] args) { + Solution solution = new JumpGameIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int jump(int[] nums) { + + /** + * 贪心算法 + */ +// int step = 0; +// int curEnd = 0; +// int curFarthest = 0; +// for (int i = 0; i < nums.length - 1; i++) { +// curFarthest = Math.max(nums[i] + i, curFarthest); +// if (i == curEnd) { +// step++; +// curEnd = curFarthest; +// } +// } +// +// return step; + + + /** + * BFS,为啥会比贪心快很多? + */ + if(nums.length<2)return 0; + int level=0,currentMax=0,i=0,nextMax=0; + + while(currentMax-i+1>0){ //nodes count of current level>0 + level++; + for(;i<=currentMax;i++){ //traverse current level , and update the max reach of next level + nextMax=Math.max(nextMax,nums[i]+i); + if(nextMax>=nums.length-1)return level; // if last element is in level+1, then the min jump=level + } + currentMax=nextMax; + } + return 0; + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/LemonadeChange.java b/Week_04/LemonadeChange.java new file mode 100644 index 00000000..41d1ed53 --- /dev/null +++ b/Week_04/LemonadeChange.java @@ -0,0 +1,110 @@ +//题号:860 +// +//在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 +// +// 顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。 +// +// 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。 +// +// 注意,一开始你手头没有任何零钱。 +// +// 如果你能给每位顾客正确找零,返回 true ,否则返回 false 。 +// +// 示例 1: +// +// 输入:[5,5,5,10,20] +//输出:true +//解释: +//前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 +//第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 +//第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 +//由于所有客户都得到了正确的找零,所以我们输出 true。 +// +// +// 示例 2: +// +// 输入:[5,5,10] +//输出:true +// +// +// 示例 3: +// +// 输入:[10,10] +//输出:false +// +// +// 示例 4: +// +// 输入:[5,5,10,10,20] +//输出:false +//解释: +//前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 +//对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 +//对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 +//由于不是每位顾客都得到了正确的找零,所以答案是 false。 +// +// +// +// +// 提示: +// +// +// 0 <= bills.length <= 10000 +// bills[i] 不是 5 就是 10 或是 20 +// +// Related Topics 贪心算法 +// 👍 213 👎 0 + + +public class LemonadeChange { + public static void main(String[] args) { + Solution solution = new LemonadeChange().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean lemonadeChange(int[] bills) { + int five = 0, ten = 0; + for (int i : bills) { + if (i == 5) five++; + else if (i == 10) {five--; ten++;} + //这儿有个默认条件是i==20 + else if (ten > 0) {ten--; five--;} + else five -= 3; + if (five < 0) return false; + } + return true; + + + /** + * 记烂账,CRUD + */ +// int five = 0, ten = 0; +// for (int bill:bills) { +// if (bill == 5) { +// five++; +// } else if (bill == 10) { +// if (five == 0) { +// return false; +// } +// five--; +// ten++; +// } else { +// if (five > 0 && ten > 0) { +// five--; +// ten--; +// } else if (five >= 3) { +// five -= 3; +// } else { +// return false; +// } +// } +// } +// return true; + + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/Minesweeper.java b/Week_04/Minesweeper.java new file mode 100644 index 00000000..3e1fe8e9 --- /dev/null +++ b/Week_04/Minesweeper.java @@ -0,0 +1,204 @@ +//题号:529 +//https://leetcode-cn.com/problems/minesweeper/solution/ +//让我们一起来玩扫雷游戏! +// +// 给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷,'E' 代表一个未挖出的空方块,'B' 代表没有相邻(上,下,左,右,和所有4个对角线) +//地雷的已挖出的空白方块,数字('1' 到 '8')表示有多少地雷与这块已挖出的方块相邻,'X' 则表示一个已挖出的地雷。 +// +// 现在给出在所有未挖出的方块中('M'或者'E')的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板: +// +// +// 如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。 +// 如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的未挖出方块都应该被递归地揭露。 +// 如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。 +// 如果在此次点击中,若无更多方块可被揭露,则返回面板。 +// +// +// +// +// 示例 1: +// +// 输入: +// +//[['E', 'E', 'E', 'E', 'E'], +// ['E', 'E', 'M', 'E', 'E'], +// ['E', 'E', 'E', 'E', 'E'], +// ['E', 'E', 'E', 'E', 'E']] +// +//Click : [3,0] +// +//输出: +// +//[['B', '1', 'E', '1', 'B'], +// ['B', '1', 'M', '1', 'B'], +// ['B', '1', '1', '1', 'B'], +// ['B', 'B', 'B', 'B', 'B']] +// +//解释: +// +// +// +// 示例 2: +// +// 输入: +// +//[['B', '1', 'E', '1', 'B'], +// ['B', '1', 'M', '1', 'B'], +// ['B', '1', '1', '1', 'B'], +// ['B', 'B', 'B', 'B', 'B']] +// +//Click : [1,2] +// +//输出: +// +//[['B', '1', 'E', '1', 'B'], +// ['B', '1', 'X', '1', 'B'], +// ['B', '1', '1', '1', 'B'], +// ['B', 'B', 'B', 'B', 'B']] +// +//解释: +// +// +// +// +// +// 注意: +// +// +// 输入矩阵的宽和高的范围为 [1,50]。 +// 点击的位置只能是未被挖出的方块 ('M' 或者 'E'),这也意味着面板至少包含一个可点击的方块。 +// 输入面板不会是游戏结束的状态(即有地雷已被挖出)。 +// 简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。 +// +// Related Topics 深度优先搜索 广度优先搜索 +// 👍 216 👎 0 + + +import java.util.LinkedList; +import java.util.Queue; + +public class Minesweeper { + public static void main(String[] args) { + Solution solution = new Minesweeper().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1}; + int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1}; + + public char[][] updateBoard(char[][] board, int[] click) { + + /** + * dfs + * + * 时间复杂度:O(nm) n 和 mm 分别代表面板的宽和高 + * 空间复杂度:O(nm) + * + */ + + +// int x = click[0], y = click[1]; +// if (board[x][y] == 'M') { +// // 规则 1 +// board[x][y] = 'X'; +// } else{ +// dfs(board, x, y); +// } +// return board; + + /** + * bfs + * 时间复杂度:O(nm) n 和 mm 分别代表面板的宽和高 + * 空间复杂度:O(nm) + */ + + int x = click[0], y = click[1]; + if (board[x][y] == 'M') { + // 规则 1 + board[x][y] = 'X'; + } else{ + bfs(board, x, y); + } + return board; + + + + } + + private void dfs(char[][] board, int x, int y) { + int cnt = 0; + for (int i = 0; i < 8; ++i) { + int tx = x + dirX[i]; + int ty = y + dirY[i]; + if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) { + continue; + } + // 不用判断 M,因为如果有 M 的话游戏已经结束了 + if (board[tx][ty] == 'M') { + ++cnt; + } + } + if (cnt > 0) { + // 规则 3 + board[x][y] = (char) (cnt + '0'); + } else { + // 规则 2 + board[x][y] = 'B'; + for (int i = 0; i < 8; ++i) { + int tx = x + dirX[i]; + int ty = y + dirY[i]; + // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了 + if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length || board[tx][ty] != 'E') { + continue; + } + dfs(board, tx, ty); + } + } + } + + + private void bfs(char[][] board, int sx, int sy) { + Queue queue = new LinkedList(); + boolean[][] visited = new boolean[board.length][board[0].length]; + queue.offer(new int[]{sx, sy}); + visited[sx][sy] = true; + while (!queue.isEmpty()) { + int[] pos = queue.poll(); + int cnt = 0, x = pos[0], y = pos[1]; + for (int i = 0; i < 8; ++i) { + int tx = x + dirX[i]; + int ty = y + dirY[i]; + if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) { + continue; + } + // 不用判断 M,因为如果有 M 的话游戏已经结束了 + if (board[tx][ty] == 'M') { + ++cnt; + } + } + if (cnt > 0) { + // 规则 3 + board[x][y] = (char) (cnt + '0'); + } else { + // 规则 2 + board[x][y] = 'B'; + for (int i = 0; i < 8; ++i) { + int tx = x + dirX[i]; + int ty = y + dirY[i]; + // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了 + if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length || board[tx][ty] != 'E' || visited[tx][ty]) { + continue; + } + queue.offer(new int[]{tx, ty}); + visited[tx][ty] = true; + } + } + } + } + + + + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/MinimumGeneticMutation.java b/Week_04/MinimumGeneticMutation.java new file mode 100644 index 00000000..0f38ce31 --- /dev/null +++ b/Week_04/MinimumGeneticMutation.java @@ -0,0 +1,307 @@ +//题号:433 +// +//一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。 +// +// 假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。 +// +// 例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。 +// +// 与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。 +// +// 现在给定3个参数 — start, end, bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变 +//化次数。如果无法实现目标变化,请返回 -1。 +// +// 注意: +// +// +// 起始基因序列默认是合法的,但是它并不一定会出现在基因库中。 +// 如果一个起始基因序列需要多次变化,那么它每一次变化之后的基因序列都必须是合法的。 +// 假定起始基因序列与目标基因序列是不一样的。 +// +// +// +// +// 示例 1: +// +// +//start: "AACCGGTT" +//end: "AACCGGTA" +//bank: ["AACCGGTA"] +// +//返回值: 1 +// +// +// 示例 2: +// +// +//start: "AACCGGTT" +//end: "AAACGGTA" +//bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"] +// +//返回值: 2 +// +// +// 示例 3: +// +// +//start: "AAAAACCC" +//end: "AACCCCCC" +//bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"] +// +//返回值: 3 +// +// 👍 67 👎 0 + + +import java.util.*; + +public class MinimumGeneticMutation { + public static void main(String[] args) { + Solution solution = new MinimumGeneticMutation().new Solution(); + solution.minMutation(new String("AACCGGTT"), new String("AACCGGTA"), new String[]{"AACCGGTA"}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + int minChange = Integer.MAX_VALUE; + + + public int minMutation(String start, String end, String[] bank) { + + /** + * BFS + * m start长度,n bank长度 + * 时间复杂度O(n*m*4)=O(m*n),空间复杂度O(3n+4)=O(n) + */ + if (start.equals(end)) { + return 0; + } + + Set bankSet = new HashSet<>(); + for(String b: bank) bankSet.add(b); + + char[] charSet = new char[]{'A', 'C', 'G', 'T'}; + + int level=0; + Set visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + queue.offer(start); + visited.add(start); + + while (!queue.isEmpty()) { + int size = queue.size(); + //由于是数组,每层只有一个,所以当前层循环完,直接步数加1 + for(int i=0;i(), 0, start, end, bank); +// return minChange == Integer.MAX_VALUE ? -1 : minChange; + + + /** + * 将diff比较放到一个函数里面,但是内存消耗会变大 + */ +// recur(new HashSet<>(), 0, start, end, bank); +// return minChange == Integer.MAX_VALUE ? -1 : minChange; + + } + + private void dfsArray(int level, String start, String end, String[] bank) { + + if (start.equals(end)) { + minChange = Math.min(level, minChange); + return; + } + + for (int j = 0; j < bank.length; j++) { + //需要一个变量存储置空之前的值 + String temp = bank[j]; + + //null不能放在后面去判断,因为会有拿temp.charAt(i)的操作 + if (temp == null) { + continue; + } + + int diff = 0; + for (int i = 0; i < bank[j].length(); i++) { + if (start.charAt(i) != temp.charAt(i)) { + if (++diff > 1) break; + } + } + + if (diff == 1) { + bank[j] = null; + dfsArray(level + 1, temp, end, bank); + bank[j] = temp; + } + + + } + + + + } + + private void recur(HashSet visited, int level, String start, String end, String[] bank) { + + if (start.equals(end)) { + minChange = Math.min(minChange, level); + return; + } + + for (String str : bank) { + if (validDiff(start, str) && !visited.contains(str)) { + visited.add(str); + recur(visited, level + 1, str, end, bank); + visited.remove(str); + } + } + + + + } + + private boolean validDiff(String start, String str) { + int diff = 0; + for (int i = 0; i < str.length(); i++) { + if (start.charAt(i) != str.charAt(i)) { + if (++diff > 1) { + break; + } + } + } + return diff == 1 ? true : false; + } + + //visited用来记录访问过的 + private void dfs1(HashSet visited, int level, String start, String end, String[] bank) { + //这里直接用的level来表示转换了几次 + if (start.equals(end)) { + minChange = Math.min(minChange, level); + return; + } + + for (String str : bank) { + int diff = 0; + for (int i = 0; i < str.length(); i++) { + //比较每一位 + if (start.charAt(i) != str.charAt(i)) { + diff++; + //每次只能改变一个字符,当dif>1直接跳过 + if (diff > 1) { + break; + } + } + } + //只相差一位且没有被访问过 + if (diff == 1 && !visited.contains(str)) { + visited.add(str); + dfs1(visited, level + 1, str, end, bank); + //这是set,无序的,写成下面这种有问题,只有list能这么写 +// visited.remove(visited.size() - 1); + visited.remove(str); + } + } + } + + + private void dfs(char[] start, char[] end, char[][] bank, int change) { + + if (Arrays.equals(start, end)) { + minChange = Math.min(minChange, change); + return; + } + + for (int j = 0; j < bank.length; j++) { + char[] piece = bank[j]; + // 已用过的片段 + if (piece == null) { + continue; + } + // 获取基因库中不同为1的片段,作为改变一次后的新start + int diff = 0; + for (int i = 0; i < start.length; i++) { + if (start[i] != piece[i]) + if(++diff>1) break; + } + if (diff == 1) { + // 置空,防止循环使用 + bank[j] = null; + dfs(piece, end, bank, change + 1); + //重置状态 + bank[j] = piece; + } + } + + + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/NumberOfIslands.java b/Week_04/NumberOfIslands.java new file mode 100644 index 00000000..b8403484 --- /dev/null +++ b/Week_04/NumberOfIslands.java @@ -0,0 +1,130 @@ +//题号:200 +//https://leetcode-cn.com/problems/number-of-islands/ +//给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 +// +// 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 +// +// 此外,你可以假设该网格的四条边均被水包围。 +// +// +// +// 示例 1: +// +// +//输入:grid = [ +// ["1","1","1","1","0"], +// ["1","1","0","1","0"], +// ["1","1","0","0","0"], +// ["0","0","0","0","0"] +//] +//输出:1 +// +// +// 示例 2: +// +// +//输入:grid = [ +// ["1","1","0","0","0"], +// ["1","1","0","0","0"], +// ["0","0","1","0","0"], +// ["0","0","0","1","1"] +//] +//输出:3 +// +// +// +// +// 提示: +// +// +// m == grid.length +// n == grid[i].length +// 1 <= m, n <= 300 +// grid[i][j] 的值为 '0' 或 '1' +// +// Related Topics 深度优先搜索 广度优先搜索 并查集 +// 👍 995 👎 0 + + +import java.util.LinkedList; +import java.util.Queue; + +public class NumberOfIslands { + public static void main(String[] args) { + Solution solution = new NumberOfIslands().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int numIslands(char[][] grid) { + + + /** + * dfs O(n^2) + */ +// int count = 0; +// for (int i = 0; i < grid.length; i++) { +// for (int j = 0; j < grid[i].length; j++) { +// if (grid[i][j] == '1') { +// dfs(grid, i, j); +// count++; +// } +// } +// } +// +// return count; + + + /** + * BFS + */ + + 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'){ + bfs(grid, i, j); + count++; + } + } + } + return count; + + + + + } + + private void bfs(char[][] grid, int i, int j) { + + Queue queue = new LinkedList<>(); + queue.add(new int[] { i, j }); + + while(!queue.isEmpty()){ +// int size = queue.size(); +// for (int k = 0; k < size; k++) { + int[] cur = queue.remove(); + i = cur[0]; j = cur[1]; + if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') { + grid[i][j] = '0'; + queue.add(new int[] { i + 1, j }); + queue.add(new int[] { i - 1, j }); + queue.add(new int[] { i, j + 1 }); + queue.add(new int[] { i, j - 1 }); + } +// } + } + + } + + 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'; + dfs(grid, i + 1, j); + dfs(grid, i, j + 1); + dfs(grid, i - 1, j); + dfs(grid, i, j - 1); + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/README.md b/Week_04/README.md index 50de3041..9085e8f9 100644 --- a/Week_04/README.md +++ b/Week_04/README.md @@ -1 +1,94 @@ -学习笔记 \ No newline at end of file +#### 学习笔记 + +##### 广度优先搜索和深度优先搜索 + +以遍历树为例子: + +- 深度优先搜索(DFS):从树的根出发,随机访问左右节点,然后一直向下遍历,直到没有叶子结点,再返回根,重复上述过程。 +- 广度优先遍历(BFS):从树的根出发,像水波纹一样,遍历每一层的节点,再依次到下一层。(求树的最小深度,最短路等常用)。 + +这两种搜索算法是常考点,例题有很多,每道题基本都有BFS和DFS两种解法,有的还是bfs+dfs共用,以及双端BFS等,单词接龙那两道题困难的题,我这会还有点似懂非懂的样子,总的说来东西还是不少的,感觉掌握的不是很牢固,只有五毒神掌慢慢消化了。 + +##### 贪心算法 + +贪心算法我其实感觉单独用的不多,多用于和动态规划比较,因为**贪心算法的要求是每一阶段都达到最优**,这个实际很难达到,但是也如视频说的,只要达到了,代码其实相对简单,理解起来没多大难度。 + +##### 二分查找 + +二分查找对数据有三个要求: + +1. 单调性(递增或者递减) +2. 边界(有限的,能确定左右指针边界) +3. 能通过下标快速访问(一般是数组,链表如果是跳表也可以) + +``` +public int binarySearch(int[] nums,int target){ + int left = 0, right = nums.length - 1; + + while (left <= right) { + + int mid = left + (right - left) / 2; + + if (nums[mid]==target) { + return mid; + } else if (nums[mid] >target) { + right = mid - 1; + } else { + left = mid + 1; + } + } +} + +``` + +二分查找会有一种变形题,即有序数组被旋转之后要找某个值,可以按照如下方法求解: + +``` +public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left < right) { + int mid = left + (right - left) / 2; + //0到mid有序,且target大于有序部分最大值或者小于最小值,target在另一半里,left=mid+1 + if (nums[0] <= nums[mid] && (nums[mid] < target || target < nums[0])) { + left = mid + 1; + } else if (target > nums[mid] && target < nums[0]) {//这儿0到mid有旋转,判断升序部分,target > nums[mid] && target < nums[0]满足这个条件target在另一部分 + left = mid + 1; + } else { + right = mid;//否则在前半部分 + } + } + //这儿left==right + return left == right && nums[left] == target ? left : -1; +} +``` + +这种题在解的时候其实相当于把二分的概念扩大了,不止找数的时候二分,还要找到有序的空间,我们始终是在有序的空间里面找到对应的target值,当排除到只剩最后一个元素时,退出循环,然后拿该数和target比较即可。 + +对于下面这个题:使用二分查找,寻找一个半有序数组 [4, 5, 6, 7, 0, 1, 2] 中间无序的地方 + +思路如下,我们要找到无序的地方,其实直接找无序的片段,不断在无序片段里用二分查找比较mid和mid+1的值,当nums[mid]>nums[mid+1]时,mid+1就是无序的位置,这里需要注意判断mid+1是否越界,**这个思路也适用于找旋转数组最大值**。 + +``` +public int search1(int[] nums) { + + int left = 0, right = nums.length - 1; + + while (left <= right) { + + int mid = left + (right - left) / 2; + + if (mid + 1 < nums.length && nums[mid] > nums[mid + 1]) { + return mid + 1; + } else if (nums[mid] >= nums[0]) { + left = mid + 1; + } else { + right = mid; + } + } + + + + return left; + } +``` + diff --git a/Week_04/SearchA2dMatrix.java b/Week_04/SearchA2dMatrix.java new file mode 100644 index 00000000..90dee66b --- /dev/null +++ b/Week_04/SearchA2dMatrix.java @@ -0,0 +1,81 @@ +//题号:74 +//https://leetcode-cn.com/problems/search-a-2d-matrix/ +//编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: +// +// +// 每行中的整数从左到右按升序排列。 +// 每行的第一个整数大于前一行的最后一个整数。 +// +// +// +// +// 示例 1: +// +// +//输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 +//输出:true +// +// +// 示例 2: +// +// +//输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 +//输出:false +// +// +// +// +// 提示: +// +// +// m == matrix.length +// n == matrix[i].length +// 1 <= m, n <= 100 +// -104 <= matrix[i][j], target <= 104 +// +// Related Topics 数组 二分查找 +// 👍 325 👎 0 + + +public class SearchA2dMatrix { + public static void main(String[] args) { + Solution solution = new SearchA2dMatrix().new Solution(); + solution.searchMatrix(new int[][]{{1, 3, 5}}, 2); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public boolean searchMatrix(int[][] matrix, int target) { + + + + + /** + * 直接在二位数组上做二分查找 + * + * n * m矩阵转换为array =>矩阵[x] [y] => a [x * m + y] + * + * 数组转换为n * m矩阵=> a [x] =>矩阵[x / m] [x%m]; + * + * 时间复杂度O(log(m*n)),空间复杂度O(1) + * + */ + int n = matrix.length; + int m = matrix[0].length; + int l = 0, r = m * n - 1; + while (l < r) { + int mid = (l + r - 1) >> 1; +// int mid = l + (r - l) / 2; + //这儿相当于用数组a[mid]和target比较 + if (matrix[mid / m][mid % m] < target) { + l = mid + 1; + } else { + r = mid; + } + } + return matrix[r / m][r % m] == target; + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/SearchInRotatedSortedArray.java b/Week_04/SearchInRotatedSortedArray.java new file mode 100644 index 00000000..c8ff49a2 --- /dev/null +++ b/Week_04/SearchInRotatedSortedArray.java @@ -0,0 +1,102 @@ +//题号:33 +//https://leetcode-cn.com/problems/search-in-rotated-sorted-array/ +//整数数组 nums 按升序排列,数组中的值 互不相同 。 +// +// 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[ +//k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2 +//,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 +// +// 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的索引,否则返回 -1 。 +// +// +// +// 示例 1: +// +// +//输入:nums = [4,5,6,7,0,1,2], target = 0 +//输出:4 +// +// +// 示例 2: +// +// +//输入:nums = [4,5,6,7,0,1,2], target = 3 +//输出:-1 +// +// 示例 3: +// +// +//输入:nums = [1], target = 0 +//输出:-1 +// +// +// +// +// 提示: +// +// +// 1 <= nums.length <= 5000 +// -10^4 <= nums[i] <= 10^4 +// nums 中的每个值都 独一无二 +// nums 肯定会在某个点上旋转 +// -10^4 <= target <= 10^4 +// +// +// +// +// 进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗? +// Related Topics 数组 二分查找 +// 👍 1199 👎 0 + + +public class SearchInRotatedSortedArray { + public static void main(String[] args) { + Solution solution = new SearchInRotatedSortedArray().new Solution(); + solution.search(new int[]{4, 5, 6, 7, 0, 1, 2}, 0); +// solution.search1(new int[]{1, 2, 4, 5, 6, 7, 0}); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left < right) { + int mid = left + (right - left) / 2; + //0到mid有序,且target大于有序部分最大值或者小于最小值,target在另一半里,left=mid+1 + if (nums[0] <= nums[mid] && (nums[mid] < target || target < nums[0])) { + left = mid + 1; + } else if (target > nums[mid] && target < nums[0]) {//这儿0到mid有旋转,判断升序部分,target > nums[mid] && target < nums[0]满足这个条件target在另一部分 + left = mid + 1; + } else { + right = mid;//否则在前半部分 + } + } + + return left == right && nums[left] == target ? left : -1; + } + + public int search1(int[] nums) { + + int left = 0, right = nums.length - 1; + + while (left <= right) { + + int mid = left + (right - left) / 2; + + if (mid + 1 < nums.length && nums[mid] > nums[mid + 1]) { + return mid + 1; + } else if (nums[mid] >= nums[0]) { + left = mid + 1; + } else { + right = mid; + } + } + + + + return left; + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/Sqrtx.java b/Week_04/Sqrtx.java new file mode 100644 index 00000000..fa98bb61 --- /dev/null +++ b/Week_04/Sqrtx.java @@ -0,0 +1,110 @@ +//题号:69 +//https://leetcode-cn.com/problems/sqrtx/ +//实现 int sqrt(int x) 函数。 +// +// 计算并返回 x 的平方根,其中 x 是非负整数。 +// +// 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 +// +// 示例 1: +// +// 输入: 4 +//输出: 2 +// +// +// 示例 2: +// +// 输入: 8 +//输出: 2 +//说明: 8 的平方根是 2.82842..., +//  由于返回类型是整数,小数部分将被舍去。 +// +// Related Topics 数学 二分查找 +// 👍 605 👎 0 + + +public class Sqrtx { + public static void main(String[] args) { + Solution solution = new Sqrtx().new Solution(); + solution.mySqrt(2147395599); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int mySqrt(int x) { + + /** + * 二分查找 + * + * 时间复杂度O(logn),空间复杂度O(1) + * + * 为啥是返回right而不是left?我理解是走到这里平方数一定是小数,int是直接截取小数部分,因此要取小的那个 + * 这时候left>right,所以是right + * + */ + if (x < 2) { + return x; + } + + //当数值大到一定值时,mid * mid超过int上限,会一直得到负数,从而得不到正确值 + //所以这里应该用long + long left = 0, right = x, mid = 1; + + + while (left <= right) { + mid = left + (right - left) / 2; + if (mid * mid == x) { + return (int) mid; + } else if (mid * mid < x) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return (int)right; + + /** + * 牛顿迭代法, + * r = (r + x/ r) / 2; 不断迭代这个公式, + * r会不断逼近完全平方根, + * 后面判断可以直接用r*r和x比, + * 也可以用r<1e-7去比 + */ + + +// if (x == 0) { +// return 0; +// } +// +//// long r = x; +//// while (r*r>x) { +//// r = (r + x/ r) / 2; +//// } +// +//// return (int)r; +// +// double C = x, x0 = x; +// while (true) { +// double xi = (x0 + C / x0)/2; +// if (Math.abs(x0 - xi) < 1e-7) { +// break; +// } +// x0 = xi; +// } +// return (int) x0; + + + + + + + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/ValidPerfectSquare.java b/Week_04/ValidPerfectSquare.java new file mode 100644 index 00000000..22673ace --- /dev/null +++ b/Week_04/ValidPerfectSquare.java @@ -0,0 +1,74 @@ +//题号:367 +//https://leetcode-cn.com/problems/valid-perfect-square/solution/ +//给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。 +// +// 说明:不要使用任何内置的库函数,如 sqrt。 +// +// 示例 1: +// +// 输入:16 +//输出:True +// +// 示例 2: +// +// 输入:14 +//输出:False +// +// Related Topics 数学 二分查找 +// 👍 194 👎 0 + + +public class ValidPerfectSquare { + public static void main(String[] args) { + Solution solution = new ValidPerfectSquare().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean isPerfectSquare(int num) { + + /** + * 二分:时间复杂度O(logN),空间复杂度O(1) + */ + if (num < 2) { + return true; + } + + long left = 2, right = num / 2, x, pinfang; + while (left <= right) { + //防止溢出 + x = left + (right - left) / 2; + pinfang = x * x; + if (pinfang == num) { + return true; + }else if (pinfang > num) { + right = x - 1; + } else { + left = x + 1; + } + + } + + return false; + } + + /** + * 牛顿迭代法 + * 时间复杂度O(logN),空间复杂度O(1) + */ + +// if (num < 2) { +// return true; +// } +// long x = num / 2; +// while (x * x > num) { +// x = (x + num / x) / 2; +// } +// +// return x * x == num; + + + } + +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_04/WalkingRobotSimulation.java b/Week_04/WalkingRobotSimulation.java new file mode 100644 index 00000000..5c003e60 --- /dev/null +++ b/Week_04/WalkingRobotSimulation.java @@ -0,0 +1,130 @@ +//题号:874 +//https://leetcode-cn.com/problems/walking-robot-simulation/description/ +//机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands : +// +// +// -2 :向左转 90 度 +// -1 :向右转 90 度 +// 1 <= x <= 9 :向前移动 x 个单位长度 +// +// +// 在网格上有一些格子被视为障碍物 obstacles 。第 i 个障碍物位于网格点 obstacles[i] = (xi, yi) 。 +// +// 机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续尝试进行该路线的其余部分。 +// +// 返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。(即,如果距离为 5 ,则返回 25 ) +// +// +// +// +// +// +// +// +// +// 注意: +// +// +// 北表示 +Y 方向。 +// 东表示 +X 方向。 +// 南表示 -Y 方向。 +// 西表示 -X 方向。 +// +// +// +// +// +// +// +// +// 示例 1: +// +// +//输入:commands = [4,-1,3], obstacles = [] +//输出:25 +//解释: +//机器人开始位于 (0, 0): +//1. 向北移动 4 个单位,到达 (0, 4) +//2. 右转 +//3. 向东移动 3 个单位,到达 (3, 4) +//距离原点最远的是 (3, 4) ,距离为 32 + 42 = 25 +// +// 示例 2: +// +// +//输入:commands = [4,-1,4,-2,4], obstacles = [[2,4]] +//输出:65 +//解释:机器人开始位于 (0, 0): +//1. 向北移动 4 个单位,到达 (0, 4) +//2. 右转 +//3. 向东移动 1 个单位,然后被位于 (2, 4) 的障碍物阻挡,机器人停在 (1, 4) +//4. 左转 +//5. 向北走 4 个单位,到达 (1, 8) +//距离原点最远的是 (1, 8) ,距离为 12 + 82 = 65 +// +// +// +// 提示: +// +// +// 1 <= commands.length <= 104 +// commands[i] is one of the values in the list [-2,-1,1,2,3,4,5,6,7,8,9]. +// 0 <= obstacles.length <= 104 +// -3 * 104 <= xi, yi <= 3 * 104 +// 答案保证小于 231 +// +// Related Topics 贪心算法 +// 👍 123 👎 0 + + +import java.util.HashSet; +import java.util.Set; + +public class WalkingRobotSimulation { + public static void main(String[] args) { + Solution solution = new WalkingRobotSimulation().new Solution(); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int robotSim(int[] commands, int[][] obstacles) { + //这里的dx,dy代表的方向, 0123 表 北东南西 + int[] dx = new int[]{0, 1, 0, -1}; + int[] dy = new int[]{1, 0, -1, 0}; + int x = 0, y = 0, di = 0; + + Set obstacleSet = new HashSet<>(); + for (int i=0;i "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。 +// +// +// 示例 2: +// +// +//输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","lo +//g"] +//输出:0 +//解释:endWord "cog" 不在字典中,所以无法进行转换。 +// +// +// +// 提示: +// +// +// 1 <= beginWord.length <= 10 +// endWord.length == beginWord.length +// 1 <= wordList.length <= 5000 +// wordList[i].length == beginWord.length +// beginWord、endWord 和 wordList[i] 由小写英文字母组成 +// beginWord != endWord +// wordList 中的所有字符串 互不相同 +// +// Related Topics 广度优先搜索 +// 👍 705 👎 0 + + +import java.util.*; + +public class WordLadder { + public static void main(String[] args) { + Solution solution = new WordLadder().new Solution(); + solution.ladderLength("hit", "cog", Arrays.asList(new String[]{"hot", "dot", "dog", "lot", "log", "cog"})); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + int minLength = Integer.MAX_VALUE; + public int ladderLength(String beginWord, String endWord, List wordList) { + + + /** + * dfs 超时, + * 这个题其实和基因那道题很类似,区别就是数据规模大了很多, + * 因此单纯用DFS直接遍历会超时 + * + */ +// Set visited = new HashSet<>(); +// dfs(0, beginWord, endWord, wordList,visited); +// +// return minLength == Integer.MAX_VALUE ? 0 : minLength; + + + /** + * bfs+Set记录 + */ + +// if (!wordList.contains(endWord)) { +// return 0; +// } +// +// Set visited = new HashSet<>(); +// Deque queue = new LinkedList<>(); +// queue.offer(beginWord); +// visited.add(beginWord); +// int level = 0; +// +// while (!queue.isEmpty()) { +// +// int size = queue.size(); +// level++; +// for (int i = 0; i < size; i++) { +// String curr = queue.poll(); +// for (String str : wordList) { +// if (visited.contains(str)) { +// continue; +// } +// +// if (!canConvert(curr, str)) { +// continue; +// } +// +// if (str.equals(endWord)) { +// return level + 1; +// } +// +// visited.add(str); +// queue.offer(str); +// +// } +// +// } +// +// +// } +// +// +// return 0; + + /** + * bfs+数组记录 + */ + +// if (!wordList.contains(endWord)) { +// return 0; +// } +// +// boolean[] visited = new boolean[wordList.size()]; +// Deque queue = new LinkedList<>(); +// int index = wordList.indexOf(beginWord); +// if (index != -1) { +// visited[index] = true; +// } +// queue.offer(beginWord); +// int level = 0; +// +// while (!queue.isEmpty()) { +// +// int size = queue.size(); +// level++; +// for (int i = 0; i < size; i++) { +// String curr = queue.poll(); +// for (int j = 0; j < wordList.size(); j++) { +// String str = wordList.get(j); +// if (visited[j]) { +// continue; +// } +// +// if (!canConvert(curr, str)) { +// continue; +// } +// +// if (str.equals(endWord)) { +// return level + 1; +// } +// +// visited[j]=true; +// queue.offer(str); +// +// } +// +// } +// +// +// } +// +// +// return 0; + + /** + * 双向BFS + */ + +// int end = wordList.indexOf(endWord); +// if (end == -1) { +// return 0; +// } +// +// wordList.add(beginWord); +// int start = wordList.size() - 1; +// Deque queue1 = new LinkedList<>(); +// Deque queue2 = new LinkedList<>(); +// Set visited1 = new HashSet<>(); +// Set visited2 = new HashSet<>(); +// queue1.offer(start); +// queue2.offer(end); +// visited1.add(start); +// visited2.add(end); +// int count = 0; +// +// while (!queue2.isEmpty() && !queue1.isEmpty()) { +// count++; +// //从节点小的一端遍历,当queue1节点多与queue2交换 +// if (queue1.size() > queue2.size()) { +// Deque temp = queue1; +// queue1 = queue2; +// queue2 = temp; +// Set t = visited1; +// visited1 = visited2; +// visited2 = t; +// } +// +// int size = queue1.size(); +// while (size-- > 0) { +// String s = wordList.get(queue1.poll()); +// for (int i = 0; i < wordList.size(); i++) { +// if (visited1.contains(i)) { +// continue; +// } +// if (!canConvert(s, wordList.get(i))) { +// continue; +// } +// if (visited2.contains(i)) { +// return count + 1; +// } +// visited1.add(i); +// queue1.offer(i); +// } +// } +// +// } +// return 0; + + /** + * 双向BFS+单词转换优化 + */ + +// int end = wordList.indexOf(endWord); +// if (end == -1) { +// return 0; +// } +// +// wordList.add(beginWord); +// Deque queue1 = new LinkedList<>(); +// Deque queue2 = new LinkedList<>(); +// Set visited1 = new HashSet<>(); +// Set visited2 = new HashSet<>(); +// queue1.offer(beginWord); +// queue2.offer(endWord); +// visited1.add(beginWord); +// visited2.add(endWord); +// +// Set allWordSet = new HashSet<>(wordList); +// +// int count = 0; +// +// while (!queue2.isEmpty() && !queue1.isEmpty()) { +// count++; +// //从节点小的一端遍历,当queue1节点多与queue2交换 +// if (queue1.size() > queue2.size()) { +// Deque temp = queue1; +// queue1 = queue2; +// queue2 = temp; +// Set t = visited1; +// visited1 = visited2; +// visited2 = t; +// } +// +// int size = queue1.size(); +// while (size-- > 0) { +// String s = queue1.poll(); +// char[] chars = s.toCharArray(); +// //这儿不在和wordlist比,而是替换字符然后查看wordlist是否包含,不需要遍历整个字符串 +// for (int i = 0; i < s.length(); i++) { +// +// char old = chars[i]; +// +// for (char c = 'a'; c <= 'z'; c++) { +// chars[i] = c; +// String str = new String(chars); +// // 已经访问过了,跳过 +// if (visited1.contains(str)) { +// continue; +// } +// // 两端遍历相遇,结束遍历,返回 count +// if (visited2.contains(str)) { +// return count + 1; +// } +// +// // 如果单词在列表中存在,将其添加到队列,并标记为已访问 +// if (allWordSet.contains(str)) { +// queue1.offer(str); +// visited1.add(str); +// } +// } +// +// chars[i] = old; +// +// } +// } +// +// } +// return 0; + + + /** + * 全球站高赞双向BFS + * 看着和一般的BFS不一样,没有用queue + */ + +// if (!wordList.contains(endWord)) { +// return 0; +// } +// +// Set wordSet = new HashSet<>(wordList); +// Set beginSet = new HashSet<>(); +// Set endSet = new HashSet<>(); +// int len = 1; +// HashSet visited = new HashSet<>(); +// +// beginSet.add(beginWord); +// endSet.add(endWord); +// +// while (!beginSet.isEmpty() && !endSet.isEmpty()) { +// //始终让beginSet最小 +// if (beginSet.size() > endSet.size()) { +// Set set = beginSet; +// beginSet = endSet; +// endSet = set; +// } +// +// Set temp = new HashSet<>(); +// +// for (String word : beginSet) { +// char[] chs = word.toCharArray(); +// for (int i = 0; i < chs.length; i++) { +// for (char c = 'a'; c <= 'z'; c++) { +// char old = chs[i]; +// chs[i] = c; +// String target = new String(chs); +// if (endSet.contains(target)) { +// return len + 1; +// } +// +// if (!visited.contains(target) && wordSet.contains(target)) { +// temp.add(target); +// visited.add(target); +// } +// chs[i] = old; +// } +// } +// } +// +// beginSet = temp; +// len++; +// +// } +// +// return 0; + + + /** + * 全球站法二 + */ +// if (beginWord == null || endWord == null || beginWord.length() != endWord.length()||!wordList.contains(endWord)) return 0; +// +// Set reached = new HashSet<>(); +// Set wordDict = new HashSet<>(wordList); +// reached.add(beginWord); +// wordDict.add(endWord); +// int distance = 1; +// while (!reached.contains(endWord)) { +// Set toAdd = new HashSet<>(); +// for (String each : reached) { +// for (int i = 0; i < each.length(); i++) { +// char[] chars = each.toCharArray(); +// for (char ch = 'a'; ch <= 'z'; ch++) { +// chars[i] = ch; +// String word = new String(chars); +// if (wordDict.contains(word)) { +// toAdd.add(word); +// wordDict.remove(word); +// } +// } +// } +// } +// distance++; +// if (toAdd.size() == 0) return 0; +// reached = toAdd; +// } +// return distance; + + /** + * 全球站法三 2,3都是BFS + */ + if (!wordList.contains(endWord)) return 0; + HashSet set = new HashSet<>(wordList); + Queue q = new LinkedList<>(); + int length = 0; + set.add(endWord); + q.add(beginWord); + + while (!q.isEmpty()) { + int size = q.size(); + for (int i = 0; i < size; i++) { + String w = q.poll(); + if (w.equals(endWord)) return length + 1; + wordMatch(w, set, q); + } + length++; + } + return 0; + + + + } + + private void wordMatch(String w, Set set, Queue q) { + for (int i = 0; i < w.length(); i++) { + char[] word = w.toCharArray(); + for (int j = 0; j < 26; j++) { + char c = (char) ('a' + j); + if (word[i] == c) continue; + word[i] = c; + String s = String.valueOf(word); + if (set.contains(s)) { + set.remove(s); + q.offer(s); + } + } + } + } + + private boolean canConvert(String curr, String str) { + + if (curr.length() != str.length()) { + return false; + } + + int diff = 0; + for (int i = 0; i < curr.length(); i++) { + if (curr.charAt(i) != str.charAt(i)) { + if(++diff>1) return false; + } + } + + return diff == 1; + } + + private void dfs(int level, String beginWord, String endWord, List wordList, Set visited) { + if (beginWord.equals(endWord)) { + minLength = Math.min(minLength, level+1); + return; + } + + for (String str : wordList) { + + int diff = 0; + for (int i = 0; i < str.length(); i++) { + //与单词库里每一个对比 + if (beginWord.charAt(i) != str.charAt(i)) { + if (++diff > 1) break; + } + } + + if (diff == 1 && !visited.contains(str)) { + visited.add(str); + dfs(level + 1, str, endWord, wordList, visited); + visited.remove(str); + } + + } + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_04/WordLadderIi.java b/Week_04/WordLadderIi.java new file mode 100644 index 00000000..4a8eb417 --- /dev/null +++ b/Week_04/WordLadderIi.java @@ -0,0 +1,257 @@ +//题号:126 +//https://leetcode-cn.com/problems/word-ladder-ii/description/ +//给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换 +//需遵循如下规则: +// +// +// 每次转换只能改变一个字母。 +// 转换后得到的单词必须是字典中的单词。 +// +// +// 说明: +// +// +// 如果不存在这样的转换序列,返回一个空列表。 +// 所有单词具有相同的长度。 +// 所有单词只由小写字母组成。 +// 字典中不存在重复的单词。 +// 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。 +// +// +// 示例 1: +// +// 输入: +//beginWord = "hit", +//endWord = "cog", +//wordList = ["hot","dot","dog","lot","log","cog"] +// +//输出: +//[ +// ["hit","hot","dot","dog","cog"], +//  ["hit","hot","lot","log","cog"] +//] +// +// +// 示例 2: +// +// 输入: +//beginWord = "hit" +//endWord = "cog" +//wordList = ["hot","dot","dog","lot","log"] +// +//输出: [] +// +//解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。 +// Related Topics 广度优先搜索 数组 字符串 回溯算法 +// 👍 398 👎 0 + + +import java.util.*; + +public class WordLadderIi { + public static void main(String[] args) { + Solution solution = new WordLadderIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List> findLadders(String beginWord, String endWord, List wordList) { + + /** + * BFS+DFS + * + * + */ +// List> res = new ArrayList<>(); +// if (!wordList.contains(endWord)) { +// return res; +// } +// HashSet dict = new HashSet<>(wordList); +// +// HashMap> nodeNeighbors = new HashMap<>();// Neighbors for every node +// HashMap distance = new HashMap<>();// Distance of every node from the start node +// +// ArrayList solution = new ArrayList<>(); +// +// dict.add(beginWord); +// bfd(beginWord,endWord,dict,nodeNeighbors,distance); +// dfs(beginWord,endWord,dict,nodeNeighbors,distance,solution,res); +// +// return res; + + /** + * 双向BFS+dfs + */ + List> res = new ArrayList<>(); + HashSet dict = new HashSet<>(wordList); + if(!dict.contains(endWord)){ + return res; + } + HashSet set1 = new HashSet<>(); + HashSet set2 = new HashSet<>(); + + set1.add(beginWord); + set2.add(endWord); + HashMap> map = new HashMap<>(); + bfs1(map, set1, set2, dict, false); + + List path = new ArrayList<>(); + path.add(beginWord); + dfs1(res, path, map, beginWord, endWord); + return res; + + + + + } + + private void dfs1(List> res, List path, HashMap> map, String start, String end){ + if(start.equals(end)){ + res.add(new ArrayList<>(path)); + return; + } + + if(!map.containsKey(start)){ + return; + } + + for(String next : map.get(start)){ + path.add(next); + dfs1(res, path, map, next, end); + path.remove(path.size() - 1); + } + } + + private void bfs1(HashMap> map, HashSet set1, HashSet set2, HashSet dict, boolean flip){ + if(set1.isEmpty()){ + return; + } + + if(set1.size() > set2.size()){ + bfs1(map, set2, set1, dict, !flip); + return; + } + + boolean done = false; + dict.removeAll(set1); + dict.removeAll(set2); + + HashSet next = new HashSet<>(); + for(String str : set1){ + char[] chs = str.toCharArray(); + for(int i = 0; i < chs.length; i++){ + char temp = chs[i]; + for(char ch = 'a'; ch <= 'z'; ch++){ + if(chs[i] != ch){ + chs[i] = ch; + String word = new String(chs); + + String key = flip ? word : str; + String val = flip ? str : word; + + List list = map.get(key) == null ? new ArrayList<>() : map.get(key); + + if(set2.contains(word)){ + done = true; + + list.add(val); + map.put(key, list); + } + + if(!done && dict.contains(word)){ + next.add(word); + + list.add(val); + map.put(key, list); + } + } + } + chs[i] = temp; + } + } + + if(!done){ + bfs1(map, set2, next, dict, !flip); + } + } + + private void bfd(String beginWord, String endWord, Set dict, HashMap> nodeNeighbors, HashMap distance) { + for (String str : dict) { + nodeNeighbors.put(str, new ArrayList<>()); + } + + Deque queue = new LinkedList<>(); + queue.offer(beginWord); + distance.put(beginWord, 0); + + while (!queue.isEmpty()) { + + int count = queue.size(); + boolean foundEnd = false; + + for (int i = 0; i < count; i++) { + String cur = queue.poll(); + int curDistance = distance.get(cur); + ArrayList neighbors = getNeighbors(cur, dict); + + + for (String neighbor : neighbors) { + nodeNeighbors.get(cur).add(neighbor); + if (!distance.containsKey(neighbor)) {// Check if visited + distance.put(neighbor, curDistance + 1); + if (endWord.equals(neighbor))// Found the shortest path + foundEnd = true; + else + queue.offer(neighbor); + } + } + } + if (foundEnd) + break; + + } + + } + + private ArrayList getNeighbors(String node, Set dict) { + + ArrayList res = new ArrayList<>(); + char chs[] = node.toCharArray(); + + for (char ch ='a'; ch <= 'z'; ch++) { + for (int i = 0; i < chs.length; i++) { + if (chs[i] == ch) continue; + char old_ch = chs[i]; + chs[i] = ch; + if (dict.contains(String.valueOf(chs))) { + res.add(String.valueOf(chs)); + } + chs[i] = old_ch; + } + + } + return res; + + + + + + } + + // DFS: output all paths with the shortest distance. + private void dfs(String cur, String end, Set dict, HashMap> nodeNeighbors, HashMap distance, ArrayList solution, List> res) { + solution.add(cur); + if (end.equals(cur)) { + res.add(new ArrayList<>(solution)); + } else { + for (String next : nodeNeighbors.get(cur)) { + if (distance.get(next) == distance.get(cur) + 1) { + dfs(next, end, dict, nodeNeighbors, distance, solution, res); + } + } + } + solution.remove(solution.size() - 1); + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From bed87670686ddab28df0626795ec384a10559cde Mon Sep 17 00:00:00 2001 From: lianght1 Date: Tue, 2 Mar 2021 14:26:15 +0800 Subject: [PATCH 05/15] WordLadder change. --- Week_04/WordLadder.java | 350 ++++++++++++---------------------------- 1 file changed, 105 insertions(+), 245 deletions(-) diff --git a/Week_04/WordLadder.java b/Week_04/WordLadder.java index 03669e17..86f83ed6 100644 --- a/Week_04/WordLadder.java +++ b/Week_04/WordLadder.java @@ -66,253 +66,126 @@ public int ladderLength(String beginWord, String endWord, List wordList) * 这个题其实和基因那道题很类似,区别就是数据规模大了很多, * 因此单纯用DFS直接遍历会超时 * + * dfs思路是找到所有可能,然后比较其中最小的, + * 当时数据规模很大的时候,且递归树分支极不平衡的情况下, + * 遍历所有可能是十分耗时的操作, + * 极端例子:一个二叉树,左子树10000个节点,右子树3个节点,当找最小子树节点时, + * dfs,会遍历完两个子树才能得到结果,而bfs只要到第三层就能知道结果,效率差别巨大。 */ // Set visited = new HashSet<>(); // dfs(0, beginWord, endWord, wordList,visited); // // return minLength == Integer.MAX_VALUE ? 0 : minLength; - - /** - * bfs+Set记录 - */ - -// if (!wordList.contains(endWord)) { -// return 0; -// } -// -// Set visited = new HashSet<>(); -// Deque queue = new LinkedList<>(); -// queue.offer(beginWord); -// visited.add(beginWord); -// int level = 0; -// -// while (!queue.isEmpty()) { -// -// int size = queue.size(); -// level++; -// for (int i = 0; i < size; i++) { -// String curr = queue.poll(); -// for (String str : wordList) { -// if (visited.contains(str)) { -// continue; -// } -// -// if (!canConvert(curr, str)) { -// continue; -// } -// -// if (str.equals(endWord)) { -// return level + 1; -// } -// -// visited.add(str); -// queue.offer(str); -// -// } -// -// } -// -// -// } -// -// -// return 0; - /** - * bfs+数组记录 + * 单向BFS, */ - +// Set wordSet = new HashSet<>(wordList); // if (!wordList.contains(endWord)) { // return 0; // } -// -// boolean[] visited = new boolean[wordList.size()]; // Deque queue = new LinkedList<>(); -// int index = wordList.indexOf(beginWord); -// if (index != -1) { -// visited[index] = true; -// } +//// Set visitedSet = new HashSet<>(); // queue.offer(beginWord); -// int level = 0; -// +//// visitedSet.add(beginWord); +// int len = 1; // while (!queue.isEmpty()) { -// // int size = queue.size(); -// level++; // for (int i = 0; i < size; i++) { -// String curr = queue.poll(); -// for (int j = 0; j < wordList.size(); j++) { -// String str = wordList.get(j); -// if (visited[j]) { -// continue; -// } -// -// if (!canConvert(curr, str)) { -// continue; -// } -// -// if (str.equals(endWord)) { -// return level + 1; -// } -// -// visited[j]=true; -// queue.offer(str); -// -// } -// -// } -// -// -// } -// -// -// return 0; - - /** - * 双向BFS - */ - -// int end = wordList.indexOf(endWord); -// if (end == -1) { -// return 0; -// } -// -// wordList.add(beginWord); -// int start = wordList.size() - 1; -// Deque queue1 = new LinkedList<>(); -// Deque queue2 = new LinkedList<>(); -// Set visited1 = new HashSet<>(); -// Set visited2 = new HashSet<>(); -// queue1.offer(start); -// queue2.offer(end); -// visited1.add(start); -// visited2.add(end); -// int count = 0; -// -// while (!queue2.isEmpty() && !queue1.isEmpty()) { -// count++; -// //从节点小的一端遍历,当queue1节点多与queue2交换 -// if (queue1.size() > queue2.size()) { -// Deque temp = queue1; -// queue1 = queue2; -// queue2 = temp; -// Set t = visited1; -// visited1 = visited2; -// visited2 = t; -// } -// -// int size = queue1.size(); -// while (size-- > 0) { -// String s = wordList.get(queue1.poll()); -// for (int i = 0; i < wordList.size(); i++) { -// if (visited1.contains(i)) { -// continue; -// } -// if (!canConvert(s, wordList.get(i))) { -// continue; -// } -// if (visited2.contains(i)) { -// return count + 1; -// } -// visited1.add(i); -// queue1.offer(i); -// } -// } -// -// } -// return 0; - - /** - * 双向BFS+单词转换优化 - */ - -// int end = wordList.indexOf(endWord); -// if (end == -1) { -// return 0; -// } -// -// wordList.add(beginWord); -// Deque queue1 = new LinkedList<>(); -// Deque queue2 = new LinkedList<>(); -// Set visited1 = new HashSet<>(); -// Set visited2 = new HashSet<>(); -// queue1.offer(beginWord); -// queue2.offer(endWord); -// visited1.add(beginWord); -// visited2.add(endWord); -// -// Set allWordSet = new HashSet<>(wordList); -// -// int count = 0; -// -// while (!queue2.isEmpty() && !queue1.isEmpty()) { -// count++; -// //从节点小的一端遍历,当queue1节点多与queue2交换 -// if (queue1.size() > queue2.size()) { -// Deque temp = queue1; -// queue1 = queue2; -// queue2 = temp; -// Set t = visited1; -// visited1 = visited2; -// visited2 = t; -// } -// -// int size = queue1.size(); -// while (size-- > 0) { -// String s = queue1.poll(); -// char[] chars = s.toCharArray(); -// //这儿不在和wordlist比,而是替换字符然后查看wordlist是否包含,不需要遍历整个字符串 -// for (int i = 0; i < s.length(); i++) { -// -// char old = chars[i]; -// -// for (char c = 'a'; c <= 'z'; c++) { -// chars[i] = c; -// String str = new String(chars); -// // 已经访问过了,跳过 -// if (visited1.contains(str)) { +// String str = queue.poll(); +// char[] chs = str.toCharArray(); +// for (int k = 0; k < chs.length; k++) { +// char old = chs[k]; +// for (char j = 'a'; j <= 'z'; j++) { +// if (j == chs[k]) { // continue; // } -// // 两端遍历相遇,结束遍历,返回 count -// if (visited2.contains(str)) { -// return count + 1; +// chs[k] = j; +// String target = new String(chs); +// if (target.equals(endWord)) { +// return len + 1; // } -// -// // 如果单词在列表中存在,将其添加到队列,并标记为已访问 -// if (allWordSet.contains(str)) { -// queue1.offer(str); -// visited1.add(str); +// //参考全球站高赞法三解法 +//// if (!visitedSet.contains(target) && wordSet.contains(target)) { +// if (wordSet.contains(target)) { +// wordSet.remove(target); +// queue.offer(target); +//// visitedSet.add(target); // } // } -// -// chars[i] = old; -// +// chs[k] = old; // } // } -// +// len++; // } // return 0; + /** + * 双端BFS + * 为了寻求更快的速度,用Set代替了queue + * 这里可以直接用wordSet保存访问记录,减少一个visitedSet变量,且wordSet会变小 + */ + Set wordSet = new HashSet<>(wordList); + if (beginWord.length()!=endWord.length()||!wordList.contains(endWord)) return 0; + Set beginSet = new HashSet<>(); + Set endSet = new HashSet<>(); + beginSet.add(beginWord); + endSet.add(endWord); + int len = 1; + //这里endSet不可能为空的,一开始endSet size为1,后面只有beginSet.size()>endSet才会交换,所以endSet一定是>=1的 + while (!beginSet.isEmpty()) { + if (beginSet.size() > endSet.size()) { + Set set = beginSet; + beginSet = endSet; + endSet = set; + } + //这个set替换代替了queue.poll()的过程 + Set temp = new HashSet<>(); + for (String word : beginSet) { + char[] chs = word.toCharArray(); + for (int i = 0; i < chs.length; i++) { + char old = chs[i]; + for (char c = 'a'; c <= 'z'; c++) { + if (chs[i] == c) continue; + chs[i] = c; + String target = new String(chs); + //两端都有这个元素即返回 + if (endSet.contains(target)) return len + 1; + if (wordSet.contains(target)) { + wordSet.remove(target); + temp.add(target); + } + } + chs[i]=old; + } + } + beginSet = temp; + len++; + } + return 0; + /** * 全球站高赞双向BFS * 看着和一般的BFS不一样,没有用queue + * + * 纠正个误区,bfs不一定要用queue + * */ // if (!wordList.contains(endWord)) { // return 0; // } -// // Set wordSet = new HashSet<>(wordList); // Set beginSet = new HashSet<>(); // Set endSet = new HashSet<>(); // int len = 1; // HashSet visited = new HashSet<>(); -// // beginSet.add(beginWord); // endSet.add(endWord); -// +// wordList.remove(beginWord); +// wordList.remove(endWord); +// //endSet一直不为空? // while (!beginSet.isEmpty() && !endSet.isEmpty()) { // //始终让beginSet最小 // if (beginSet.size() > endSet.size()) { @@ -320,20 +193,21 @@ public int ladderLength(String beginWord, String endWord, List wordList) // beginSet = endSet; // endSet = set; // } -// // Set temp = new HashSet<>(); -// // for (String word : beginSet) { // char[] chs = word.toCharArray(); // for (int i = 0; i < chs.length; i++) { // for (char c = 'a'; c <= 'z'; c++) { +// //当前字符相等就没必要转换了 +// if (c == chs[i]) { +// continue; +// } // char old = chs[i]; // chs[i] = c; // String target = new String(chs); // if (endSet.contains(target)) { // return len + 1; // } -// // if (!visited.contains(target) && wordSet.contains(target)) { // temp.add(target); // visited.add(target); @@ -342,15 +216,11 @@ public int ladderLength(String beginWord, String endWord, List wordList) // } // } // } -// // beginSet = temp; // len++; -// // } -// // return 0; - /** * 全球站法二 */ @@ -384,24 +254,30 @@ public int ladderLength(String beginWord, String endWord, List wordList) /** * 全球站法三 2,3都是BFS + * + * 这个虽然是BFS,但是思路和奇妙,常规都是用一个set记录访问,然后用 + * !visited.contains(target) && wordSet.contains(target)这个去判断, + * 但是这个思路是每次碰到包含的元素直接移出wordSet,这样wordSet变小, + * 且少访问一个visitedSet,因为java的set.contain()是近似O(1),如果有hash冲突 + * 其实还是有消耗的 + * + * */ - if (!wordList.contains(endWord)) return 0; - HashSet set = new HashSet<>(wordList); - Queue q = new LinkedList<>(); - int length = 0; - set.add(endWord); - q.add(beginWord); - - while (!q.isEmpty()) { - int size = q.size(); - for (int i = 0; i < size; i++) { - String w = q.poll(); - if (w.equals(endWord)) return length + 1; - wordMatch(w, set, q); - } - length++; - } - return 0; +// if (!wordList.contains(endWord)) return 0; +// HashSet set = new HashSet<>(wordList); +// Queue q = new LinkedList<>(); +// int length = 0; +// q.add(beginWord); +// while (!q.isEmpty()) { +// int size = q.size(); +// for (int i = 0; i < size; i++) { +// String w = q.poll(); +// if (w.equals(endWord)) return length + 1; +// wordMatch(w, set, q); +// } +// length++; +// } +// return 0; @@ -423,22 +299,6 @@ private void wordMatch(String w, Set set, Queue q) { } } - private boolean canConvert(String curr, String str) { - - if (curr.length() != str.length()) { - return false; - } - - int diff = 0; - for (int i = 0; i < curr.length(); i++) { - if (curr.charAt(i) != str.charAt(i)) { - if(++diff>1) return false; - } - } - - return diff == 1; - } - private void dfs(int level, String beginWord, String endWord, List wordList, Set visited) { if (beginWord.equals(endWord)) { minLength = Math.min(minLength, level+1); From 90e8a15756fd0db37ba0dd1ea17fe36b1f63d7e0 Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 14 Mar 2021 22:57:06 +0800 Subject: [PATCH 06/15] six week. --- Week_05/CoinChange.java | 118 ++++++++++++++++++++++ Week_05/DecodeWays.java | 148 ++++++++++++++++++++++++++++ Week_05/MaximumProductSubarray.java | 100 +++++++++++++++++++ Week_05/MaximumSubarray.java | 139 ++++++++++++++++++++++++++ Week_05/MinimumPathSum.java | 93 +++++++++++++++++ Week_05/README.md | 47 ++++++++- Week_05/Triangle.java | 125 +++++++++++++++++++++++ 7 files changed, 769 insertions(+), 1 deletion(-) create mode 100644 Week_05/CoinChange.java create mode 100644 Week_05/DecodeWays.java create mode 100644 Week_05/MaximumProductSubarray.java create mode 100644 Week_05/MaximumSubarray.java create mode 100644 Week_05/MinimumPathSum.java create mode 100644 Week_05/Triangle.java diff --git a/Week_05/CoinChange.java b/Week_05/CoinChange.java new file mode 100644 index 00000000..658c8022 --- /dev/null +++ b/Week_05/CoinChange.java @@ -0,0 +1,118 @@ +//题号:322 +//https://leetcode-cn.com/problems/coin-change/ +//给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 +// -1。 +// +// 你可以认为每种硬币的数量是无限的。 +// +// +// +// 示例 1: +// +// +//输入:coins = [1, 2, 5], amount = 11 +//输出:3 +//解释:11 = 5 + 5 + 1 +// +// 示例 2: +// +// +//输入:coins = [2], amount = 3 +//输出:-1 +// +// 示例 3: +// +// +//输入:coins = [1], amount = 0 +//输出:0 +// +// +// 示例 4: +// +// +//输入:coins = [1], amount = 1 +//输出:1 +// +// +// 示例 5: +// +// +//输入:coins = [1], amount = 2 +//输出:2 +// +// +// +// +// 提示: +// +// +// 1 <= coins.length <= 12 +// 1 <= coins[i] <= 231 - 1 +// 0 <= amount <= 104 +// +// Related Topics 动态规划 +// 👍 1107 👎 0 + + +public class CoinChange { + public static void main(String[] args) { + Solution solution = new CoinChange().new Solution(); + solution.coinChange(new int[]{2}, 3); + } + + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public int coinChange(int[] coins, int amount) { + + /** + * 动态规划: + * + * 最优子结构为f(n)=f(n-c)+1 + * 上面的1我是这么理解的,你从f(n-c)到f(n),会花费一枚硬币,因此要+1 + * + * + * 1、找重复性:program(i)=Math.min(subProgram(i-1),subProgram(i-3),subProgram(i-5))+1 + * 2、状态数组定义:dp[],这里dp存的是最少的硬币数,n代表的是金额数 + * 3、状态转移方程:dp[i]=Math.min(dp[i-1],dp[i-3],dp[i-5])+1; + * + * 时间复杂度O(m*n),空间复杂度O(n) + * + */ + + int[] dp = new int[amount + 1]; + for (int i = 1; i <= amount; i++) { + int min = amount + 1; + for (int j = 0; j < coins.length; j++) { + //下面这样写是为了保证dp[i]是dp[i-coin[j]]里最小的,两种写法都行, + //还有种写法是先给数组赋值,然后在比,其实都是为了保证最小 + if (i - coins[j] >= 0 && dp[i - coins[j]] + 1 < min) { + min = dp[i - coins[j]] + 1; +// if (i - coins[j] >= 0) { +// min = Math.min(dp[i - coins[j]] + 1, min); + } + } + dp[i] = min; + } + + return dp[amount] > amount ? -1 : dp[amount]; + + +// int max = amount + 1; +// int dp[] = new int[amount + 1]; +// Arrays.fill(dp, max); +// dp[0] = 0; +// for (int i = 1; i <= amount; i++) { +// for (int j = 0; j < coins.length; j++) { +// if (coins[j] <= i) { +// dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); +// } +// } +// } +// return dp[amount] > amount ? -1 : dp[amount]; + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_05/DecodeWays.java b/Week_05/DecodeWays.java new file mode 100644 index 00000000..bc73d935 --- /dev/null +++ b/Week_05/DecodeWays.java @@ -0,0 +1,148 @@ +//题号:91 +//https://leetcode-cn.com/problems/decode-ways/ +//一条包含字母 A-Z 的消息通过以下映射进行了 编码 : +// +// +//'A' -> 1 +//'B' -> 2 +//... +//'Z' -> 26 +// +// +// 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"111" 可以将 "1" 中的每个 "1" 映射为 "A +//" ,从而得到 "AAA" ,或者可以将 "11" 和 "1"(分别为 "K" 和 "A" )映射为 "KA" 。注意,"06" 不能映射为 "F" ,因为 " +//6" 和 "06" 不同。 +// +// 给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数 。 +// +// 题目数据保证答案肯定是一个 32 位 的整数。 +// +// +// +// 示例 1: +// +// +//输入:s = "12" +//输出:2 +//解释:它可以解码为 "AB"(1 2)或者 "L"(12)。 +// +// +// 示例 2: +// +// +//输入:s = "226" +//输出:3 +//解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 +// +// +// 示例 3: +// +// +//输入:s = "0" +//输出:0 +//解释:没有字符映射到以 0 开头的数字。含有 0 的有效映射是 'J' -> "10" 和 'T'-> "20" 。由于没有字符,因此没有有效的方法对此进行 +//解码,因为所有数字都需要映射。 +// +// +// 示例 4: +// +// +//输入:s = "06" +//输出:0 +//解释:"06" 不能映射到 "F" ,因为字符串开头的 0 无法指向一个有效的字符。  +// +// +// +// +// 提示: +// +// +// 1 <= s.length <= 100 +// s 只包含数字,并且可能包含前导零。 +// +// Related Topics 字符串 动态规划 +// 👍 641 👎 0 + + +public class DecodeWays { + public static void main(String[] args) { + Solution solution = new DecodeWays().new Solution(); + solution.numDecodings("06"); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int numDecodings(String s) { + /** + * 动态规划: + * 1、重复子问题:program(i)=subProgram(i-1)+一个字符+subProgram(i-2)+两个字符组合 + * 2、状态转移数组:dp[],dp值代表编码总数 + * 3、状态转移方程:dp[i]=dp[i-1]+一个字符+dp[i-2]+两个字符(这时候一个两个字符要分情况讨论) + * + * 字符串分情况讨论: + * 1、当末尾为0,即s.charAt(i)=='0',这时候只有i-1为1和2才会有效,数字为10/20,超过2找不到编码字母, + * 前面多少种就是多少种,因此dp[i]=dp[i-2],不是1或者2则是0 + * 2、i-1为1,i可以为任意数,且可以组成一个或者组成两个dp[i]=dp[i-1]+dp[i-2] + * 3、i-1为2,i只能小于7,前面已经排除0了,因此是1-6,dp[i]=dp[i-1]+dp[i-2]else dp[i]=dp[i-2] + * + */ + + + if (s.equals("0")) { + return 0; + } + int[] dp = new int[s.length() + 1]; + //这里的dp[0]我理解知识一个凑数的,为了算出dp[2] + dp[0] = 1; + //比如"06"这种,不能直接赋值成1,要根据第一位是什么再赋值 + dp[1] = s.charAt(0) == '0' ? 0 : 1; + + + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) == '0') { + if (s.charAt(i - 1) == '1' || s.charAt(i - 1) == '2') { + dp[i + 1] = dp[i - 1]; + } else { + return 0; + } + } else if (s.charAt(i - 1) == '1' || s.charAt(i - 1) == '2' && s.charAt(i) < '7') { + dp[i + 1] = dp[i] + dp[i - 1]; + } else { + dp[i + 1] = dp[i]; + } + } + + return dp[s.length()]; + + + /** + * 和上面相似的写法 + */ + +// if (s.equals('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++) { +// if (s.charAt(i-1) == '1' || s.charAt(i-1) == '2' && s.charAt(i) < '7') { +// if (s.charAt(i) == '0') { +// dp[i + 1] = dp[i - 1]; +// } else { +// dp[i + 1] = dp[i] + dp[i - 1]; +// } +// } else if (s.charAt(i) == '0') { +// return 0; +// } else { +// dp[i + 1] = dp[i]; +// } +// } +// +// return dp[s.length()]; + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_05/MaximumProductSubarray.java b/Week_05/MaximumProductSubarray.java new file mode 100644 index 00000000..5f16a849 --- /dev/null +++ b/Week_05/MaximumProductSubarray.java @@ -0,0 +1,100 @@ +//题号:152 +//https://leetcode-cn.com/problems/maximum-product-subarray/description/ +//给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 +// +// +// +// 示例 1: +// +// 输入: [2,3,-2,4] +//输出: 6 +//解释: 子数组 [2,3] 有最大乘积 6。 +// +// +// 示例 2: +// +// 输入: [-2,0,-1] +//输出: 0 +//解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 +// Related Topics 数组 动态规划 +// 👍 978 👎 0 + + +public class MaximumProductSubarray { + public static void main(String[] args) { + Solution solution = new MaximumProductSubarray().new Solution(); + solution.maxProduct(new int[]{-4,-3,-2}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int maxProduct(int[] nums) { + + /** + * 动态规划 + * + * 1、program(i)=Math.max(subProgramMax(i-1)*nums[i],nums[i],subProgramMin(i-1)*nums[i]); + * 2、状态数组dpMax[],dpMin[],或者两个变量:dpMax,dpMin + * 3、dp转移方程:dp[i]=Math.max(dp/max[i-1]*nums[i],nums[i],dp/min(i-1)*nums[i]); + * + * 时间复杂度O(n),空间复杂度O(n) + * + */ + +// if (nums == null || nums.length == 0) { +// return 0; +// } +// +// int[] dpMax = new int[nums.length]; +// int[] dpMin = new int[nums.length]; +// +// dpMax[0] = nums[0]; +// dpMin[0] = nums[0]; +// int max = nums[0]; +// +// +// for (int i = 1; i < nums.length; i++) { +// dpMax[i] = Math.max(dpMax[i - 1] * nums[i], Math.max(nums[i], dpMin[i - 1] * nums[i])); +// dpMin[i] = Math.min(dpMax[i - 1] * nums[i], Math.min(nums[i], dpMin[i - 1] * nums[i])); +// max = max > dpMax[i] ? max : dpMax[i]; +// } +// +// return max; + + + /** + * 动态规划优化,将数组改为变量 + * + * 时间复杂度O(n),空间复杂度O(1) + * + */ + if (nums == null || nums.length == 0) { + return 0; + } + + + int dpMax = nums[0]; + int dpMin = nums[0]; + int max = nums[0]; + + for (int i = 1; i < nums.length; i++) { + //这儿需要两个零时变量存储dpMax和dpMin的值,不然会在计算的时候被覆盖,导致dpMin计算有问题 + int tMax = dpMax, tMin = dpMin; + dpMax = Math.max(tMax * nums[i], Math.max(nums[i], tMin * nums[i])); + dpMin = Math.min(tMax * nums[i], Math.min(nums[i], tMin * nums[i])); + max = Math.max(max, dpMax); + } + + return max; + + + + + + + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_05/MaximumSubarray.java b/Week_05/MaximumSubarray.java new file mode 100644 index 00000000..e00c651b --- /dev/null +++ b/Week_05/MaximumSubarray.java @@ -0,0 +1,139 @@ +//题号:53 +//给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 +// +// +// +// 示例 1: +// +// +//输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +//输出:6 +//解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 +// +// +// 示例 2: +// +// +//输入:nums = [1] +//输出:1 +// +// +// 示例 3: +// +// +//输入:nums = [0] +//输出:0 +// +// +// 示例 4: +// +// +//输入:nums = [-1] +//输出:-1 +// +// +// 示例 5: +// +// +//输入:nums = [-100000] +//输出:-100000 +// +// +// +// +// 提示: +// +// +// 1 <= nums.length <= 3 * 104 +// -105 <= nums[i] <= 105 +// +// +// +// +// 进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。 +// Related Topics 数组 分治算法 动态规划 +// 👍 2947 👎 0 + + +public class MaximumSubarray { + public static void main(String[] args) { + Solution solution = new MaximumSubarray().new Solution(); + solution.maxSubArray(new int[]{5,4,-1,7,8}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int maxSubArray(int[] nums) { + + + /** + * 因为这个有连续性,一旦Nums[i]不要, + * 那就只能从新开始从nums[i]开始,不能跳着取 + * + * 因此状态转移方程为dp[i]=Math.max(dp[i-1]+nums[i],nums[i]). + * + * 且dp方程记录的是每个位置的最大值,但是最后一个位置不一定是全局最大值, + * 因此需要一个变量在遍历的时候记录最大值 + * + * 时间复杂度O(n),空间复杂度O(n) + * + */ +// int max = nums[0]; +// int[] dp = new int[nums.length]; +// dp[0] = nums[0]; +// //有i-1,i要从1开始 +// for (int i = 1; i < nums.length; i++) { +// dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]); +// max = Math.max(max, dp[i]); +// } +// +// +// return max; + + + /** + * 其实可以在原数组上直接进行缓存存储,直接用Nums代替dp进行计算 + * + * 时间复杂度O(n),空间复杂度O(1) + * + */ + +// int max = nums[0]; +// for (int i = 1; i < nums.length; i++) { +// nums[i] = Math.max(nums[i - 1] + nums[i], nums[i]); +// max = Math.max(max, nums[i]); +// } +// +// +// return max; + + + /** + * 动态规划优化: + * 因为都是从前往后推的,只会用到dp[i-1], + * 因此可以直接用一个变量记录,将复杂度降到了O(1) + * + * max的默认值要是nums[0],否则只有一个负数时会错取了preMax的值,0 + * + * 时间复杂度O(n),空间复杂度O(1) + */ + + int max = nums[0]; + int preMax = 0; + + for (int i = 0; i < nums.length; i++) { + + preMax = Math.max(preMax + nums[i], nums[i]); + max = Math.max(preMax, max); + + } + + + return max; + + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_05/MinimumPathSum.java b/Week_05/MinimumPathSum.java new file mode 100644 index 00000000..9bb52745 --- /dev/null +++ b/Week_05/MinimumPathSum.java @@ -0,0 +1,93 @@ +//题号:64 +// +//给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 +// +// 说明:每次只能向下或者向右移动一步。 +// +// +// +// 示例 1: +// +// +//输入:grid = [[1,3,1],[1,5,1],[4,2,1]] +//输出:7 +//解释:因为路径 1→3→1→1→1 的总和最小。 +// +// +// 示例 2: +// +// +//输入:grid = [[1,2,3],[4,5,6]] +//输出:12 +// +// +// +// +// 提示: +// +// +// m == grid.length +// n == grid[i].length +// 1 <= m, n <= 200 +// 0 <= grid[i][j] <= 100 +// +// Related Topics 数组 动态规划 +// 👍 805 👎 0 + + +public class MinimumPathSum { + public static void main(String[] args) { + Solution solution = new MinimumPathSum().new Solution(); + solution.minPathSum(new int[][]{{1, 3, 1}, {1, 5, 1}, {4, 2, 1}}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int minPathSum(int[][] grid) { + + /** + * 动态规划 + * + * 1、重复性,子问题:program(i,j)=Math(subProgram(i-1,j),subProgram(i,j-1))+grid[i][j]; + * 2、状态数组:dp[i][j] + * 3、状态转移方程:dp[i][j]=Math.min(dp[i-1][j]+dp[i][j-1])+grid[i][j] + * + * 时间复杂度O(n^2),空间复杂度O(1) + * + */ + + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[i].length; j++) { + if (i > 0 && j > 0) { + grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1])+grid[i][j]; + } else if (i > 0) { + grid[i][j] = grid[i - 1][j] + grid[i][j]; + } else if (j > 0) { + grid[i][j] = grid[i][j - 1] + grid[i][j]; + } + } + } + + return grid[grid.length - 1][grid[0].length - 1]; + + + /** + * 不同判断方式 + */ +// for (int i = 0; i < grid.length; i++) { +// for (int j = 0; j < grid[0].length; j++) { +// if (i==0 && j==0) continue; +// 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 - 1][j], grid[i][j - 1]) + grid[i][j]; +// } +// } +// } +// return grid[grid.length - 1][grid[0].length - 1]; + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_05/README.md b/Week_05/README.md index 50de3041..725663f1 100644 --- a/Week_05/README.md +++ b/Week_05/README.md @@ -1 +1,46 @@ -学习笔记 \ No newline at end of file +#### 动态规划 + +##### 特点 + +1. 分解成重复子问题 +2. 找最优子结构 + +##### 做题方式 + +- 自顶向下(以Fibonacci为例,从最上面往下推导,先找f(n),然后依次找f(n-1),f(n-2),最后找到f(1),这种比较符合人脑思维),这种一般是递归+记忆化搜索(就是所谓的加缓存)。 +- 自底向上(从最下面往上推到,先找到f(1),f(2),以这两个为基础推导到f(n),这个一般是计算机执行方式,也就是递归执行路径),因为递归其实也就是迭代for loop,自底向上就是直接for loop,用已知的值推导出未知的值,然后一直递推。 + +##### 动态规划关键点 + +1. 最优子结构 opt[i,j]=best_of(opt[n-1],opt[n-2],....) + +2. 存储中间状态:opt[i] + +3. 递推公式:(美其名曰:状态转移方程或者DP方程) + + Fib:opt[i]=opt[i-1]+opt[i-2] + + 二维路径:opt[i,j]=opt[i+1,j]+opt[i,j+1](需判断a[i,j]是否为空地) + +##### 动态规划小结 + +1. 打破自己的思维惯性,形成机器思维。(机器思维->找重复性) +2. 理解复杂逻辑的关键。 +3. 也是职业进阶的要点要领。(不要凡事亲力亲为,要放权给下面人做,允许下面人犯错) + +##### 本周总结 + +动态规划这块内容,感觉真的不好理解,这周课程的每个视频我平均看了4-5次,才大概理清动态规划的工作原理。这个很考验抽象功底啊,很多问题都需要抽象出一定的东西在进行动态规划,现在做题除了二维矩阵,其他我都感觉抽象不出来。 + +我理解动态规划可以由递归分治推导而来,加上一定的缓存(记忆化搜索),然后可以慢慢弄出来动态规划的方程。但是这种**自顶向下**的方式其实是很低效的,符合人脑思维,但是实质还是递归/分治+缓存。而真正的动态规划一定是**自下而上**,直接由已知数值推导出目标参数,直接for loop往上推。(**这个很考基本功啊,递归不太行基本懵逼,多练才是正途**) + +这两天做题,总结了下动态规划解题思路: + +1. **寻找重复性,最优子结构**:这个不一定是固定的,可能还会根据不同条件去分情况讨论。(比如编码那道题) +2. **定义状态数组**:这个除了有的题不好定义,一般根据参数是什么就定义什么,比如矩阵就二维,但我感觉比较有问题的点是一些数组的初始值定义,初学者总会出问题。 +3. **定义状态转移方程**:大部分的题,第一个最优子结构找出来,状态转移方程基本就定好了。 + +动态规划的题,数组的下标,感觉很容易出问题,稍不注意就下标越界。 + +这块题做和消化都要花挺长时间的,而且题还很很多,后续要多花点精力在这边,把没做完的题刷完。 + diff --git a/Week_05/Triangle.java b/Week_05/Triangle.java new file mode 100644 index 00000000..62655806 --- /dev/null +++ b/Week_05/Triangle.java @@ -0,0 +1,125 @@ +//题号:120 +//https://leetcode-cn.com/problems/triangle/description/ +//给定一个三角形 triangle ,找出自顶向下的最小路径和。 +// +// 每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果 +//正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。 +// +// +// +// 示例 1: +// +// +//输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] +//输出:11 +//解释:如下面简图所示: +// 2 +// 3 4 +// 6 5 7 +//4 1 8 3 +//自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 +// +// +// 示例 2: +// +// +//输入:triangle = [[-10]] +//输出:-10 +// +// +// +// +// 提示: +// +// +// 1 <= triangle.length <= 200 +// triangle[0].length == 1 +// triangle[i].length == triangle[i - 1].length + 1 +// -104 <= triangle[i][j] <= 104 +// +// +// +// +// 进阶: +// +// +// 你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗? +// +// Related Topics 数组 动态规划 +// 👍 709 👎 0 + + +import java.util.List; + +public class Triangle { + public static void main(String[] args) { + Solution solution = new Triangle().new Solution(); +// solution.minimumTotal(Arrays.asList(new Integer[]{{2}, {3, 4}, {6, 5, 7}, {4, 1, 8, 3}})); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int minimumTotal(List> triangle) { + + /** + * 动态规划:这儿只能从下往上推,而不能从上往下,因为从下往上都会走到f[0][0]那个点, + * 就是最小值,但是从上往下到最后一行,会是多个,推不出来 + * + * 状态转移数组:f[i][j]=Math.min(f[i+1][j],f[i+1][j+1])+f[i][j]; + * + * 时间复杂度O(n^2),空间复杂度O(n^2) + * + */ +// if (triangle.isEmpty()) { +// return 0; +// } +// +// int n = triangle.size(); +// //数组+1,有效防止下标越界 +// int[][] dp = new int[n + 1][n + 1]; +// +// +// /** +// * 这里是三角形,因此j的最大值只能到i +// */ +// for (int i = n - 1; i >= 0; i--) { +//// for (int j = 0; j <= i; j++) { +// for (int j = i; j >= 0; j--) { +// dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j); +// } +// } +// +// return dp[0][0]; + + /** + * 优化,因为每次只会用下一行的数据往上递推,因此 + * dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j); + * 可以直接写成dp[j]+Math.min(dp[j],dp[j+1])+triangle.get(i).get(j) + * 用一维数组代替二位数组 + * + * 时间复杂度O(n^2),空间复杂度O(n) + */ + + if (triangle.isEmpty()) { + return 0; + } + + int n = triangle.size(); + int[] dp = new int[n + 1]; + + + for (int i = n - 1; i >= 0; i--) { + //这儿只能从0推到i,不能反着来 + for (int j = 0; j <= i; j++) { + dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); + } + } + + return dp[0]; + + + } + +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From 9f331b98ee35d7748cf5702d3d325741924ca5e0 Mon Sep 17 00:00:00 2001 From: lianght1 Date: Sun, 14 Mar 2021 22:59:42 +0800 Subject: [PATCH 07/15] resend six week. --- Week_05/README.md | 47 +------------------ {Week_05 => Week_06}/CoinChange.java | 0 {Week_05 => Week_06}/DecodeWays.java | 0 .../MaximumProductSubarray.java | 0 {Week_05 => Week_06}/MaximumSubarray.java | 0 {Week_05 => Week_06}/MinimumPathSum.java | 0 Week_06/README.md | 47 ++++++++++++++++++- {Week_05 => Week_06}/Triangle.java | 0 8 files changed, 47 insertions(+), 47 deletions(-) rename {Week_05 => Week_06}/CoinChange.java (100%) rename {Week_05 => Week_06}/DecodeWays.java (100%) rename {Week_05 => Week_06}/MaximumProductSubarray.java (100%) rename {Week_05 => Week_06}/MaximumSubarray.java (100%) rename {Week_05 => Week_06}/MinimumPathSum.java (100%) rename {Week_05 => Week_06}/Triangle.java (100%) diff --git a/Week_05/README.md b/Week_05/README.md index 725663f1..50de3041 100644 --- a/Week_05/README.md +++ b/Week_05/README.md @@ -1,46 +1 @@ -#### 动态规划 - -##### 特点 - -1. 分解成重复子问题 -2. 找最优子结构 - -##### 做题方式 - -- 自顶向下(以Fibonacci为例,从最上面往下推导,先找f(n),然后依次找f(n-1),f(n-2),最后找到f(1),这种比较符合人脑思维),这种一般是递归+记忆化搜索(就是所谓的加缓存)。 -- 自底向上(从最下面往上推到,先找到f(1),f(2),以这两个为基础推导到f(n),这个一般是计算机执行方式,也就是递归执行路径),因为递归其实也就是迭代for loop,自底向上就是直接for loop,用已知的值推导出未知的值,然后一直递推。 - -##### 动态规划关键点 - -1. 最优子结构 opt[i,j]=best_of(opt[n-1],opt[n-2],....) - -2. 存储中间状态:opt[i] - -3. 递推公式:(美其名曰:状态转移方程或者DP方程) - - Fib:opt[i]=opt[i-1]+opt[i-2] - - 二维路径:opt[i,j]=opt[i+1,j]+opt[i,j+1](需判断a[i,j]是否为空地) - -##### 动态规划小结 - -1. 打破自己的思维惯性,形成机器思维。(机器思维->找重复性) -2. 理解复杂逻辑的关键。 -3. 也是职业进阶的要点要领。(不要凡事亲力亲为,要放权给下面人做,允许下面人犯错) - -##### 本周总结 - -动态规划这块内容,感觉真的不好理解,这周课程的每个视频我平均看了4-5次,才大概理清动态规划的工作原理。这个很考验抽象功底啊,很多问题都需要抽象出一定的东西在进行动态规划,现在做题除了二维矩阵,其他我都感觉抽象不出来。 - -我理解动态规划可以由递归分治推导而来,加上一定的缓存(记忆化搜索),然后可以慢慢弄出来动态规划的方程。但是这种**自顶向下**的方式其实是很低效的,符合人脑思维,但是实质还是递归/分治+缓存。而真正的动态规划一定是**自下而上**,直接由已知数值推导出目标参数,直接for loop往上推。(**这个很考基本功啊,递归不太行基本懵逼,多练才是正途**) - -这两天做题,总结了下动态规划解题思路: - -1. **寻找重复性,最优子结构**:这个不一定是固定的,可能还会根据不同条件去分情况讨论。(比如编码那道题) -2. **定义状态数组**:这个除了有的题不好定义,一般根据参数是什么就定义什么,比如矩阵就二维,但我感觉比较有问题的点是一些数组的初始值定义,初学者总会出问题。 -3. **定义状态转移方程**:大部分的题,第一个最优子结构找出来,状态转移方程基本就定好了。 - -动态规划的题,数组的下标,感觉很容易出问题,稍不注意就下标越界。 - -这块题做和消化都要花挺长时间的,而且题还很很多,后续要多花点精力在这边,把没做完的题刷完。 - +学习笔记 \ No newline at end of file diff --git a/Week_05/CoinChange.java b/Week_06/CoinChange.java similarity index 100% rename from Week_05/CoinChange.java rename to Week_06/CoinChange.java diff --git a/Week_05/DecodeWays.java b/Week_06/DecodeWays.java similarity index 100% rename from Week_05/DecodeWays.java rename to Week_06/DecodeWays.java diff --git a/Week_05/MaximumProductSubarray.java b/Week_06/MaximumProductSubarray.java similarity index 100% rename from Week_05/MaximumProductSubarray.java rename to Week_06/MaximumProductSubarray.java diff --git a/Week_05/MaximumSubarray.java b/Week_06/MaximumSubarray.java similarity index 100% rename from Week_05/MaximumSubarray.java rename to Week_06/MaximumSubarray.java diff --git a/Week_05/MinimumPathSum.java b/Week_06/MinimumPathSum.java similarity index 100% rename from Week_05/MinimumPathSum.java rename to Week_06/MinimumPathSum.java diff --git a/Week_06/README.md b/Week_06/README.md index 50de3041..725663f1 100644 --- a/Week_06/README.md +++ b/Week_06/README.md @@ -1 +1,46 @@ -学习笔记 \ No newline at end of file +#### 动态规划 + +##### 特点 + +1. 分解成重复子问题 +2. 找最优子结构 + +##### 做题方式 + +- 自顶向下(以Fibonacci为例,从最上面往下推导,先找f(n),然后依次找f(n-1),f(n-2),最后找到f(1),这种比较符合人脑思维),这种一般是递归+记忆化搜索(就是所谓的加缓存)。 +- 自底向上(从最下面往上推到,先找到f(1),f(2),以这两个为基础推导到f(n),这个一般是计算机执行方式,也就是递归执行路径),因为递归其实也就是迭代for loop,自底向上就是直接for loop,用已知的值推导出未知的值,然后一直递推。 + +##### 动态规划关键点 + +1. 最优子结构 opt[i,j]=best_of(opt[n-1],opt[n-2],....) + +2. 存储中间状态:opt[i] + +3. 递推公式:(美其名曰:状态转移方程或者DP方程) + + Fib:opt[i]=opt[i-1]+opt[i-2] + + 二维路径:opt[i,j]=opt[i+1,j]+opt[i,j+1](需判断a[i,j]是否为空地) + +##### 动态规划小结 + +1. 打破自己的思维惯性,形成机器思维。(机器思维->找重复性) +2. 理解复杂逻辑的关键。 +3. 也是职业进阶的要点要领。(不要凡事亲力亲为,要放权给下面人做,允许下面人犯错) + +##### 本周总结 + +动态规划这块内容,感觉真的不好理解,这周课程的每个视频我平均看了4-5次,才大概理清动态规划的工作原理。这个很考验抽象功底啊,很多问题都需要抽象出一定的东西在进行动态规划,现在做题除了二维矩阵,其他我都感觉抽象不出来。 + +我理解动态规划可以由递归分治推导而来,加上一定的缓存(记忆化搜索),然后可以慢慢弄出来动态规划的方程。但是这种**自顶向下**的方式其实是很低效的,符合人脑思维,但是实质还是递归/分治+缓存。而真正的动态规划一定是**自下而上**,直接由已知数值推导出目标参数,直接for loop往上推。(**这个很考基本功啊,递归不太行基本懵逼,多练才是正途**) + +这两天做题,总结了下动态规划解题思路: + +1. **寻找重复性,最优子结构**:这个不一定是固定的,可能还会根据不同条件去分情况讨论。(比如编码那道题) +2. **定义状态数组**:这个除了有的题不好定义,一般根据参数是什么就定义什么,比如矩阵就二维,但我感觉比较有问题的点是一些数组的初始值定义,初学者总会出问题。 +3. **定义状态转移方程**:大部分的题,第一个最优子结构找出来,状态转移方程基本就定好了。 + +动态规划的题,数组的下标,感觉很容易出问题,稍不注意就下标越界。 + +这块题做和消化都要花挺长时间的,而且题还很很多,后续要多花点精力在这边,把没做完的题刷完。 + diff --git a/Week_05/Triangle.java b/Week_06/Triangle.java similarity index 100% rename from Week_05/Triangle.java rename to Week_06/Triangle.java From 6a71a201fefa01555bd98f56a219c37928b731ce Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 21 Mar 2021 22:15:51 +0800 Subject: [PATCH 08/15] week 7. --- Week_07/ClimbingStairs.java | 225 +++++++++++++++ Week_07/GenerateParentheses4DP.java | 415 +++++++++++++++++++++++++++ Week_07/MinimumGeneticMutation1.java | 289 +++++++++++++++++++ Week_07/NQueens.java | 230 +++++++++++++++ Week_07/README.md | 53 +++- Week_07/ValidSudoku.java | 130 +++++++++ Week_07/WordLadder1.java | 327 +++++++++++++++++++++ 7 files changed, 1668 insertions(+), 1 deletion(-) create mode 100644 Week_07/ClimbingStairs.java create mode 100644 Week_07/GenerateParentheses4DP.java create mode 100644 Week_07/MinimumGeneticMutation1.java create mode 100644 Week_07/NQueens.java create mode 100644 Week_07/ValidSudoku.java create mode 100644 Week_07/WordLadder1.java diff --git a/Week_07/ClimbingStairs.java b/Week_07/ClimbingStairs.java new file mode 100644 index 00000000..3f49edd1 --- /dev/null +++ b/Week_07/ClimbingStairs.java @@ -0,0 +1,225 @@ +//70 +//https://leetcode-cn.com/problems/climbing-stairs/ +//假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 +// +// 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? +// +// 注意:给定 n 是一个正整数。 +// +// 示例 1: +// +// 输入: 2 +//输出: 2 +//解释: 有两种方法可以爬到楼顶。 +//1. 1 阶 + 1 阶 +//2. 2 阶 +// +// 示例 2: +// +// 输入: 3 +//输出: 3 +//解释: 有三种方法可以爬到楼顶。 +//1. 1 阶 + 1 阶 + 1 阶 +//2. 1 阶 + 2 阶 +//3. 2 阶 + 1 阶 +// +// Related Topics 动态规划 +// 👍 1431 👎 0 + +import java.util.HashMap; +import java.util.Map; + +public class ClimbingStairs { + public static void main(String[] args) { + Solution solution = new ClimbingStairs().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + Map dpMap = new HashMap<>(); + public int climbStairs(int n) { + + /** + * 法一:动态规划 + */ +// if (n <= 2) { +// return n; +// } +// +// //这里用数组缓存了算出来的数据,不用每次零时计算 +// //因为下标是从1开始,而不是0,因此到n需要多加1 +// int[] f = new int[n + 1]; +// f[1] = 1; +// f[2] = 2; +// +// for (int i = 3; i <= n; i++) { +// f[i] = f[i - 1] + f[i - 2]; +// } +// +// return f[n]; + + /** + * 法二:公式求解,不推荐 + */ + +// double sqrt5 = Math.sqrt(5); +// double fibn = Math.pow((1 + sqrt5) / 2, n + 1) - Math.pow((1 - sqrt5) / 2, n + 1); +// +// return (int) (fibn/sqrt5); + /** + * 法三:动态规划优化 + * 实际上我们不需要缓存所有情况,只需要缓存三个结果即可 + * f(3)=f(2)+f(1) + * f(4)=f(3)+f(2) + * + * f(4)我们只需要知道前两个状态即可,前两个状态可以根据 + * 上一次计算得到 + * int one_step=1; + * int two_step=2; + * int all_way=one_step+two_step; + * + * 存储倒退两步的结果 + * one_step=two_step; + * 存储倒退一步的结果 + * two_step=all_way; + * 进而用这两个变量得到下一步的结果 + * + * 可以理解有f(1),f(2),f(3)结果,然后要得到f(4), + * 将f(1)=f(2),f(2)=f(3),f(4)=f(1)+f(2),这样循环,所有都能由f(1)+f(2)得到 + * 然后将这里的f(1),f(2)换成one_step,two_step + * + * 2021-02-23 + * + * 1、f(3)=f(2)+f(1) + * 2、f(4)=f(3)+f(2) + * 3、f(5)=f(4)+f(3) + * 变量形式: + * 有三个变量,one,two,all + * one代表一步,two代表两步,all代表第n步走多少 + * 1、all=one+two + * 这时候已经得出当前值,但是只有两个变量,我们需要用着两个变量得到下一次的结果, + * 因此需要对着两个变量做改动,如f(4)的时候,需要f(3)和f(2)的值,那我们就将f(3)和f(2)的值 + * 分别赋给两个变量two和one,后续一直重复上述操作即可 + * note:这里只能从前往后赋值,将2,3换行会导致one,two都变成all + * 2、one=two; + * 3、two=all; + * + */ + + if (n <= 2) { + return n; + } + + int one_step = 2; + int two_step = 1; + int all_way = 0; + + for (int i = 3; i <= n; i++) { + all_way = one_step + two_step; + two_step = one_step; + one_step = all_way; + } + + return all_way; + + + /** + * 法四:递归加hashMap缓存 + */ + +// if (n <= 2) { +// return n; +// } +// +// if (dpMap.containsKey(n)) { +// return dpMap.get(n); +// } +// +// int temp = climbStairs(n - 1) + climbStairs(n - 2); +// dpMap.put(n, temp); +// +// return temp; + + + /** + * 法五:递归+数组缓存 + */ +// int[] cache = new int[n + 1]; +// +// return recur(cache, n); + + } + + private int recur(int[] cache, int n) { + + if (n <= 2) { + return n; + } + + if (cache[n] != 0) { + return cache[n]; + } + + int temp = recur(cache, n - 1) + recur(cache, n - 2); + + cache[n] = temp; + + return temp; + + } + + + //公式求解 +// public int climbStairs(int n) { +// +// double sqrt5 = Math.sqrt(5); +// double fibn = Math.pow((1 + sqrt5) / 2, n + 1) - Math.pow((1 - sqrt5) / 2, n + 1); +// +// return (int) (fibn/sqrt5); +// } + + /** + * 动态规划优化 + */ +// public int climbStairs(int n) { +// +// if (n <= 2) { +// return n; +// } +// +// int one_step = 2; +// int two_step = 1; +// int all_way = 0; +// +// for (int i = 3; i <= n; i++) { +// all_way = one_step + two_step; +// two_step = one_step; +// one_step = all_way; +// } +// +// return all_way; +// } + + /** + * 递归加缓存优化 + */ +// Map dpMap = new HashMap<>(); +// public int climbStairs(int n) { +// if (n <= 2) { +// return n; +// } +// +// if (dpMap.containsKey(n)) { +// return dpMap.get(n); +// } +// int temp = climbStairs(n - 1) + climbStairs(n - 2); +// dpMap.put(n, temp); +// return temp; +// +// } + + +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_07/GenerateParentheses4DP.java b/Week_07/GenerateParentheses4DP.java new file mode 100644 index 00000000..c0c64c07 --- /dev/null +++ b/Week_07/GenerateParentheses4DP.java @@ -0,0 +1,415 @@ +//题号:22 +//https://leetcode-cn.com/problems/generate-parentheses/ +//数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 +// +// +// +// 示例 1: +// +// +//输入:n = 3 +//输出:["((()))","(()())","(())()","()(())","()()()"] +// +// +// 示例 2: +// +// +//输入:n = 1 +//输出:["()"] +// +// +// +// +// 提示: +// +// +// 1 <= n <= 8 +// +// Related Topics 字符串 回溯算法 +// 👍 1565 👎 0 + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class GenerateParentheses4DP { + public static void main(String[] args) { + Solution solution = new GenerateParentheses4DP().new Solution(); + solution.generateParenthesis(3); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List generateParenthesis(int n) { + + /** + * 动态规划法一: + * + * 中国站的动态规划感觉写的不是很好,不易于理解,通过全国站这个 + * 去理解吧 + * 主要就是分为左右两个部分,然后动态转移方程为S[n]="("+s[n-1-i]+")"+s[i] + * + * + * Keypoints: divide pattern into left and right part. + * Left part is wrapped by symbol "(" and ")". + * So, we get the sequence with these pattern. (LEFT)RIGHT. + * + * F[0] = [""] + * + * F[1] = (F[0])F0[0] = ["()"] + * + * F[2] = (F[0])F[1], (F[1])F[0] + * -> (F[0])F[1] = ()F[1] = ()() = ["()()"] + * -> (F[1])F[0] = (())F[0] = (()) = ["(())"] + * so, F[2] = ["()()", "(())"] + * + * F[3] = (F[0])F[2], (F[1])F[1], (F[2])F[0] = ["()()()", "()(())", "(())()", "(()())", "((()))"] + * + */ + + HashMap> map = new HashMap<>(); + map.put(0, new ArrayList<>(Arrays.asList(""))); + for (int i=1; i<=n; i++) { + //这儿每次右边对应的都是i-1-j,第一次j是0,因此右边就是i-1,后面因为j++,k--始终让k就是右边的值 + for (int j=0, k=i-1; j(Arrays.asList(tmp))); + } else { + map.get(i).add(tmp); + } + } + } + } + } + return map.get(n); + + + /** + * 动态规划法二: + * f(n) = [ (f(0))f(n-1), (f(1))f(n-2), ..., (f(n-1)) ], andf(0) = "". + * + * 动态转移方程S(n)="("+S(n-1-i)+")"+S(i) + */ + + +// List> parens = new ArrayList<>(); +// List zero_list = Arrays.asList(""); +// parens.add(zero_list); +// for (int i = 1; i < n + 1; i++) { +// List n_list = new ArrayList<>(); +// for (int j = 0; j < i; j++) { +// for (String s1: parens.get(j)) { +// for (String s2: parens.get(i - j - 1)) { +// n_list.add('(' + s1 + ')' + s2); +// } +// } +// } +// parens.add(n_list); +// } +// return parens.get(n); + + + /** + * 动态规划法三: + * 大概看了下,这个和上面法二一样的,但是丑了很多,感觉像是抄的全国站的解法。。。理解上面解法即可 + * + */ + +// List> res = new ArrayList<>(); +// +// List list = new ArrayList<>(); +// list.add(""); +// res.add(list); +// +// +// for (int i = 1; i <= n; i++) { +// List cur = new ArrayList<>(); +// for (int j = 0; j < i; j++) { +// //note:i+j=n-1 +// List str1 = res.get(j); +// List str2 = res.get(i - j - 1); +// +// for (String s1 : str1) { +// for (String s2 : str2) { +// String temp = "(" + s1 + ")" + s2; +// cur.add(temp); +// } +// } +// } +// res.add(cur); +// } +// +// return res.get(n); + + + + + + + + + + + + + + + /** + * dfs + */ + +// List res = new ArrayList<>(); +// dfs(0, 0, n, res, ""); +// return res; + + + /** + * dfs优化 + * + * 在原来dfs的基础上可以将字符串拼接改成char[]数组赋值,减去了 + * string在拼接的时候的时间动态构建StringBuilder的消耗 + * + */ + +// List res = new ArrayList<>(); +// //这里第三个参数i是用来记录char当前层的位置的 +// dfsChars(0, 0, 0, n, res, new char[2 * n]); +// +// return res; + + + + /** + * bfs + * 有的题解说下面这种解法是BFS, + * 但是我看着不像BFS,因为它没有拿到当前queue的size去遍历的过程,也就是没有每层去 + * 横扫所有节点的过程,看着就像是dfs的迭代写法,只是stack用了queue, + * 这里取出顺序其实并没什么影响 + * + */ + +// List res = new ArrayList<>(); +// if (n == 0) { +// return res; +// } +// Queue queue = new LinkedList<>(); +// queue.offer(new Node("", 0, 0)); +// +// while (!queue.isEmpty()) { +// +// int size = queue.size(); +// Node curNode = queue.poll(); +//// for (int i = 0; i < size; i++) { +// if (curNode.left == n && curNode.right == n) { +// res.add(curNode.res); +// } +// if (curNode.left < n) { +// queue.offer(new Node(curNode.res + "(", curNode.left + 1, curNode.right)); +// } +// if (curNode.left > curNode.right) { +// queue.offer(new Node(curNode.res + ")", curNode.left, curNode.right + 1)); +// } +//// } +// } +// return res; + + + /** + * 这个应该才是真正的bfs + */ +// Deque strQ = new LinkedList<>(); +// Deque leftQ = new LinkedList<>(); +// Deque rightQ = new LinkedList<>(); +// strQ.offer(""); +// leftQ.offer(0); +// rightQ.offer(0); +// //下面两种终止条件判断都可以 +// while (leftQ.peek() + rightQ.peek() < 2 * n) { +//// while (strQ.peek().length() < 2 * n) { +// int size = strQ.size(); +// while (size-- > 0) { +// String cur = strQ.poll(); +// int left = leftQ.poll(); +// int right = rightQ.poll(); +// if (left < n) { +// strQ.offer(cur + "("); +// leftQ.offer(left + 1); +// rightQ.offer(right); +// } +// if (right < left) { +// strQ.offer(cur + ")"); +// leftQ.offer(left); +// rightQ.offer(right + 1); +// } +// +// } +// } +// /** +// * 因为这种情况下会每层去加括号并且会把上一层的取出来, +// * 最后得到的queue就是最后一层的括号值 +// */ +// return (List) strQ; + + + + + +// List result = new LinkedList<>(); +// +// //第一层初始应该是空串,因为出口判断条件不一样,因此max也不一样 +// recur1(0, 0, n, result, ""); +//// recur(0,2 * 3, result, ""); +// +// +// return result; + } + + private void dfsChars(int left, int right, int i, int n, List res, char[] chars) { + + //curr.length一定是2*n,不能用curr.length==2*n + if (chars.length == i) { + res.add(new String(chars)); + return; + } + + if (left < n) { + chars[i] = '('; + dfsChars(left + 1, right, i + 1, n, res, chars); + } + + if (right < left) { + chars[i] = ')'; + dfsChars(left, right + 1, i + 1, n, res, chars); + } + + } + + + private void dfs(int left, int right, int max, List res, String s) { + + if (s.length() == max * 2) { + res.add(s); + return; + } + + //当前层逻辑 + if (left < max) { + dfs(left + 1, right, max, res, s+"("); + } + + if (right < left) { + dfs(left, right + 1, max, res, s+")"); + } + + + + + } + + + private void recur2(int left, int right, int n, List res, String s) { + + //结束条件 + if (left == n && right == n) { + res.add(s); + return; + } + + //当前逻辑 + //进入下一层,加上条件剪枝 + if (left result,String str){ + + + if (deep == max) { + result.add(str); + System.out.println(str); + return; + } + + //current logic + + //drop down + /** + * 这儿对应递归两种可能,左分支和右分支 + */ + recur(deep + 1, max, result, str+ "("); + recur(deep + 1, max, result, str+ ")"); + + + } + + + /** + * 条件判断可以直接在递归过程中判断, + * 1、左括号可以随便加,只要不超过做大限制即可 + * 2、右括号必须在有左括号的时候才能加,右括号<左括号 + */ + public void recur1(int left, int right, int max, List result, String str) { + + if (left == max && right == max) { +// if (str.length() == max * 2) { + result.add(str); + System.out.println(str); + return; + } + + //current logic + + //drop down + /** + * 这儿对应递归两种可能,左分支和右分支 + * 可以加些条件进行剪枝 + */ + if (left < max) + recur1(left + 1, right, max, result, str + "("); + if (right < left) + recur1(left, right + 1, max, result, str + ")"); + + + } + + class Node { + /** + * 当前得到的字符串 + */ + private String res; + /** + * 剩余左括号数量 + */ + private int left; + /** + * 剩余右括号数量 + */ + private int right; + + public Node(String str, int left, int right) { + this.res = str; + this.left = left; + this.right = right; + } + } + + + + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_07/MinimumGeneticMutation1.java b/Week_07/MinimumGeneticMutation1.java new file mode 100644 index 00000000..048f4a2d --- /dev/null +++ b/Week_07/MinimumGeneticMutation1.java @@ -0,0 +1,289 @@ +//题号:433 +// +//一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。 +// +// 假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。 +// +// 例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。 +// +// 与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。 +// +// 现在给定3个参数 — start, end, bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变 +//化次数。如果无法实现目标变化,请返回 -1。 +// +// 注意: +// +// +// 起始基因序列默认是合法的,但是它并不一定会出现在基因库中。 +// 如果一个起始基因序列需要多次变化,那么它每一次变化之后的基因序列都必须是合法的。 +// 假定起始基因序列与目标基因序列是不一样的。 +// +// +// +// +// 示例 1: +// +// +//start: "AACCGGTT" +//end: "AACCGGTA" +//bank: ["AACCGGTA"] +// +//返回值: 1 +// +// +// 示例 2: +// +// +//start: "AACCGGTT" +//end: "AAACGGTA" +//bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"] +// +//返回值: 2 +// +// +// 示例 3: +// +// +//start: "AAAAACCC" +//end: "AACCCCCC" +//bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"] +// +//返回值: 3 +// +// 👍 67 👎 0 + + +import java.util.*; + +public class MinimumGeneticMutation1 { + public static void main(String[] args) { + Solution solution = new MinimumGeneticMutation1().new Solution(); + solution.minMutation(new String("AACCGGTT"), new String("AACCGGTA"), new String[]{"AACCGGTA"}); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + int minChange = Integer.MAX_VALUE; + + + public int minMutation(String start, String end, String[] bank) { + + /** + * BFS + * m start长度,n bank长度 + * 时间复杂度O(n*m*4)=O(m*n),空间复杂度O(3n+4)=O(n) + */ + if (start.equals(end)) return 0; + Set bankSet = new HashSet<>(); + for(String b: bank) bankSet.add(b); + char[] charSet = new char[]{'A', 'C', 'G', 'T'}; + int level=0; + Set visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + queue.offer(start); + visited.add(start); + while (!queue.isEmpty()) { + int size = queue.size(); + for(int i=0;i(), 0, start, end, bank); +// return minChange == Integer.MAX_VALUE ? -1 : minChange; + + + /** + * 将diff比较放到一个函数里面,但是内存消耗会变大 + */ +// recur(new HashSet<>(), 0, start, end, bank); +// return minChange == Integer.MAX_VALUE ? -1 : minChange; + + } + + private void dfsArray(int level, String start, String end, String[] bank) { + + if (start.equals(end)) { + minChange = Math.min(level, minChange); + return; + } + + for (int j = 0; j < bank.length; j++) { + //需要一个变量存储置空之前的值 + String temp = bank[j]; + + //null不能放在后面去判断,因为会有拿temp.charAt(i)的操作 + if (temp == null) { + continue; + } + + int diff = 0; + for (int i = 0; i < bank[j].length(); i++) { + if (start.charAt(i) != temp.charAt(i)) { + if (++diff > 1) break; + } + } + + if (diff == 1) { + bank[j] = null; + dfsArray(level + 1, temp, end, bank); + bank[j] = temp; + } + + + } + + + + } + + private void recur(HashSet visited, int level, String start, String end, String[] bank) { + + if (start.equals(end)) { + minChange = Math.min(minChange, level); + return; + } + + for (String str : bank) { + if (validDiff(start, str) && !visited.contains(str)) { + visited.add(str); + recur(visited, level + 1, str, end, bank); + visited.remove(str); + } + } + + + + } + + private boolean validDiff(String start, String str) { + int diff = 0; + for (int i = 0; i < str.length(); i++) { + if (start.charAt(i) != str.charAt(i)) { + if (++diff > 1) { + break; + } + } + } + return diff == 1 ? true : false; + } + + //visited用来记录访问过的 + private void dfs1(HashSet visited, int level, String start, String end, String[] bank) { + //这里直接用的level来表示转换了几次 + if (start.equals(end)) { + minChange = Math.min(minChange, level); + return; + } + for (String str : bank) { + int diff = 0; + for (int i = 0; i < str.length(); i++) { + //比较每一位 + if (start.charAt(i) != str.charAt(i)) { + diff++; + //每次只能改变一个字符,当dif>1直接跳过 + if (diff > 1) break; + } + } + //只相差一位且没有被访问过 + if (diff == 1 && !visited.contains(str)) { + visited.add(str); + dfs1(visited, level + 1, str, end, bank); + //这是set,无序的,写成下面这种有问题,只有list能这么写 +// visited.remove(visited.size() - 1); + visited.remove(str); + } + } + } + + + private void dfs(char[] start, char[] end, char[][] bank, int change) { + + if (Arrays.equals(start, end)) { + minChange = Math.min(minChange, change); + return; + } + + for (int j = 0; j < bank.length; j++) { + char[] piece = bank[j]; + // 已用过的片段 + if (piece == null) { + continue; + } + // 获取基因库中不同为1的片段,作为改变一次后的新start + int diff = 0; + for (int i = 0; i < start.length; i++) { + if (start[i] != piece[i]) + if(++diff>1) break; + } + if (diff == 1) { + // 置空,防止循环使用 + bank[j] = null; + dfs(piece, end, bank, change + 1); + //重置状态 + bank[j] = piece; + } + } + + + + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_07/NQueens.java b/Week_07/NQueens.java new file mode 100644 index 00000000..8a6b2793 --- /dev/null +++ b/Week_07/NQueens.java @@ -0,0 +1,230 @@ +//题号:51 +//https://leetcode-cn.com/problems/n-queens/ +//n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 +// +// 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 +// +// +// +// 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 +// +// +// +// 示例 1: +// +// +//输入:n = 4 +//输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +//解释:如上图所示,4 皇后问题存在两个不同的解法。 +// +// +// 示例 2: +// +// +//输入:n = 1 +//输出:[["Q"]] +// +// +// +// +// 提示: +// +// +// 1 <= n <= 9 +// 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。 +// +// +// +// Related Topics 回溯算法 +// 👍 753 👎 0 + + +import java.util.*; + +public class NQueens { + public static void main(String[] args) { + Solution solution = new NQueens().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + private Set col = new HashSet<>(); + private Set diag1 = new HashSet<>(); + private Set diag2 = new HashSet<>(); + + + + public List> solveNQueens(int n) { + + /** + * 直接递归 + */ +// char[][] chess = new char[n][n]; +// +// for (int i = 0; i < n; i++) { +//// Arrays.fill(chess[i], '.'); +// for (int j = 0; j < n; j++) { +// chess[i][j] = '.'; +// } +// } +// +// List> result = new ArrayList<>(); +// +// recurce(result, chess, 0); +// +// return result; + + /** + * 递归+Set缓存优化 + * 这个为啥还没不加缓存快啊? + */ +// List> res = new ArrayList<>(); +// dfs(0, res, new ArrayList<>(), n); +// return res; + + /** + * 递归+数组缓存优化 + */ + List> res = new ArrayList<>(); + boolean[] visited = new boolean[n]; + //2*n-1个斜对角线 + boolean[] dia1 = new boolean[2*n-1]; + boolean[] dia2 = new boolean[2*n-1]; + + dfsArray(0, visited, dia1, dia2, new ArrayList<>(),res, n); + + return res; + + } + + private void dfsArray(int rowIndex, boolean[] visited, boolean[] dia1, boolean[] dia2, ArrayList list,List> res, int n) { + if(rowIndex == n){ + res.add(new ArrayList(list)); + return; + } + + for(int i=0;i> res, List list, int n) { + + if (n == row) { + res.add(new ArrayList<>(list)); + return; + } + + + for (int i = 0; i < n; i++) { + if (col.contains(i) || diag1.contains(row + i) || diag2.contains(row - i)) { + continue; + } + char[] charArray = new char[n]; + Arrays.fill(charArray, '.'); + charArray[i] = 'Q'; + String rowString = new String(charArray); + + list.add(rowString); + col.add(i); + diag1.add(row + i); + diag2.add(row - i); + + dfs(row + 1, res, list, n); + + list.remove(list.size() - 1); + col.remove(i); + diag1.remove(row + i); + diag2.remove(row - i); + + } + + + } + + + private void recurce(List> res, char[][] chess, int row) { + if (row == chess.length) { + res.add(construct(chess)); + return; + } + + for (int i = 0; i < chess.length; i++) { + if (isValid(chess,row,i)) { + chess[row][i] = 'Q'; + recurce(res, chess, row + 1); + chess[row][i] = '.'; + } + } + + + } + + private boolean isValid(char[][] chess, int row,int col) { + + for (int i = 0; i < row; i++) { + //同一列有皇后 + if (chess[i][col] == 'Q') { + return false; + } + + int rowDiff = row - i; + if (col + rowDiff < chess.length && chess[i][col + rowDiff] == 'Q') { + return false; + } + + if (col - rowDiff >= 0 && chess[i][col - rowDiff] == 'Q') { + return false; + } + + +// if (chess[i][col] == 'Q') { +// return false; +// } +// for (int j = 0; j < chess.length; j++) { +// //斜率相等 +// if (chess[i][j] == 'Q' && (i + j == row + col || i - j == row - col)) { +// return false; +// } +// } + + } + + + + + return true; + } + + private List construct(char[][] chess) { + List path = new ArrayList<>(); + for (int i = 0; i < chess.length; i++) { + path.add(new String(chess[i])); + } + return path; + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_07/README.md b/Week_07/README.md index 50de3041..4ce54dd0 100644 --- a/Week_07/README.md +++ b/Week_07/README.md @@ -1 +1,52 @@ -学习笔记 \ No newline at end of file +学习笔记 + +#### 剪枝 + +剪枝,我理解就是我们每次递归或者回溯都会有相应的递归树,递归的路径其实就是树的枝叶,在实际情况中其实有很多“枝叶”是不符合条件的,我们完全没有必要走下去,这时候我们就可以根据适当的条件把这些枝叶减掉,从而减少了递归场景,节省了时间,就和现实生活中的剪枝一样。 + +**剪枝**这个概念其实在递归那章就会用到,或者说我们在做正常的递归或者dfs求解都会用到,不然实实在在的傻递归复杂度就太高了。 + +#### 双向BFS + +双向BFS会比单向bfs快很多,而且是随着你遍历的层级越远,差别越明显,因为单向BFS会随着你遍历的直线距离越远遍历的范围会越大,元素也会越多,而双向BFS则会用两个起点同时往中间扩散,当两个在中间相遇,就是我们要遍历的结果,而且它会优先从节点少的端点遍历,这样能进一步提高速度。 + +双向BFS模板: + +```java +Set start=new HashSet(); +Set end=new HashSet(); +start.add(start); +end.add(end); + +while(!start.isEmpty&&!end.isEmpty){ + //始终保证从最小的一端搜索 + if(start.size() "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。 +// +// +// 示例 2: +// +// +//输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","lo +//g"] +//输出:0 +//解释:endWord "cog" 不在字典中,所以无法进行转换。 +// +// +// +// 提示: +// +// +// 1 <= beginWord.length <= 10 +// endWord.length == beginWord.length +// 1 <= wordList.length <= 5000 +// wordList[i].length == beginWord.length +// beginWord、endWord 和 wordList[i] 由小写英文字母组成 +// beginWord != endWord +// wordList 中的所有字符串 互不相同 +// +// Related Topics 广度优先搜索 +// 👍 705 👎 0 + + +import java.util.*; + +public class WordLadder1 { + public static void main(String[] args) { + Solution solution = new WordLadder1().new Solution(); + solution.ladderLength("hit", "cog", Arrays.asList(new String[]{"hot", "dot", "dog", "lot", "log", "cog"})); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + int minLength = Integer.MAX_VALUE; + public int ladderLength(String beginWord, String endWord, List wordList) { + + + /** + * dfs 超时, + * 这个题其实和基因那道题很类似,区别就是数据规模大了很多, + * 因此单纯用DFS直接遍历会超时 + * + * dfs思路是找到所有可能,然后比较其中最小的, + * 当时数据规模很大的时候,且递归树分支极不平衡的情况下, + * 遍历所有可能是十分耗时的操作, + * 极端例子:一个二叉树,左子树10000个节点,右子树3个节点,当找最小子树节点时, + * dfs,会遍历完两个子树才能得到结果,而bfs只要到第三层就能知道结果,效率差别巨大。 + */ +// Set visited = new HashSet<>(); +// dfs(0, beginWord, endWord, wordList,visited); +// +// return minLength == Integer.MAX_VALUE ? 0 : minLength; + + /** + * 单向BFS, + */ +// Set wordSet = new HashSet<>(wordList); +// if (!wordList.contains(endWord)) { +// return 0; +// } +// Deque queue = new LinkedList<>(); +//// Set visitedSet = new HashSet<>(); +// queue.offer(beginWord); +//// visitedSet.add(beginWord); +// int len = 1; +// while (!queue.isEmpty()) { +// int size = queue.size(); +// for (int i = 0; i < size; i++) { +// String str = queue.poll(); +// char[] chs = str.toCharArray(); +// for (int k = 0; k < chs.length; k++) { +// char old = chs[k]; +// for (char j = 'a'; j <= 'z'; j++) { +// if (j == chs[k]) { +// continue; +// } +// chs[k] = j; +// String target = new String(chs); +// if (target.equals(endWord)) { +// return len + 1; +// } +// //参考全球站高赞法三解法 +//// if (!visitedSet.contains(target) && wordSet.contains(target)) { +// if (wordSet.contains(target)) { +// wordSet.remove(target); +// queue.offer(target); +//// visitedSet.add(target); +// } +// } +// chs[k] = old; +// } +// } +// len++; +// } +// return 0; + + + /** + * 双端BFS + * 为了寻求更快的速度,用Set代替了queue + * 这里可以直接用wordSet保存访问记录,减少一个visitedSet变量,且wordSet会变小 + */ + Set wordSet = new HashSet<>(wordList); + if (beginWord.length()!=endWord.length()||!wordList.contains(endWord)) return 0; + Set beginSet = new HashSet<>(); + Set endSet = new HashSet<>(); + beginSet.add(beginWord); + endSet.add(endWord); + int len = 1; + //这里endSet不可能为空的,一开始endSet size为1,后面只有beginSet.size()>endSet才会交换,所以endSet一定是>=1的 + while (!beginSet.isEmpty()) { + if (beginSet.size() > endSet.size()) { + Set set = beginSet; + beginSet = endSet; + endSet = set; + } + //这个set替换代替了queue.poll()的过程 + Set temp = new HashSet<>(); + for (String word : beginSet) { + char[] chs = word.toCharArray(); + for (int i = 0; i < chs.length; i++) { + char old = chs[i]; + for (char c = 'a'; c <= 'z'; c++) { + if (chs[i] == c) continue; + chs[i] = c; + String target = new String(chs); + //两端都有这个元素即返回 + if (endSet.contains(target)) return len + 1; + if (wordSet.contains(target)) { + wordSet.remove(target); + temp.add(target); + } + } + chs[i]=old; + } + } + beginSet = temp; + len++; + } + return 0; + + /** + * 全球站高赞双向BFS + * 看着和一般的BFS不一样,没有用queue + * + * 纠正个误区,bfs不一定要用queue + * + */ + +// if (!wordList.contains(endWord)) { +// return 0; +// } +// Set wordSet = new HashSet<>(wordList); +// Set beginSet = new HashSet<>(); +// Set endSet = new HashSet<>(); +// int len = 1; +// HashSet visited = new HashSet<>(); +// beginSet.add(beginWord); +// endSet.add(endWord); +// wordList.remove(beginWord); +// wordList.remove(endWord); +// //endSet一直不为空? +// while (!beginSet.isEmpty() && !endSet.isEmpty()) { +// //始终让beginSet最小 +// if (beginSet.size() > endSet.size()) { +// Set set = beginSet; +// beginSet = endSet; +// endSet = set; +// } +// Set temp = new HashSet<>(); +// for (String word : beginSet) { +// char[] chs = word.toCharArray(); +// for (int i = 0; i < chs.length; i++) { +// for (char c = 'a'; c <= 'z'; c++) { +// //当前字符相等就没必要转换了 +// if (c == chs[i]) { +// continue; +// } +// char old = chs[i]; +// chs[i] = c; +// String target = new String(chs); +// if (endSet.contains(target)) { +// return len + 1; +// } +// if (!visited.contains(target) && wordSet.contains(target)) { +// temp.add(target); +// visited.add(target); +// } +// chs[i] = old; +// } +// } +// } +// beginSet = temp; +// len++; +// } +// return 0; + + /** + * 全球站法二 + */ +// if (beginWord == null || endWord == null || beginWord.length() != endWord.length()||!wordList.contains(endWord)) return 0; +// +// Set reached = new HashSet<>(); +// Set wordDict = new HashSet<>(wordList); +// reached.add(beginWord); +// wordDict.add(endWord); +// int distance = 1; +// while (!reached.contains(endWord)) { +// Set toAdd = new HashSet<>(); +// for (String each : reached) { +// for (int i = 0; i < each.length(); i++) { +// char[] chars = each.toCharArray(); +// for (char ch = 'a'; ch <= 'z'; ch++) { +// chars[i] = ch; +// String word = new String(chars); +// if (wordDict.contains(word)) { +// toAdd.add(word); +// wordDict.remove(word); +// } +// } +// } +// } +// distance++; +// if (toAdd.size() == 0) return 0; +// reached = toAdd; +// } +// return distance; + + /** + * 全球站法三 2,3都是BFS + * + * 这个虽然是BFS,但是思路和奇妙,常规都是用一个set记录访问,然后用 + * !visited.contains(target) && wordSet.contains(target)这个去判断, + * 但是这个思路是每次碰到包含的元素直接移出wordSet,这样wordSet变小, + * 且少访问一个visitedSet,因为java的set.contain()是近似O(1),如果有hash冲突 + * 其实还是有消耗的 + * + * + */ +// if (!wordList.contains(endWord)) return 0; +// HashSet set = new HashSet<>(wordList); +// Queue q = new LinkedList<>(); +// int length = 0; +// q.add(beginWord); +// while (!q.isEmpty()) { +// int size = q.size(); +// for (int i = 0; i < size; i++) { +// String w = q.poll(); +// if (w.equals(endWord)) return length + 1; +// wordMatch(w, set, q); +// } +// length++; +// } +// return 0; + } + private void wordMatch(String w, Set set, Queue q) { + for (int i = 0; i < w.length(); i++) { + char[] word = w.toCharArray(); + for (int j = 0; j < 26; j++) { + char c = (char) ('a' + j); + if (word[i] == c) continue; + word[i] = c; + String s = String.valueOf(word); + if (set.contains(s)) { + set.remove(s); + q.offer(s); + } + } + } + } + + private void dfs(int level, String beginWord, String endWord, List wordList, Set visited) { + if (beginWord.equals(endWord)) { + minLength = Math.min(minLength, level+1); + return; + } + + for (String str : wordList) { + + int diff = 0; + for (int i = 0; i < str.length(); i++) { + //与单词库里每一个对比 + if (beginWord.charAt(i) != str.charAt(i)) { + if (++diff > 1) break; + } + } + + if (diff == 1 && !visited.contains(str)) { + visited.add(str); + dfs(level + 1, str, endWord, wordList, visited); + visited.remove(str); + } + + } + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From 8dad633d1993b45210adb07bb01101c66064c355 Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 28 Mar 2021 23:56:13 +0800 Subject: [PATCH 09/15] week 8. --- Week_08/ImplementTriePrefixTree.java | 171 ++++++++++++++++++++ Week_08/NQueens1.java | 230 +++++++++++++++++++++++++++ Week_08/NQueensIi.java | 125 +++++++++++++++ Week_08/NumberOf1Bits.java | 77 +++++++++ Week_08/NumberOfIslands1.java | 205 ++++++++++++++++++++++++ Week_08/NumberOfProvinces.java | 106 ++++++++++++ Week_08/SurroundedRegions.java | 132 +++++++++++++++ Week_08/WordSearchIi.java | 170 ++++++++++++++++++++ 8 files changed, 1216 insertions(+) create mode 100644 Week_08/ImplementTriePrefixTree.java create mode 100644 Week_08/NQueens1.java create mode 100644 Week_08/NQueensIi.java create mode 100644 Week_08/NumberOf1Bits.java create mode 100644 Week_08/NumberOfIslands1.java create mode 100644 Week_08/NumberOfProvinces.java create mode 100644 Week_08/SurroundedRegions.java create mode 100644 Week_08/WordSearchIi.java diff --git a/Week_08/ImplementTriePrefixTree.java b/Week_08/ImplementTriePrefixTree.java new file mode 100644 index 00000000..3d5d79db --- /dev/null +++ b/Week_08/ImplementTriePrefixTree.java @@ -0,0 +1,171 @@ +//题号:208 +//实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 +// +// 示例: +// +// Trie trie = new Trie(); +// +//trie.insert("apple"); +//trie.search("apple"); // 返回 true +//trie.search("app"); // 返回 false +//trie.startsWith("app"); // 返回 true +//trie.insert("app"); +//trie.search("app"); // 返回 true +// +// 说明: +// +// +// 你可以假设所有的输入都是由小写字母 a-z 构成的。 +// 保证所有输入均为非空字符串。 +// +// Related Topics 设计 字典树 +// 👍 560 👎 0 + + +import java.util.HashMap; +import java.util.Map; + +public class ImplementTriePrefixTree { + public static void main(String[] args) { + Trie trie = new ImplementTriePrefixTree().new Trie(); + trie.insert("apple"); + trie.search("apple"); // 返回 true + trie.search("app"); // 返回 false + trie.startsWith("app"); // 返回 true + trie.insert("alone"); + trie.search("alone"); + } + + //leetcode submit region begin(Prohibit modification and deletion) +class Trie { + + /** + * 数组实现,适用于知道每个节点的范围,这里是已知只有26个小写字母,但对常规情况不大试用。 + * 这里准确的说应该是TrieNode数组,但是为了一个数组新建一个类没多大必要,因此可以混用, + * 知道是怎么个意思就行 + */ +// private Trie[] next; +// private boolean isEnd; +// +// /** +// * Initialize your data structure here. +// */ +// public Trie() { +// next = new Trie[26]; +// isEnd = false; +// } +// +// /** +// * Inserts a word into the trie. +// */ +// public void insert(String word) { +// if (word == null || word.length() == 0) return; +// Trie curr = this; +// for (char ch : word.toCharArray()) { +// int u = ch - 'a'; +// //如果null则新new一个节点 +// if (curr.next[u] == null) curr.next[u] = new Trie(); +// //否则找到下一个节点 +// curr = curr.next[u]; +// } +// //赋值完成将状态置成true +// curr.isEnd = true; +// } +// +// /** +// * Returns if the word is in the trie. +// */ +// public boolean search(String word) { +// Trie node = searchPrefix(word); +// return node != null && node.isEnd; +// } +// +// /** +// * Returns if there is any word in the trie that starts with the given prefix. +// */ +// public boolean startsWith(String prefix) { +// Trie node = searchPrefix(prefix); +// return node != null; +// } +// +// +// private Trie searchPrefix(String word) { +// Trie node = this; +// for (char c : word.toCharArray()) { +// node = node.next[c - 'a']; +// if (node == null) return null; +// } +// return node; +// } + + + /** + * hashMap实现,对于任意长度节点都能cover,试用于一般场景 + */ + TrieNode head; + /** Initialize your data structure here. */ + public Trie() { + head = new TrieNode(); + } + + /** Inserts a word into the trie. */ + public void insert(String word) { + if(word == null) + return; + TrieNode node = head; + for(char ch : word.toCharArray()) { + if(!node.charToNode.containsKey(ch)){ + node.charToNode.put(ch, new TrieNode()); + } + node = node.charToNode.get(ch); + } + node.isEnd = true; + } + + /** Returns if the word is in the trie. */ + public boolean search(String word) { +// if(word == null) return false; + TrieNode 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) { +// if(prefix == null) return false; + TrieNode node = searchPrefix(prefix); + return node != null; + + } + + private TrieNode searchPrefix(String prefix) { + TrieNode node = head; + for(char ch : prefix.toCharArray()) { + if(!node.charToNode.containsKey(ch)) return null; + node = node.charToNode.get(ch); + } + return node; + } + + + class TrieNode{ + Map charToNode; + boolean isEnd = false; + public TrieNode(){ + charToNode = new HashMap(); + } + } + + + } + +/** + * Your Trie object will be instantiated and called as such: + * Trie obj = new Trie(); + * obj.insert(word); + * boolean param_2 = obj.search(word); + * boolean param_3 = obj.startsWith(prefix); + */ +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/NQueens1.java b/Week_08/NQueens1.java new file mode 100644 index 00000000..06f5989c --- /dev/null +++ b/Week_08/NQueens1.java @@ -0,0 +1,230 @@ +//题号:51 +//https://leetcode-cn.com/problems/n-queens/ +//n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 +// +// 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 +// +// +// +// 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 +// +// +// +// 示例 1: +// +// +//输入:n = 4 +//输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +//解释:如上图所示,4 皇后问题存在两个不同的解法。 +// +// +// 示例 2: +// +// +//输入:n = 1 +//输出:[["Q"]] +// +// +// +// +// 提示: +// +// +// 1 <= n <= 9 +// 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。 +// +// +// +// Related Topics 回溯算法 +// 👍 753 👎 0 + + +import java.util.*; + +public class NQueens1 { + public static void main(String[] args) { + Solution solution = new NQueens1().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + private Set col = new HashSet<>(); + private Set diag1 = new HashSet<>(); + private Set diag2 = new HashSet<>(); + + + + public List> solveNQueens(int n) { + + /** + * 直接递归 + */ +// char[][] chess = new char[n][n]; +// +// for (int i = 0; i < n; i++) { +//// Arrays.fill(chess[i], '.'); +// for (int j = 0; j < n; j++) { +// chess[i][j] = '.'; +// } +// } +// +// List> result = new ArrayList<>(); +// +// recurce(result, chess, 0); +// +// return result; + + /** + * 递归+Set缓存优化 + * 这个为啥还没不加缓存快啊? + */ +// List> res = new ArrayList<>(); +// dfs(0, res, new ArrayList<>(), n); +// return res; + + /** + * 递归+数组缓存优化 + */ + List> res = new ArrayList<>(); + boolean[] visited = new boolean[n]; + //2*n-1个斜对角线 + boolean[] dia1 = new boolean[2*n-1]; + boolean[] dia2 = new boolean[2*n-1]; + + dfsArray(0, visited, dia1, dia2, new ArrayList<>(),res, n); + + return res; + + } + + private void dfsArray(int rowIndex, boolean[] visited, boolean[] dia1, boolean[] dia2, ArrayList list,List> res, int n) { + if(rowIndex == n){ + res.add(new ArrayList(list)); + return; + } + + for(int i=0;i> res, List list, int n) { + + if (n == row) { + res.add(new ArrayList<>(list)); + return; + } + + + for (int i = 0; i < n; i++) { + if (col.contains(i) || diag1.contains(row + i) || diag2.contains(row - i)) { + continue; + } + char[] charArray = new char[n]; + Arrays.fill(charArray, '.'); + charArray[i] = 'Q'; + String rowString = new String(charArray); + + list.add(rowString); + col.add(i); + diag1.add(row + i); + diag2.add(row - i); + + dfs(row + 1, res, list, n); + + list.remove(list.size() - 1); + col.remove(i); + diag1.remove(row + i); + diag2.remove(row - i); + + } + + + } + + + private void recurce(List> res, char[][] chess, int row) { + if (row == chess.length) { + res.add(construct(chess)); + return; + } + + for (int i = 0; i < chess.length; i++) { + if (isValid(chess,row,i)) { + chess[row][i] = 'Q'; + recurce(res, chess, row + 1); + chess[row][i] = '.'; + } + } + + + } + + private boolean isValid(char[][] chess, int row,int col) { + + for (int i = 0; i < row; i++) { + //同一列有皇后 + if (chess[i][col] == 'Q') { + return false; + } + + int rowDiff = row - i; + if (col + rowDiff < chess.length && chess[i][col + rowDiff] == 'Q') { + return false; + } + + if (col - rowDiff >= 0 && chess[i][col - rowDiff] == 'Q') { + return false; + } + + +// if (chess[i][col] == 'Q') { +// return false; +// } +// for (int j = 0; j < chess.length; j++) { +// //斜率相等 +// if (chess[i][j] == 'Q' && (i + j == row + col || i - j == row - col)) { +// return false; +// } +// } + + } + + + + + return true; + } + + private List construct(char[][] chess) { + List path = new ArrayList<>(); + for (int i = 0; i < chess.length; i++) { + path.add(new String(chess[i])); + } + return path; + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_08/NQueensIi.java b/Week_08/NQueensIi.java new file mode 100644 index 00000000..5c75370b --- /dev/null +++ b/Week_08/NQueensIi.java @@ -0,0 +1,125 @@ +//n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 +// +// 给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。 +// +// +// +// +// +// 示例 1: +// +// +//输入:n = 4 +//输出:2 +//解释:如上图所示,4 皇后问题存在两个不同的解法。 +// +// +// 示例 2: +// +// +//输入:n = 1 +//输出:1 +// +// +// +// +// 提示: +// +// +// 1 <= n <= 9 +// 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。 +// +// +// +// Related Topics 回溯算法 +// 👍 249 👎 0 + +public class NQueensIi { + public static void main(String[] args) { + Solution solution = new NQueensIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + + private int count = 0; + private int size; + + public int totalNQueens(int n) { + + /** + * 数组缓存的递归 + * 时间复杂度:O(n!) + * 空间复杂度:O(n+2n-1+2n-1) + * + */ + +// boolean[] cols = new boolean[n]; +// boolean[] pie = new boolean[2 * n - 1]; +// boolean[] na = new boolean[2 * n - 1]; +// +// +// dfs(0, cols, pie, na, n); +// +// return count; + + + /** + * 位运算 + * 时间复杂度:O(n!) + * 空间复杂度:O(1) + */ + + size=(1<> 1); + } + + } + + private void dfs(int row, boolean[] cols, boolean[] pie, boolean[] na, int n) { + + if (row == n) { + count++; + return; + } + + for (int i = 0; i < n; i++) { + if (cols[i] || pie[row + i] || na[row - i + n - 1]) { + continue; + } + + cols[i] = true; + pie[row + i] = true; + na[row - i + n - 1] = true; + + dfs(row + 1, cols, pie, na, n); + cols[i] = false; + pie[row + i] = false; + na[row - i + n - 1] = false; + + } + + + + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/NumberOf1Bits.java b/Week_08/NumberOf1Bits.java new file mode 100644 index 00000000..9d14e452 --- /dev/null +++ b/Week_08/NumberOf1Bits.java @@ -0,0 +1,77 @@ +//编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。 +// +// +// +// 提示: +// +// +// 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的 +//还是无符号的,其内部的二进制表示形式都是相同的。 +// 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。 +// +// +// +// +// 示例 1: +// +// +//输入:00000000000000000000000000001011 +//输出:3 +//解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 +// +// +// 示例 2: +// +// +//输入:00000000000000000000000010000000 +//输出:1 +//解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 +// +// +// 示例 3: +// +// +//输入:11111111111111111111111111111101 +//输出:31 +//解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 +// +// +// +// 提示: +// +// +// 输入必须是长度为 32 的 二进制串 。 +// +// +// +// +// +// +// +// 进阶: +// +// +// 如果多次调用这个函数,你将如何优化你的算法? +// +// Related Topics 位运算 +// 👍 332 👎 0 + +public class NumberOf1Bits { + public static void main(String[] args) { + Solution solution = new NumberOf1Bits().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +public class Solution { + // you need to treat n as an unsigned value + public int hammingWeight(int n) { + int count = 0; + while (n != 0) { + count++; + n = n & (n - 1); + } + return count; + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/NumberOfIslands1.java b/Week_08/NumberOfIslands1.java new file mode 100644 index 00000000..8ac02e43 --- /dev/null +++ b/Week_08/NumberOfIslands1.java @@ -0,0 +1,205 @@ +//题号:200 +//https://leetcode-cn.com/problems/number-of-islands/ +//给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 +// +// 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 +// +// 此外,你可以假设该网格的四条边均被水包围。 +// +// +// +// 示例 1: +// +// +//输入:grid = [ +// ["1","1","1","1","0"], +// ["1","1","0","1","0"], +// ["1","1","0","0","0"], +// ["0","0","0","0","0"] +//] +//输出:1 +// +// +// 示例 2: +// +// +//输入:grid = [ +// ["1","1","0","0","0"], +// ["1","1","0","0","0"], +// ["0","0","1","0","0"], +// ["0","0","0","1","1"] +//] +//输出:3 +// +// +// +// +// 提示: +// +// +// m == grid.length +// n == grid[i].length +// 1 <= m, n <= 300 +// grid[i][j] 的值为 '0' 或 '1' +// +// Related Topics 深度优先搜索 广度优先搜索 并查集 +// 👍 995 👎 0 + + +import java.util.LinkedList; +import java.util.Queue; + +public class NumberOfIslands1 { + public static void main(String[] args) { + Solution solution = new NumberOfIslands1().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int numIslands(char[][] grid) { + + + /** + * 并查集 + */ + + int m = grid.length; + int n = grid[0].length; + int count = 0; + UnionFind uf = new UnionFind(m * n); + int[][] directions = {{1, 0}, {0, 1}}; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[i].length; j++) { + if (grid[i][j] == '0') { + count++; + } else { + //只往右下判断即可 +// if(i>0&&grid[i-1][j]=='1')uf.union(i*n+j,(i-1)*n+j); +// if(i0&&grid[i][j-1]=='1') uf.union(i*n+j,i*n+j-1); +// if(j queue = new LinkedList<>(); + queue.add(new int[] { i, j }); + + while(!queue.isEmpty()){ +// int size = queue.size(); +// for (int k = 0; k < size; k++) { + int[] cur = queue.remove(); + i = cur[0]; j = cur[1]; + if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') { + grid[i][j] = '0'; + queue.add(new int[] { i + 1, j }); + queue.add(new int[] { i - 1, j }); + queue.add(new int[] { i, j + 1 }); + queue.add(new int[] { i, j - 1 }); + } +// } + } + + } + + 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'; + dfs(grid, i + 1, j); + dfs(grid, i, j + 1); + dfs(grid, i - 1, j); + dfs(grid, i, j - 1); + } + } +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_08/NumberOfProvinces.java b/Week_08/NumberOfProvinces.java new file mode 100644 index 00000000..962111a8 --- /dev/null +++ b/Week_08/NumberOfProvinces.java @@ -0,0 +1,106 @@ +// +// +// 有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连 +//。 +// +// 省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。 +// +// 给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 +//isConnected[i][j] = 0 表示二者不直接相连。 +// +// 返回矩阵中 省份 的数量。 +// +// +// +// 示例 1: +// +// +//输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] +//输出:2 +// +// +// 示例 2: +// +// +//输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] +//输出:3 +// +// +// +// +// 提示: +// +// +// 1 <= n <= 200 +// n == isConnected.length +// n == isConnected[i].length +// isConnected[i][j] 为 1 或 0 +// isConnected[i][i] == 1 +// isConnected[i][j] == isConnected[j][i] +// +// +// +// Related Topics 深度优先搜索 并查集 +// 👍 524 👎 0 + +public class NumberOfProvinces { + public static void main(String[] args) { + Solution solution = new NumberOfProvinces().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int findCircleNum(int[][] isConnected) { + /** + * 并查集 + * 是不是所有并查集能解的题都能用dfs和bfs解? + */ + 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.count; + } + + class UnionFind { + + private int count = 0; + private int[] parent; + + public UnionFind(int n) { + count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public int find(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + if (rootP == rootQ) return; + parent[rootP] = rootQ; + count--; + } + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/SurroundedRegions.java b/Week_08/SurroundedRegions.java new file mode 100644 index 00000000..b32bacb8 --- /dev/null +++ b/Week_08/SurroundedRegions.java @@ -0,0 +1,132 @@ +//给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充 +//。 +// +// +// +// +// 示例 1: +// +// +//输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X" +//,"X"]] +//输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] +//解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都 +//会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 +// +// +// 示例 2: +// +// +//输入:board = [["X"]] +//输出:[["X"]] +// +// +// +// +// 提示: +// +// +// m == board.length +// n == board[i].length +// 1 <= m, n <= 200 +// board[i][j] 为 'X' 或 'O' +// +// +// +// Related Topics 深度优先搜索 广度优先搜索 并查集 +// 👍 503 👎 0 + +public class SurroundedRegions { + public static void main(String[] args) { + Solution solution = new SurroundedRegions().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public void solve(char[][] board) { + /** + * 并查集 + */ + + if (board == null || board.length == 0) + return; + + int rows = board.length; + int cols = board[0].length; + + // 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点 + UnionFind uf = new UnionFind(rows * cols + 1); + int dummyNode = rows * cols; + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + if (board[i][j] == 'O') { + // 遇到O进行并查集操作合并 + if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) { + // 边界上的O,把它和dummyNode 合并成一个连通区域. + uf.union(i * cols + j, dummyNode); + } else { + // 和上下左右合并成一个连通区域. + if (i > 0 && board[i - 1][j] == 'O') + uf.union(i * cols + j, (i - 1) * cols + j); + if (i < rows - 1 && board[i + 1][j] == 'O') + uf.union(i * cols + j, (i + 1) * cols + j); + if (j > 0 && board[i][j - 1] == 'O') + uf.union(i * cols + j, i * cols + j - 1); + if (j < cols - 1 && board[i][j + 1] == 'O') + uf.union(i * cols + j, i * cols + j + 1); + } + } + } + } + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + if (uf.find(i * cols + j) == uf.find(dummyNode)) { + // 和dummyNode 在一个连通区域的,那么就是O; + board[i][j] = 'O'; + } else { + board[i][j] = 'X'; + } + } + } + + } + + + + 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--; + } + + } + +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/WordSearchIi.java b/Week_08/WordSearchIi.java new file mode 100644 index 00000000..9a5d84f7 --- /dev/null +++ b/Week_08/WordSearchIi.java @@ -0,0 +1,170 @@ +//题号:212 +//给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words,找出所有同时在二维网格和字典中出现的单词。 +// +// 单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使 +//用。 +// +// +// +// 示例 1: +// +// +//输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l" +//,"v"]], words = ["oath","pea","eat","rain"] +//输出:["eat","oath"] +// +// +// 示例 2: +// +// +//输入:board = [["a","b"],["c","d"]], words = ["abcb"] +//输出:[] +// +// +// +// +// 提示: +// +// +// m == board.length +// n == board[i].length +// 1 <= m, n <= 12 +// board[i][j] 是一个小写英文字母 +// 1 <= words.length <= 3 * 104 +// 1 <= words[i].length <= 10 +// words[i] 由小写英文字母组成 +// words 中的所有字符串互不相同 +// +// Related Topics 字典树 回溯算法 +// 👍 350 👎 0 + + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class WordSearchIi { + public static void main(String[] args) { + Solution solution = new WordSearchIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public List findWords(char[][] board, String[] words) { + + + /** + * 直接DFS,超时 + */ +// int m = board.length; +// int n = board[0].length; +// +// Set res = new HashSet<>(); +// if (board.length==0 || board[0].length==0 || words.length==0) { +// return new ArrayList<>(res); +// } +// +// for (String word : words) { +// // 在循环内定义visited,重启上一个word对visited改变 +// boolean[][] visited = new boolean[m][n]; +// for (int i = 0; i < m; i++) { +// for (int j = 0; j < n; j++) { +// // word已经被存到结果中,continue +// if (res.contains(word)) continue; +// // word长度为1,直接和board中字符匹配 +// if (word.length()==1) { +// String s = String.valueOf(board[i][j]); +// if (word.equals(s)) { +// res.add(word); +// continue; +// } +// } +// // word首字母与board中字符相同时,进入dfs +// if (board[i][j]==word.charAt(0)) { +// visited[i][j] = true; +// dfs(board, res, visited, i, j, word, 0); +// visited[i][j] = false; +// } +// } +// } +// } +// return new ArrayList<>(res); + + /** + * 用Trie对dfs进行剪枝 + */ + + List res = new ArrayList<>(); + TrieNode root = buildTrie(words); + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + dfsWithTrie(board, i, j, root, res); + } + } + return res; + + + + } + + private void dfsWithTrie(char[][] board, int i, int j, TrieNode p, List res) { + char c = board[i][j]; + if (c == '#' || p.next[c - 'a'] == null) return; + p = p.next[c - 'a']; + if (p.word != null) { // found one + res.add(p.word); + p.word = null; // de-duplicate + } + + board[i][j] = '#'; + //这儿可以用二维数组表示方向 + if (i > 0) dfsWithTrie(board, i - 1, j ,p, res); + if (j > 0) dfsWithTrie(board, i, j - 1, p, res); + if (i < board.length - 1) dfsWithTrie(board, i + 1, j, p, res); + if (j < board[0].length - 1) dfsWithTrie(board, i, j + 1, p, res); + board[i][j] = c; + } + + private TrieNode buildTrie(String[] words) { + TrieNode root = new TrieNode(); + for (String w : words) { + TrieNode p = root; + for (char c : w.toCharArray()) { + int i = c - 'a'; + if (p.next[i] == null) p.next[i] = new TrieNode(); + p = p.next[i]; + } + p.word = w; + } + return root; + } + + class TrieNode { + TrieNode[] next = new TrieNode[26]; + String word; + } + + + + private void dfs(char[][] board, Set res, boolean[][] visited, int i, int j, String word, int pos) { + if (board[i][j] != word.charAt(pos)) return; + // pos 与 word 长度相同,说明已经找到了 + if ((pos+1)==word.length()) { + res.add(word); + return; + } + // 方向搜索二维数组 + int [][] direction = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; + for (int [] dir : direction) { + int newi = i + dir[0], newj = j + dir[1]; + if (newi>-1 && newj>-1 && newi Date: Mon, 29 Mar 2021 00:22:18 +0800 Subject: [PATCH 10/15] add forget file. --- Week_08/PowerOfTwo.java | 34 ++++++++++ Week_08/README.md | 135 ++++++++++++++++++++++++++++++++++++++- Week_08/ReverseBits.java | 69 ++++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 Week_08/PowerOfTwo.java create mode 100644 Week_08/ReverseBits.java diff --git a/Week_08/PowerOfTwo.java b/Week_08/PowerOfTwo.java new file mode 100644 index 00000000..b4891340 --- /dev/null +++ b/Week_08/PowerOfTwo.java @@ -0,0 +1,34 @@ +//给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 +// +// 示例 1: +// +// 输入: 1 +//输出: true +//解释: 20 = 1 +// +// 示例 2: +// +// 输入: 16 +//输出: true +//解释: 24 = 16 +// +// 示例 3: +// +// 输入: 218 +//输出: false +// Related Topics 位运算 数学 +// 👍 297 👎 0 + +public class PowerOfTwo { + public static void main(String[] args) { + Solution solution = new PowerOfTwo().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean isPowerOfTwo(int n) { + return (n > 0) && (n & (n - 1)) == 0; + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_08/README.md b/Week_08/README.md index 50de3041..151d84ac 100644 --- a/Week_08/README.md +++ b/Week_08/README.md @@ -1 +1,134 @@ -学习笔记 \ No newline at end of file +学习笔记 + +#### 字典树 + +字典树在搜索的时候经常会用到,可以用O(1)的复杂度找到查找的词组且补全高频查找结果,一般搜索引擎查询补全用的比较多。 + +字典树也是一种树,只是这个树和我们前面学的BFS和二叉堆不太一样,这个是颗N叉树,再者数据存储也不太一样,正常树的节点是存储完整的数据,而字典树每个节点其实只会存一个字符,当其到达尾部之后(一般条件是isEnd为true),将整个遍历路径连起来会组成一个字符串,这就是查询的结果。 + +字典树的缺点是空间占用会很大,是一种典型的空间换时间的思想。 + +#### 并查集 + +并查集这块东西会比较死,记住模板然后认清使用场景,直接套上去用就行了,基本是定式操作,主要是牢记模板。 + +其使用场景主要是组团和配对问题。判断两个人是否是朋友,判断群组关系等。 + +并查集代码模板如下: + +```java +public 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--; + } + +} +``` + +#### AVL树 + +是严格的二叉平衡树,它的范围在{-1,0,1}之间,当某个节点不在这个范围之后,就需要进行相应的旋转进行平衡。 + +旋转主要有四种: + +1. 左旋 +2. 右旋 +3. 左右旋 +4. 右左旋 + +但是AVL有个很大的问题,节点需要存储额外信息,且需要频繁的去平衡,但是实际工程中存在一定程度的不平衡其实也是能接受的。 + +因此红黑树这种近似平衡二叉树就诞生了。 + +#### 红黑树 + +红黑树是一个近似平衡的二叉树,它不需要频繁的去触发平衡操作。它能确保任何一个几点左右子树的高度差小于两倍。 + +红黑树是满足一下条件的二叉搜索树: + +- 每个结点要么是红色,要么是黑色 +- 根结点是黑色 +- 每个叶结点(NIL结点,空结点)是黑色的 +- 不能有相邻接的两个红色节点 +- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色结点 + +#### 位运算 + +因为计算机是用二进制而不是十进制存储,因此位运算会非常高效。 + +常见操作: + +- 左移 << +- 右移 >> +- 或运算 | 0011|1011=1011 +- 与运算 & 0011&1011=0011 +- 按位取反 ~ ~0011=1100 +- 按位异或(相同为零不同为一) ^ 0011^1011 + +##### 异或高级操作 + +异或:相同为零,不同为一。也可用“不进位加法”来理解。 + +异或操作的一些特点: + +x^0=x(当x不为0,结果就是x;当x为0,根据相同为零得出来的也是0,还是x) + +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 + +##### 指定位置的位运算 + +1. 将x最右边的n位清零:x&(~0<>n)&1 +3. 获取x的第n位幂值:x&(1< x&1==1 + + x%2==0 -> x&1==0 + +x/2->x>>1 + +x=x&(x-1) 清零最低位1 + +x&-x=>得到最低位的1 + +x&~x=>0 \ No newline at end of file diff --git a/Week_08/ReverseBits.java b/Week_08/ReverseBits.java new file mode 100644 index 00000000..bf81c7b8 --- /dev/null +++ b/Week_08/ReverseBits.java @@ -0,0 +1,69 @@ +//题号:190 +//https://leetcode-cn.com/problems/reverse-bits/ +//颠倒给定的 32 位无符号整数的二进制位。 +// +// +// +// 示例 1: +// +// 输入: 00000010100101000001111010011100 +//输出: 00111001011110000010100101000000 +//解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, +// 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 +// +// 示例 2: +// +// 输入:11111111111111111111111111111101 +//输出:10111111111111111111111111111111 +//解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293, +//  因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。 +// +// +// +// 提示: +// +// +// 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的 +//还是无符号的,其内部的二进制表示形式都是相同的。 +// 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -10737418 +//25。 +// +// +// +// +// 进阶: +//如果多次调用这个函数,你将如何优化你的算法? +// Related Topics 位运算 +// 👍 283 👎 0 + + +public class ReverseBits { + public static void main(String[] args) { + Solution solution = new ReverseBits().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +public class Solution { + // you need treat n as an unsigned value + public int reverseBits(int n) { + + /** + * 位运算要练一下 + */ + int ans = 0; + for (int i = 0; i < 32; i++) { + //java里面位运算都需要用括号括起来不然编译器会不认识 + ans = (ans << 1) + (n & 1); + n >>= 1; + } + + return ans >>> 0; + + + + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From d9e3fea77c70884c7487b0093d2028154039e9a9 Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 4 Apr 2021 23:22:47 +0800 Subject: [PATCH 11/15] week 9. --- Week_09/DecodeWays1.java | 148 +++++++++++++++++++++ Week_09/LruCache.java | 166 ++++++++++++++++++++++++ Week_09/MergeIntervals.java | 105 +++++++++++++++ Week_09/MinCostClimbingStairs.java | 65 ++++++++++ Week_09/README.md | 201 ++++++++++++++++++++++++++++- Week_09/RelativeSortArray.java | 98 ++++++++++++++ Week_09/ValidAnagram1.java | 135 +++++++++++++++++++ 7 files changed, 917 insertions(+), 1 deletion(-) create mode 100644 Week_09/DecodeWays1.java create mode 100644 Week_09/LruCache.java create mode 100644 Week_09/MergeIntervals.java create mode 100644 Week_09/MinCostClimbingStairs.java create mode 100644 Week_09/RelativeSortArray.java create mode 100644 Week_09/ValidAnagram1.java diff --git a/Week_09/DecodeWays1.java b/Week_09/DecodeWays1.java new file mode 100644 index 00000000..ef8c4ff7 --- /dev/null +++ b/Week_09/DecodeWays1.java @@ -0,0 +1,148 @@ +//题号:91 +//https://leetcode-cn.com/problems/decode-ways/ +//一条包含字母 A-Z 的消息通过以下映射进行了 编码 : +// +// +//'A' -> 1 +//'B' -> 2 +//... +//'Z' -> 26 +// +// +// 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"111" 可以将 "1" 中的每个 "1" 映射为 "A +//" ,从而得到 "AAA" ,或者可以将 "11" 和 "1"(分别为 "K" 和 "A" )映射为 "KA" 。注意,"06" 不能映射为 "F" ,因为 " +//6" 和 "06" 不同。 +// +// 给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数 。 +// +// 题目数据保证答案肯定是一个 32 位 的整数。 +// +// +// +// 示例 1: +// +// +//输入:s = "12" +//输出:2 +//解释:它可以解码为 "AB"(1 2)或者 "L"(12)。 +// +// +// 示例 2: +// +// +//输入:s = "226" +//输出:3 +//解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 +// +// +// 示例 3: +// +// +//输入:s = "0" +//输出:0 +//解释:没有字符映射到以 0 开头的数字。含有 0 的有效映射是 'J' -> "10" 和 'T'-> "20" 。由于没有字符,因此没有有效的方法对此进行 +//解码,因为所有数字都需要映射。 +// +// +// 示例 4: +// +// +//输入:s = "06" +//输出:0 +//解释:"06" 不能映射到 "F" ,因为字符串开头的 0 无法指向一个有效的字符。  +// +// +// +// +// 提示: +// +// +// 1 <= s.length <= 100 +// s 只包含数字,并且可能包含前导零。 +// +// Related Topics 字符串 动态规划 +// 👍 641 👎 0 + + +public class DecodeWays1 { + public static void main(String[] args) { + Solution solution = new DecodeWays1().new Solution(); + solution.numDecodings("06"); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int numDecodings(String s) { + /** + * 动态规划: + * 1、重复子问题:program(i)=subProgram(i-1)+一个字符+subProgram(i-2)+两个字符组合 + * 2、状态转移数组:dp[],dp值代表编码总数 + * 3、状态转移方程:dp[i]=dp[i-1]+一个字符+dp[i-2]+两个字符(这时候一个两个字符要分情况讨论) + * + * 字符串分情况讨论: + * 1、当末尾为0,即s.charAt(i)=='0',这时候只有i-1为1和2才会有效,数字为10/20,超过2找不到编码字母, + * 前面多少种就是多少种,因此dp[i]=dp[i-2],不是1或者2则是0 + * 2、i-1为1,i可以为任意数,且可以组成一个或者组成两个dp[i]=dp[i-1]+dp[i-2] + * 3、i-1为2,i只能小于7,前面已经排除0了,因此是1-6,dp[i]=dp[i-1]+dp[i-2]else dp[i]=dp[i-2] + * + */ + + + if (s.equals("0")) { + return 0; + } + int[] dp = new int[s.length() + 1]; + //这里的dp[0]我理解知识一个凑数的,为了算出dp[2] + dp[0] = 1; + //比如"06"这种,不能直接赋值成1,要根据第一位是什么再赋值 + dp[1] = s.charAt(0) == '0' ? 0 : 1; + + + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) == '0') { + if (s.charAt(i - 1) == '1' || s.charAt(i - 1) == '2') { + dp[i + 1] = dp[i - 1]; + } else { + return 0; + } + } else if (s.charAt(i - 1) == '1' || s.charAt(i - 1) == '2' && s.charAt(i) < '7') { + dp[i + 1] = dp[i] + dp[i - 1]; + } else { + dp[i + 1] = dp[i]; + } + } + + return dp[s.length()]; + + + /** + * 和上面相似的写法 + */ + +// if (s.equals('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++) { +// if (s.charAt(i-1) == '1' || s.charAt(i-1) == '2' && s.charAt(i) < '7') { +// if (s.charAt(i) == '0') { +// dp[i + 1] = dp[i - 1]; +// } else { +// dp[i + 1] = dp[i] + dp[i - 1]; +// } +// } else if (s.charAt(i) == '0') { +// return 0; +// } else { +// dp[i + 1] = dp[i]; +// } +// } +// +// return dp[s.length()]; + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_09/LruCache.java b/Week_09/LruCache.java new file mode 100644 index 00000000..a63b4a1a --- /dev/null +++ b/Week_09/LruCache.java @@ -0,0 +1,166 @@ +//运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 +// +// +// +// 实现 LRUCache 类: +// +// +// LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 +// int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 +// void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上 +//限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 +// +// +// +// +// +// +// 进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作? +// +// +// +// 示例: +// +// +//输入 +//["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] +//[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] +//输出 +//[null, null, null, 1, null, -1, null, -1, 3, 4] +// +//解释 +//LRUCache lRUCache = new LRUCache(2); +//lRUCache.put(1, 1); // 缓存是 {1=1} +//lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} +//lRUCache.get(1); // 返回 1 +//lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} +//lRUCache.get(2); // 返回 -1 (未找到) +//lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} +//lRUCache.get(1); // 返回 -1 (未找到) +//lRUCache.get(3); // 返回 3 +//lRUCache.get(4); // 返回 4 +// +// +// +// +// 提示: +// +// +// 1 <= capacity <= 3000 +// 0 <= key <= 3000 +// 0 <= value <= 104 +// 最多调用 3 * 104 次 get 和 put +// +// Related Topics 设计 +// 👍 1298 👎 0 + +import java.util.HashMap; +import java.util.Map; + +public class LruCache { + public static void main(String[] args) { + LruCache LruCache = new LruCache(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class LRUCache { + + /** + * 运用双向链表原因是为了加快查询速度 + */ + + class DLinkedNode{ + int key; + int value; + DLinkedNode prev; + DLinkedNode next; + public DLinkedNode(){} + + public DLinkedNode(int key, int value) { + this.key = key; + this.value = value; + } + } + + private Map cache = new HashMap<>(); + private int size; + private int capacity; + private DLinkedNode head, tail; + + + public LRUCache(int capacity) { + this.size = 0; + this.capacity = capacity; + //使用伪头部和伪尾部节点 + head = new DLinkedNode(); + tail = new DLinkedNode(); + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + + DLinkedNode node = cache.get(key); + if (node == null) { + return -1; + } + + //如果key存在,先通过哈希表找到位置O(1),然后将其移到头部 + moveToHead(node); + return node.value; + } + + private void moveToHead(DLinkedNode node) { + removeNode(node); + addToHead(node); + } + + private void removeNode(DLinkedNode node) { + node.prev.next = node.next; + node.next.prev = node.prev; + } + + public void put(int key, int value) { + DLinkedNode node = cache.get(key); + if (node == null) { + //如果不存在则新建并加入缓存 + DLinkedNode newNode = new DLinkedNode(key, value); + cache.put(key, newNode); + //添加到双向链表头部 + addToHead(newNode); + if (++size > capacity) { + //如果链表满了,则删除尾部节点(最久没用的) + DLinkedNode tail = removeTail(); + //删除哈希表对应项 + cache.remove(tail.key); + size--; + } + } else { + //如果存在,修改值并移到头部 + node.value = value; + moveToHead(node); + } + } + + private DLinkedNode removeTail() { + DLinkedNode res = tail.prev; + removeNode(res); + return res; + } + + private void addToHead(DLinkedNode node) { + node.prev = head; + node.next = head.next; + head.next.prev = node; + head.next = 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); + */ +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_09/MergeIntervals.java b/Week_09/MergeIntervals.java new file mode 100644 index 00000000..9ffab0bd --- /dev/null +++ b/Week_09/MergeIntervals.java @@ -0,0 +1,105 @@ +//以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返 +//回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。 +// +// +// +// 示例 1: +// +// +//输入:intervals = [[1,3],[2,6],[8,10],[15,18]] +//输出:[[1,6],[8,10],[15,18]] +//解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +// +// +// 示例 2: +// +// +//输入:intervals = [[1,4],[4,5]] +//输出:[[1,5]] +//解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 +// +// +// +// 提示: +// +// +// 1 <= intervals.length <= 104 +// intervals[i].length == 2 +// 0 <= starti <= endi <= 104 +// +// Related Topics 排序 数组 +// 👍 882 👎 0 + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public class MergeIntervals { + public static void main(String[] args) { + Solution solution = new MergeIntervals().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int[][] merge(int[][] intervals) { + + /** + * 时间复杂度O(nlogn),空间复杂度O(logn) + */ + +// if (intervals.length == 0) { +// return new int[0][2]; +// } +// Arrays.sort(intervals, new Comparator() { +// public int compare(int[] interval1, int[] interval2) { +// return interval1[0] - interval2[0]; +// } +// }); +// List merged = new ArrayList(); +// for (int i = 0; i < intervals.length; ++i) { +// int L = intervals[i][0], R = intervals[i][1]; +// if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) { +// merged.add(new int[]{L, R}); +// } else { +// merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R); +// } +// } +// return merged.toArray(new int[merged.size()][]); + + + /** + * 时间复杂度O(nlogn),空间复杂度O(logn) + * + * 对二维数组sort和将二维数组变成两个一维数组在sort时间差别这么大么? + * + */ + int length=intervals.length; + if(length<=1) + return intervals; + + int[] start = new int[length]; + int[] end = new int[length]; + for(int i=0;i result = new LinkedList<>(); + while(endIndexend[endIndex]){ + result.add(new int[]{start[startIndex],end[endIndex]}); + startIndex=endIndex+1; + } + endIndex++; + } + return result.toArray(new int[result.size()][]); + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_09/MinCostClimbingStairs.java b/Week_09/MinCostClimbingStairs.java new file mode 100644 index 00000000..d9a5a406 --- /dev/null +++ b/Week_09/MinCostClimbingStairs.java @@ -0,0 +1,65 @@ +//数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。 +// +// 每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 +// +// 请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。 +// +// +// +// 示例 1: +// +// +//输入:cost = [10, 15, 20] +//输出:15 +//解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。 +// +// +// 示例 2: +// +// +//输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] +//输出:6 +//解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。 +// +// +// +// +// 提示: +// +// +// cost 的长度范围是 [2, 1000]。 +// cost[i] 将会是一个整型数据,范围为 [0, 999] 。 +// +// Related Topics 数组 动态规划 +// 👍 535 👎 0 + +public class MinCostClimbingStairs { + public static void main(String[] args) { + Solution solution = new MinCostClimbingStairs().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int minCostClimbingStairs(int[] cost) { + /** + * 分解为子问题:S(i)=Math.min(S(i-1)+cost[i],S(i-2)+cost[i-1]) + * 状态转移数组:dp[],表示第N个位置花费的最小体力值 + * 状态转移方程:dp[i]=Math.min(dp[i-1]+cost[i],dp[i-2]+cost[i-1]) + */ + + int[] dp = new int[cost.length]; + dp[0] = 0; + dp[1] = Math.min(cost[0], cost[1]); + + for (int i = 2; i < cost.length; i++) { + dp[i] = Math.min(dp[i - 1] + cost[i], dp[i - 2] + cost[i - 1]); + } + + return dp[cost.length - 1]; + + + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/Week_09/README.md b/Week_09/README.md index 50de3041..e1bc5b05 100644 --- a/Week_09/README.md +++ b/Week_09/README.md @@ -1 +1,200 @@ -学习笔记 \ No newline at end of file +##学习笔记 +##### 布隆过滤器和LRU Cache + +布隆过滤器一般会和哈希表一起拿来比较,哈希表会将数据value通过hash函数映射之后准确的存储起来,这样其实会占用一定的内存。在实际工业应用中,我们其实不需要准确的指导存储的内容,而只需要知道这个东西是有还是没有这两个状态即可,而在这种场景下Bloom Filter就应运而生。 + +**Bloom Filter**是一个很长的二进制向量和一系列随机映射函数。它可以用于检测一个函数是否在一个集合中。 + +- 优点:空间效率和查询时间都远远超过一半算法 +- 缺点:有一定的误识别率和删除困难 + +造成误识别率的原因是不同元素放进布隆过滤器时可能导致分配的二进制位重合(感觉和hash冲突优点类似)。因此布隆过滤器判断元素是否存有两种情况:如果判断不存在,那一定是不存在的;而判断存在,它可能是不存在的,这个还需要去具体的数据库或者缓存里面去进一步验证。 + +**LRU Cache(Last Recently Used cache)**是一个使用比较多的缓存替换算法,最近最长使用缓存,它比较符合人脑的思维,如果一个物品,你如果很长时间没有使用的话,算法就会将其舍弃,且一个物品你前段时间没用最近开始使用了,算法会将这个物品放到最前面。它的实现一般基于HashMap+LinkedList(双向链表)实现,java里面有现成的实现->linkedHashMap。 + +#### 排序算法 + +主要分为两类,比较类排序和非比较类排序,比较类在java里面可以理解为会传递Comparator,而非比较类的话一般是对整形数据进行排序,时间复杂度一般是线性的为O(n)。 + +##### 比较类排序 + +- 交换排序 + - 冒泡排序 + - 快速排序 +- 插入排序 + - 简单插入排序 + - 希尔排序 +- 选择排序 + - 简单选择排序 + - 堆排序 +- 归并排序 + - 二路归并排序 + - 多路归并排序 + +##### 非比较类排序 + +- 计数排序 +- 桶排序 +- 基数排序 + +排序里面比较不太好理解的主要是快速排序和归并排序,它们都是基于递归分治的思想进行的排序,只是具体实现细节有点区别而已。 + +快速排序模板 + +```java +public static 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); + } + + static int partition(int[] a, int begin, int end) {    + // pivot: 标杆位置,counter: 小于pivot的元素的个数    + int pivot = end, counter = begin;    + for (int i = begin; i < end; i++) {        + if (a[i] < a[pivot]) {            + int temp = a[counter]; + a[counter] = a[i]; a[i] = temp;            + counter++;        + }    + }    + int temp = a[pivot]; + a[pivot] = a[counter]; + a[counter] = temp;    + return counter; + } +``` + +归并排序模板: + +```java +public static void mergeSort(int[] array, int left, int right) {    + if (right <= left) return;    + int mid = (left + right) >> 1; // (left + right) / 2    + mergeSort(array, left, mid);    + mergeSort(array, mid + 1, right);    + merge(array, left, mid, right); + } + + public static void merge(int[] arr, 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++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];        + }        + + while (i <= mid)   temp[k++] = arr[i++];        + while (j <= right) temp[k++] = arr[j++];  +       + for (int p = 0; p < temp.length; p++) {            + arr[left + p] = temp[p];        + }        + // 也可以用 System.arraycopy(a, start1, b, start2, length)    } +``` + +堆排序模板: + +```java +static void heapify(int[] array, int length, int i) {    + int left = 2 * i + 1, right = 2 * i + 2;    + int largest = i;    + if (left < length && array[left] > array[largest]) {        + largest = left;    + }    + + if (right < length && array[right] > array[largest]) {        + largest = right;    + }    + + if (largest != i) {        + int temp = array[i]; + array[i] = array[largest]; + array[largest] = temp;        + heapify(array, length, largest);    + } + } + + + public static void heapSort(int[] array) {    + if (array.length == 0) return;    + int length = array.length;    + for (int i = length / 2-1; i >= 0; i-)  heapify(array, length, i);    + for (int i = length - 1; i >= 0; i--) {        + int temp = array[0]; + array[0] = array[i]; + array[i] = temp;        + heapify(array, i, 0);    + } + } +``` + +#### 高级动态规划 + +高级动态规划和普通动态规划的区别主要是维度上去了,普通的动态规划维度一般是1-2维,推理相对简单,且其实不太需要你去进行过多的逻辑抽象,但是到了高级动态规划就不一样了,需要你一定的逻辑抽象能力,且状态转移方程定义会比较复杂,有时候状态定义过多还需要进行**状态压缩**。这些都对基本功有一定的要求。 + +这块其实也没有什么好的方法,运用五毒神掌,多练多想,形成思维记忆即可。 +###三种基础排序模板 +#### 排序算法 + +##### 选择排序 + +```java + if(n<=0) return; + for(int i=0;i= 0; --j) { + if (a[j] > value) { + a[j+1] = a[j]; // 数据移动 + } else { + break; + } + } + a[j+1] = value; // 插入数据 + } + +``` + +##### 冒泡排序 + +```java + // 插入排序,a表示数组,n表示数组大小 + if (n <= 1) return; + + for (int i = 0; i < n; ++i) { + // 提前退出冒泡循环的标志位 + boolean flag = false; + for (int j = 0; j < n - i - 1; ++j) { + if (a[j] > a[j+1]) { // 交换 + int tmp = a[j]; + a[j] = a[j+1]; + a[j+1] = tmp; + flag = true; // 表示有数据交换 + } + } + if (!flag) break; // 没有数据交换,提前退出 + } + +``` + diff --git a/Week_09/RelativeSortArray.java b/Week_09/RelativeSortArray.java new file mode 100644 index 00000000..a2932e76 --- /dev/null +++ b/Week_09/RelativeSortArray.java @@ -0,0 +1,98 @@ +//题号:1122 +//给你两个数组,arr1 和 arr2, +// +// +// arr2 中的元素各不相同 +// arr2 中的每个元素都出现在 arr1 中 +// +// +// 对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末 +//尾。 +// +// +// +// 示例: +// +// +//输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6] +//输出:[2,2,2,1,4,3,3,9,6,7,19] +// +// +// +// +// 提示: +// +// +// 1 <= arr1.length, arr2.length <= 1000 +// 0 <= arr1[i], arr2[i] <= 1000 +// arr2 中的元素 arr2[i] 各不相同 +// arr2 中的每个元素 arr2[i] 都出现在 arr1 中 +// +// Related Topics 排序 数组 +// 👍 165 👎 0 + + +public class RelativeSortArray { + public static void main(String[] args) { + Solution solution = new RelativeSortArray().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int[] relativeSortArray(int[] arr1, int[] arr2) { + + int[] count = new int[10001]; + + for (int n : arr1) count[n]++; + + int i = 0; + for (int n : arr2) { + while (count[n]-- > 0) { + arr1[i++] = n; + } + } + + + for (int j = 0; j < count.length; j++) { + while (count[j]-- > 0) { + arr1[i++] = j; + } + } + + return arr1; + + + + + + + + /** + * 计数排序 + * + * 计数排序用数组天然有顺序,而map在arr2拿完数之后还需要排序, + * 可以用treeMap进行自动排序,但会牺牲一定的性能 + * + */ +// int[] cnt = new int[1001]; +// //计数arr1出现的次数 +// for(int n : arr1) cnt[n]++; +// int i = 0; +// //将arr2的值输出到arr1 +// for(int n : arr2) { +// while(cnt[n]-- > 0) { +// arr1[i++] = n; +// } +// } +// //将剩余arr1的值输出,因为是升序遍历,因此出来的数也是升序,这里直接从头遍历感觉不是很好,想想是否能优化 +// for(int n = 0; n < cnt.length; n++) { +// while(cnt[n]-- > 0) { +// arr1[i++] = n; +// } +// } +// return arr1; + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file diff --git a/Week_09/ValidAnagram1.java b/Week_09/ValidAnagram1.java new file mode 100644 index 00000000..a58c1e21 --- /dev/null +++ b/Week_09/ValidAnagram1.java @@ -0,0 +1,135 @@ +//题号:242 +//https://leetcode-cn.com/problems/valid-anagram/ +//给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 +// +// 示例 1: +// +// 输入: s = "anagram", t = "nagaram" +//输出: true +// +// +// 示例 2: +// +// 输入: s = "rat", t = "car" +//输出: false +// +// 说明: +//你可以假设字符串只包含小写字母。 +// +// 进阶: +//如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况? +// Related Topics 排序 哈希表 +// 👍 336 👎 0 + + +public class ValidAnagram1 { + public static void main(String[] args) { + Solution solution = new ValidAnagram1().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean isAnagram(String s, String t) { + + if (s.length() != t.length()) { + return false; + } + + //只会有26个字母,且都是小写 + int[] chars = new int[26]; + + for (char c : s.toCharArray()) { + chars[c - 'a']++; + } + + for (char c : t.toCharArray()) { + chars[c - 'a']--; + if (chars[c - 'a'] < 0) { + return false; + } + } + return true; + + + + + + + + + + + + + + /** + * 法一:直接用封装的hashMap + * 时间复杂度O(n),空间复杂度O(n) + */ + +// if (s.length() != t.length()) { +// return false; +// } +// +// Map charMap = new HashMap<>(); +// for (char c : s.toCharArray()) { +// charMap.put(c, charMap.getOrDefault(c, 0) + 1); +// } +// +// for (char c : t.toCharArray()) { +// int count = charMap.getOrDefault(c, -1) - 1; +// //这里只要在这里面动态判断是否小于0即可 +// if (count < 0) { +// return false; +// } +// charMap.put(c, count); +// } +// return true; + + + /** + * 法二:用数组代表哈希表 + * 哈希表主要是key-value映射能为我们省去大量查询时间,这种数据量很大的 + * 情况下确实很有用,但对于数据量和mapping关系已知(26个字母)的情况下,我们可 + * 以用数组替代,从而省去了mapping的操作 + * + * 时间复杂度O(n),空间复杂度O(1) 26 + * + */ + +// if (s.length() != t.length()) { +// return false; +// } +// //只会有26个字母,且都是小写 +// int[] table = new int[26]; +// +// for (char c : s.toCharArray()) { +// table[c - 'a']++; +// } +// +// for (char c : t.toCharArray()) { +// if (--table[c - 'a'] < 0) { +// return false; +// } +// } +// +// return true; + + /** + * 法三,排序直接比较,复杂度太高,不推荐 + * 时间复杂度O(nlogn),空间复杂度O(logn) + */ + +// if (s.length() != t.length()) { +// return false; +// } +// char[] str1 = s.toCharArray(); +// char[] str2 = t.toCharArray(); +// Arrays.sort(str1); +// Arrays.sort(str2); +// return Arrays.equals(str1, str2); + + } +} +//leetcode submit region end(Prohibit modification and deletion) + + } \ No newline at end of file From 8b4152925eede31b94229cdacba66f158da2d7de Mon Sep 17 00:00:00 2001 From: InspirationBO Date: Sun, 4 Apr 2021 23:30:05 +0800 Subject: [PATCH 12/15] Update README.md --- Week_09/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Week_09/README.md b/Week_09/README.md index e1bc5b05..8478ea7c 100644 --- a/Week_09/README.md +++ b/Week_09/README.md @@ -1,4 +1,4 @@ -##学习笔记 +## 学习笔记 ##### 布隆过滤器和LRU Cache 布隆过滤器一般会和哈希表一起拿来比较,哈希表会将数据value通过hash函数映射之后准确的存储起来,这样其实会占用一定的内存。在实际工业应用中,我们其实不需要准确的指导存储的内容,而只需要知道这个东西是有还是没有这两个状态即可,而在这种场景下Bloom Filter就应运而生。 From f314bb3ebe3c891701ca1d2ce0b36f9f63a03723 Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 11 Apr 2021 01:48:05 +0800 Subject: [PATCH 13/15] week 10. --- .../FirstUniqueCharacterInAString.java" | 109 ++++++++++++++++++ .../JewelsAndStones.java" | 80 +++++++++++++ .../LengthOfLastWord.java" | 77 +++++++++++++ .../ToLowerCase.java" | 61 ++++++++++ .../ValidPalindrome.java" | 60 ++++++++++ .../ValidPalindromeIi.java" | 67 +++++++++++ ...25\344\270\232\346\200\273\347\273\223.md" | 28 +++++ 7 files changed, 482 insertions(+) create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/FirstUniqueCharacterInAString.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/JewelsAndStones.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/LengthOfLastWord.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ToLowerCase.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindrome.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindromeIi.java" create mode 100644 "Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/FirstUniqueCharacterInAString.java" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/FirstUniqueCharacterInAString.java" new file mode 100644 index 00000000..6a012c4e --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/FirstUniqueCharacterInAString.java" @@ -0,0 +1,109 @@ +//题号:387 +//给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 +// +// +// +// 示例: +// +// s = "leetcode" +//返回 0 +// +//s = "loveleetcode" +//返回 2 +// +// +// +// +// 提示:你可以假定该字符串只包含小写字母。 +// Related Topics 哈希表 字符串 +// 👍 364 👎 0 + + +public class FirstUniqueCharacterInAString { + public static void main(String[] args) { + Solution solution = new FirstUniqueCharacterInAString().new Solution(); + solution.firstUniqChar("loveleetcode"); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public int firstUniqChar(String s) { + /** + * 也是统计字母出现次数,但是用map记录 + */ + +// if (s.isEmpty()) { +// return -1; +// } +// +// Map countMap = new HashMap<>(s.length()); +// for (int i = 0; i < s.length(); i++) { +// countMap.put(s.charAt(i), countMap.getOrDefault(s.charAt(i), 0) + 1); +// } +// +// for (int i = 0; i < s.length(); i++) { +// char c = s.charAt(i); +// //这里是只出现一次是1不是不出现0,好低级的错误 +// if (countMap.get(c) == 1) { +// return i; +// } +// } +// +// return -1; + + + /** + * hash表底层是数组,在对于目标size已知的情况下可以直接用数组存储 + */ + + //代表'a'-'z'这些字符出现的次数 + int[] chars = new int[26]; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + //这儿可以'z'-c也可以c-'a',是一样的,保证统一即可 + chars['z' - c]++; + } + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (chars['z' - c] == 1) { + return i; + } + } + + return -1; + + + /** + * 运用HashMap+queue + */ + +// if(s.length() == 0) return -1; +// if(s.length() == 1) return 0; +// Deque queue = new LinkedList<>(); +// +// HashMap set = new HashMap<>(); +// +// for(int i= 0; i= 0 && s.charAt(end) == ' ') end--; + + //只有一个空格的情况" " + if (end<0) return 0; + + int start = end; + while (start >= 0 && s.charAt(start) != ' ') start--; + + return end - start; + + + + + + + } + +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ToLowerCase.java" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ToLowerCase.java" new file mode 100644 index 00000000..41407662 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ToLowerCase.java" @@ -0,0 +1,61 @@ +//实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串。 +// +// +// +// 示例 1: +// +// +//输入: "Hello" +//输出: "hello" +// +// 示例 2: +// +// +//输入: "here" +//输出: "here" +// +// 示例 3: +// +// +//输入: "LOVELY" +//输出: "lovely" +// +// Related Topics 字符串 +// 👍 146 👎 0 + +public class ToLowerCase { + public static void main(String[] args) { + Solution solution = new ToLowerCase().new Solution(); + solution.toLowerCase("Hello"); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public String toLowerCase(String str) { + + + if (str == null | str.length() == 0) { + return ""; + } + +// char[] chars = new char[26]; +// +// for (int i = 0; i < 26; i++) { +// chars[i] = (char) ('a' + i); +// } + + char[] charArray = str.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + if (charArray[i]>='A'&&charArray[i]<='Z') { +// charArray[i] = chars[str.charAt(i) - 'A']; + // 利用所有大写字母ASCII码可以通过+32变成小写字母 + charArray[i] += 32; + } + } + + + return new String(charArray); + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindrome.java" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindrome.java" new file mode 100644 index 00000000..27d0ce0c --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindrome.java" @@ -0,0 +1,60 @@ +//给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 +// +// 说明:本题中,我们将空字符串定义为有效的回文串。 +// +// 示例 1: +// +// 输入: "A man, a plan, a canal: Panama" +//输出: true +// +// +// 示例 2: +// +// 输入: "race a car" +//输出: false +// +// Related Topics 双指针 字符串 +// 👍 364 👎 0 + +public class ValidPalindrome { + public static void main(String[] args) { + Solution solution = new ValidPalindrome().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean isPalindrome(String s) { + + + /** + * 双指针 + * 时间复杂度O(n) + * 空间复杂度O(1) + */ + if (s.isEmpty()) { + return true; + } + + int left = 0, right = s.length() - 1; + + while (left <= right) { + + if (!Character.isLetterOrDigit(s.charAt(left))) { + left++; + } else if (!Character.isLetterOrDigit(s.charAt(right))) { + right--; + } else { + if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) { + return false; + } + left++; + right--; + } + } + + return true; + + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindromeIi.java" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindromeIi.java" new file mode 100644 index 00000000..ef749480 --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/ValidPalindromeIi.java" @@ -0,0 +1,67 @@ +//给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。 +// +// 示例 1: +// +// +//输入: "aba" +//输出: True +// +// +// 示例 2: +// +// +//输入: "abca" +//输出: True +//解释: 你可以删除c字符。 +// +// +// 注意: +// +// +// 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。 +// +// Related Topics 字符串 +// 👍 336 👎 0 + +public class ValidPalindromeIi { + public static void main(String[] args) { + Solution solution = new ValidPalindromeIi().new Solution(); + } + //leetcode submit region begin(Prohibit modification and deletion) +class Solution { + public boolean validPalindrome(String s) { + + /** + * 贪心算法,双指针 + */ + int low = 0, high = s.length() - 1; + while (low < high) { + char c1 = s.charAt(low), c2 = s.charAt(high); + if (c1 == c2) { + ++low; + --high; + } else { + return validPalindrome(s, low, high - 1) || validPalindrome(s, low + 1, high); + } + } + return true; + + + + + + } + + private boolean validPalindrome(String s, int low, int high) { + for (int i = low, j = high; i < j; ++i, --j) { + char c1 = s.charAt(i), c2 = s.charAt(j); + if (c1 != c2) { + return false; + } + } + return true; + } +} +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" new file mode 100644 index 00000000..b27e9e5e --- /dev/null +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" @@ -0,0 +1,28 @@ +### 毕业总结 + +为期十周的算法训练营结束了,感觉过的好快,但是收获还是挺大的,除了数据结构个和算法相关知识点的掌握外,特别是对**刻意练习**这个观点有了很强烈的认同,五毒神掌的思维不止能刷题,用到其他地方也是可以的。 + +其实看看这十周学的东西还是挺多的。 + +**数据结构:** + +- 数组,链表,栈,队列,双端队列,Map,Set,跳表 +- 树,图,堆,二叉搜索树,avl树,红黑树,B+树。 + +**算法和思维**: + +- 递归,回溯,分治, +- BFS, DFS,双向BFS,启发式搜索(A*) +- 贪心算法 +- 二分查找 +- 排序 +- 动态规划 +- 位运算 +- 布隆过滤器 +- LRU Cache +- 字典树和并查集 +- 字符串比较算法 + +其中有的知识点掌握的还行,如基本的数据结构,都知道使用场景和实现了,在了解其实现后在实际开发中确实会用的更加得心应手了。但有的还是不够熟练,需要过遍数,如并查集,位运算,字符串算法KMP算法等,现在基本还是半懵逼状态,需要多做题去消化。比较庆幸的一点是,对于**递归**和**动态规划**还是有自己的理解,其核心思想都大概掌握了,接下来就是多做题强化了。 + +最后感谢算法训练营所有老师和助教以及班班的帮助! From 7f8c279d002373714351d9d88c964281f3cd79d8 Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 11 Apr 2021 14:01:43 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E6=AF=95=E4=B8=9A=E6=80=BB=E7=BB=93(?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...25\344\270\232\346\200\273\347\273\223.md" | 124 +++++++++++++++++- 1 file changed, 118 insertions(+), 6 deletions(-) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" index b27e9e5e..1a6b6ae5 100644 --- "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" @@ -1,15 +1,127 @@ -### 毕业总结 +## 毕业总结 为期十周的算法训练营结束了,感觉过的好快,但是收获还是挺大的,除了数据结构个和算法相关知识点的掌握外,特别是对**刻意练习**这个观点有了很强烈的认同,五毒神掌的思维不止能刷题,用到其他地方也是可以的。 其实看看这十周学的东西还是挺多的。 -**数据结构:** +#### **数据结构:** + +###### 数组 + +这是一种内存连续的线性表结构,每个存储单元占用的内存空间大小固定,且知道首地址,因此可以很快的用 首地址+空间大小 计算出任意元素的内存地址,**查找的时间复杂度为O(1)**,**但是插入和删除的平均时间复杂度为O(n)**,因为会涉及数据迁移,实际运用的时候需要注意,在进行大块内存申请的时候可能会失败,即使当前空间足够,因为数组需要的是连续空间,而现在内存里面大部分可能是分散的空间,所以这种时候比较推荐使用链表,对空间利用率会高点。 + +###### 链表 + +是一种线性的存储结构,里面存储单元以节点的形式存在,节点里会包含指针(指向下一个节点,当指针指向null则代表到了尾部)和存储的数据。 + +**链表查找是O(n),因为需要用指针从头开始遍历,插入删除则是O(1)**。在实际中会做很多优化,如双向链表,以及和map一起使用等。链表相比数组是每个节点的内存空间随机,且数据非顺序,这样存储会十分灵活。 + +###### 栈 + +先入后出的数据结构,一般用来做括号匹配,以及面积判断,接雨水等问题求救。 + +###### 队列 + +队列是一种先入先出的数据结构,在实际业务场景中运用的还是比较多的,如消息中间件等,队列里有一个特殊存在,双端队列,可以两头操作,基本一个数据结构就囊括了栈和队列所有特性。 + +###### hashMap + +**哈希表**是一种在数组上扩展出来的数据结构,它实际还是用数组存储的数据,只是做了一定封装。哈希表一个特点是可以根据输入O(1)复杂度找到对应的数据,其实就是利用数组查询是O(1)的特性以及hash函数对输入数据进行转换为数组下标index的过程。 + +**哈希函数**的设计其实很讲究,一个好的哈希函数这几能有效避免哈希冲突,且运算速度很快,但是实际很难得到两者平衡,大多数都是根据实际场景实际分析,可能就是工程中的妥协吧。 + +**哈希冲突**指的是当数据量大到一定程度,会导致两个不同的数据通过哈希函数计算被映射到了同一个index下,这时候你的查询就不是O(1)了,就可能是O(m)(m代表同一地址存了多少个元素),或者是O(logm)。工业上的解决办法通常有两种,链表法和红黑树。 + +当元素个数<=8时,用链表,当元素个数>8时,则用红黑树。 + +###### Set + +set是一种无序且不重复的数据结构,长用于去重和缩小数据量的操作,java里面的实现比较有意思,就是直接用map的key当做Set的value,然后map的value直接用一个固定值站位,因为不会用到它。 + +###### 跳表 + +跳表(Skip-list)只能用于元素有序的情况,你可以想象成链表的升级版,它在链表上加了索引,从而提升了查询速度,在现在的开源项目中一般都用跳表代替红黑树,易实现好维护。 + +###### 树 + +树是一种十分常用的数据结构,其实当一个链表的节点由一个变成两个或多个就变成树,树的实现有很多,二叉搜索树,avl树,红黑树,B+树等。 + +因为二叉树分为左右两个子树,且左右子树和二叉树特性相同,因此常常用递归去实现树的操作。 + +###### 二叉搜索树 + +空树也是二叉搜索树,二叉搜索树的左儿子小于根节点,且根节点小于右儿子,左右子树同理可得。 + +二叉搜索树在查找的时候时间复杂度为O(logn),它的插入和查找类似,先进行查找,如果查找的位置没有节点,则在那个位置插入。 + +删除的话会麻烦点,当删除的节点是叶子节点,直接删除就行,当删除的节点是父亲节点,就需要将该父亲节点右子树里面最小的节点放到该父亲节点。 + +###### 堆 + +堆常用来求topk的问题,堆分为大顶堆和小顶堆,以大顶堆为例,堆里元素子节点都小于父亲节点,左右子树同理可得。 + +堆的构建时间复杂度是O(logn) + +堆的查找也是O(logn) + +堆的删除会比较麻烦,它会将堆顶元素取走,然后将堆尾的元素放到堆顶,然后重新构建整个堆。时间复杂度也是O(logn) + +###### avl树 + +严格的平衡二叉树,需要记住四种旋转平衡,但是就是由于太严格了,有了很多不必要的操作,所以后面相处了红黑树。 + +###### 红黑树 + +红黑树是一个近似平衡的二叉树,它不需要频繁的去触发平衡操作,这样会省去很多不必要的操作。它能确保任何一个几点左右子树的高度差小于两倍。 + +#### **算法和思维**: + +##### 递归,回溯,分治 + +严格的来说回溯和分治是算法思维,但其依赖的本质还是递归,递归是计算机的一种常用方法,特别是对于复杂逻辑,当你在找到这些复杂逻辑的重复性之后,然后将其抽象为函数,在不断调用这个函数的过程,就是递归。 + +递归的一个很重要特点就是找重复性,找到重复性之后,计算机能很好的去完成不断重复的东西,且特别高效。 + +递归模板:这个需要在O(1)时间反应出来 + +```java +public void recur(level,max,....){ +//终止条件 +if(max>level) +return; + +//当前层逻辑 + +//进入下一层 +recur(level+1,max,....) + +//重置状态,一般用户对多种状态进行尝试,回溯等 +} +``` + +回溯就是不断的尝试每种可能,直到找到正确的结果为止,回溯一般时间复杂度很高,是指数级的,因此需要搭配剪枝一起使用从而控制时间复杂度。 + +分治是采用分而治之的思想,将一个大问题拆成几个小问题,然后分别将小问题求解之后,在组装其结果,最后得出最终结果。计算机天生适合处理分治递归等问题,因为它们能并行。 + +###### DFS,BFS,双向BFS,启发式搜索(A*) + +上面这些都是搜索算法,在一个图或者树中对目标结果进行搜索,根据方式不同分为 + +深度优先搜索是指将每条路径直接走到底,当该条路径遍历完都没有找到目标结果时,在换另一条,直到找到。有种不撞南墙不回头的意思。 + +广度优先搜索是指按照波纹扩散的方式将对每条路的下一层节点进行遍历,直到找到目标节点。我感觉实际中推荐用BFS,因为DFS当数据量太大时很可能带来较高的时间复杂度。 + +双向BFS指的是在BFS的基础上进行优化,常规BFS是从起点到终点扩散,而双向BFS是从起点和终点同时向中间扩散,且每次优先用节点少的那端扩散,当起点和终点直接深度很深的时候,双向BFS能比常规BFS快很多。 + +启发式搜索(A*),这个定义就比较灵活了,你可以自己制定比较规则,然后按照你制定的比较规则进行遍历。 + +###### 贪心算法 + + + + -- 数组,链表,栈,队列,双端队列,Map,Set,跳表 -- 树,图,堆,二叉搜索树,avl树,红黑树,B+树。 -**算法和思维**: - 递归,回溯,分治, - BFS, DFS,双向BFS,启发式搜索(A*) @@ -25,4 +137,4 @@ 其中有的知识点掌握的还行,如基本的数据结构,都知道使用场景和实现了,在了解其实现后在实际开发中确实会用的更加得心应手了。但有的还是不够熟练,需要过遍数,如并查集,位运算,字符串算法KMP算法等,现在基本还是半懵逼状态,需要多做题去消化。比较庆幸的一点是,对于**递归**和**动态规划**还是有自己的理解,其核心思想都大概掌握了,接下来就是多做题强化了。 -最后感谢算法训练营所有老师和助教以及班班的帮助! +最后感谢算法训练营所有老师和助教以及班班的帮助! \ No newline at end of file From b27e7be61aa4e67c3b609727552cd985bc2b76fa Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 11 Apr 2021 23:17:57 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E6=AF=95=E4=B8=9A=E6=80=BB=E7=BB=93?= =?UTF-8?q?=E8=A1=A5=E5=85=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...25\344\270\232\346\200\273\347\273\223.md" | 189 +++++++++++++++--- 1 file changed, 159 insertions(+), 30 deletions(-) diff --git "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" index 1a6b6ae5..79f92210 100644 --- "a/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" +++ "b/Week_10 \346\257\225\344\270\232\346\200\273\347\273\223/\346\257\225\344\270\232\346\200\273\347\273\223.md" @@ -1,4 +1,4 @@ -## 毕业总结 +### 毕业总结 为期十周的算法训练营结束了,感觉过的好快,但是收获还是挺大的,除了数据结构个和算法相关知识点的掌握外,特别是对**刻意练习**这个观点有了很强烈的认同,五毒神掌的思维不止能刷题,用到其他地方也是可以的。 @@ -6,25 +6,25 @@ #### **数据结构:** -###### 数组 +##### 数组 这是一种内存连续的线性表结构,每个存储单元占用的内存空间大小固定,且知道首地址,因此可以很快的用 首地址+空间大小 计算出任意元素的内存地址,**查找的时间复杂度为O(1)**,**但是插入和删除的平均时间复杂度为O(n)**,因为会涉及数据迁移,实际运用的时候需要注意,在进行大块内存申请的时候可能会失败,即使当前空间足够,因为数组需要的是连续空间,而现在内存里面大部分可能是分散的空间,所以这种时候比较推荐使用链表,对空间利用率会高点。 -###### 链表 +##### 链表 是一种线性的存储结构,里面存储单元以节点的形式存在,节点里会包含指针(指向下一个节点,当指针指向null则代表到了尾部)和存储的数据。 **链表查找是O(n),因为需要用指针从头开始遍历,插入删除则是O(1)**。在实际中会做很多优化,如双向链表,以及和map一起使用等。链表相比数组是每个节点的内存空间随机,且数据非顺序,这样存储会十分灵活。 -###### 栈 +##### 栈 先入后出的数据结构,一般用来做括号匹配,以及面积判断,接雨水等问题求救。 -###### 队列 +##### 队列 队列是一种先入先出的数据结构,在实际业务场景中运用的还是比较多的,如消息中间件等,队列里有一个特殊存在,双端队列,可以两头操作,基本一个数据结构就囊括了栈和队列所有特性。 -###### hashMap +##### hashMap **哈希表**是一种在数组上扩展出来的数据结构,它实际还是用数组存储的数据,只是做了一定封装。哈希表一个特点是可以根据输入O(1)复杂度找到对应的数据,其实就是利用数组查询是O(1)的特性以及hash函数对输入数据进行转换为数组下标index的过程。 @@ -34,21 +34,21 @@ 当元素个数<=8时,用链表,当元素个数>8时,则用红黑树。 -###### Set +##### Set set是一种无序且不重复的数据结构,长用于去重和缩小数据量的操作,java里面的实现比较有意思,就是直接用map的key当做Set的value,然后map的value直接用一个固定值站位,因为不会用到它。 -###### 跳表 +##### 跳表 跳表(Skip-list)只能用于元素有序的情况,你可以想象成链表的升级版,它在链表上加了索引,从而提升了查询速度,在现在的开源项目中一般都用跳表代替红黑树,易实现好维护。 -###### 树 +##### 树 树是一种十分常用的数据结构,其实当一个链表的节点由一个变成两个或多个就变成树,树的实现有很多,二叉搜索树,avl树,红黑树,B+树等。 因为二叉树分为左右两个子树,且左右子树和二叉树特性相同,因此常常用递归去实现树的操作。 -###### 二叉搜索树 +##### 二叉搜索树 空树也是二叉搜索树,二叉搜索树的左儿子小于根节点,且根节点小于右儿子,左右子树同理可得。 @@ -56,7 +56,7 @@ set是一种无序且不重复的数据结构,长用于去重和缩小数据 删除的话会麻烦点,当删除的节点是叶子节点,直接删除就行,当删除的节点是父亲节点,就需要将该父亲节点右子树里面最小的节点放到该父亲节点。 -###### 堆 +##### 堆 堆常用来求topk的问题,堆分为大顶堆和小顶堆,以大顶堆为例,堆里元素子节点都小于父亲节点,左右子树同理可得。 @@ -66,11 +66,11 @@ set是一种无序且不重复的数据结构,长用于去重和缩小数据 堆的删除会比较麻烦,它会将堆顶元素取走,然后将堆尾的元素放到堆顶,然后重新构建整个堆。时间复杂度也是O(logn) -###### avl树 +##### avl树 严格的平衡二叉树,需要记住四种旋转平衡,但是就是由于太严格了,有了很多不必要的操作,所以后面相处了红黑树。 -###### 红黑树 +##### 红黑树 红黑树是一个近似平衡的二叉树,它不需要频繁的去触发平衡操作,这样会省去很多不必要的操作。它能确保任何一个几点左右子树的高度差小于两倍。 @@ -103,38 +103,167 @@ recur(level+1,max,....) 分治是采用分而治之的思想,将一个大问题拆成几个小问题,然后分别将小问题求解之后,在组装其结果,最后得出最终结果。计算机天生适合处理分治递归等问题,因为它们能并行。 -###### DFS,BFS,双向BFS,启发式搜索(A*) +##### DFS,BFS,双向BFS,启发式搜索(A*) 上面这些都是搜索算法,在一个图或者树中对目标结果进行搜索,根据方式不同分为 -深度优先搜索是指将每条路径直接走到底,当该条路径遍历完都没有找到目标结果时,在换另一条,直到找到。有种不撞南墙不回头的意思。 +深度优先搜索是指将每条路径直接走到底,当该条路径遍历完都没有找到目标结果时,在换另一条,直到找到。有种不撞南墙不回头的意思。其实现一般是递归,当然栈也可以,但是多用递归。 -广度优先搜索是指按照波纹扩散的方式将对每条路的下一层节点进行遍历,直到找到目标节点。我感觉实际中推荐用BFS,因为DFS当数据量太大时很可能带来较高的时间复杂度。 +广度优先搜索是指按照波纹扩散的方式将对每条路的下一层节点进行遍历,直到找到目标节点。我感觉实际中推荐用BFS,因为DFS当数据量太大时很可能带来较高的时间复杂度。实现一般是队列。 -双向BFS指的是在BFS的基础上进行优化,常规BFS是从起点到终点扩散,而双向BFS是从起点和终点同时向中间扩散,且每次优先用节点少的那端扩散,当起点和终点直接深度很深的时候,双向BFS能比常规BFS快很多。 +双向BFS指的是在BFS的基础上进行优化,常规BFS是从起点到终点扩散,而双向BFS是从起点和终点同时向中间扩散,且每次优先用节点少的那端扩散,当起点和终点直接深度很深的时候,双向BFS能比常规BFS快很多。双向BFS在代码实现的时候就不是用queue而是用两个set去遍历了。 启发式搜索(A*),这个定义就比较灵活了,你可以自己制定比较规则,然后按照你制定的比较规则进行遍历。 -###### 贪心算法 +##### 贪心算法 +在计算的每个阶段都得到最优解,从而得到全局的最优解,它可以说是动态规划的一种特殊情况,即每个阶段的解刚好就是它自己,没有额外的回退或者转换。**贪心算法**比较麻烦的是你能证明或者想到这个题能用贪心解,当你知道能用贪心解,其代码实现一般是比较简单的,主要是判定和证明比较麻烦。 +##### 二分查找 +二分查找试用数据集有三个特性: +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 - left) / 2; + + if (nums[mid]==target) { + return mid; + } else if (nums[mid] >target) { + right = mid - 1; + } else { + left = mid + 1; + } + } +} +``` + +##### 动态规划 + +动态规划是算法里面的大头,也是难点,我理解所有递归的问题都可以用动态规划推到出来解法。 + +它有两种解题方式,**自顶向下**,先由递归思路往下走,然后在加入记忆化缓存,然后反着推出状态转移方程,这种方法初学者前期用的比较多,便于理解,缺点是会比较慢,因为需要先推一次递归。**自下而上**,直接从下而上,根据有限的初始值得到最终想要的结果,这个需要对动态转移方程定义有熟练的掌握才行,需要多做题去掌握和熟练这种方式。以斐波那契数列为例,我们可以由f(0)和f(1)一直向上递推,得到f(n)。 + +##### 位运算 + +计算机底层的计算,直接在二进制上面进行运算,计算速度会比高级语言的计算快,一个有一定代码功底的开发基本都要会的东西。 + +##### 布隆过滤器 + +布隆过滤器算是对哈希表的一种泛化数据结构,当在某些业务场景下,我们只需要知道这个东西在集合里面是否存在,而不需要知道这个东西的具体值时,布隆过滤器就派上用场了。 + +布隆过滤器的实现原理是利用一系列随机哈希函数将元素存在一个很长的二进制位将元素里,一个元素会占几个bit位,但是当可能存在一个bit位被多个元素占用的时候,这个时候就会发生误判。 + +因此布隆过滤器的优缺点也就出来了,**优点**就是空间效率和查询时间都远远高于一般的算法,**缺点**就是会发生误判和删除困难。 + +**布隆过滤器里面元素如果不存在一定是不存在的,但是存在是可能存在的,你还需要进一步验证是否存在。** + +因此布隆过滤器一般被用作最外层的过滤网,删除一些没用的信息,比如垃圾邮件判定等。 + +##### LRU Cache + +是一个使用比较多的缓存替换算法,最近最长使用缓存,它比较符合人脑的思维,如果一个物品,你如果很长时间没有使用的话,算法就会将其舍弃,且一个物品你前段时间没用最近开始使用了,算法会将这个物品放到最前面。它的实现一般基于HashMap+LinkedList(双向链表)实现,java里面有现成的实现->linkedHashMap。 + +##### 字典树 + +字典树在搜索的时候经常会用到,可以用O(1)的复杂度找到查找的词组且补全高频查找结果,一般搜索引擎查询补全用的比较多。 + +字典树也是一种树,只是这个树和我们前面学的BFS和二叉堆不太一样,这个是颗N叉树,再者数据存储也不太一样,正常树的节点是存储完整的数据,而字典树每个节点其实只会存一个字符,当其到达尾部之后(一般条件是isEnd为true),将整个遍历路径连起来会组成一个字符串,这就是查询的结果。 + +字典树的缺点是空间占用会很大,是一种典型的空间换时间的思想。 + +##### 并查集 + +并查集这块东西会比较死,记住模板然后认清使用场景,直接套上去用就行了,基本是定式操作,主要是牢记模板。 + +其使用场景主要是组团和配对问题。判断两个人是否是朋友,判断群组关系等。 + +PS:并查集现在做的题比较少,还没有产生概念,需要加强。 + +##### 排序 + +排序算法有很多,初级的如冒泡排序,选择排序,插入排序等,都是O(n^2)的时间复杂度,还有线性排序有计数排序,桶排序,基数排序等都是时间复杂度O(n)的算法,但是试用场景很局限,都是必须是常数Integer类型的排序,对于字符串这种完全不适用。 + +高级排序主要有快速排序和归并排序,这两个都是用的分治的思想,只是思考方式有点不一样。其时间复杂度是O(nlogn),模板如下: + +快速排序: + +```java +public static 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); + } + + static int partition(int[] a, int begin, int end) {    + // pivot: 标杆位置,counter: 小于pivot的元素的个数    + int pivot = end, counter = begin;    + for (int i = begin; i < end; i++) {        + if (a[i] < a[pivot]) {            + int temp = a[counter]; + a[counter] = a[i]; a[i] = temp;            + counter++;        + }    + }    + int temp = a[pivot]; + a[pivot] = a[counter]; + a[counter] = temp;    + return counter; + } +``` + +归并排序: + +​ + +```java +public static void mergeSort(int[] array, int left, int right) {    + if (right <= left) return;    + int mid = (left + right) >> 1; // (left + right) / 2    + mergeSort(array, left, mid);    + mergeSort(array, mid + 1, right);    + merge(array, left, mid, right); + } + + public static void merge(int[] arr, 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++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];        + }        + + while (i <= mid)   temp[k++] = arr[i++];        + while (j <= right) temp[k++] = arr[j++];  +       + for (int p = 0; p < temp.length; p++) {            + arr[left + p] = temp[p];        + }        + // 也可以用 System.arraycopy(a, start1, b, start2, length)    } +``` + +当然还有利用前面堆这种数据结构的堆排序,时间复杂度是O(nlogn)。每个元素建堆是logn,n个元素就是nlogn。 + +##### 字符串比较算法 + +字符串的基本操作在实际开发中还是比较常用的,但是初级字符串比较大多数会比较简单,而高级的字符串比较算法现在掌握的还不够,题基本没来得及练,至于kmp算法,基本是懵逼的。先用时间消化吧。 + +**以上就是算法训练营所有内容,虽然看起来挺少,但是知识点细分和理解掌握还是会话很多时间的。上面其实有很多知识点当时学的时候由于时间原因没有掌握的那么透彻,需要花时间去掌握的。** + +都毕业了,对于我来说感觉还是任重而道远,这个状态去面试可能还有问题,应该会拿2-3个月时间准备下。整体来看我有的知识点掌握的还行,如基本的数据结构,都知道使用场景和实现了,在了解其实现后在实际开发中确实会用的更加得心应手了。但有的还是不够熟练,需要过遍数,如并查集,位运算,字符串算法KMP算法等,现在基本还是半懵逼状态,需要多做题去消化。比较庆幸的一点是,对于**递归**和**动态规划**还是有自己的理解,其核心思想都大概掌握了,接下来就是多做题强化了。 -- 递归,回溯,分治, -- BFS, DFS,双向BFS,启发式搜索(A*) -- 贪心算法 -- 二分查找 -- 排序 -- 动态规划 -- 位运算 -- 布隆过滤器 -- LRU Cache -- 字典树和并查集 -- 字符串比较算法 +**就像超哥说的那样,“师傅领进门,修行在个人。”他只能帮我们提点一下,后面还是要靠自己。你的努力程度,决定你能走多远。** -其中有的知识点掌握的还行,如基本的数据结构,都知道使用场景和实现了,在了解其实现后在实际开发中确实会用的更加得心应手了。但有的还是不够熟练,需要过遍数,如并查集,位运算,字符串算法KMP算法等,现在基本还是半懵逼状态,需要多做题去消化。比较庆幸的一点是,对于**递归**和**动态规划**还是有自己的理解,其核心思想都大概掌握了,接下来就是多做题强化了。 +算法训练营结束了,但是对我来说**毕业也只是开始,多刷题多学习,争取能拿到自己心仪公司的offer,加油!** 最后感谢算法训练营所有老师和助教以及班班的帮助! \ No newline at end of file