From 0abde2f1da9eddd50aa0f6b085b1c94d3d355d29 Mon Sep 17 00:00:00 2001 From: zhuzhen Date: Fri, 6 Dec 2019 21:12:14 +0800 Subject: [PATCH 1/3] 713-Week 08 --- .../LeetCode_14_LongestCommonPrefix.java | 49 ++++++ Week 08/id_713/LeetCode_14_ReverseString.java | 48 ++++++ .../LeetCode_151_ReverseWordsInAString.java | 133 +++++++++++++++ ...Code_300_LongestIncreasingSubsequence.java | 103 ++++++++++++ ...ode_387_FirstUniqueCharacterInAString.java | 42 +++++ .../LeetCode_8_StringToIntegerAtoi.java | 95 +++++++++++ Week 08/id_713/LeetCode_91_DecodeWays.java | 130 +++++++++++++++ Week 08/id_713/NOTE.md | 157 ++++++++++++++++++ .../LeetCode_58_LengthOfLastWord.java | 46 +++++ .../practise/LeetCode_709_ToLowerCase.java | 47 ++++++ .../LeetCode_771_JewelsAndStones.java | 57 +++++++ 11 files changed, 907 insertions(+) create mode 100644 Week 08/id_713/LeetCode_14_LongestCommonPrefix.java create mode 100644 Week 08/id_713/LeetCode_14_ReverseString.java create mode 100644 Week 08/id_713/LeetCode_151_ReverseWordsInAString.java create mode 100644 Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java create mode 100644 Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java create mode 100644 Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java create mode 100644 Week 08/id_713/LeetCode_91_DecodeWays.java create mode 100644 Week 08/id_713/NOTE.md create mode 100644 Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java create mode 100644 Week 08/id_713/practise/LeetCode_709_ToLowerCase.java create mode 100644 Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java diff --git a/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java b/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java new file mode 100644 index 000000000..72a9c183f --- /dev/null +++ b/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java @@ -0,0 +1,49 @@ +package id_713; + +/** + * 14. 最长公共前缀 + */ +public class LeetCode_14_LongestCommonPrefix { + + + /* + 编写一个函数来查找字符串数组中的最长公共前缀。 + + 如果不存在公共前缀,返回空字符串 ""。 + + 示例 1: + + 输入: ["flower","flow","flight"] + 输出: "fl" + + 示例 2: + + 输入: ["dog","racecar","car"] + 输出: "" + 解释: 输入不存在公共前缀。 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/longest-common-prefix + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + /* + 水平扫描法 + */ + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) return ""; + + for (int i = 0; i < strs[0].length(); i++) { + char c = strs[0].charAt(i); + for (int j = 0; j < strs.length; j++) { + if (i == strs[j].length() || strs[j].charAt(j) != c) { + return strs[0].substring(0, i); + } + } + } + + return strs[0]; + } +} diff --git a/Week 08/id_713/LeetCode_14_ReverseString.java b/Week 08/id_713/LeetCode_14_ReverseString.java new file mode 100644 index 000000000..478760cc8 --- /dev/null +++ b/Week 08/id_713/LeetCode_14_ReverseString.java @@ -0,0 +1,48 @@ +package id_713; + +/** + * 344. 反转字符串 + */ +public class LeetCode_14_ReverseString { + + + /* + 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 + + 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 + + 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 + + + + 示例 1: + + 输入:["h","e","l","l","o"] + 输出:["o","l","l","e","h"] + + 示例 2: + + 输入:["H","a","n","n","a","h"] + 输出:["h","a","n","n","a","H"] + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/reverse-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + public void reverseString(char[] s) { + if (s == null || s.length == 0) return; + + int i = 0, j = s.length - 1; + + while (i < j) { + char tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + i++; + j--; + } + } + +} diff --git a/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java b/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java new file mode 100644 index 000000000..8ba93afb0 --- /dev/null +++ b/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java @@ -0,0 +1,133 @@ +package id_713; + +import java.util.Arrays; +import java.util.Collections; + +/** + * 151. 翻转字符串里的单词 + */ +public class LeetCode_151_ReverseWordsInAString { + + /* + 给定一个字符串,逐个翻转字符串中的每个单词。 + + + + 示例 1: + + 输入: "the sky is blue" + 输出: "blue is sky the" + + 示例 2: + + 输入: " hello world! " + 输出: "world! hello" + 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + + 示例 3: + + 输入: "a good example" + 输出: "example good a" + 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + + + + 说明: + + 无空格字符构成一个单词。 + 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + + + + 进阶: + + 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/reverse-words-in-a-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public String reverseWords(String s) { + if (s == null) return null; + + String[] words = s.trim().split(" +"); + Collections.reverse(Arrays.asList(words)); + return String.join(" ", words); + } + + + public String reverseWords2(String s) { + StringBuilder sb = new StringBuilder(); + int i = s.length() - 1; + + while (i >= 0) { + if (s.charAt(i) == ' ') { + i--; + continue; + } + + int start = s.lastIndexOf(' ', i); + sb.append(" "); + sb.append(s.substring(start + 1, i + 1)); + i = start - 1; + + + } + + if (sb.length() > 0) { + sb.deleteCharAt(0); + } + + return sb.toString(); + } + + + public String reverseWords3(String s) { + if (s == null) return null; + + char[] chars = s.toCharArray(); + int n = chars.length; + + reverse(chars, 0, n - 1); + reverseWord(chars, n); + return cleanSpaces(chars, n); + + } + + private void reverseWord(char[] chars, int n) { + int i = 0, j = 0; + + while (i < n) { + while (i < j || i < n && chars[i] == ' ') i++; + while (j < i || j < n && chars[j] == ' ') j++; + reverse(chars, i, j - 1); + } + } + + private void reverse(char[] chars, int i, int j) { + while (i < j) { + char tmp = chars[i]; + chars[i] = chars[j]; + chars[j] = tmp; + } + } + + + private String cleanSpaces(char[] chars, int n) { + int i = 0, j = 0; + while (j < n) { + while (j < n && chars[j] == ' ') j++; + while (j < n && chars[j] != ' ') chars[i++] = chars[j++]; + while (j < n && chars[j] == ' ') j++; + + if (j < n) { + chars[i++] = ' '; + } + } + + return new String(chars).substring(0, i); + } + +} diff --git a/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java b/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java new file mode 100644 index 000000000..77db783a3 --- /dev/null +++ b/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java @@ -0,0 +1,103 @@ +package id_713; + +/** + * 300. 最长上升子序列 + */ +public class LeetCode_300_LongestIncreasingSubsequence { + + /* + 给定一个无序的整数数组,找到其中最长上升子序列的长度。 + + 示例: + + 输入: [10,9,2,5,3,7,101,18] + 输出: 4 + 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 + + 说明: + + 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 + 你算法的时间复杂度应该为 O(n2) 。 + + 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/longest-increasing-subsequence + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + + /* + + + 参考: https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/ + + 朴素动态规划: + 状态定义: + dp[i]的值表示 nums 前i个数字的最长子序列长度 + 转移方程 + 设 j 属于 [0, i), 考虑每轮计算新的dp[i]时, 遍历[0, i)列表区间, 做以下判断 + 1. 当 nums[i] > nums[j]时, nums[i]可以接在nums[j]之后, 此情况下最长上升子序列长度为 dp[j] + 1 + 2. 当 nums[i] <= nums[j]时, nums[i] 无法接在nums[i]之后, 此情况上升序列不成立, 跳过 + + 上述所有1情况下 计算出的 dp[j] + 1的最大值, 为直到i的上升子序列长度(dp[i]) + 实现方式为 遍历j时, 每轮执行 dp[i] = max(dp[i], dp[j] + 1) + + 转移方程: dp[i] = max(dp[i], dp[j] + 1) for j in [0, i) + 初始状态: dp[i]所有元素置1, 含义是: 每个元素都至少可以单独成为子序列, 长度为1 + 返回值: 返回dp数组中最大值, 即可得到全局最长上升子序列长度 + + 高级动态规划 + 1. 动态规划中, 通过线性遍历来计算dp的复杂度无法降低 + 2. 每轮计算中, 需要通过线性遍历 [0, k)区间元素来得到 dp[k]. 考虑, 是否可以通过重新设计状态定义, 使整个dp为一个排序列表 + 这样在计算每个dp[k]时, 就可以通过二分查找法遍历 [0, k)区间元素, 将此部分复杂度有 O(N) 降低至 O(logN) + + 新状态定义 + 维护一个列表 dp, 其中每个元素 dp[k]的值代表 长度为 k+1的子序列尾部元素值 + 如 [1,4,6]序列, 长度为1,2,3的子序列尾部元素值分别为 dp = [1,4,6] + + 状态转移设计 + 设常量数字N, 和随机数x, 可以推出: 当N越小时, N < x的纪律越大 + 如 N = 0 比 N = 1000更可能满足 N < x + + 在遍历计算每个 tails[k],不断更新长度为 [1,k]的子序列尾部元素值,始终保持每个尾部元素值最小 (例如 [1,5,3]], 遍历到元素 555 时,长度为 222 的子序列尾部元素值为 555;当遍历到元素 333 时,尾部元素值应更新至 333,因为 333 遇到比它大的数字的几率更大)。 + + + + + + [10 9 2 5 3 7 21 18] + + [0 0 0 0 0 0 0 0] + [10 0 0 0 0 0 0 0] + [9 0 0 0 0 0 0 0] + [2 0 0 0 0 0 0 0] + [2 5 0 0 0 0 0 0] + [2 3 0 0 0 0 0 0] + [2 3 7 0 0 0 0 0] + [2 3 7 21 0 0 0 0] + [2 3 7 18 0 0 0 0] + + 数组的长度, 即为最大上升子序列 + */ + + public int lengthOfLIS(int[] nums) { + + int[] tails = new int[nums.length]; + int res = 0; + + for (int num : nums) { + int i = 0, j = res; + while (i < j) { + int m = (i + j) / 2; + if (tails[m] < num) i = m + 1; + else j = m; + } + tails[i] = num; + if (res == j) res++; + } + + return res; + } +} diff --git a/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java b/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java new file mode 100644 index 000000000..b94d64c2b --- /dev/null +++ b/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java @@ -0,0 +1,42 @@ +package id_713; + +import java.util.HashMap; + +/** + * 387. 字符串中的第一个唯一字符 + */ +public class LeetCode_387_FirstUniqueCharacterInAString { + + /* + 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 + + 案例: + + s = "leetcode" + 返回 0. + + s = "loveleetcode", + 返回 2. + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/first-unique-character-in-a-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public int firstUniqChar(String s) { + HashMap map = new HashMap<>(); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + map.put(c, map.getOrDefault(c, 0) + 1); + } + + for (int i = 0; i < s.length(); i++) { + if (map.get(s.charAt(i)) == 1) { + return i; + } + } + + return -1; + } +} diff --git a/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java b/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java new file mode 100644 index 000000000..d1be1331c --- /dev/null +++ b/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java @@ -0,0 +1,95 @@ +package id_713; + +/** + * 8. 字符串转换整数 (atoi) + */ +public class LeetCode_8_StringToIntegerAtoi { + + /* + 请你来实现一个 atoi 函数,使其能将字符串转换成整数。 + + 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。 + + 当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。 + + 该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。 + + 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。 + + 在任何情况下,若函数不能进行有效的转换时,请返回 0。 + + 说明: + + 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。 + + 示例 1: + + 输入: "42" + 输出: 42 + + 示例 2: + + 输入: " -42" + 输出: -42 + 解释: 第一个非空白字符为 '-', 它是一个负号。 + 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。 + + 示例 3: + + 输入: "4193 with words" + 输出: 4193 + 解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。 + + 示例 4: + + 输入: "words and 987" + 输出: 0 + 解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 + 因此无法执行有效的转换。 + + 示例 5: + + 输入: "-91283472332" + 输出: -2147483648 + 解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 + 因此返回 INT_MIN (−231) 。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/string-to-integer-atoi + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public int myAtoi(String str) { + int index = 0, sign = 1, total = 0; + + // 如果是空串 + if(str == null || str.length() == 0) return 0; + + // 去除空格, 如果使用trim的话, 会使用额外的空间, 原因: java的String是不可变的 + while (str.charAt(index) == ' ' && index < str.length()) + index++; + + // 处理符号 + if(str.charAt(index) == '+' || str.charAt(index) == '-'){ + sign = str.charAt(index) == '+' ? 1 : -1; + index++; + } + + + // 转换 + while (index < str.length()) { + int digit = str.charAt(index) - '0'; + if (digit < 0 || digit > 9) break; + + if (Integer.MAX_VALUE / 10 < total || Integer.MAX_VALUE / 10 == total && Integer.MAX_VALUE % 10 < digit) { + return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; + } + + total = 10 * total + digit; + index++; + } + + return total * sign; + } + +} diff --git a/Week 08/id_713/LeetCode_91_DecodeWays.java b/Week 08/id_713/LeetCode_91_DecodeWays.java new file mode 100644 index 000000000..4fc047c54 --- /dev/null +++ b/Week 08/id_713/LeetCode_91_DecodeWays.java @@ -0,0 +1,130 @@ +package id_713; + +/** + * 91. 解码方法 + */ +public class LeetCode_91_DecodeWays { + + /* + 一条包含字母 A-Z 的消息通过以下方式进行了编码: + + 'A' -> 1 + 'B' -> 2 + ... + 'Z' -> 26 + + 给定一个只包含数字的非空字符串,请计算解码方法的总数。 + + 示例 1: + + 输入: "12" + 输出: 2 + 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 + + 示例 2: + + 输入: "226" + 输出: 3 + 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/decode-ways + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + /* + 分析: + + 以22067举例, 从后往前遍历 + + 1. 首先提取如果为7, 只有1种 -> G + 2. 其次为67, 也只有一种 -> FG + 3. 067 -> 0 + 4. 2067 -> (20 67) + (2 067) = (20 67) -> TFG + 5. 22067 -> (2 2067) + (22 067) + (2 2067) -> BTFG + + 得到规律: + 如果开始为0, 结果为0 + 如果开始的数加上第二个数 <= 26. 结果为 (start + 1) + (start + 2) + 如果开始的数加上第二个数 > 26. 结果为 (start + 1) + + 以上是递归的思路, 导致了大量重复的计算 + + 引入一个dp数组, 用来计算以某个字符为开始的解码数量, 动态规划是一个填表的过程 + 递推方程: + 如果 nums[i] = 0, dp[i] = 0 + 如果 nums[i] + nums[i + 1] <= 26 + dp[i] =dp[i + 1] + dp[i + 2] + 否则 + dp[i] = dp[i + 1] + + + 以2206为例, 假设此处 nums[]={2,2,0,6}, 新建一个数组 + int[] dp = new int[len + 1]; + 之所以长度为len+1, 因为这样不需要将最后一个6单独处理, 并将dp[len]=1 + + 0 1 2 3 4 + [0 0 0 0 1] // 将 dp[len]=1, 算上0, 此时长度为 len + 1, 所以索引是 len + [0 0 0 1 1] // 此时处理 dp[3], 按照递推式, 因为 nums[3] = 6,所以 dp[3] = 1 + [0 0 0 1 1] // 此时处理 dp[2], 按照递推式, 因为 nums[2] = 0,所以 dp[2] = 0 + [0 1 0 1 1] // 此时处理 dp[1], 按照递推式, 因为 nums[1]*10 + nums[2] = 20, 所以dp[1] = dp[2] + dp[3] = 1 + [1 1 0 1 1] // 此时处理 dp[0], 按照递推式, 因为 nums[0]*10 + nums[1] = 22, 所以dp[0] = dp[1] + dp[2] = 1 + + */ + + + public int numDecodings(String s) { + if (s == null || s.length() == 0) return 0; + + // 提取s的长度 + int n = s.length(); + + int[] dp = new int[n + 1]; + dp[n] = 1; + dp[n - 1] = (s.charAt(n - 1) == '0' ? 0 : 1); + + for (int i = n - 2; i >= 0; i--) { + if (s.charAt(i) == '0') { + dp[i] = 0; + continue; + } + + if ((s.charAt(i) - '0' * 10 + s.charAt(i + 1) - '0') <= 26) { + dp[i] = dp[i + 1] + dp[i + 2]; + } else { + dp[i] = dp[i + 1]; + } + } + + return dp[0]; + } + + public int numDecodingsPlus(String s) { + if (s == null || s.length() == 0) return 0; + + // 提取s的长度 + int len = s.length(); + + int help = 1; // 相当于 初始化 dp[len + 1[ = 1 的意思 + int res = (s.charAt(len - 1) != '0' ? 1 : 0); // 倒数第2个值, 也就是是 + + for (int i = len - 2; i >= 0; i--) { + if (s.charAt(i) == '0') { + help = res; + res = 0; + continue; + } + + if ((s.charAt(i - '0') * 10 + s.charAt(i + 1 - '0')) <= 26) { + res += help; + help = res - help; // help用于存储res之前的值 + } else { + help = res; + } + } + + return res; + } + +} diff --git a/Week 08/id_713/NOTE.md b/Week 08/id_713/NOTE.md new file mode 100644 index 000000000..6ef0940b0 --- /dev/null +++ b/Week 08/id_713/NOTE.md @@ -0,0 +1,157 @@ +# NOTE + + + + + +# 高级动态规划 + +## 常见DP题目和状态转移方程 + +### 爬楼梯问题 + +* 转换成斐波那契数列 +* f(n) = f(n - 1) + f(n - 2), 其中 f(1) = 1, f(2) = 1 + +```python +# 朴素 +def f(n): + if n <= 1: return 1; + return f(n - 1) + f(n - 2) + +# 带缓存 +def f(n): + if n <= 1: return 1 + if n not in mem: + mem[n] = f(n - 1) + f(n - 2) + return mem[n] + +def f(n): + x, y = 1, 1 + for i in ranges (1, n): + y, x = x + y, y + return y +``` + + + +### 不同路径问题 + +* f(x, y) = f(x - 1, y) + f(x, y - 1) + +```python +def (x, y): + dp[[0] * (m + 1) for _ in range(n + 1)] + dp[1][1] = 1 + for i in range(1, y + 1): + for j in range(1, x + 1): + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + return dp[y][x] +``` + + + +### 打家劫舍 + +* dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + +```python +dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]) +dp[i][1] = dp[i - 1][0] + +``` + + + +### 最小路径和 + +* dp[i] [j]状态定义: minPath(A[1 -> i] [1 -> j]) +* dp[i] [j] = min(dp[i - 1] [j], dp[i] [j - 1]) +A[i] [j] + + + +### 股票买卖 + +```python +dp[i][k][0 or 1] 0 <= i <= n-1, 1 <= k <= K +# i为天数 +# k为最多交易次数 +# [0, 1]是否持有股票 +# 总状态数: n * K * 2种状态 + +for 0 <= i < n: + for i <= k <= K: + for s in [0, 1]: + dp[i][k][s] = max(buy, sell, rest) + +dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) +# 解释: 今天没有持有股票, 有2种可能 +# 1.昨天就没有持有, 然后今天选择rest +# 2.昨天持有了股票, 今天卖掉了 +dp[i][k][1] = max(dp[i-1[k][1]], dp[i-1][k-1][0] - prices[i]) +# 解释: 今天持有股票, 有2种可能 +# 1.昨天持有股票, 今天rest +# 2.昨天没有持有, 今天买入 +``` + + + +--- + +## 复杂度来源 + +* 拥有更多维度 +* dp方程复杂 + + + + + +### 爬楼梯变种 + +* 一次可以上 一级,二级,三级 + +```python +# 上一级, 上二级, 上三级 +# 状态转移方程 +a[i] = a[i - 1] + a[i - 2] + a[i -3] + +a[0] = 1 +a[1] = 2 +a[2] = 3 + +for (int i = 2; i < n; i++) { + a[i] = a[i - 1] + a[i - 2] + a[i - 3] +} + +return a[n - 1] + +# 不再是固定部署, 给定数组 +a[i][k] + +for (int i = 2; i < n; i++) { + for (int j = 0; i < m; j++) { + for (int k = 0; k < m; k++) { + + } + } +} +``` + + + +### 编辑距离 + +```python +# 如果word1[i]与word2[j]相同, 显然dp[i][j] = dp[i - 1][j - 1] + +# 如果word1[i]与word2[j], 不同那么dp[i][j]可以通过 +# 1.在dp[i - 1][j - 1]的基础上做replace操作达到目的 +# 2.在dp[i - 1][j]的基础上做insert操作达到目的 +# 3.在dp[i][j - 1]的基础上做delete操作达到目的 +# 取三者最小情况即可 + +``` + + + diff --git a/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java b/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java new file mode 100644 index 000000000..d6f121579 --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java @@ -0,0 +1,46 @@ +package id_713.practise; + +/** + * 58. 最后一个单词的长度 + */ +public class LeetCode_58_LengthOfLastWord { + /* + 给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。 + + 如果不存在最后一个单词,请返回 0 。 + + 说明:一个单词是指由字母组成,但不包含任何空格的字符串。 + + 示例: + + 输入: "Hello World" + 输出: 5 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/length-of-last-word + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + + */ + + public int lengthOfLastWord(String s) { + if (s == null) return 0; + s = s.trim(); + if (s.length() == 0) return 0; + + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == ' ') + return s.length() - 1 - i; + } + + return s.length(); + } + + public int lengthOfLastWord2(String s) { + if (s == null || s.length() == 0) return 0; + s = s.trim(); + int lastSpace = s.lastIndexOf(" "); + return s.length() - 1 - lastSpace; + } + +} diff --git a/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java b/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java new file mode 100644 index 000000000..18ad20fbd --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java @@ -0,0 +1,47 @@ +package id_713.practise; + +/** + * 709. 转换成小写字母 + */ +public class LeetCode_709_ToLowerCase { + /* + 实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串。 + + 示例 1: + + 输入: "Hello" + 输出: "hello" + + 示例 2: + + 输入: "here" + 输出: "here" + + 示例 3: + + 输入: "LOVELY" + 输出: "lovely" + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/to-lower-case + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public String toLowerCase(String str) { + + char[] chars = new char[str.length()]; + + for (int i = 0; i < str.length(); i++) { + chars[i] = str.charAt(i) >= 65 && str.charAt(i) <= 90 + ? (char) (str.charAt(i) + 32) + : str.charAt(i); + } + + return new String(chars); + } + + public String toLowerCase2(String str) { + return str.toLowerCase(); + } + +} diff --git a/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java b/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java new file mode 100644 index 000000000..df0db4858 --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java @@ -0,0 +1,57 @@ +package id_713.practise; + +import java.util.HashSet; +import java.util.Set; + +/** + * 771. 宝石与石头 + */ +public class LeetCode_771_JewelsAndStones { + /* + 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 + + J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 + + 示例 1: + + 输入: J = "aA", S = "aAAbbbb" + 输出: 3 + + 示例 2: + + 输入: J = "z", S = "ZZ" + 输出: 0 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/jewels-and-stones + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + + */ + + public int numJewelsInStones(String J, String S) { + Set set = new HashSet<>(); + + for (char c : J.toCharArray()) { + set.add(c); + } + + int res = 0; + for (char c : S.toCharArray()) { + if (set.contains(c)) + res++; + } + return res; + } + + public int numJewelsInStones2(String J, String S) { + int res = 0; + for (char s : S.toCharArray()) { + if(J.indexOf(s) != -1) + res++; + } + + return res; + } + +} From 00a9b9bd42218ad61027c9a03f34332efbda7425 Mon Sep 17 00:00:00 2001 From: zhuzhen Date: Mon, 9 Dec 2019 11:42:24 +0800 Subject: [PATCH 2/3] =?UTF-8?q?713-Week=2008=E9=9C=80=E8=A6=81=E4=BB=8E?= =?UTF-8?q?=E6=BA=90=E4=B8=8A=E6=8B=89=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LeetCode_14_LongestCommonPrefix.java | 49 ------ Week 08/id_713/LeetCode_14_ReverseString.java | 48 ------ .../LeetCode_151_ReverseWordsInAString.java | 133 --------------- ...Code_300_LongestIncreasingSubsequence.java | 103 ------------ ...ode_387_FirstUniqueCharacterInAString.java | 42 ----- .../LeetCode_8_StringToIntegerAtoi.java | 95 ----------- Week 08/id_713/LeetCode_91_DecodeWays.java | 130 --------------- Week 08/id_713/NOTE.md | 157 ------------------ .../LeetCode_58_LengthOfLastWord.java | 46 ----- .../practise/LeetCode_709_ToLowerCase.java | 47 ------ .../LeetCode_771_JewelsAndStones.java | 57 ------- 11 files changed, 907 deletions(-) delete mode 100644 Week 08/id_713/LeetCode_14_LongestCommonPrefix.java delete mode 100644 Week 08/id_713/LeetCode_14_ReverseString.java delete mode 100644 Week 08/id_713/LeetCode_151_ReverseWordsInAString.java delete mode 100644 Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java delete mode 100644 Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java delete mode 100644 Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java delete mode 100644 Week 08/id_713/LeetCode_91_DecodeWays.java delete mode 100644 Week 08/id_713/NOTE.md delete mode 100644 Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java delete mode 100644 Week 08/id_713/practise/LeetCode_709_ToLowerCase.java delete mode 100644 Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java diff --git a/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java b/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java deleted file mode 100644 index 72a9c183f..000000000 --- a/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java +++ /dev/null @@ -1,49 +0,0 @@ -package id_713; - -/** - * 14. 最长公共前缀 - */ -public class LeetCode_14_LongestCommonPrefix { - - - /* - 编写一个函数来查找字符串数组中的最长公共前缀。 - - 如果不存在公共前缀,返回空字符串 ""。 - - 示例 1: - - 输入: ["flower","flow","flight"] - 输出: "fl" - - 示例 2: - - 输入: ["dog","racecar","car"] - 输出: "" - 解释: 输入不存在公共前缀。 - - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/longest-common-prefix - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - - /* - 水平扫描法 - */ - public String longestCommonPrefix(String[] strs) { - if (strs == null || strs.length == 0) return ""; - - for (int i = 0; i < strs[0].length(); i++) { - char c = strs[0].charAt(i); - for (int j = 0; j < strs.length; j++) { - if (i == strs[j].length() || strs[j].charAt(j) != c) { - return strs[0].substring(0, i); - } - } - } - - return strs[0]; - } -} diff --git a/Week 08/id_713/LeetCode_14_ReverseString.java b/Week 08/id_713/LeetCode_14_ReverseString.java deleted file mode 100644 index 478760cc8..000000000 --- a/Week 08/id_713/LeetCode_14_ReverseString.java +++ /dev/null @@ -1,48 +0,0 @@ -package id_713; - -/** - * 344. 反转字符串 - */ -public class LeetCode_14_ReverseString { - - - /* - 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 - - 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 - - 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 - - - - 示例 1: - - 输入:["h","e","l","l","o"] - 输出:["o","l","l","e","h"] - - 示例 2: - - 输入:["H","a","n","n","a","h"] - 输出:["h","a","n","n","a","H"] - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/reverse-string - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - - public void reverseString(char[] s) { - if (s == null || s.length == 0) return; - - int i = 0, j = s.length - 1; - - while (i < j) { - char tmp = s[i]; - s[i] = s[j]; - s[j] = tmp; - i++; - j--; - } - } - -} diff --git a/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java b/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java deleted file mode 100644 index 8ba93afb0..000000000 --- a/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java +++ /dev/null @@ -1,133 +0,0 @@ -package id_713; - -import java.util.Arrays; -import java.util.Collections; - -/** - * 151. 翻转字符串里的单词 - */ -public class LeetCode_151_ReverseWordsInAString { - - /* - 给定一个字符串,逐个翻转字符串中的每个单词。 - - - - 示例 1: - - 输入: "the sky is blue" - 输出: "blue is sky the" - - 示例 2: - - 输入: " hello world! " - 输出: "world! hello" - 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 - - 示例 3: - - 输入: "a good example" - 输出: "example good a" - 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 - - - - 说明: - - 无空格字符构成一个单词。 - 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 - 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 - - - - 进阶: - - 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/reverse-words-in-a-string - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - public String reverseWords(String s) { - if (s == null) return null; - - String[] words = s.trim().split(" +"); - Collections.reverse(Arrays.asList(words)); - return String.join(" ", words); - } - - - public String reverseWords2(String s) { - StringBuilder sb = new StringBuilder(); - int i = s.length() - 1; - - while (i >= 0) { - if (s.charAt(i) == ' ') { - i--; - continue; - } - - int start = s.lastIndexOf(' ', i); - sb.append(" "); - sb.append(s.substring(start + 1, i + 1)); - i = start - 1; - - - } - - if (sb.length() > 0) { - sb.deleteCharAt(0); - } - - return sb.toString(); - } - - - public String reverseWords3(String s) { - if (s == null) return null; - - char[] chars = s.toCharArray(); - int n = chars.length; - - reverse(chars, 0, n - 1); - reverseWord(chars, n); - return cleanSpaces(chars, n); - - } - - private void reverseWord(char[] chars, int n) { - int i = 0, j = 0; - - while (i < n) { - while (i < j || i < n && chars[i] == ' ') i++; - while (j < i || j < n && chars[j] == ' ') j++; - reverse(chars, i, j - 1); - } - } - - private void reverse(char[] chars, int i, int j) { - while (i < j) { - char tmp = chars[i]; - chars[i] = chars[j]; - chars[j] = tmp; - } - } - - - private String cleanSpaces(char[] chars, int n) { - int i = 0, j = 0; - while (j < n) { - while (j < n && chars[j] == ' ') j++; - while (j < n && chars[j] != ' ') chars[i++] = chars[j++]; - while (j < n && chars[j] == ' ') j++; - - if (j < n) { - chars[i++] = ' '; - } - } - - return new String(chars).substring(0, i); - } - -} diff --git a/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java b/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java deleted file mode 100644 index 77db783a3..000000000 --- a/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java +++ /dev/null @@ -1,103 +0,0 @@ -package id_713; - -/** - * 300. 最长上升子序列 - */ -public class LeetCode_300_LongestIncreasingSubsequence { - - /* - 给定一个无序的整数数组,找到其中最长上升子序列的长度。 - - 示例: - - 输入: [10,9,2,5,3,7,101,18] - 输出: 4 - 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 - - 说明: - - 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 - 你算法的时间复杂度应该为 O(n2) 。 - - 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/longest-increasing-subsequence - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - - - /* - - - 参考: https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/ - - 朴素动态规划: - 状态定义: - dp[i]的值表示 nums 前i个数字的最长子序列长度 - 转移方程 - 设 j 属于 [0, i), 考虑每轮计算新的dp[i]时, 遍历[0, i)列表区间, 做以下判断 - 1. 当 nums[i] > nums[j]时, nums[i]可以接在nums[j]之后, 此情况下最长上升子序列长度为 dp[j] + 1 - 2. 当 nums[i] <= nums[j]时, nums[i] 无法接在nums[i]之后, 此情况上升序列不成立, 跳过 - - 上述所有1情况下 计算出的 dp[j] + 1的最大值, 为直到i的上升子序列长度(dp[i]) - 实现方式为 遍历j时, 每轮执行 dp[i] = max(dp[i], dp[j] + 1) - - 转移方程: dp[i] = max(dp[i], dp[j] + 1) for j in [0, i) - 初始状态: dp[i]所有元素置1, 含义是: 每个元素都至少可以单独成为子序列, 长度为1 - 返回值: 返回dp数组中最大值, 即可得到全局最长上升子序列长度 - - 高级动态规划 - 1. 动态规划中, 通过线性遍历来计算dp的复杂度无法降低 - 2. 每轮计算中, 需要通过线性遍历 [0, k)区间元素来得到 dp[k]. 考虑, 是否可以通过重新设计状态定义, 使整个dp为一个排序列表 - 这样在计算每个dp[k]时, 就可以通过二分查找法遍历 [0, k)区间元素, 将此部分复杂度有 O(N) 降低至 O(logN) - - 新状态定义 - 维护一个列表 dp, 其中每个元素 dp[k]的值代表 长度为 k+1的子序列尾部元素值 - 如 [1,4,6]序列, 长度为1,2,3的子序列尾部元素值分别为 dp = [1,4,6] - - 状态转移设计 - 设常量数字N, 和随机数x, 可以推出: 当N越小时, N < x的纪律越大 - 如 N = 0 比 N = 1000更可能满足 N < x - - 在遍历计算每个 tails[k],不断更新长度为 [1,k]的子序列尾部元素值,始终保持每个尾部元素值最小 (例如 [1,5,3]], 遍历到元素 555 时,长度为 222 的子序列尾部元素值为 555;当遍历到元素 333 时,尾部元素值应更新至 333,因为 333 遇到比它大的数字的几率更大)。 - - - - - - [10 9 2 5 3 7 21 18] - - [0 0 0 0 0 0 0 0] - [10 0 0 0 0 0 0 0] - [9 0 0 0 0 0 0 0] - [2 0 0 0 0 0 0 0] - [2 5 0 0 0 0 0 0] - [2 3 0 0 0 0 0 0] - [2 3 7 0 0 0 0 0] - [2 3 7 21 0 0 0 0] - [2 3 7 18 0 0 0 0] - - 数组的长度, 即为最大上升子序列 - */ - - public int lengthOfLIS(int[] nums) { - - int[] tails = new int[nums.length]; - int res = 0; - - for (int num : nums) { - int i = 0, j = res; - while (i < j) { - int m = (i + j) / 2; - if (tails[m] < num) i = m + 1; - else j = m; - } - tails[i] = num; - if (res == j) res++; - } - - return res; - } -} diff --git a/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java b/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java deleted file mode 100644 index b94d64c2b..000000000 --- a/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java +++ /dev/null @@ -1,42 +0,0 @@ -package id_713; - -import java.util.HashMap; - -/** - * 387. 字符串中的第一个唯一字符 - */ -public class LeetCode_387_FirstUniqueCharacterInAString { - - /* - 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 - - 案例: - - s = "leetcode" - 返回 0. - - s = "loveleetcode", - 返回 2. - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/first-unique-character-in-a-string - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - public int firstUniqChar(String s) { - HashMap map = new HashMap<>(); - - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - map.put(c, map.getOrDefault(c, 0) + 1); - } - - for (int i = 0; i < s.length(); i++) { - if (map.get(s.charAt(i)) == 1) { - return i; - } - } - - return -1; - } -} diff --git a/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java b/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java deleted file mode 100644 index d1be1331c..000000000 --- a/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java +++ /dev/null @@ -1,95 +0,0 @@ -package id_713; - -/** - * 8. 字符串转换整数 (atoi) - */ -public class LeetCode_8_StringToIntegerAtoi { - - /* - 请你来实现一个 atoi 函数,使其能将字符串转换成整数。 - - 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。 - - 当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。 - - 该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。 - - 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。 - - 在任何情况下,若函数不能进行有效的转换时,请返回 0。 - - 说明: - - 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。 - - 示例 1: - - 输入: "42" - 输出: 42 - - 示例 2: - - 输入: " -42" - 输出: -42 - 解释: 第一个非空白字符为 '-', 它是一个负号。 - 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。 - - 示例 3: - - 输入: "4193 with words" - 输出: 4193 - 解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。 - - 示例 4: - - 输入: "words and 987" - 输出: 0 - 解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 - 因此无法执行有效的转换。 - - 示例 5: - - 输入: "-91283472332" - 输出: -2147483648 - 解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 - 因此返回 INT_MIN (−231) 。 - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/string-to-integer-atoi - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - public int myAtoi(String str) { - int index = 0, sign = 1, total = 0; - - // 如果是空串 - if(str == null || str.length() == 0) return 0; - - // 去除空格, 如果使用trim的话, 会使用额外的空间, 原因: java的String是不可变的 - while (str.charAt(index) == ' ' && index < str.length()) - index++; - - // 处理符号 - if(str.charAt(index) == '+' || str.charAt(index) == '-'){ - sign = str.charAt(index) == '+' ? 1 : -1; - index++; - } - - - // 转换 - while (index < str.length()) { - int digit = str.charAt(index) - '0'; - if (digit < 0 || digit > 9) break; - - if (Integer.MAX_VALUE / 10 < total || Integer.MAX_VALUE / 10 == total && Integer.MAX_VALUE % 10 < digit) { - return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; - } - - total = 10 * total + digit; - index++; - } - - return total * sign; - } - -} diff --git a/Week 08/id_713/LeetCode_91_DecodeWays.java b/Week 08/id_713/LeetCode_91_DecodeWays.java deleted file mode 100644 index 4fc047c54..000000000 --- a/Week 08/id_713/LeetCode_91_DecodeWays.java +++ /dev/null @@ -1,130 +0,0 @@ -package id_713; - -/** - * 91. 解码方法 - */ -public class LeetCode_91_DecodeWays { - - /* - 一条包含字母 A-Z 的消息通过以下方式进行了编码: - - 'A' -> 1 - 'B' -> 2 - ... - 'Z' -> 26 - - 给定一个只包含数字的非空字符串,请计算解码方法的总数。 - - 示例 1: - - 输入: "12" - 输出: 2 - 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 - - 示例 2: - - 输入: "226" - 输出: 3 - 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/decode-ways - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - - /* - 分析: - - 以22067举例, 从后往前遍历 - - 1. 首先提取如果为7, 只有1种 -> G - 2. 其次为67, 也只有一种 -> FG - 3. 067 -> 0 - 4. 2067 -> (20 67) + (2 067) = (20 67) -> TFG - 5. 22067 -> (2 2067) + (22 067) + (2 2067) -> BTFG - - 得到规律: - 如果开始为0, 结果为0 - 如果开始的数加上第二个数 <= 26. 结果为 (start + 1) + (start + 2) - 如果开始的数加上第二个数 > 26. 结果为 (start + 1) - - 以上是递归的思路, 导致了大量重复的计算 - - 引入一个dp数组, 用来计算以某个字符为开始的解码数量, 动态规划是一个填表的过程 - 递推方程: - 如果 nums[i] = 0, dp[i] = 0 - 如果 nums[i] + nums[i + 1] <= 26 - dp[i] =dp[i + 1] + dp[i + 2] - 否则 - dp[i] = dp[i + 1] - - - 以2206为例, 假设此处 nums[]={2,2,0,6}, 新建一个数组 - int[] dp = new int[len + 1]; - 之所以长度为len+1, 因为这样不需要将最后一个6单独处理, 并将dp[len]=1 - - 0 1 2 3 4 - [0 0 0 0 1] // 将 dp[len]=1, 算上0, 此时长度为 len + 1, 所以索引是 len - [0 0 0 1 1] // 此时处理 dp[3], 按照递推式, 因为 nums[3] = 6,所以 dp[3] = 1 - [0 0 0 1 1] // 此时处理 dp[2], 按照递推式, 因为 nums[2] = 0,所以 dp[2] = 0 - [0 1 0 1 1] // 此时处理 dp[1], 按照递推式, 因为 nums[1]*10 + nums[2] = 20, 所以dp[1] = dp[2] + dp[3] = 1 - [1 1 0 1 1] // 此时处理 dp[0], 按照递推式, 因为 nums[0]*10 + nums[1] = 22, 所以dp[0] = dp[1] + dp[2] = 1 - - */ - - - public int numDecodings(String s) { - if (s == null || s.length() == 0) return 0; - - // 提取s的长度 - int n = s.length(); - - int[] dp = new int[n + 1]; - dp[n] = 1; - dp[n - 1] = (s.charAt(n - 1) == '0' ? 0 : 1); - - for (int i = n - 2; i >= 0; i--) { - if (s.charAt(i) == '0') { - dp[i] = 0; - continue; - } - - if ((s.charAt(i) - '0' * 10 + s.charAt(i + 1) - '0') <= 26) { - dp[i] = dp[i + 1] + dp[i + 2]; - } else { - dp[i] = dp[i + 1]; - } - } - - return dp[0]; - } - - public int numDecodingsPlus(String s) { - if (s == null || s.length() == 0) return 0; - - // 提取s的长度 - int len = s.length(); - - int help = 1; // 相当于 初始化 dp[len + 1[ = 1 的意思 - int res = (s.charAt(len - 1) != '0' ? 1 : 0); // 倒数第2个值, 也就是是 - - for (int i = len - 2; i >= 0; i--) { - if (s.charAt(i) == '0') { - help = res; - res = 0; - continue; - } - - if ((s.charAt(i - '0') * 10 + s.charAt(i + 1 - '0')) <= 26) { - res += help; - help = res - help; // help用于存储res之前的值 - } else { - help = res; - } - } - - return res; - } - -} diff --git a/Week 08/id_713/NOTE.md b/Week 08/id_713/NOTE.md deleted file mode 100644 index 6ef0940b0..000000000 --- a/Week 08/id_713/NOTE.md +++ /dev/null @@ -1,157 +0,0 @@ -# NOTE - - - - - -# 高级动态规划 - -## 常见DP题目和状态转移方程 - -### 爬楼梯问题 - -* 转换成斐波那契数列 -* f(n) = f(n - 1) + f(n - 2), 其中 f(1) = 1, f(2) = 1 - -```python -# 朴素 -def f(n): - if n <= 1: return 1; - return f(n - 1) + f(n - 2) - -# 带缓存 -def f(n): - if n <= 1: return 1 - if n not in mem: - mem[n] = f(n - 1) + f(n - 2) - return mem[n] - -def f(n): - x, y = 1, 1 - for i in ranges (1, n): - y, x = x + y, y - return y -``` - - - -### 不同路径问题 - -* f(x, y) = f(x - 1, y) + f(x, y - 1) - -```python -def (x, y): - dp[[0] * (m + 1) for _ in range(n + 1)] - dp[1][1] = 1 - for i in range(1, y + 1): - for j in range(1, x + 1): - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - return dp[y][x] -``` - - - -### 打家劫舍 - -* dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) - -```python -dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]) -dp[i][1] = dp[i - 1][0] - -``` - - - -### 最小路径和 - -* dp[i] [j]状态定义: minPath(A[1 -> i] [1 -> j]) -* dp[i] [j] = min(dp[i - 1] [j], dp[i] [j - 1]) +A[i] [j] - - - -### 股票买卖 - -```python -dp[i][k][0 or 1] 0 <= i <= n-1, 1 <= k <= K -# i为天数 -# k为最多交易次数 -# [0, 1]是否持有股票 -# 总状态数: n * K * 2种状态 - -for 0 <= i < n: - for i <= k <= K: - for s in [0, 1]: - dp[i][k][s] = max(buy, sell, rest) - -dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) -# 解释: 今天没有持有股票, 有2种可能 -# 1.昨天就没有持有, 然后今天选择rest -# 2.昨天持有了股票, 今天卖掉了 -dp[i][k][1] = max(dp[i-1[k][1]], dp[i-1][k-1][0] - prices[i]) -# 解释: 今天持有股票, 有2种可能 -# 1.昨天持有股票, 今天rest -# 2.昨天没有持有, 今天买入 -``` - - - ---- - -## 复杂度来源 - -* 拥有更多维度 -* dp方程复杂 - - - - - -### 爬楼梯变种 - -* 一次可以上 一级,二级,三级 - -```python -# 上一级, 上二级, 上三级 -# 状态转移方程 -a[i] = a[i - 1] + a[i - 2] + a[i -3] - -a[0] = 1 -a[1] = 2 -a[2] = 3 - -for (int i = 2; i < n; i++) { - a[i] = a[i - 1] + a[i - 2] + a[i - 3] -} - -return a[n - 1] - -# 不再是固定部署, 给定数组 -a[i][k] - -for (int i = 2; i < n; i++) { - for (int j = 0; i < m; j++) { - for (int k = 0; k < m; k++) { - - } - } -} -``` - - - -### 编辑距离 - -```python -# 如果word1[i]与word2[j]相同, 显然dp[i][j] = dp[i - 1][j - 1] - -# 如果word1[i]与word2[j], 不同那么dp[i][j]可以通过 -# 1.在dp[i - 1][j - 1]的基础上做replace操作达到目的 -# 2.在dp[i - 1][j]的基础上做insert操作达到目的 -# 3.在dp[i][j - 1]的基础上做delete操作达到目的 -# 取三者最小情况即可 - -``` - - - diff --git a/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java b/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java deleted file mode 100644 index d6f121579..000000000 --- a/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java +++ /dev/null @@ -1,46 +0,0 @@ -package id_713.practise; - -/** - * 58. 最后一个单词的长度 - */ -public class LeetCode_58_LengthOfLastWord { - /* - 给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。 - - 如果不存在最后一个单词,请返回 0 。 - - 说明:一个单词是指由字母组成,但不包含任何空格的字符串。 - - 示例: - - 输入: "Hello World" - 输出: 5 - - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/length-of-last-word - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - - */ - - public int lengthOfLastWord(String s) { - if (s == null) return 0; - s = s.trim(); - if (s.length() == 0) return 0; - - for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) == ' ') - return s.length() - 1 - i; - } - - return s.length(); - } - - public int lengthOfLastWord2(String s) { - if (s == null || s.length() == 0) return 0; - s = s.trim(); - int lastSpace = s.lastIndexOf(" "); - return s.length() - 1 - lastSpace; - } - -} diff --git a/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java b/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java deleted file mode 100644 index 18ad20fbd..000000000 --- a/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java +++ /dev/null @@ -1,47 +0,0 @@ -package id_713.practise; - -/** - * 709. 转换成小写字母 - */ -public class LeetCode_709_ToLowerCase { - /* - 实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串。 - - 示例 1: - - 输入: "Hello" - 输出: "hello" - - 示例 2: - - 输入: "here" - 输出: "here" - - 示例 3: - - 输入: "LOVELY" - 输出: "lovely" - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/to-lower-case - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - */ - - public String toLowerCase(String str) { - - char[] chars = new char[str.length()]; - - for (int i = 0; i < str.length(); i++) { - chars[i] = str.charAt(i) >= 65 && str.charAt(i) <= 90 - ? (char) (str.charAt(i) + 32) - : str.charAt(i); - } - - return new String(chars); - } - - public String toLowerCase2(String str) { - return str.toLowerCase(); - } - -} diff --git a/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java b/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java deleted file mode 100644 index df0db4858..000000000 --- a/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java +++ /dev/null @@ -1,57 +0,0 @@ -package id_713.practise; - -import java.util.HashSet; -import java.util.Set; - -/** - * 771. 宝石与石头 - */ -public class LeetCode_771_JewelsAndStones { - /* - 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 - - J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 - - 示例 1: - - 输入: J = "aA", S = "aAAbbbb" - 输出: 3 - - 示例 2: - - 输入: J = "z", S = "ZZ" - 输出: 0 - - - 来源:力扣(LeetCode) - 链接:https://leetcode-cn.com/problems/jewels-and-stones - 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 - - */ - - public int numJewelsInStones(String J, String S) { - Set set = new HashSet<>(); - - for (char c : J.toCharArray()) { - set.add(c); - } - - int res = 0; - for (char c : S.toCharArray()) { - if (set.contains(c)) - res++; - } - return res; - } - - public int numJewelsInStones2(String J, String S) { - int res = 0; - for (char s : S.toCharArray()) { - if(J.indexOf(s) != -1) - res++; - } - - return res; - } - -} From 06cf5306c2cd317c74f6e970443205fdeb29ac45 Mon Sep 17 00:00:00 2001 From: zhuzhen Date: Mon, 9 Dec 2019 11:53:36 +0800 Subject: [PATCH 3/3] 713-Week 08 --- .../LeetCode_14_LongestCommonPrefix.java | 49 ++++++ Week 08/id_713/LeetCode_14_ReverseString.java | 48 ++++++ .../LeetCode_151_ReverseWordsInAString.java | 133 ++++++++++++++++ ...Code_300_LongestIncreasingSubsequence.java | 103 +++++++++++++ ...ode_387_FirstUniqueCharacterInAString.java | 42 +++++ .../LeetCode_8_StringToIntegerAtoi.java | 95 ++++++++++++ Week 08/id_713/LeetCode_91_DecodeWays.java | 130 ++++++++++++++++ Week 08/id_713/NOTE.md | 144 +++++++++++++++++- .../LeetCode_58_LengthOfLastWord.java | 46 ++++++ .../practise/LeetCode_709_ToLowerCase.java | 47 ++++++ .../LeetCode_771_JewelsAndStones.java | 57 +++++++ 11 files changed, 893 insertions(+), 1 deletion(-) create mode 100644 Week 08/id_713/LeetCode_14_LongestCommonPrefix.java create mode 100644 Week 08/id_713/LeetCode_14_ReverseString.java create mode 100644 Week 08/id_713/LeetCode_151_ReverseWordsInAString.java create mode 100644 Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java create mode 100644 Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java create mode 100644 Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java create mode 100644 Week 08/id_713/LeetCode_91_DecodeWays.java mode change 100755 => 100644 Week 08/id_713/NOTE.md create mode 100644 Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java create mode 100644 Week 08/id_713/practise/LeetCode_709_ToLowerCase.java create mode 100644 Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java diff --git a/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java b/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java new file mode 100644 index 000000000..72a9c183f --- /dev/null +++ b/Week 08/id_713/LeetCode_14_LongestCommonPrefix.java @@ -0,0 +1,49 @@ +package id_713; + +/** + * 14. 最长公共前缀 + */ +public class LeetCode_14_LongestCommonPrefix { + + + /* + 编写一个函数来查找字符串数组中的最长公共前缀。 + + 如果不存在公共前缀,返回空字符串 ""。 + + 示例 1: + + 输入: ["flower","flow","flight"] + 输出: "fl" + + 示例 2: + + 输入: ["dog","racecar","car"] + 输出: "" + 解释: 输入不存在公共前缀。 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/longest-common-prefix + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + /* + 水平扫描法 + */ + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) return ""; + + for (int i = 0; i < strs[0].length(); i++) { + char c = strs[0].charAt(i); + for (int j = 0; j < strs.length; j++) { + if (i == strs[j].length() || strs[j].charAt(j) != c) { + return strs[0].substring(0, i); + } + } + } + + return strs[0]; + } +} diff --git a/Week 08/id_713/LeetCode_14_ReverseString.java b/Week 08/id_713/LeetCode_14_ReverseString.java new file mode 100644 index 000000000..478760cc8 --- /dev/null +++ b/Week 08/id_713/LeetCode_14_ReverseString.java @@ -0,0 +1,48 @@ +package id_713; + +/** + * 344. 反转字符串 + */ +public class LeetCode_14_ReverseString { + + + /* + 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 + + 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 + + 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 + + + + 示例 1: + + 输入:["h","e","l","l","o"] + 输出:["o","l","l","e","h"] + + 示例 2: + + 输入:["H","a","n","n","a","h"] + 输出:["h","a","n","n","a","H"] + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/reverse-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + public void reverseString(char[] s) { + if (s == null || s.length == 0) return; + + int i = 0, j = s.length - 1; + + while (i < j) { + char tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + i++; + j--; + } + } + +} diff --git a/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java b/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java new file mode 100644 index 000000000..8ba93afb0 --- /dev/null +++ b/Week 08/id_713/LeetCode_151_ReverseWordsInAString.java @@ -0,0 +1,133 @@ +package id_713; + +import java.util.Arrays; +import java.util.Collections; + +/** + * 151. 翻转字符串里的单词 + */ +public class LeetCode_151_ReverseWordsInAString { + + /* + 给定一个字符串,逐个翻转字符串中的每个单词。 + + + + 示例 1: + + 输入: "the sky is blue" + 输出: "blue is sky the" + + 示例 2: + + 输入: " hello world! " + 输出: "world! hello" + 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + + 示例 3: + + 输入: "a good example" + 输出: "example good a" + 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + + + + 说明: + + 无空格字符构成一个单词。 + 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + + + + 进阶: + + 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/reverse-words-in-a-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public String reverseWords(String s) { + if (s == null) return null; + + String[] words = s.trim().split(" +"); + Collections.reverse(Arrays.asList(words)); + return String.join(" ", words); + } + + + public String reverseWords2(String s) { + StringBuilder sb = new StringBuilder(); + int i = s.length() - 1; + + while (i >= 0) { + if (s.charAt(i) == ' ') { + i--; + continue; + } + + int start = s.lastIndexOf(' ', i); + sb.append(" "); + sb.append(s.substring(start + 1, i + 1)); + i = start - 1; + + + } + + if (sb.length() > 0) { + sb.deleteCharAt(0); + } + + return sb.toString(); + } + + + public String reverseWords3(String s) { + if (s == null) return null; + + char[] chars = s.toCharArray(); + int n = chars.length; + + reverse(chars, 0, n - 1); + reverseWord(chars, n); + return cleanSpaces(chars, n); + + } + + private void reverseWord(char[] chars, int n) { + int i = 0, j = 0; + + while (i < n) { + while (i < j || i < n && chars[i] == ' ') i++; + while (j < i || j < n && chars[j] == ' ') j++; + reverse(chars, i, j - 1); + } + } + + private void reverse(char[] chars, int i, int j) { + while (i < j) { + char tmp = chars[i]; + chars[i] = chars[j]; + chars[j] = tmp; + } + } + + + private String cleanSpaces(char[] chars, int n) { + int i = 0, j = 0; + while (j < n) { + while (j < n && chars[j] == ' ') j++; + while (j < n && chars[j] != ' ') chars[i++] = chars[j++]; + while (j < n && chars[j] == ' ') j++; + + if (j < n) { + chars[i++] = ' '; + } + } + + return new String(chars).substring(0, i); + } + +} diff --git a/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java b/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java new file mode 100644 index 000000000..77db783a3 --- /dev/null +++ b/Week 08/id_713/LeetCode_300_LongestIncreasingSubsequence.java @@ -0,0 +1,103 @@ +package id_713; + +/** + * 300. 最长上升子序列 + */ +public class LeetCode_300_LongestIncreasingSubsequence { + + /* + 给定一个无序的整数数组,找到其中最长上升子序列的长度。 + + 示例: + + 输入: [10,9,2,5,3,7,101,18] + 输出: 4 + 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 + + 说明: + + 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 + 你算法的时间复杂度应该为 O(n2) 。 + + 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/longest-increasing-subsequence + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + + /* + + + 参考: https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/ + + 朴素动态规划: + 状态定义: + dp[i]的值表示 nums 前i个数字的最长子序列长度 + 转移方程 + 设 j 属于 [0, i), 考虑每轮计算新的dp[i]时, 遍历[0, i)列表区间, 做以下判断 + 1. 当 nums[i] > nums[j]时, nums[i]可以接在nums[j]之后, 此情况下最长上升子序列长度为 dp[j] + 1 + 2. 当 nums[i] <= nums[j]时, nums[i] 无法接在nums[i]之后, 此情况上升序列不成立, 跳过 + + 上述所有1情况下 计算出的 dp[j] + 1的最大值, 为直到i的上升子序列长度(dp[i]) + 实现方式为 遍历j时, 每轮执行 dp[i] = max(dp[i], dp[j] + 1) + + 转移方程: dp[i] = max(dp[i], dp[j] + 1) for j in [0, i) + 初始状态: dp[i]所有元素置1, 含义是: 每个元素都至少可以单独成为子序列, 长度为1 + 返回值: 返回dp数组中最大值, 即可得到全局最长上升子序列长度 + + 高级动态规划 + 1. 动态规划中, 通过线性遍历来计算dp的复杂度无法降低 + 2. 每轮计算中, 需要通过线性遍历 [0, k)区间元素来得到 dp[k]. 考虑, 是否可以通过重新设计状态定义, 使整个dp为一个排序列表 + 这样在计算每个dp[k]时, 就可以通过二分查找法遍历 [0, k)区间元素, 将此部分复杂度有 O(N) 降低至 O(logN) + + 新状态定义 + 维护一个列表 dp, 其中每个元素 dp[k]的值代表 长度为 k+1的子序列尾部元素值 + 如 [1,4,6]序列, 长度为1,2,3的子序列尾部元素值分别为 dp = [1,4,6] + + 状态转移设计 + 设常量数字N, 和随机数x, 可以推出: 当N越小时, N < x的纪律越大 + 如 N = 0 比 N = 1000更可能满足 N < x + + 在遍历计算每个 tails[k],不断更新长度为 [1,k]的子序列尾部元素值,始终保持每个尾部元素值最小 (例如 [1,5,3]], 遍历到元素 555 时,长度为 222 的子序列尾部元素值为 555;当遍历到元素 333 时,尾部元素值应更新至 333,因为 333 遇到比它大的数字的几率更大)。 + + + + + + [10 9 2 5 3 7 21 18] + + [0 0 0 0 0 0 0 0] + [10 0 0 0 0 0 0 0] + [9 0 0 0 0 0 0 0] + [2 0 0 0 0 0 0 0] + [2 5 0 0 0 0 0 0] + [2 3 0 0 0 0 0 0] + [2 3 7 0 0 0 0 0] + [2 3 7 21 0 0 0 0] + [2 3 7 18 0 0 0 0] + + 数组的长度, 即为最大上升子序列 + */ + + public int lengthOfLIS(int[] nums) { + + int[] tails = new int[nums.length]; + int res = 0; + + for (int num : nums) { + int i = 0, j = res; + while (i < j) { + int m = (i + j) / 2; + if (tails[m] < num) i = m + 1; + else j = m; + } + tails[i] = num; + if (res == j) res++; + } + + return res; + } +} diff --git a/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java b/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java new file mode 100644 index 000000000..b94d64c2b --- /dev/null +++ b/Week 08/id_713/LeetCode_387_FirstUniqueCharacterInAString.java @@ -0,0 +1,42 @@ +package id_713; + +import java.util.HashMap; + +/** + * 387. 字符串中的第一个唯一字符 + */ +public class LeetCode_387_FirstUniqueCharacterInAString { + + /* + 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 + + 案例: + + s = "leetcode" + 返回 0. + + s = "loveleetcode", + 返回 2. + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/first-unique-character-in-a-string + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public int firstUniqChar(String s) { + HashMap map = new HashMap<>(); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + map.put(c, map.getOrDefault(c, 0) + 1); + } + + for (int i = 0; i < s.length(); i++) { + if (map.get(s.charAt(i)) == 1) { + return i; + } + } + + return -1; + } +} diff --git a/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java b/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java new file mode 100644 index 000000000..d1be1331c --- /dev/null +++ b/Week 08/id_713/LeetCode_8_StringToIntegerAtoi.java @@ -0,0 +1,95 @@ +package id_713; + +/** + * 8. 字符串转换整数 (atoi) + */ +public class LeetCode_8_StringToIntegerAtoi { + + /* + 请你来实现一个 atoi 函数,使其能将字符串转换成整数。 + + 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。 + + 当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。 + + 该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。 + + 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。 + + 在任何情况下,若函数不能进行有效的转换时,请返回 0。 + + 说明: + + 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。 + + 示例 1: + + 输入: "42" + 输出: 42 + + 示例 2: + + 输入: " -42" + 输出: -42 + 解释: 第一个非空白字符为 '-', 它是一个负号。 + 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。 + + 示例 3: + + 输入: "4193 with words" + 输出: 4193 + 解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。 + + 示例 4: + + 输入: "words and 987" + 输出: 0 + 解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 + 因此无法执行有效的转换。 + + 示例 5: + + 输入: "-91283472332" + 输出: -2147483648 + 解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 + 因此返回 INT_MIN (−231) 。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/string-to-integer-atoi + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public int myAtoi(String str) { + int index = 0, sign = 1, total = 0; + + // 如果是空串 + if(str == null || str.length() == 0) return 0; + + // 去除空格, 如果使用trim的话, 会使用额外的空间, 原因: java的String是不可变的 + while (str.charAt(index) == ' ' && index < str.length()) + index++; + + // 处理符号 + if(str.charAt(index) == '+' || str.charAt(index) == '-'){ + sign = str.charAt(index) == '+' ? 1 : -1; + index++; + } + + + // 转换 + while (index < str.length()) { + int digit = str.charAt(index) - '0'; + if (digit < 0 || digit > 9) break; + + if (Integer.MAX_VALUE / 10 < total || Integer.MAX_VALUE / 10 == total && Integer.MAX_VALUE % 10 < digit) { + return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; + } + + total = 10 * total + digit; + index++; + } + + return total * sign; + } + +} diff --git a/Week 08/id_713/LeetCode_91_DecodeWays.java b/Week 08/id_713/LeetCode_91_DecodeWays.java new file mode 100644 index 000000000..4fc047c54 --- /dev/null +++ b/Week 08/id_713/LeetCode_91_DecodeWays.java @@ -0,0 +1,130 @@ +package id_713; + +/** + * 91. 解码方法 + */ +public class LeetCode_91_DecodeWays { + + /* + 一条包含字母 A-Z 的消息通过以下方式进行了编码: + + 'A' -> 1 + 'B' -> 2 + ... + 'Z' -> 26 + + 给定一个只包含数字的非空字符串,请计算解码方法的总数。 + + 示例 1: + + 输入: "12" + 输出: 2 + 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 + + 示例 2: + + 输入: "226" + 输出: 3 + 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/decode-ways + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + + /* + 分析: + + 以22067举例, 从后往前遍历 + + 1. 首先提取如果为7, 只有1种 -> G + 2. 其次为67, 也只有一种 -> FG + 3. 067 -> 0 + 4. 2067 -> (20 67) + (2 067) = (20 67) -> TFG + 5. 22067 -> (2 2067) + (22 067) + (2 2067) -> BTFG + + 得到规律: + 如果开始为0, 结果为0 + 如果开始的数加上第二个数 <= 26. 结果为 (start + 1) + (start + 2) + 如果开始的数加上第二个数 > 26. 结果为 (start + 1) + + 以上是递归的思路, 导致了大量重复的计算 + + 引入一个dp数组, 用来计算以某个字符为开始的解码数量, 动态规划是一个填表的过程 + 递推方程: + 如果 nums[i] = 0, dp[i] = 0 + 如果 nums[i] + nums[i + 1] <= 26 + dp[i] =dp[i + 1] + dp[i + 2] + 否则 + dp[i] = dp[i + 1] + + + 以2206为例, 假设此处 nums[]={2,2,0,6}, 新建一个数组 + int[] dp = new int[len + 1]; + 之所以长度为len+1, 因为这样不需要将最后一个6单独处理, 并将dp[len]=1 + + 0 1 2 3 4 + [0 0 0 0 1] // 将 dp[len]=1, 算上0, 此时长度为 len + 1, 所以索引是 len + [0 0 0 1 1] // 此时处理 dp[3], 按照递推式, 因为 nums[3] = 6,所以 dp[3] = 1 + [0 0 0 1 1] // 此时处理 dp[2], 按照递推式, 因为 nums[2] = 0,所以 dp[2] = 0 + [0 1 0 1 1] // 此时处理 dp[1], 按照递推式, 因为 nums[1]*10 + nums[2] = 20, 所以dp[1] = dp[2] + dp[3] = 1 + [1 1 0 1 1] // 此时处理 dp[0], 按照递推式, 因为 nums[0]*10 + nums[1] = 22, 所以dp[0] = dp[1] + dp[2] = 1 + + */ + + + public int numDecodings(String s) { + if (s == null || s.length() == 0) return 0; + + // 提取s的长度 + int n = s.length(); + + int[] dp = new int[n + 1]; + dp[n] = 1; + dp[n - 1] = (s.charAt(n - 1) == '0' ? 0 : 1); + + for (int i = n - 2; i >= 0; i--) { + if (s.charAt(i) == '0') { + dp[i] = 0; + continue; + } + + if ((s.charAt(i) - '0' * 10 + s.charAt(i + 1) - '0') <= 26) { + dp[i] = dp[i + 1] + dp[i + 2]; + } else { + dp[i] = dp[i + 1]; + } + } + + return dp[0]; + } + + public int numDecodingsPlus(String s) { + if (s == null || s.length() == 0) return 0; + + // 提取s的长度 + int len = s.length(); + + int help = 1; // 相当于 初始化 dp[len + 1[ = 1 的意思 + int res = (s.charAt(len - 1) != '0' ? 1 : 0); // 倒数第2个值, 也就是是 + + for (int i = len - 2; i >= 0; i--) { + if (s.charAt(i) == '0') { + help = res; + res = 0; + continue; + } + + if ((s.charAt(i - '0') * 10 + s.charAt(i + 1 - '0')) <= 26) { + res += help; + help = res - help; // help用于存储res之前的值 + } else { + help = res; + } + } + + return res; + } + +} diff --git a/Week 08/id_713/NOTE.md b/Week 08/id_713/NOTE.md old mode 100755 new mode 100644 index a6321d6e2..9e96a1bc3 --- a/Week 08/id_713/NOTE.md +++ b/Week 08/id_713/NOTE.md @@ -1,4 +1,146 @@ # NOTE - + + + + +# 高级动态规划 + +## 常见DP题目和状态转移方程 + +### 爬楼梯问题 + +* 转换成斐波那契数列 +* f(n) = f(n - 1) + f(n - 2), 其中 f(1) = 1, f(2) = 1 + +```python +# 朴素 +def f(n): + if n <= 1: return 1; + return f(n - 1) + f(n - 2) + +# 带缓存 +def f(n): + if n <= 1: return 1 + if n not in mem: + mem[n] = f(n - 1) + f(n - 2) + return mem[n] + +def f(n): + x, y = 1, 1 + for i in ranges (1, n): + y, x = x + y, y + return y +``` + + + +### 不同路径问题 + +* f(x, y) = f(x - 1, y) + f(x, y - 1) + +```python +def (x, y): + dp[[0] * (m + 1) for _ in range(n + 1)] + dp[1][1] = 1 + for i in range(1, y + 1): + for j in range(1, x + 1): + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + return dp[y][x] +``` + + + +### 打家劫舍 + +* dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + +```python +dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]) +dp[i][1] = dp[i - 1][0] + +``` + + + +### 最小路径和 + +* dp[i] [j]状态定义: minPath(A[1 -> i] [1 -> j]) +* dp[i] [j] = min(dp[i - 1] [j], dp[i] [j - 1]) +A[i] [j] + + + +### 股票买卖 + +```python +dp[i][k][0 or 1] 0 <= i <= n-1, 1 <= k <= K +# i为天数 +# k为最多交易次数 +# [0, 1]是否持有股票 +# 总状态数: n * K * 2种状态 + +for 0 <= i < n: + for i <= k <= K: + for s in [0, 1]: + dp[i][k][s] = max(buy, sell, rest) + +dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) +# 解释: 今天没有持有股票, 有2种可能 +# 1.昨天就没有持有, 然后今天选择rest +# 2.昨天持有了股票, 今天卖掉了 +dp[i][k][1] = max(dp[i-1[k][1]], dp[i-1][k-1][0] - prices[i]) +# 解释: 今天持有股票, 有2种可能 +# 1.昨天持有股票, 今天rest +# 2.昨天没有持有, 今天买入 +``` + + + +--- + +## 复杂度来源 + +* 拥有更多维度 +* dp方程复杂 + + + + + +### 爬楼梯变种 + +* 一次可以上 一级,二级,三级 + +```python +# 上一级, 上二级, 上三级 +# 状态转移方程 +a[i] = a[i - 1] + a[i - 2] + a[i -3] + +a[0] = 1 +a[1] = 2 +a[2] = 3 + +for (int i = 2; i < n; i++) { + a[i] = a[i - 1] + a[i - 2] + a[i - 3] +} + +return a[n - 1] +``` + + + +### 编辑距离 + +```python +# 如果word1[i]与word2[j]相同, 显然dp[i][j] = dp[i - 1][j - 1] + +# 如果word1[i]与word2[j], 不同那么dp[i][j]可以通过 +# 1.在dp[i - 1][j - 1]的基础上做replace操作达到目的 +# 2.在dp[i - 1][j]的基础上做insert操作达到目的 +# 3.在dp[i][j - 1]的基础上做delete操作达到目的 +# 取三者最小情况即可 + +``` + + diff --git a/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java b/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java new file mode 100644 index 000000000..d6f121579 --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_58_LengthOfLastWord.java @@ -0,0 +1,46 @@ +package id_713.practise; + +/** + * 58. 最后一个单词的长度 + */ +public class LeetCode_58_LengthOfLastWord { + /* + 给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。 + + 如果不存在最后一个单词,请返回 0 。 + + 说明:一个单词是指由字母组成,但不包含任何空格的字符串。 + + 示例: + + 输入: "Hello World" + 输出: 5 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/length-of-last-word + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + + */ + + public int lengthOfLastWord(String s) { + if (s == null) return 0; + s = s.trim(); + if (s.length() == 0) return 0; + + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == ' ') + return s.length() - 1 - i; + } + + return s.length(); + } + + public int lengthOfLastWord2(String s) { + if (s == null || s.length() == 0) return 0; + s = s.trim(); + int lastSpace = s.lastIndexOf(" "); + return s.length() - 1 - lastSpace; + } + +} diff --git a/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java b/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java new file mode 100644 index 000000000..18ad20fbd --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_709_ToLowerCase.java @@ -0,0 +1,47 @@ +package id_713.practise; + +/** + * 709. 转换成小写字母 + */ +public class LeetCode_709_ToLowerCase { + /* + 实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串。 + + 示例 1: + + 输入: "Hello" + 输出: "hello" + + 示例 2: + + 输入: "here" + 输出: "here" + + 示例 3: + + 输入: "LOVELY" + 输出: "lovely" + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/to-lower-case + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + */ + + public String toLowerCase(String str) { + + char[] chars = new char[str.length()]; + + for (int i = 0; i < str.length(); i++) { + chars[i] = str.charAt(i) >= 65 && str.charAt(i) <= 90 + ? (char) (str.charAt(i) + 32) + : str.charAt(i); + } + + return new String(chars); + } + + public String toLowerCase2(String str) { + return str.toLowerCase(); + } + +} diff --git a/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java b/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java new file mode 100644 index 000000000..df0db4858 --- /dev/null +++ b/Week 08/id_713/practise/LeetCode_771_JewelsAndStones.java @@ -0,0 +1,57 @@ +package id_713.practise; + +import java.util.HashSet; +import java.util.Set; + +/** + * 771. 宝石与石头 + */ +public class LeetCode_771_JewelsAndStones { + /* + 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 + + J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 + + 示例 1: + + 输入: J = "aA", S = "aAAbbbb" + 输出: 3 + + 示例 2: + + 输入: J = "z", S = "ZZ" + 输出: 0 + + + 来源:力扣(LeetCode) + 链接:https://leetcode-cn.com/problems/jewels-and-stones + 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 + + */ + + public int numJewelsInStones(String J, String S) { + Set set = new HashSet<>(); + + for (char c : J.toCharArray()) { + set.add(c); + } + + int res = 0; + for (char c : S.toCharArray()) { + if (set.contains(c)) + res++; + } + return res; + } + + public int numJewelsInStones2(String J, String S) { + int res = 0; + for (char s : S.toCharArray()) { + if(J.indexOf(s) != -1) + res++; + } + + return res; + } + +}