├── problems ├── 0110.bbt │ └── readme.md ├── 0118.pt │ └── readme.md ├── 0069.sqrt-x │ └── readme.md ├── 0100.same-tree │ └── readme.md ├── 0112.path-sum │ └── readme.md ├── 0119.pt-II │ └── readme.md ├── 0067.add-binary │ └── readme.md ├── 0088.merge-sorted-array │ └── readme.md ├── 0108.convert-sa-to-bst │ └── readme.md ├── 0111.minimum-depth-of-bt │ └── readme.md ├── 0104.maximum-depth-of-binary-tree │ └── readme.md ├── 0107.bt-level-order-travelsal-II │ └── readme.md ├── 0083.remove-duplicates-from-sorted-list │ └── readme.md ├── 0058.length-of-last-word │ └── readme.md ├── 0121.best-time-to-buy-and-sell-stock │ ├── demo.py │ └── README.md ├── 0151.reverse-words-in-a-string │ └── README.md ├── 0169.majority-element │ └── README.md ├── 0041.first-missing-positive │ └── README.md ├── 0394.decode-string │ └── README.md ├── 0283.move-zeros │ └── README.md ├── 0066.plus-one │ └── readme.md ├── 0170.two-sum-data-structure │ └── readme.md ├── 0028.implement-strStr │ └── readme.md ├── 0038.count-and-say │ └── readme.md ├── 0122.best-time-to-buy-and-sell-stockII │ └── README.md ├── 0136.single-number │ └── README.md ├── 0035.search-insert-position │ └── readme.md ├── 0026.remove-duplicates-from-sorted-array │ └── readme.md ├── 0070.climbing-stairs │ └── README.md ├── 0007.reverse-integer │ └── readme.md ├── 0064.qiu-12n-lcof │ └── README.md ├── 0155.min-stack │ └── README.md ├── 0056-1.shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof │ └── README.md ├── 0123.best-time-to-buy-and-sell-stockIII │ └── readme.md ├── 0020.validParentheses │ └── README.md ├── 0027.remove-element │ └── readme.md ├── 0509.fibonacci-number │ └── README.md ├── 0236.lowest-common-ancestor-of-a-binary-tree │ └── README.md ├── 0053.maximum-sub-array │ └── readme.md ├── 0004.median-of-two-sorted-arrays │ └── README.md ├── 0003.longest-substring-without-repeating-characters │ └── README.md ├── 0001.two-sum │ └── README.md ├── 0322.coin-change │ └── README.md ├── 0724.find-pivot-index │ └── README.md ├── 0098.validate-binary-search-tree │ └── README.md ├── 0045.jump-game-ii │ └── README.md ├── 1431.kids-with-the-greatest-number-of-candies │ └── README.md ├── 0560.subarray-sum-equals-k │ └── README.md ├── 0009.PalindromeNumber │ └── README.md ├── 0572.subtree-of-another-tree │ └── README.md ├── 0238.product-of-array-except-self │ └── README.md ├── 0046II.ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof │ └── README.md ├── 0152.maximum-product-subarray │ └── README.md ├── 0102.binary-tree-level-order-traversal │ └── README.md ├── 0050.powx-n │ └── README.md ├── 0202.happy-number │ └── README.md ├── 0680.valid-palindrome-ii │ └── README.md ├── 0221.maximal-square │ └── README.md ├── 0022.generate-parentheses │ └── README.md ├── 0837.new-21-game │ └── README.md ├── 1111.maximum-nesting-depth-of-two-valid-parentheses-strings │ └── README.md ├── 0006.zigzag-conversion │ └── README.md ├── 0146.lru-cache │ └── README.md ├── 0107.rotate-matrix-lcci │ └── README.md ├── 0200.number-of-islands │ └── README.md ├── 0198.house-robber │ └── README.md ├── 0054.spiral-matrix │ └── README.md ├── 0287.find-the-duplicate-number │ └── README.md ├── 1143.longest-common-subsequence │ └── README.md ├── 0002.add-two-numbers │ └── README.md ├── 0014.LongestCommonPrefix │ └── README.md ├── 0072.edit-distance │ └── README.md ├── 0542.01-matrix │ └── README.md ├── 0033.search-in-rotated-sorted-array │ └── README.md ├── 0199.binary-tree-right-side-view │ └── README.md ├── 1371.find-the-longest-substring-containing-vowels-in-even-counts │ └── README.md ├── 0974.subarray-sums-divisible-by-k │ └── README.md ├── 1248.count-number-of-nice-subarrays │ └── README.md ├── 0015.3sum │ └── README.md └── 0912.sort-an-array │ └── README.md ├── _config.yml ├── data_structure └── binary_tree │ └── 手写算法.md ├── sword_to_offer └── 1.实现单例模式.md ├── .gitignore └── README.md /problems/0110.bbt/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0118.pt/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0069.sqrt-x/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0100.same-tree/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0112.path-sum/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0119.pt-II/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /problems/0067.add-binary/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0088.merge-sorted-array/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0108.convert-sa-to-bst/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0111.minimum-depth-of-bt/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0104.maximum-depth-of-binary-tree/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0107.bt-level-order-travelsal-II/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0083.remove-duplicates-from-sorted-list/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /problems/0058.length-of-last-word/readme.md: -------------------------------------------------------------------------------- 1 | 计算一句话中最后一个单词的长度 处理一些边界情况 2 | 3 | 4 | #### Python 5 | 6 | 7 | ```python 8 | class problem.Solution(object): 9 | 10 | # @param s, a string 11 | # @return an integer 12 | def lengthOfLastWord(self, s): 13 | 14 | len_s = len(s) 15 | if 0 == len_s: 16 | return 0 17 | 18 | index = len_s - 1 19 | while index >= 0 and ' ' == s[index]: 20 | index -= 1 21 | len_last_word = 0 22 | while index >= 0 and ' ' != s[index]: 23 | len_last_word += 1 24 | index -= 1 25 | return len_last_word 26 | ``` -------------------------------------------------------------------------------- /problems/0121.best-time-to-buy-and-sell-stock/demo.py: -------------------------------------------------------------------------------- 1 | class Solution(object): 2 | def maxProfit(self, prices): 3 | """ 4 | :type prices: List[int] 5 | :rtype: int 6 | """ 7 | maxprofit = 0 8 | if len(prices) <= 1: 9 | return 0 10 | minprice = prices[0] 11 | for i in range(1, len(prices)): 12 | if prices[i] < minprice: 13 | minprice = prices[i] 14 | maxprofit = max(maxprofit, prices[i] - minprice) 15 | return maxprofit 16 | 17 | 18 | print(Solution().maxProfit([7,1,5,3,6,4])) 19 | 20 | print(Solution().maxProfit([7,6,4,3,1])) 21 | -------------------------------------------------------------------------------- /problems/0151.reverse-words-in-a-string/README.md: -------------------------------------------------------------------------------- 1 | # 翻转字符串里的单词 2 | 3 | [原题链接](https://leetcode-cn.com/problems/reverse-words-in-a-string/) 4 | 5 | ## 题目 6 | 7 | 给定一个字符串,逐个翻转字符串中的每个单词。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: "the sky is blue" 12 | 输出: "blue is sky the" 13 | ``` 14 | 15 | 示例 2: 16 | ```text 17 | 输入: "  hello world!  " 18 | 输出: "world! hello" 19 | ``` 20 | 21 | 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 22 | 23 | 示例 3: 24 | ```text 25 | 输入: "a good   example" 26 | 输出: "example good a" 27 | ``` 28 | 29 | 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 30 | 31 | 说明: 32 | 33 | 无空格字符构成一个单词。 34 | 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 35 | 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 36 | 37 | -------------------------------------------------------------------------------- /problems/0169.majority-element/README.md: -------------------------------------------------------------------------------- 1 | ## 169.求众数 2 | 3 | ### 题目 4 | 5 | 给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 6 | 7 | 你可以假设数组是非空的,并且给定的数组总是存在众数。 8 | 9 | 示例 1: 10 | 11 | 输入: [3,2,3] 12 | 输出: 3 13 | 14 | 示例 2: 15 | 16 | 输入: [2,2,1,1,1,2,2] 17 | 输出: 2 18 | 19 | 20 | ### 解题思路 21 | 22 | #### 2 python 23 | 24 | 利用python当中的collections 对数组进行统计 里面出现次数最多的就是我们需要的 25 | 26 | ``` 27 | class problem.Solution(object): 28 | def majorityElement(self, nums): 29 | """ 30 | :type nums: List[int] 31 | :rtype: int 32 | """ 33 | counts = collections.Counter(nums) 34 | return max(counts.keys(), key=counts.get) 35 | ``` -------------------------------------------------------------------------------- /problems/0041.first-missing-positive/README.md: -------------------------------------------------------------------------------- 1 | # 41. [first-missing-positive](https://leetcode-cn.com/problems/first-missing-positive/description/) 2 | 3 | # 题目 4 | 给定一个未排序的整数数组,找出其中没有出现的最小的正整数。 5 | 6 | 示例 1: 7 | 8 | 输入: [1,2,0] 9 | 输出: 3 10 | 示例 2: 11 | 12 | 输入: [3,4,-1,1] 13 | 输出: 2 14 | 示例 3: 15 | 16 | 输入: [7,8,9,11,12] 17 | 输出: 1 18 | 说明: 19 | 20 | 你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。 21 | 22 | # 解题思路 23 | 24 | 建立{1,...,len(nums)}集合,求差集 25 | 26 | ## Python 27 | 28 | ```python 29 | 30 | class problem.Solution: 31 | def firstMissingPositive(self, nums): 32 | """ 33 | :type nums: List[int] 34 | :rtype: int 35 | """ 36 | s=set([i for i in range(1,len(nums)+2)]) 37 | return sorted(list(s-set(nums)))[0] 38 | 39 | ``` -------------------------------------------------------------------------------- /problems/0394.decode-string/README.md: -------------------------------------------------------------------------------- 1 | # 字符串解码 2 | 3 | [原题链接](https://leetcode-cn.com/problems/decode-string/) 4 | 5 | ## 题目 6 | 7 | 给定一个经过编码的字符串,返回它解码后的字符串。 8 | 9 | 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 10 | 11 | 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 12 | 13 | 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 14 | 15 | 示例: 16 | 1. s = "3[a]2[bc]", 返回 "aaabcbc". 17 | 2. s = "3[a2[c]]", 返回 "accaccacc". 18 | 3. s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 19 | 20 | ## 题解 21 | 22 | 我们可以把字母、数字和括号看成是独立的 TOKEN,并用栈来维护这些 TOKEN。具体的做法是,遍历这个栈: 23 | 1. 如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈 24 | 2. 如果当前的字符为字母或者左括号,直接进栈 25 | 3. 如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串,此时取出栈顶的数字(此时栈顶一定是数字),就是这个字符串应该出现的次数,我们根据这个次数和字符串构造出新的字符串并进栈 26 | 27 | -------------------------------------------------------------------------------- /problems/0283.move-zeros/README.md: -------------------------------------------------------------------------------- 1 | ## 283. 移动零 2 | 3 | ### 题目 4 | 5 | 給定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 6 | 7 | 实例: 8 | 9 | 输入: [0,1,0,3,12] 10 | 11 | 输出:[1,3,12,0,0] 12 | 13 | 说明: 14 | 15 | + 必须在原数组上操作,不能拷贝额外的数组。 16 | + 尽量减少操作次数。 17 | 18 | ### 解题思路 19 | 20 | 在原数组上操作,以节省内存 21 | 22 | 首先遍历数组 数出有多少个0 再将0先移除 然后添加到末尾 即可 23 | 24 | #### 2 Python 25 | 26 | ``` 27 | class problem.Solution(object): 28 | def moveZeroes(self, nums): 29 | """ 30 | :type nums: List[int] 31 | :rtype: void Do not return anything, modify nums in-place instead. 32 | """ 33 | number_of_zero = nums.count(0) 34 | for i in range(number_of_zero): 35 | nums.remove(0) 36 | nums.extend([0]*number_of_zero) 37 | 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /problems/0066.plus-one/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 题目 4 | 5 | 6 | ### 解题思路 7 | 8 | 用一个int数组代表一个数值,最左边是最高位,现要求加上1,同样地返回一个int数组代表计算结果。 9 | 非常简单的模拟加法,注意进位问题,如果是99,999这种,最后加上1后还要在数组最左边插入一个元素1。 10 | 11 | #### Python 12 | 13 | ```python 14 | class problem.Solution(object): 15 | # @param digits, a list of integer digits 16 | # @return a list of integer digits 17 | def plusOne(self, digits): 18 | len_s = len(digits) 19 | carry = 1 20 | for i in range(len_s - 1, -1, -1): 21 | total = digits[i] + carry 22 | digit = int(total % 10) 23 | carry = int(total / 10) 24 | digits[i] = digit 25 | if carry == 1: 26 | digits.insert(0, 1) 27 | return digits 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /problems/0170.two-sum-data-structure/readme.md: -------------------------------------------------------------------------------- 1 | 170.two sum III 2 | 3 | ### 解题思路 4 | 5 | python 当中的字典一定要深入理解 6 | 7 | 在此设计添加的值为key,value是key添加的个数 8 | 9 | add 的时间复杂度为 1 10 | 11 | find 的时间复杂度是 n 12 | 13 | #### Python 14 | 15 | ```python 16 | class TwoSum(object): 17 | 18 | # initialize your data structure here 19 | def __init__(self): 20 | self.table = dict() 21 | 22 | # @return nothing 23 | def add(self, number): 24 | self.table[number] = self.table.get(number, 0) + 1 25 | 26 | # @param value, an integer 27 | # @return a Boolean 28 | def find(self, value): 29 | for i in self.table.keys(): 30 | j = value - i 31 | if i == j and self.table.get(i) > 1 or i != j and self.table.get(j, 0) > 0: 32 | return True 33 | return False 34 | ``` -------------------------------------------------------------------------------- /problems/0121.best-time-to-buy-and-sell-stock/README.md: -------------------------------------------------------------------------------- 1 | ## 121.买卖股票的最佳时机 2 | 3 | ### 题目 4 | 5 | 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 6 | 7 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 8 | 9 | 注意你不能在买入股票前卖出股票。 10 | 11 | 示例 1: 12 | 13 | 输入: [7,1,5,3,6,4] 14 | 输出: 5 15 | 16 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 17 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 18 | 19 | 示例 2: 20 | 21 | 输入: [7,6,4,3,1] 22 | 输出: 0 23 | 24 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 25 | 26 | 27 | ### 解题思路 28 | 29 | 根据题目的条件 先将买入的价格设置为第一个值 如果后续有比它小的 则替换为小的 30 | 最大的利润差初值设置为0 如果后面的值大于买入值 则取差值 否则取0 顺势找到最大的利润差值。 31 | 注意边界条件 比如数组的长度为0或者1这种情况。 32 | 33 | 34 | ``` 35 | class problem.Solution(object): 36 | def maxProfit(self, prices): 37 | """ 38 | :type prices: List[int] 39 | :rtype: int 40 | """ 41 | maxprofit = 0 42 | if len(prices) <= 1: 43 | return 0 44 | minprice = prices[0] 45 | for i in range(1, len(prices)): 46 | if prices[i] < minprice: 47 | minprice = prices[i] 48 | maxprofit = max(maxprofit, prices[i] - minprice) 49 | return maxprofit 50 | ``` -------------------------------------------------------------------------------- /problems/0028.implement-strStr/readme.md: -------------------------------------------------------------------------------- 1 | ## 28.实现strStr() 2 | 3 | ### 题目 4 | 5 | 实现 strStr() 函数。 6 | 7 | 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 8 | 9 | 示例 1: 10 | 11 | 输入: haystack = "hello", needle = "ll" 12 | 输出: 2 13 | 14 | 示例 2: 15 | 16 | 输入: haystack = "aaaaa", needle = "bba" 17 | 输出: -1 18 | 19 | 说明: 20 | 21 | 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 22 | 23 | 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 24 | 25 | 26 | ### 解题思路 27 | 28 | 对 python 内置字符串函数 find 的实现 29 | 30 | #### Python 31 | 32 | ``` 33 | class problem.Solution(object): 34 | def strStr(self, haystack, needle): 35 | """ 36 | :type haystack: str 37 | :type needle: str 38 | :rtype: int 39 | """ 40 | # return haystack.find(needle) 41 | for i in range(len(haystack) + 1): 42 | for j in range(len(needle) + 1): 43 | if j == len(needle): 44 | return i 45 | if i + j == len(haystack): 46 | return -1 47 | if needle[j] != haystack[i + j]: 48 | break 49 | 50 | ``` -------------------------------------------------------------------------------- /problems/0038.count-and-say/readme.md: -------------------------------------------------------------------------------- 1 | ## 38.报数 2 | 3 | 4 | ### 题目 5 | 6 | 报数序列是指一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下: 7 | 8 | 1 -- 1 9 | 2 -- 11 10 | 3 -- 21 11 | 4 -- 1211 12 | 5 -- 111221 13 | 14 | 1 被读作 "one 1" ("一个一") , 即 11。 15 | 11 被读作 "two 1s" ("两个一"), 即 21。 16 | 21 被读作 "one 2", "one 1" ("一个二" , "一个一") , 即 1211。 17 | 18 | 给定一个正整数 n ,输出报数序列的第 n 项。 19 | 20 | 注意:整数顺序将表示为一个字符串。 21 | 22 | 示例 1: 23 | 24 | 输入: 1 25 | 输出: "1" 26 | 27 | 示例 2: 28 | 29 | 输入: 4 30 | 输出: "1211" 31 | 32 | 33 | ### 解题思路 34 | 35 | 依次查询每一个字符出现的次数,并统计下来,然后在结果中存入即可,题目数据量比较小。 36 | 37 | #### Python 38 | 39 | ``` 40 | class problem.Solution(object): 41 | # @param {integer} n 42 | # @return {string} 43 | def countAndSay(self, n): 44 | seq=['1'];top=1; 45 | while n-1>0: 46 | n-=1;index=0;bak=[] 47 | i=0 48 | while i prices[i-1]: 50 | maxpro += prices[i] - prices[i-1] 51 | return maxpro 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /problems/0136.single-number/README.md: -------------------------------------------------------------------------------- 1 | # 只出现一次的数字 2 | 3 | [原题链接](https://leetcode-cn.com/problems/single-number/) 4 | 5 | ## 题目 6 | 7 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 8 | 9 | 说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 10 | 11 | 示例 1: 12 | 13 | 输入: [2,2,1] 14 | 输出: 1 15 | 16 | 示例 2: 17 | 18 | 输入: [4,1,2,1,2] 19 | 输出: 4 20 | 21 | ## 题解 22 | 23 | 用异或解决,没难度 24 | 25 | ## 代码 26 | 27 | ### Java 28 | 29 | ```java 30 | class Solution { 31 | public int singleNumber(int[] nums) { 32 | int result = 0; 33 | for (int num : nums) { 34 | result ^= num; 35 | } 36 | return result; 37 | } 38 | } 39 | ``` 40 | 41 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 99.75% 的用户 42 | - 内存消耗 : 41 MB , 在所有 Java 提交中击败了 5.71% 的用户 43 | 44 | ### Go 45 | 46 | ```go 47 | func singleNumber(nums []int) int { 48 | res := 0 49 | for _, num := range nums { 50 | res ^= num 51 | } 52 | return res 53 | } 54 | ``` 55 | 56 | - 执行用时 : 12 ms , 在所有 Go 提交中击败了 80.06% 的用户 57 | - 内存消耗 : 4.7 MB , 在所有 Go 提交中击败了 100.00% 的用户 58 | 59 | ### Python 60 | 61 | ```python 62 | class Solution: 63 | def singleNumber(self, nums: List[int]) -> int: 64 | ans = 0 65 | for num in nums: 66 | ans = ans ^ num 67 | return ans 68 | ``` 69 | 70 | - 执行用时 : 48 ms , 在所有 Python3 提交中击败了 70.69% 的用户 71 | - 内存消耗 : 15.3 MB , 在所有 Python3 提交中击败了 5.26% 的用户 72 | -------------------------------------------------------------------------------- /problems/0035.search-insert-position/readme.md: -------------------------------------------------------------------------------- 1 | ## 35.搜索插入位置 2 | 3 | ### 题目 4 | 5 | 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 6 | 7 | 你可以假设数组中无重复元素。 8 | 9 | 示例 1: 10 | 11 | 输入: [1,3,5,6], 5 12 | 输出: 2 13 | 14 | 示例 2: 15 | 16 | 输入: [1,3,5,6], 2 17 | 输出: 1 18 | 19 | 示例 3: 20 | 21 | 输入: [1,3,5,6], 7 22 | 输出: 4 23 | 24 | 示例 4: 25 | 26 | 输入: [1,3,5,6], 0 27 | 输出: 0 28 | 29 | ### 解题思路 30 | 31 | 常用二分查找来实现 32 | 33 | 34 | ```python 35 | class problem.Solution(object): 36 | def helper(self, nums, target): 37 | length = len(nums) 38 | if length <= 2: 39 | for i in range(length): 40 | if nums[i] < target: 41 | self.sum += 1 42 | else: 43 | mid = length / 2 44 | if nums[mid] == target: 45 | self.sum += mid 46 | elif nums[mid] > target: 47 | self.helper(nums[0:mid], target) 48 | else: 49 | self.helper(nums[mid + 1:], target) 50 | self.sum += mid + 1 51 | 52 | def searchInsert(self, nums, target): 53 | self.length = len(nums) 54 | if nums[0] > target: 55 | return 0 56 | elif nums[-1] < target: 57 | return self.length 58 | else: 59 | self.sum = 0 60 | self.helper(nums, target) 61 | return self.sum 62 | 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /problems/0026.remove-duplicates-from-sorted-array/readme.md: -------------------------------------------------------------------------------- 1 | ## 26.删除排序数组中的重复项 2 | 3 | ### 题目 4 | 5 | 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 6 | 7 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 8 | 9 | 示例 1: 10 | 11 | 给定数组 nums = [1,1,2], 12 | 13 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 14 | 15 | 你不需要考虑数组中超出新长度后面的元素。 16 | 17 | 示例 2: 18 | 19 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 20 | 21 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 22 | 23 | 你不需要考虑数组中超出新长度后面的元素。 24 | 25 | 说明: 26 | 27 | 为什么返回数值是整数,但输出的答案是数组呢? 28 | 29 | 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 30 | 31 | 你可以想象内部操作如下: 32 | 33 | // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 34 | int len = removeDuplicates(nums); 35 | 36 | // 在函数里修改输入数组对于调用者是可见的。 37 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 38 | for (int i = 0; i < len; i++) { 39 | print(nums[i]); 40 | } 41 | 42 | ### 解题思路 43 | 44 | 遍历数组 发现重复的则把后面的往前移动 覆盖重复值 45 | 46 | #### Python 47 | 48 | 49 | ``` 50 | class problem.Solution(object): 51 | def removeDuplicates(self, nums): 52 | """ 53 | :type nums: List[int] 54 | :rtype: int 55 | """ 56 | # 删除列表中的重复项,返回操作后的长度 57 | # [1,1,1,2,3,4,4,4,5] -> [1,2,3,4,5] 5 58 | 59 | if len(nums) <= 1: 60 | return len(nums) 61 | 62 | s = 0 63 | 64 | for f in range(1, len(nums)): 65 | if nums[s] != nums[f]: 66 | s += 1 67 | nums[s] = nums[f] 68 | return s + 1 69 | 70 | ``` -------------------------------------------------------------------------------- /problems/0070.climbing-stairs/README.md: -------------------------------------------------------------------------------- 1 | # 爬楼梯 2 | 3 | [原题链接](https://leetcode-cn.com/problems/climbing-stairs/) 4 | 5 | ## 题目 6 | 7 | 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 8 | 9 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 10 | 11 | 注意:给定 n 是一个正整数。 12 | 13 | 示例 1: 14 | ```text 15 | 输入: 2 16 | 输出: 2 17 | 解释: 有两种方法可以爬到楼顶。 18 | 1. 1 阶 + 1 阶 19 | 2. 2 阶 20 | ``` 21 | 22 | 示例 2: 23 | ```text 24 | 输入: 3 25 | 输出: 3 26 | 解释: 有三种方法可以爬到楼顶。 27 | 1. 1 阶 + 1 阶 + 1 阶 28 | 2. 1 阶 + 2 阶 29 | 3. 2 阶 + 1 阶 30 | ``` 31 | 32 | ## 题解 33 | 34 | 我没思路,想不出来,虽然我也明白这种题目是要用动态规划去做,而且看了下提交记录,我在上个月前曾经做过。 35 | 36 | 如果用 f(n) 表示在第n阶有多少种方法,只有两分钟可能 37 | 1. 在 n-2 阶时走两步 38 | 2. 在 n-1 阶时走一步 39 | 40 | 所以状态转移方程就是: f(n) = f(n-1) + f(n-2) 41 | 42 | ## 代码 43 | 44 | ### Java 动态规划 45 | 46 | ```text 47 | class Solution { 48 | public int climbStairs(int n) { 49 | int[] dp = new int[n + 2]; 50 | dp[0] = 0; 51 | dp[1] = 1; 52 | for (int i = 2; i <= n + 1; i++) { 53 | dp[i] = dp[i - 1] + dp[i - 2]; 54 | } 55 | return dp[n + 1]; 56 | } 57 | } 58 | ``` 59 | 60 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 61 | - 内存消耗 : 36.3 MB , 在所有 Java 提交中击败了 5.66% 的用户 62 | 63 | 我看了答案才知道,这道题本质上是斐波那契数列,ε=(´ο`*)))唉 64 | 65 | ### Java 空间优化版 66 | 67 | ```java 68 | class Solution { 69 | public int climbStairs(int n) { 70 | if (n <= 2) { 71 | return n; 72 | } 73 | int i = 1, j = 2; 74 | for (int m = 2; m < n; m++) { 75 | int tmp = i + j; 76 | i = j; 77 | j = tmp; 78 | } 79 | return j; 80 | } 81 | } 82 | ``` 83 | 84 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 85 | - 内存消耗 : 36.7 MB , 在所有 Java 提交中击败了 5.66% 的用户 86 | 87 | -------------------------------------------------------------------------------- /problems/0007.reverse-integer/readme.md: -------------------------------------------------------------------------------- 1 | # [7.反转整数](https://leetcode-cn.com/problems/reverse-integer/description/) 2 | 3 | # 题目 4 | 5 | 给定一个 32 位有符号整数,将整数中的数字进行反转。 6 | 7 | 示例 1: 8 | 9 | 输入: 123 10 | 输出: 321 11 | 12 | 示例 2: 13 | 14 | 输入: -123 15 | 输出: -321 16 | 17 | 示例 3: 18 | 19 | 输入: 120 20 | 输出: 21 21 | 注意: 22 | 23 | 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。根据这个假设,如果反转后的整数溢出,则返回 0。 24 | 25 | # 解题思路 26 | 27 | 取绝对值之后 除10 对10取余 反转过来 注意题目的限制范围 28 | 29 | ## Python 30 | 31 | ```python 32 | class problem.Solution(object): 33 | def reverse(self, x): 34 | """ 35 | :type x: int 36 | :rtype: int 37 | """ 38 | y = x if x>=0 else -x 39 | z = 0 40 | while y>0: 41 | z = z*10 + y%10 42 | y /= 10 43 | 44 | z = z if x>=0 else -z 45 | if z>0x7fffffff or z<-0x80000000: 46 | return 0 47 | return z 48 | 49 | ``` 50 | 51 | 利用Python的字符串反转操作来实现对整数的反转,反转后的字符串要重新转换为整数。同上面一样,要注意正负和溢出情况。 52 | 53 | ```python 54 | class problem.Solution(object): 55 | def reverse(self, x): 56 | """ 57 | :type x: int 58 | :rtype: int 59 | """ 60 | x = int(str(x)[::-1]) if x >= 0 else - int(str(-x)[::-1]) 61 | return x if x < 2147483648 and x >= -2147483648 else 0 62 | ``` 63 | 64 | ## Golang 65 | 66 | ```golang 67 | func reverse(x int) int { 68 | sign := 1 69 | if x < 0 { 70 | sign = -1 71 | x = -1 * x 72 | } 73 | 74 | res := 0 75 | for x > 0 { 76 | temp := x % 10 77 | res = res * 10 + temp 78 | x = x / 10 79 | } 80 | 81 | res = sign * res 82 | 83 | if res > math.MaxInt32 || res < math.MinInt32 { 84 | res = 0 85 | } 86 | 87 | return res 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /problems/0064.qiu-12n-lcof/README.md: -------------------------------------------------------------------------------- 1 | # 求1+2+…+n 2 | 3 | [原题链接](https://leetcode-cn.com/problems/qiu-12n-lcof/) 4 | 5 | ## 题目 6 | 7 | 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: n = 3 12 | 输出: 6 13 | ``` 14 | 15 | 示例 2: 16 | ```text 17 | 输入: n = 9 18 | 输出: 45 19 | ``` 20 | 21 | 限制:1 <= n <= 10000 22 | 23 | ## 题解 24 | 25 | 初步想法是位运算。然后发现错了,哈哈。 26 | 27 | 正常递归写法应该是 28 | ```java 29 | public int sumNums(int n) { 30 | if (n == 1) { 31 | return 1; 32 | } 33 | n += sumNums(n - 1); 34 | return n; 35 | } 36 | ``` 37 | 38 | 但是使用了if关键字,用**逻辑运算符**可以达到同样效果。 39 | 40 | ## 代码 41 | 42 | ### Java 43 | 44 | ```java 45 | class Solution { 46 | public int sumNums(int n) { 47 | boolean flag = n > 0 && (n += sumNums(n - 1)) > 0; 48 | return n; 49 | } 50 | } 51 | ``` 52 | 53 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 54 | - 内存消耗 : 36.9 MB , 在所有 Java 提交中击败了 100.00% 的用户 55 | 56 | 看不懂来个变形吧 57 | 58 | ```java 59 | public int sumNums(int n) { 60 | boolean flag; 61 | if (n > 0) { 62 | flag = (n += sumNums(n - 1)) > 0; 63 | } else { 64 | flag = false; 65 | } 66 | return n; 67 | } 68 | ``` 69 | 70 | 如果 n <= 0 就直接return了,不会继续递归,这种题很没意思 71 | 72 | ### Go 73 | 74 | ```go 75 | func sumNums(n int) int { 76 | _ = n > 0 && func() bool { n += sumNums(n - 1); return true }() 77 | return n 78 | } 79 | ``` 80 | 81 | > Go 语言中整数和 bool 类型是不能隐式转换的,所以使用匿名函数封装一下 82 | 83 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 84 | - 内存消耗 : 2.6 MB , 在所有 Go 提交中击败了 100.00% 的用户 85 | 86 | ### Python 87 | ```python 88 | class Solution: 89 | def sumNums(self, n: int) -> int: 90 | return sum(range(1, n + 1)) 91 | ``` 92 | 93 | - 执行用时 : 40 ms , 在所有 Python3 提交中击败了 87.19% 的用户 94 | - 内存消耗 : 13.7 MB , 在所有 Python3 提交中击败了 100.00% 的用户 95 | 96 | -------------------------------------------------------------------------------- /problems/0155.min-stack/README.md: -------------------------------------------------------------------------------- 1 | # 最小栈 2 | 3 | [原题链接](https://leetcode-cn.com/problems/min-stack/) 4 | 5 | ## 题目 6 | 7 | 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 8 | 9 | - push(x) —— 将元素 x 推入栈中。 10 | - pop() —— 删除栈顶的元素。 11 | - top() —— 获取栈顶元素。 12 | - getMin() —— 检索栈中的最小元素。 13 | 14 | 15 | 示例: 16 | 17 | 输入: 18 | ```text 19 | ["MinStack","push","push","push","getMin","pop","top","getMin"] 20 | [[],[-2],[0],[-3],[],[],[],[]] 21 | ``` 22 | 23 | 输出: 24 | ```text 25 | [null,null,null,null,-3,null,0,-2] 26 | ``` 27 | 28 | 解释: 29 | ```text 30 | MinStack minStack = new MinStack(); 31 | minStack.push(-2); 32 | minStack.push(0); 33 | minStack.push(-3); 34 | minStack.getMin(); --> 返回 -3. 35 | minStack.pop(); 36 | minStack.top(); --> 返回 0. 37 | minStack.getMin(); --> 返回 -2. 38 | ``` 39 | 40 | 提示: 41 | - pop、top 和 getMin 操作总是在 非空栈 上调用。 42 | 43 | ## 题解 44 | 45 | 直接看代码吧。注意栈是先进后出,栈顶是数组中最后一个 46 | 47 | ## 代码 48 | 49 | ### Go 50 | 51 | ```go 52 | type MinStack struct { 53 | stack []int 54 | minStack []int 55 | } 56 | 57 | func Constructor() MinStack { 58 | return MinStack{ 59 | stack: []int{}, 60 | minStack: []int{math.MaxInt64}, 61 | } 62 | } 63 | 64 | func (this *MinStack) Push(x int) { 65 | this.stack = append(this.stack, x) 66 | top := this.minStack[len(this.minStack)-1] 67 | this.minStack = append(this.minStack, min(x, top)) 68 | } 69 | 70 | func (this *MinStack) Pop() { 71 | this.stack = this.stack[:len(this.stack)-1] 72 | this.minStack = this.minStack[:len(this.minStack)-1] 73 | } 74 | 75 | func (this *MinStack) Top() int { 76 | return this.stack[len(this.stack)-1] 77 | } 78 | 79 | func (this *MinStack) GetMin() int { 80 | return this.minStack[len(this.minStack)-1] 81 | } 82 | 83 | func min(x int, y int) int { 84 | if x > y { 85 | return y 86 | } 87 | return x 88 | } 89 | ``` 90 | 91 | - 执行用时 : 12 ms , 在所有 Go 提交中击败了 99.70% 的用户 92 | - 内存消耗 : 7.8 MB , 在所有 Go 提交中击败了 16.67% 的用户 93 | 94 | -------------------------------------------------------------------------------- /problems/0056-1.shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/README.md: -------------------------------------------------------------------------------- 1 | # 数组中数字出现的次数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) 4 | 5 | ## 题目 6 | 7 | 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。 8 | 9 | 示例 1: 10 | ```text 11 | 输入:nums = [4,1,4,6] 12 | 输出:[1,6] 或 [6,1] 13 | ``` 14 | 15 | 示例 2: 16 | ```text 17 | 输入:nums = [1,2,10,4,1,4,3,3] 18 | 输出:[2,10] 或 [10,2] 19 | ``` 20 | 21 | 限制: 22 | 23 | 2 <= nums <= 10000 24 | 25 | ## 题解 26 | 27 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/4PwxaB.png) 28 | 29 | 看到这道题我就想起了异或,之前有做过类似的题目,异或有一个性质:`a ^ b ^ a ^ c = b ^ c` 30 | 31 | 这样就可以把重复的a排除,得到`b ^ c`的值,然后我就卡在这里了。 32 | 33 | 这道题说时间复杂度是O(n),我一直以为只能循环数组一遍,然后就怎么也想不出答案了。最后看了题解才知道,O(n)是可以循环两遍数组的,只要不是嵌套循环,时间复杂度都是O(n)。 34 | 35 | 由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1. 36 | 37 | 我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。 38 | 这样肯定能保证2. 相同的数字分成相同组, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是 39 | 说两个独特的的数字在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 40 | 41 | 42 | 43 | ## 代码 44 | 45 | ### Java 46 | 47 | ```java 48 | public class Solution { 49 | public int[] singleNumbers(int[] nums) { 50 | int sum = 0; 51 | for (int num : nums) { 52 | sum ^= num; 53 | } 54 | int first = 1; 55 | while ((sum & first) == 0) { 56 | first <<= 1; 57 | } 58 | // first为1,所以我们可以根据数组元素的二进制低位第一位是否为1,将数组分为2类, 59 | // 示例数组可以分为 低位第一位为0:[4,4,6] 低位第一位为1:[1] 60 | // 此时再将两个数组两两异或就可以得到最终结果。 61 | int[] result = new int[2]; 62 | for (int num : nums) { 63 | //将数组分类。 64 | if ((num & first) == 0) { 65 | result[0] ^= num; 66 | } else { 67 | result[1] ^= num; 68 | } 69 | } 70 | return result; 71 | } 72 | } 73 | ``` 74 | 75 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 100.00% 的用户 76 | - 内存消耗 : 41.5 MB , 在所有 Java 提交中击败了 100.00% 的用户 77 | 78 | -------------------------------------------------------------------------------- /problems/0123.best-time-to-buy-and-sell-stockIII/readme.md: -------------------------------------------------------------------------------- 1 | ## 123.买卖股票的最佳时机 III 2 | 3 | ### 题目 4 | 5 | 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 6 | 7 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 8 | 9 | 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 10 | 11 | 示例 1: 12 | 13 | 输入: [3,3,5,0,0,3,1,4] 14 | 输出: 6 15 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 16 | 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 17 | 18 | 示例 2: 19 | 20 | 输入: [1,2,3,4,5] 21 | 输出: 4 22 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 23 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 24 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 25 | 26 | 示例 3: 27 | 28 | 输入: [7,6,4,3,1] 29 | 输出: 0 30 | 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 31 | 32 | 33 | ### 解题思路 34 | 35 | 由题目121演变而来 最多交易两次 36 | 37 | 将天数分开,前i天调用一次121的算法,后面的天调用一次121的算法,但是要注意如果外层循环i,里面再循环121的算法,会超时,这时我们考虑用两个数组来存储结果,pre\_profit和pro\_profit,其中pre\_profit[i]表示i天之前的最大利润,pro\_profit[i]表示i天之后的最大利润,前i天的很好理解和121一样的写法,后i天注意要从后往前动态规划。 38 | 39 | ```python 40 | class problem.Solution(object): 41 | def maxProfit(self, prices): 42 | n = len(prices) 43 | if n < 2: 44 | return 0 45 | pre_max_profit = [0 for i in range(n)] 46 | pro_max_profit = [0 for i in range(n)] 47 | max_profit = 0 48 | pre_min_buy = prices[0] 49 | for i in range(1,n): 50 | # pre i days 51 | pre_min_buy = min(pre_min_buy,prices[i]) 52 | pre_max_profit[i]=max(pre_max_profit[i-1], prices[i]-pre_min_buy) 53 | 54 | pro_max_sell = prices[n-1] 55 | for k in range(n-2,-1,-1): 56 | pro_max_sell = max(pro_max_sell,prices[k]) 57 | pro_max_profit[k]=max(pro_max_profit[k+1], pro_max_sell-prices[k]) 58 | for j in range(0,n): 59 | max_profit = max(max_profit,pre_max_profit[j]+pro_max_profit[j]) 60 | return max_profit 61 | 62 | 63 | print(problem.Solution().maxProfit([3,3,5,0,0,3,1,4])) 64 | 65 | ``` -------------------------------------------------------------------------------- /problems/0020.validParentheses/README.md: -------------------------------------------------------------------------------- 1 | # 20.ValidParentheses 2 | 3 | 4 | [问题链接](https://leetcode-cn.com/problems/valid-parentheses/description/) 5 | 6 | # 题目 7 | 8 | 给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 9 | 10 | 有效字符串需满足: 11 | 12 | 1. 左括号必须用相同类型的右括号闭合。 13 | 2. 左括号必须以正确的顺序闭合。 14 | 15 | 注意空字符串可被认为是有效字符串。 16 | 17 | **示例 2:** 18 | 19 | ``` 20 | 输入: "()[]{}" 21 | 输出: true 22 | ``` 23 | 24 | **示例 3:** 25 | 26 | ``` 27 | 输入: "(]" 28 | 输出: false 29 | ``` 30 | 31 | **示例 4:** 32 | 33 | ``` 34 | 输入: "([)]" 35 | 输出: false 36 | ``` 37 | 38 | **示例 5:** 39 | 40 | ``` 41 | 输入: "{[]}" 42 | 输出: true 43 | ``` 44 | 45 | 46 | 47 | # 解题思路 48 | 49 | 可以利用栈——LIFO(先进先出)的特点,对于传入的字符串,从第一个符号开始,往栈中push值,当遇到一个右括号,则直接与栈中最顶层的括号对比是否为“一对”,如果是则将顶层的值出栈,如果不是则程序返回false。大致思路是这样,再进行一些细节处理就可以完成啦。下面是java代码: 50 | 51 | 52 | 53 | ```java 54 | public boolean isValid(String s) { 55 | Stack stack = new Stack<>(); 56 | if (s.length() % 2 != 0) { 57 | return false; 58 | } 59 | for (int i = 0; i < s.length(); i++) { 60 | 61 | char nowChar = s.charAt(i); 62 | 63 | if (isLeftCouple(nowChar)) { 64 | stack.push(nowChar); 65 | continue; 66 | } 67 | 68 | if (stack.empty()) { 69 | return false; 70 | } 71 | 72 | char preChar = stack.peek(); 73 | 74 | if (!isCouple(preChar, nowChar)) { 75 | return false; 76 | } 77 | stack.pop(); 78 | } 79 | return stack.empty(); 80 | } 81 | 82 | private static boolean isLeftCouple(char a) { 83 | return a == '(' || a == '{' || a == '['; 84 | } 85 | 86 | private static boolean isCouple(char a, char b) { 87 | switch (a) { 88 | case '(': 89 | return b == ')'; 90 | case '[': 91 | return b == ']'; 92 | case '{': 93 | return b == '}'; 94 | default: 95 | return false; 96 | } 97 | } 98 | ``` 99 | 100 | -------------------------------------------------------------------------------- /problems/0027.remove-element/readme.md: -------------------------------------------------------------------------------- 1 | ## 27.移除元素 2 | 3 | ### 题目 4 | 5 | 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 6 | 7 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 8 | 9 | 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 10 | 11 | 示例 1: 12 | 13 | 给定 nums = [3,2,2,3], val = 3, 14 | 15 | 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 16 | 17 | 你不需要考虑数组中超出新长度后面的元素。 18 | 19 | 示例 2: 20 | 21 | 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 22 | 23 | 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 24 | 25 | 注意这五个元素可为任意顺序。 26 | 27 | 你不需要考虑数组中超出新长度后面的元素。 28 | 29 | 说明: 30 | 31 | 为什么返回数值是整数,但输出的答案是数组呢? 32 | 33 | 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 34 | 35 | 你可以想象内部操作如下: 36 | 37 | // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 38 | int len = removeElement(nums, val); 39 | 40 | // 在函数里修改输入数组对于调用者是可见的。 41 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 42 | for (int i = 0; i < len; i++) { 43 | print(nums[i]); 44 | } 45 | 46 | 47 | 48 | ### 解决思路 49 | 50 | 遍历一次 遇到要删除的元素 跳过 将数组重写一次 51 | 52 | #### Python 53 | 54 | ``` 55 | class problem.Solution(object): 56 | def removeElement(self, nums, val): 57 | """ 58 | :type nums: List[int] 59 | :type val: int 60 | :rtype: int 61 | """ 62 | if len(nums) == 0: 63 | return 0; 64 | j = 0 65 | for i in range(0, len(nums)): 66 | if nums[i] != val : 67 | nums[j] = nums[i] 68 | j = j + 1 69 | return j 70 | 71 | ``` 72 | 73 | #### C++ 74 | ``` 75 | class solution{ 76 | public: 77 | int removeElement(int A[],int length,int elem){ 78 | 79 | int j = 0; 80 | 81 | //length = 数组长度 - a[]长度 82 | for(int i = 0; i < n; i++){ 83 | if (A[i] == elem){ 84 | 85 | //如果相等,说明当前数组的下标i,正是要remove的value,和目标elem一致 - 新数组长度不+ 86 | continue; 87 | 88 | }else{ 89 | 90 | //如果不等的,就放到新的数组里 - 最终取A[j] 91 | A[j] = A[i]; 92 | 93 | //说明不相等,j++,j表示当前数组长度 94 | j++; 95 | } 96 | 97 | } 98 | return j; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /problems/0509.fibonacci-number/README.md: -------------------------------------------------------------------------------- 1 | # 斐波那契数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/fibonacci-number/) 4 | 5 | ## 题目 6 | 7 | 斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 8 | 9 | - F(0) = 0,   F(1) = 1 10 | - F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 11 | 12 | 给定 N,计算 F(N)。 13 | 14 | 15 | 示例 1: 16 | ```text 17 | 输入:2 18 | 输出:1 19 | 解释:F(2) = F(1) + F(0) = 1 + 0 = 1. 20 | ``` 21 | 22 | 示例 2: 23 | ```text 24 | 输入:3 25 | 输出:2 26 | 解释:F(3) = F(2) + F(1) = 1 + 1 = 2. 27 | ``` 28 | 29 | 示例 3: 30 | ```text 31 | 输入:4 32 | 输出:3 33 | 解释:F(4) = F(3) + F(2) = 2 + 1 = 3. 34 | ``` 35 | 36 | 37 | 提示: 38 | - 0 ≤ N ≤ 30 39 | 40 | # 题解 41 | 42 | 刚开始拿到这题时,我竟然想不出方法,首先递归肯定是不会去考虑的,然后想到了动态规划,写完了,发现可以在动态规划的基础上进行优化,即不需要数组,只需要常量即可,这样空间会有优化。 43 | 44 | ## 代码 45 | 46 | ### Java 方法一 47 | 48 | ```java 49 | class Solution { 50 | public int fib(int N) { 51 | if (N == 0) { 52 | return 0; 53 | } 54 | int[] dp = new int[N + 1]; 55 | dp[0] = 0; 56 | dp[1] = 1; 57 | for (int i = 2; i <= N; i++) { 58 | dp[i] = dp[i - 1] + dp[i - 2]; 59 | } 60 | return dp[N]; 61 | } 62 | } 63 | ``` 64 | 65 | - 执行用时: 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 66 | - 内存消耗: 36.6 MB , 在所有 Java 提交中击败了 16.64% 的用户 67 | 68 | ### Java 方法一 69 | 70 | 可以看到上面一种方法用了数组去存储了所有值,但是我们真的需要吗? 71 | 72 | 答案是不,数组可以改为常量。一个还是两个需要自己琢磨。 73 | 74 | 重要理论:**所有能使用动态规划数组去解题的,可以考虑用常量去优化空间复杂度** 75 | 76 | ```java 77 | class Solution { 78 | public int fib(int N) { 79 | if (N == 0) { 80 | return 0; 81 | } 82 | if (N == 1 || N == 2) { 83 | return 1; 84 | } 85 | int res = 0; 86 | int pre = 1, last = 1; 87 | for (int i = 3; i <= N; i++) { 88 | res = pre + last; 89 | pre = last; 90 | last = res; 91 | } 92 | return last; 93 | } 94 | } 95 | ``` 96 | 97 | - 执行用时: 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 98 | - 内存消耗: 36.6 MB , 在所有 Java 提交中击败了 12.63% 的用户 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /problems/0236.lowest-common-ancestor-of-a-binary-tree/README.md: -------------------------------------------------------------------------------- 1 | # 二叉树的最近公共祖先 2 | 3 | [原题链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 8 | 9 | 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 10 | 11 | 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] 12 | 13 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/WcRwyJ.jpg) 14 | 15 | 示例 1: 16 | 17 | 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 18 | 输出: 3 19 | 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 20 | 示例 2: 21 | 22 | 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 23 | 输出: 5 24 | 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。 25 |   26 | 27 | 说明: 28 | 1. 所有节点的值都是唯一的。 29 | 2. p、q 为不同节点且均存在于给定的二叉树中。 30 | 31 | ## 思路 32 | 33 | 想不出来,直接看的答案。(二叉树真的想不出来,需要好好复习下儿二叉树专题) 34 | 35 | 递归解法 36 | 37 | 题目意思: 38 | 1. 如果 p 和 q 都存在,则返回它们的公共祖先; 39 | 2. 如果只存在一个,则返回存在的一个; 40 | 3. 如果 p 和 q 都不存在,则返回NULL 41 | 42 | 具体思路: 43 | 1. 如果当前结点 root 等于NULL,则直接返回NULL 44 | 2. 如果 root 等于 p 或者 q ,那这棵树一定返回 p 或者 q 45 | 3. 然后递归左右子树,因为是递归,使用函数后可认为左右子树已经算出结果,用 left 和 right 表示 46 | 4. 此时若 left 为空,那最终结果只要看 right;若 right 为空,那最终结果只要看 left 47 | 5. 如果 left 和 right 都非空,因为只给了 p 和 q 两个结点,都非空,说明一边一个,因此 root 是他们的最近公共祖先 48 | 6. 如果 left 和 right 都为空,则返回空(其实已经包含在前面的情况中了) 49 | 50 | 时间复杂度是`O(n)`:每个结点最多遍历一次或用主定理, 51 | 空间复杂度是`O(n)`:需要系统栈空间 52 | 53 | ## 代码 54 | 55 | ### Java 56 | ```java 57 | public class Solution { 58 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 59 | if (root == null) { 60 | return null; 61 | } 62 | if (root == p || root == q) { 63 | return root; 64 | } 65 | TreeNode left = lowestCommonAncestor(root.left, p, q); 66 | TreeNode right = lowestCommonAncestor(root.right, p, q); 67 | if (left != null && right != null) { 68 | return root; 69 | } else if (left != null) { 70 | return left; 71 | } else { 72 | return right; 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | - 执行用时 : 7 ms , 在所有 Java 提交中击败了 99.88% 的用户 79 | - 内存消耗 : 41.4 MB , 在所有 Java 提交中击败了 5.71% 的用户 80 | 81 | -------------------------------------------------------------------------------- /problems/0053.maximum-sub-array/readme.md: -------------------------------------------------------------------------------- 1 | ## 53.最大子序列 2 | 3 | ### 题目 4 | 5 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 6 | 7 | 示例: 8 | 9 | 输入: [-2,1,-3,4,-1,2,1,-5,4], 10 | 输出: 6 11 | 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 12 | 13 | 进阶: 14 | 15 | 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 16 | 17 | ### 解题思路 18 | 19 | **Kadane's algorithm** 20 | 21 | max_sum 必然是以A[i](取值范围为A[0] ~ A[n-1])结尾的某段构成的,也就是说max_sum的candidate必然是以A[i]结果的。如果遍历每个candidate,然后进行比较,那么就能找到最大的max_sum了。 22 | 23 | 24 | 假设把A[i]之前的连续段叫做sum。可以很容易想到: 25 | 26 | 27 | 1. 如果sum>=0,就可以和A[i]拼接在一起构成新的sum'。因为不管A[i]多大,加上一个正数总会更大,这样形成一个新的candidate。 28 | 29 | 30 | 2. 反之,如果sum<0,就没必要和A[I]拼接在一起了。因为不管A[i]多小,加上一个负数总会更小。此时由于题目要求数组连续,所以没法保留原sum,所以只能让sum等于从A[i]开始的新的一段数了,这一段数字形成新的candidate。 31 | 32 | 33 | 3. 如果每次得到新的candidate都和全局的max_sum进行比较,那么必然能找到最大的max sum subarray. 34 | 35 | 在循环过程中,用max_sum记录历史最大的值。从A[0]到A[n-1]一步一步地进行。 36 | 37 | 38 | #### Python 39 | 40 | ``` 41 | class problem.Solution(object): 42 | def maxSubArray(self, array): 43 | """ 44 | :type array: List[int] 45 | :rtype: int 46 | """ 47 | if len(array) == 1: 48 | return array[0] 49 | cur = pos = array[0] 50 | for i in range(1, len(array)): 51 | pos = max(pos + array[i], array[i]) 52 | cur = max(cur, pos) 53 | return cur 54 | ``` 55 | 56 | 以上是时间复杂度为n的算法 57 | 58 | 由于题目提示还有时间复杂度更少的办法 所以做后续的尝试 59 | 60 | 采用分治法 进行处理 使 61 | 62 | 63 | ``` 64 | class Solution(object): 65 | def maxSubArray(self, nums): 66 | """ 67 | :type nums: List[int] 68 | :rtype: int 69 | """ 70 | if len(nums) == 1: 71 | return nums[0] 72 | left_max = self.maxSubArray(nums[:len(nums)/2]) 73 | right_max = self.maxSubArray(nums[len(nums)/2:]) 74 | 75 | right_tmp = nums[len(nums)/2] 76 | left_tmp = nums[len(nums)/2-1] 77 | 78 | sum = 0 79 | for num in reversed(nums[:len(nums)/2]): 80 | sum = sum + num 81 | if sum > left_tmp: 82 | left_tmp = sum 83 | 84 | 85 | sum = 0 86 | for num in nums[len(nums)/2:]: 87 | sum = sum + num 88 | if sum > right_tmp: 89 | right_tmp = sum 90 | 91 | if right_tmp < 0 and left_tmp < 0: 92 | return max(left_max, right_max, right_tmp, left_tmp) 93 | else: 94 | return max(left_max, right_max, right_tmp+left_tmp) 95 | ``` 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /problems/0004.median-of-two-sorted-arrays/README.md: -------------------------------------------------------------------------------- 1 | # 两个排序数组的中位数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/median-of-two-sorted-arrays) 4 | 5 | ## 题目 6 | 7 | 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。 8 | 9 | 请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。 10 | 11 | 示例 1: 12 | ``` 13 | nums1 = [1, 3] 14 | nums2 = [2] 15 | ``` 16 | 中位数是 2.0 17 | 18 | 示例 2: 19 | ``` 20 | nums1 = [1, 2] 21 | nums2 = [3, 4] 22 | ``` 23 | 中位数是 (2 + 3)/2 = 2.5 24 | 25 | 26 | ## 思路 27 | 28 | https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/ 29 | 30 | 中位数的定义:将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。此时元素较小的子集的最大值和元素较大的子集的最小值的平均值就是中位数 31 | 32 | 提到时间复杂度为O(log(m+n))的算法,很容易想到的就是二分查找,所以现在要做的就是在两个排序数组中进行二分查找。 33 | 34 | 具体思路如下,可以将问题转化为在两个数组中找第K个大的数,先在两个数组中分别找出第k/2大的数,再比较这两个第k/2大的数,这里假设两个数组为A,B。那么比较结果会有下面几种情况: 35 | 1. A[k/2]=B[k/2],那么第k大的数就是A[k/2] 36 | 2. A[k/2]>B[k/2],那么第k大的数肯定在A[0:k/2+1]和B[k/2:]中,这样就将原来的所有数的总和减少到一半了,再在这个范围里面找第k/2大的数即可,这样也达到了二分查找的区别了。 37 | 3. A[k/2] < B[k/2],那么第k大的数肯定在B[0:k/2+1]和A[k/2:]中,同理在这个范围找第k/2大的数就可以了。 38 | 39 | ## 代码 40 | 41 | ### Python 42 | ```java 43 | public class Solution { 44 | 45 | public double findMedianSortedArrays(int[] nums1, int[] nums2) { 46 | int n = nums1.length; 47 | int m = nums2.length; 48 | int left = (n + m + 1) / 2; 49 | int right = (n + m + 2) / 2; 50 | //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。 51 | return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5; 52 | } 53 | 54 | private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) { 55 | int len1 = end1 - start1 + 1; 56 | int len2 = end2 - start2 + 1; 57 | // 让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 58 | if (len1 > len2) { 59 | return getKth(nums2, start2, end2, nums1, start1, end1, k); 60 | } 61 | if (len1 == 0) { 62 | return nums2[start2 + k - 1]; 63 | } 64 | 65 | if (k == 1) { 66 | return Math.min(nums1[start1], nums2[start2]); 67 | } 68 | 69 | int i = start1 + Math.min(len1, k / 2) - 1; 70 | int j = start2 + Math.min(len2, k / 2) - 1; 71 | 72 | if (nums1[i] > nums2[j]) { 73 | return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1)); 74 | } else { 75 | return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1)); 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /problems/0003.longest-substring-without-repeating-characters/README.md: -------------------------------------------------------------------------------- 1 | 2 | # [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/) 3 | 4 | 5 | 给定一个字符串,找出不含有重复字符的最长子串的长度。 6 | 7 | 示例: 8 | 9 | 给定 `"abcabcbb"` ,没有重复字符的最长子串是 `"abc"` ,那么长度就是3。 10 | 11 | 给定 `"bbbbb"` ,最长的子串就是 `"b"` ,长度是1。 12 | 13 | 给定 `"pwwkew"` ,最长子串是 `"wke"` ,长度是3。请注意答案必须是一个子串,`"pwke"` 是 子序列 而不是子串。 14 | 15 | 16 | # 解法 17 | 18 | 19 | ## 思路(时间复杂度为O(n)) 20 | 21 | 1. 遍历字符串,过程中将出现过的字符存入字典,key为字符,value为字符下标 22 | 2. 用maxLength保存遍历过程中找到的最大不重复子串的长度 23 | 3. 用start保存最长子串的开始下标 24 | 4. 如果字符已经出现在字典中,更新start的值 25 | 5. 如果字符不在字典中,更新maxLength的值 26 | 6. return maxLength 27 | 28 | ## Python 29 | 30 | ```python 31 | class problem.Solution(object): 32 | def lengthOfLongestSubstring(self, s): 33 | """ 34 | :type s: str 35 | :rtype: int 36 | """ 37 | start = maxLength = 0 38 | usedChar = {} 39 | 40 | for i in range(len(s)): 41 | if s[i] in usedChar and start <= usedChar[s[i]]: 42 | start = usedChar[s[i]] + 1 43 | else: 44 | maxLength = max(maxLength, i - start + 1) 45 | usedChar[s[i]] = i 46 | 47 | return maxLength 48 | 49 | ``` 50 | 51 | ## Golang 52 | 53 | ```golang 54 | func lengthOfLongestSubstring(s string) int { 55 | // location[s[i]] == j 表示: 56 | // s中第i个字符串,上次出现在s的j位置,所以,在s[j+1:i]中没有s[i] 57 | // location[s[i]] == -1 表示: s[i] 在s中第一次出现 58 | location := [256]int{} // 只有256长是因为,假定输入的字符串只有ASCII字符 59 | for i := range location { 60 | location[i] = -1 // 先设置所有的字符都没有见过 61 | } 62 | 63 | maxLen, left := 0, 0 64 | 65 | for i := 0; i < len(s); i++ { 66 | // 说明s[i]已经在s[left:i+1]中重复了 67 | // 并且s[i]上次出现的位置在location[s[i]] 68 | if location[s[i]] >= left { 69 | left = location[s[i]] + 1 // 在s[left:i+1]中去除s[i]字符及其之前的部分 70 | } else if i+1-left > maxLen { 71 | // fmt.Println(s[left:i+1]) 72 | maxLen = i + 1 - left 73 | } 74 | location[s[i]] = i 75 | } 76 | 77 | return maxLen 78 | } 79 | ``` 80 | 81 | 提交之后发现更有意思的方法 82 | 83 | ```golang 84 | func lengthOfLongestSubstring(s string) int { 85 | baseMap := make(map[byte]int) 86 | length, max, start := 0, 0, 0 87 | for k, v := range []byte(s) { 88 | if p, ok := baseMap[v]; ok && p >= start { 89 | start = p + 1 90 | } 91 | length = k - start + 1 92 | baseMap[v] = k 93 | if length > max { 94 | max = length 95 | } 96 | } 97 | return max 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /problems/0001.two-sum/README.md: -------------------------------------------------------------------------------- 1 | # 两数之和 2 | 3 | [原题链接](https://leetcode-cn.com/problems/two-sum/) 4 | 5 | ## 题目 6 | 7 | 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。 8 | 9 | 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。 10 | 11 | 示例: 12 | 13 | ```bash 14 | 给定 nums = [2, 7, 11, 15], target = 9 15 | 16 | 因为 nums[0] + nums[1] = 2 + 7 = 9 17 | 所以返回 [0, 1] 18 | ``` 19 | 20 | ## 解题思路 21 | 22 | 字典储存之前的状态,避免重复。 23 | 24 | 时间复杂度:O(n) 25 | 空间复杂度:O(1) 26 | 27 | ## 代码 28 | 29 | ### JAVA 30 | 31 | ```java 32 | import java.util.HashMap; 33 | import java.util.Map; 34 | 35 | class Solution { 36 | public int[] twoSum(final int[] nums, final int target) { 37 | if (nums.length <= 1) { 38 | return new int[] { -1, -1 }; 39 | } 40 | Map map = new HashMap<>(); 41 | for (int i = 0; i < nums.length; i++) { 42 | int res = target - nums[i]; 43 | if (map.containsKey(res)) { 44 | return new int[] { map.get(res), i }; 45 | } else { 46 | map.put(nums[i], i); 47 | } 48 | } 49 | return new int[] { -1, -1 }; 50 | } 51 | 52 | public static void main(String[] args) { 53 | int[] nums = new int[] { 2, 7, 11, 15 }; 54 | int[] ans = new problem.Solution().twoSum(nums, 9); 55 | System.out.println(ans[0]); 56 | System.out.println(ans[1]); 57 | // 0, 1 58 | } 59 | } 60 | ``` 61 | 62 | - 执行用时 : 3 ms , 在所有 Java 提交中击败了 91.28% 的用户 63 | - 内存消耗 : 41.4 MB , 在所有 Java 提交中击败了 5.02% 的用户 64 | 65 | ### Go 66 | 67 | ```go 68 | package main 69 | 70 | import "fmt" 71 | 72 | func twoSum(nums []int, target int) []int { 73 | maps := make(map[int]int, len(nums)) 74 | for i, j := range nums { 75 | if index, ok := maps[target-j]; ok { 76 | return []int{index, i} 77 | } 78 | maps[j] = i 79 | } 80 | return nil 81 | } 82 | func main() { 83 | nums := []int{2, 7, 11, 15} 84 | fmt.Println(twoSum(nums, 9)) 85 | } 86 | 87 | ``` 88 | 89 | - 执行用时 : 12 ms , 在所有 Go 提交中击败了 40.21% 的用户 90 | - 内存消耗 : 3.4 MB , 在所有 Go 提交中击败了 58.79% 的用户 91 | 92 | ### Python 93 | 94 | ```python 95 | class Solution: 96 | def twoSum(self, nums, target): 97 | if len(nums) <= 1: 98 | return False 99 | d = dict() 100 | for i in range(len(nums)): 101 | if nums[i] in d: 102 | return [d[nums[i]], i] 103 | else: 104 | d[target - nums[i]] = i 105 | ``` 106 | 107 | - 执行用时 : 28 ms , 在所有 Python3 提交中击败了 99.63% 的用户 108 | - 内存消耗 : 15.1 MB , 在所有 Python3 提交中击败了 5.00% 的用户 109 | -------------------------------------------------------------------------------- /problems/0322.coin-change/README.md: -------------------------------------------------------------------------------- 1 | # 零钱兑换 2 | 3 | [原题链接](https://leetcode-cn.com/problems/coin-change/) 4 | 5 | ## 题目 6 | 7 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: coins = [1, 2, 5], amount = 11 12 | 输出: 3 13 | 解释: 11 = 5 + 5 + 1 14 | ``` 15 | 16 | 示例 2: 17 | ```text 18 | 输入: coins = [2], amount = 3 19 | 输出: -1 20 | ``` 21 | 22 | 说明: 23 | - 你可以认为每种硬币的数量是无限的。 24 | 25 | # 题解 26 | 27 | 1. **确定 base case**,这个很简单,显然目标金额 `amount` 为 `0` 时算法返回 `0`,因为不需要任何硬币就已经凑出目标金额了。 28 | 2. **确定「状态」**,也就是原问题和子问题中会变化的变量。由于硬币数量无限,硬币的面额也是题目给定的,只有目标金额会不断地向 `base case` 靠近,所以唯一的「状态」就是目标金额 `amount`。 29 | 3. **确定「选择」**,也就是导致「状态」产生变化的行为。目标金额为什么变化呢,因为你在选择硬币,你每选择一枚硬币,就相当于减少了目标金额。所以说所有硬币的面值,就是你的「选择」。 30 | 4. **明确 dp 函数/数组的定义**。我们这里讲的是自顶向下的解法,所以会有一个递归的 `dp` 函数,一般来说函数的参数就是状态转移中会变化的量,也就是上面说到的「状态」;函数的返回值就是题目要求我们计算的量。就本题来说,状态只有一个,即「目标金额」,题目要求我们计算凑出目标金额所需的最少硬币数量。所以我们可以这样定义 dp 函数: 31 | 32 | dp(n) 的定义:输入一个目标金额 n,返回凑出目标金额 n 的最少硬币数量。 33 | 34 | 搞清楚上面这几个关键点,解法的伪码就可以写出来了: 35 | 36 | ```python 37 | # 伪码框架 38 | def coinChange(coins: List[int], amount: int): 39 | 40 | # 定义:要凑出金额 n,至少要 dp(n) 个硬币 41 | def dp(n): 42 | # 做选择,选择需要硬币最少的那个结果 43 | for coin in coins: 44 | res = min(res, 1 + dp(n - coin)) 45 | return res 46 | 47 | # 题目要求的最终结果是 dp(amount) 48 | return dp(amount) 49 | ``` 50 | 51 | 状态转义方程: 52 | 53 | ![](https://gblobscdn.gitbook.com/assets%2F-LrtQOWSnDdXhp3kYN4k%2Fsync%2F5381a5e30482682c1c6f111e882991113b8661f7.png?alt=media) 54 | 55 | 这里有个地方我不是很理解,就是 `i - coin` 56 | 57 | 举个例子,coins = [1, 2, 5], amount = 11 58 | 59 | 如果 i = 11 60 | 61 | 1. `dp[11] = min(dp[11], dp[11 - 1] + 1)` = `dp[11] = min(dp[11], dp[10] + 1)` 62 | 1. `dp[11] = min(dp[11], dp[11 - 2] + 1)` = `dp[11] = min(dp[11], dp[9] + 1)` 63 | 1. `dp[11] = min(dp[11], dp[11 - 5] + 1)` = `dp[11] = min(dp[11], dp[6] + 1)` 64 | 65 | 很显然,dp[11] 不是最小的,所以我们需要求dp[10]、dp[9]、dp[6]的值,可以快速算出分别是:2、4、2,最终答案是3。 66 | 67 | 再来形象一点的理解,蛋糕有11块,每次只能拿走 [1, 2, 5] 块,最少几次能拿完。每次拿走的选择有很多,但是也有限制,就是剩下的块数必须大于本次拿走的快速 68 | 69 | 70 | # 代码 71 | 72 | ### Java 73 | 74 | ```java 75 | class Solution { 76 | public int coinChange(int[] coins, int amount) { 77 | if (amount == 0) { 78 | return 0; 79 | } 80 | // 当目标金额为 i 时,至少需要 dp[i] 枚硬币凑出。 81 | int[] dp = new int[amount + 1]; 82 | dp[0] = 0; 83 | for (int i = 1; i < amount + 1; i++) { 84 | dp[i] = amount + 1; 85 | for (int coin : coins) { 86 | if (coin <= i) { 87 | dp[i] = Math.min(dp[i], dp[i - coin] + 1); 88 | } 89 | } 90 | } 91 | return dp[amount] == amount + 1 ? -1 : dp[amount]; 92 | } 93 | } 94 | ``` 95 | 96 | - 执行用时: 20 ms , 在所有 Java 提交中击败了 30.06% 的用户 97 | - 内存消耗: 39.2 MB , 在所有 Java 提交中击败了 77.92% 的用户 98 | -------------------------------------------------------------------------------- /problems/0724.find-pivot-index/README.md: -------------------------------------------------------------------------------- 1 | # 寻找数组的中心索引 2 | 3 | [原题链接](https://leetcode-cn.com/problems/find-pivot-index/) 4 | 5 | ## 题目 6 | 7 | 给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。 8 | 9 | 我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。 10 | 11 | 如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。 12 | 13 | 14 | 示例 1: 15 | ```text 16 | 输入: 17 | nums = [1, 7, 3, 6, 5, 6] 18 | 输出:3 19 | 解释: 20 | 索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6 = 11) 相等。 21 | 同时, 3 也是第一个符合要求的中心索引。 22 | ``` 23 | 24 | 示例 2: 25 | ```text 26 | 输入: 27 | nums = [1, 2, 3] 28 | 输出:-1 29 | 解释: 30 | 数组中不存在满足此条件的中心索引。 31 | ``` 32 | 33 | 34 | 说明: 35 | - nums 的长度范围为 [0, 10000]。 36 | - 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。 37 | 38 | # 题解 39 | 40 | 一个多月没刷题了,以后要坚持下来。 41 | 42 | 自己想不出来方法,看的答案,发现核心的方法是"前缀和"。以后但凡是需要计算数组和的都要考虑"前缀和"这个知识点。 43 | 44 | 首先需要计算出数组元素相加总和`sum`,然后再次遍历数组,并记录从0到现在的总和`leftSum` 45 | 46 | 如果有 `sum - leftSum = n + leftSum` 那么就存在。一定要从左往右遍历 47 | 48 | ## 代码 49 | 50 | ### Java 51 | 52 | ```java 53 | class Solution { 54 | public int pivotIndex(int[] nums) { 55 | int n = nums.length; 56 | if (n == 0) { 57 | return -1; 58 | } 59 | int sum = 0; 60 | for (int num : nums) { 61 | sum += num; 62 | } 63 | int sumLeft = 0; 64 | for (int i = 0; i < n; i++) { 65 | if (sum - sumLeft * 2 == nums[i]) { 66 | return i; 67 | } 68 | sumLeft += nums[i]; 69 | } 70 | return -1; 71 | } 72 | } 73 | ``` 74 | 75 | - 执行用时: 1 ms , 在所有 Java 提交中击败了 100.00% 的用户 76 | - 内存消耗: 40.9 MB , 在所有 Java 提交中击败了 5.03% 的用户 77 | 78 | ### Go 79 | 80 | ```go 81 | func pivotIndex(nums []int) int { 82 | n := len(nums) 83 | if n == 0 { 84 | return -1 85 | } 86 | sum := 0 87 | for i := 0; i < n; i++ { 88 | sum += nums[i] 89 | } 90 | leftSum := 0 91 | for i := 0; i < n; i++ { 92 | if sum-leftSum*2 == nums[i] { 93 | return i 94 | } 95 | leftSum += nums[i] 96 | } 97 | return -1 98 | } 99 | ``` 100 | 101 | - 执行用时: 20 ms , 在所有 Go 提交中击败了 95.95% 的用户 102 | - 内存消耗: 6 MB , 在所有 Go 提交中击败了 47.44% 的用户 103 | 104 | ### Python 105 | 106 | 别使用`sum()`函数 107 | 108 | ```python 109 | class Solution: 110 | def pivotIndex(self, nums: List[int]) -> int: 111 | n = len(nums) 112 | if n == 0: 113 | return -1 114 | sum = 0 115 | for num in nums: 116 | sum += num 117 | left_sum = 0 118 | for i in range(n): 119 | if sum - left_sum * 2 == nums[i]: 120 | return i 121 | left_sum += nums[i] 122 | return -1 123 | ``` 124 | 125 | - 执行用时: 72 ms , 在所有 Python3 提交中击败了 57.97% 的用户 126 | - 内存消耗: 14.5 MB , 在所有 Python3 提交中击败了 90.35% 的用户 127 | -------------------------------------------------------------------------------- /problems/0098.validate-binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | # 验证二叉搜索树 2 | 3 | [原题链接](https://leetcode-cn.com/problems/validate-binary-search-tree/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 8 | 9 | 假设一个二叉搜索树具有如下特征: 10 | 1. 节点的左子树只包含小于当前节点的数。 11 | 2. 节点的右子树只包含大于当前节点的数。 12 | 3. 所有左子树和右子树自身必须也是二叉搜索树。 13 | 14 | 示例 1: 15 | ```text 16 | 输入: 17 | 2 18 | / \ 19 | 1 3 20 | 输出: true 21 | ``` 22 | 23 | 示例 2: 24 | ```text 25 | 输入: 26 | 5 27 | / \ 28 | 1 4 29 |   / \ 30 |   3 6 31 | 输出: false 32 | 解释: 输入为: [5,1,4,null,null,3,6]。 33 |   根节点的值为 5 ,但是其右子节点值为 4 。 34 | ``` 35 | 36 | ## 题解 37 | 38 | ### 解法一 39 | 40 | 可以利用它本身的性质来做,即 左 < 根 < 右,初始化时带入系统最大值和最小值,在递归过程中换成它们自己的节点值。 41 | 用 long 代替 int 就是为了包括 int 的边界条件。 42 | 43 | ### 解法二 44 | 45 | 中序遍历+数组 46 | 47 | ### 解法三 48 | 49 | 栈+中序遍历 50 | 51 | ## 代码 52 | 53 | ```java 54 | class Solution { 55 | public boolean isValidBST(TreeNode root) { 56 | if (root == null) { 57 | return true; 58 | } 59 | return valid(root, Long.MIN_VALUE, Long.MAX_VALUE); 60 | } 61 | 62 | private boolean valid(TreeNode root, long min, long max) { 63 | if (root == null) { 64 | return true; 65 | } 66 | if (root.val <= min || root.val >= max) { 67 | return false; 68 | } 69 | return valid(root.left, min, root.val) && valid(root.right, root.val, max); 70 | } 71 | } 72 | ``` 73 | 74 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 75 | 内存消耗 : 39.7 MB , 在所有 Java 提交中击败了 5.80% 的用户 76 | 77 | ### Go 78 | 79 | ```go 80 | func isValidBST(root *TreeNode) bool { 81 | var list []int 82 | helper(root, &list) 83 | for i := 1; i < len(list); i++ { 84 | if list[i] <= list[i-1] { 85 | return false 86 | } 87 | } 88 | return true 89 | } 90 | 91 | func helper(root *TreeNode, list *[]int) { 92 | if root == nil { 93 | return 94 | } 95 | helper(root.Left, list) 96 | *list = append(*list, root.Val) 97 | helper(root.Right, list) 98 | } 99 | ``` 100 | 101 | - 执行用时 : 4 ms , 在所有 Go 提交中击败了 98.53% 的用户 102 | - 内存消耗 : 5.9 MB , 在所有 Go 提交中击败了 40.00% 的用户 103 | 104 | ### Python 105 | 106 | ```python 107 | class Solution: 108 | def isValidBST(self, root: TreeNode) -> bool: 109 | if not root: 110 | return True 111 | pre = None 112 | stack = list() 113 | while root or stack: 114 | if root: 115 | stack.append(root) 116 | root = root.left 117 | else: 118 | root = stack.pop() 119 | if pre and pre.val >= root.val: 120 | return False 121 | pre = root 122 | root = root.right 123 | return True 124 | ``` 125 | 126 | - 执行用时 : 52 ms , 在所有 Python3 提交中击败了 80.27% 的用户 127 | - 内存消耗 : 16.2 MB , 在所有 Python3 提交中击败了 9.52% 的用户 128 | -------------------------------------------------------------------------------- /problems/0045.jump-game-ii/README.md: -------------------------------------------------------------------------------- 1 | # 跳跃游戏 II 2 | 3 | [原题链接](https://leetcode-cn.com/problems/jump-game-ii/) 4 | 5 | ## 题目 6 | 7 | 给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。 8 | 9 | 示例: 10 | ```text 11 | 输入: [2,3,1,1,4] 12 | 输出: 2 13 | 解释: 跳到最后一个位置的最小跳跃数是 2。 14 |   从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 15 | ``` 16 | 17 | 说明: 假设你总是可以到达数组的最后一个位置。 18 | 19 | ## 题解 20 | 21 | 绝对的贪心是鼠目寸光,但是如果加上下一次的贪心就是最佳解法。每次跳跃的时候考虑这次跳跃后和下一次跳跃能到达的最远距离 22 | 23 | 第一次跳跃,nums[i] = 2,有两种选择,1或2(所有的跳跃都不考虑0,0没有任何意义) 24 | 25 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/lmgCuw.png) 26 | 27 | 如果选择1 28 | 29 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/5GIraw.png) 30 | 31 | 如果选择2 32 | 33 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/19RoZ8.png) 34 | 35 | 如何在当前节点选择最远的跳跃,`i + nums[i]`,刚开始没想明白,`i`表示的是前面,`nums[i]`表示的是后面 36 | 37 | ## 代码 38 | 39 | ### Java 40 | 41 | ```java 42 | class Solution { 43 | public int jump(int[] nums) { 44 | if (nums.length < 2) { 45 | return 0; 46 | } 47 | // 已跳跃次数 48 | int times = 0; 49 | // 已到达的索引位置 50 | int reached = 0; 51 | // 最大能跳跃到的索引位置 52 | int max = 0; 53 | for (int i = 0; i < nums.length; i++) { 54 | // 已到达的索引位置小于遍历到的位置 55 | if (reached < i) { 56 | times++; 57 | reached = max; 58 | } 59 | // 当前可跳范围内,可以达到的的更远的位置 60 | max = Math.max(max, i + nums[i]); 61 | } 62 | return times; 63 | } 64 | } 65 | ``` 66 | 67 | - 执行用时 : 2 ms , 在所有 Java 提交中击败了 94.93% 的用户 68 | - 内存消耗 : 41.8 MB , 在所有 Java 提交中击败了 5.00% 的用户 69 | 70 | ### Go 71 | 72 | ```go 73 | func jump(nums []int) int { 74 | n := len(nums) 75 | max := 0 76 | step := 0 77 | reached := 0 78 | for i := 0; i < n-1; i++ { 79 | max = maxNum(max, i+nums[i]) 80 | if reached == i { 81 | step++ 82 | reached = max 83 | } 84 | } 85 | return step 86 | } 87 | 88 | func maxNum(a, b int) int { 89 | if a > b { 90 | return a 91 | } 92 | return b 93 | } 94 | ``` 95 | 96 | - 执行用时 : 8 ms , 在所有 Go 提交中击败了 95.88% 的用户 97 | - 内存消耗 : 4.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 98 | 99 | > 如果先更新最远距离,则无需遍历到最后一位。只需要到最后第二位即可 100 | 101 | ### Python 102 | 103 | ```python 104 | class Solution: 105 | def jump(self, nums: List[int]) -> int: 106 | step, max_num, reached = 0, 0, 0 107 | for i in range(len(nums) - 1): 108 | max_num = max(max_num, i + nums[i]) 109 | if reached == i: 110 | reached = max_num 111 | step += 1 112 | return step 113 | ``` 114 | 115 | - 执行用时 : 52 ms , 在所有 Python3 提交中击败了 88.09% 的用户 116 | - 内存消耗 : 15.3 MB , 在所有 Python3 提交中击败了 12.50% 的用户 117 | 118 | --- 119 | 120 | 这道题还是有难度的,刚开始我想的是用动态规划,但是找不到状态转移方程。最后看到答案用的贪心法,刷新了自己对贪心的理解。 121 | 122 | -------------------------------------------------------------------------------- /problems/1431.kids-with-the-greatest-number-of-candies/README.md: -------------------------------------------------------------------------------- 1 | # 对称二叉树 2 | 3 | [原题链接](https://leetcode-cn.com/problems/symmetric-tree/) 4 | 5 | ## 题目 6 | 7 | 给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。 8 | 9 | 对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。 10 | 11 | 示例 1: 12 | ```text 13 | 输入:candies = [2,3,5,1,3], extraCandies = 3 14 | 输出:[true,true,true,false,true] 15 | 解释: 16 | 孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。 17 | 孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。 18 | 孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。 19 | 孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。 20 | 孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。 21 | ``` 22 | 23 | 示例 2: 24 | 25 | ```text 26 | 输入:candies = [4,2,1,1,2], extraCandies = 1 27 | 输出:[true,false,false,false,false] 28 | 解释:只有 1 个额外糖果,所以不管额外糖果给谁,只有孩子 1 可以成为拥有糖果最多的孩子。 29 | 示例 3: 30 | 31 | 输入:candies = [12,1,12], extraCandies = 10 32 | 输出:[true,false,true] 33 | ``` 34 | 35 | 提示: 36 | 1. 2 <= candies.length <= 100 37 | 2. 1 <= candies[i] <= 100 38 | 3. 1 <= extraCandies <= 50 39 | 40 | ## 题解 41 | 42 | 把题目读三遍,就知道怎么写了。 43 | 44 | 估计因为今天是儿童节所以才来这么简单的题目。 45 | 46 | ## 代码 47 | 48 | ### Java 49 | 50 | ```java 51 | class Solution { 52 | public List kidsWithCandies(int[] candies, int extraCandies) { 53 | List ans = new ArrayList<>(); 54 | int max = candies[0]; 55 | for (int candy : candies) { 56 | if (candy > max) { 57 | max = candy; 58 | } 59 | } 60 | for (int candy : candies) { 61 | ans.add(candy + extraCandies >= max); 62 | } 63 | return ans; 64 | } 65 | } 66 | ``` 67 | 68 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 99.84% 的用户 69 | - 内存消耗 : 40 MB , 在所有 Java 提交中击败了 100.00% 的用户 70 | 71 | ### Go 72 | 73 | ```go 74 | func kidsWithCandies(candies []int, extraCandies int) []bool { 75 | var ans []bool 76 | max := candies[0] 77 | for _, v := range candies { 78 | if v > max { 79 | max = v 80 | } 81 | } 82 | for _, v := range candies { 83 | if v + extraCandies >= max { 84 | ans = append(ans, true) 85 | } else { 86 | ans = append(ans, false) 87 | } 88 | } 89 | return ans 90 | } 91 | ``` 92 | 93 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 94 | - 内存消耗 : 2.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 95 | 96 | ### Python 97 | 98 | ```python 99 | class Solution: 100 | def kidsWithCandies(self, candies: List[int], extraCandies: int) -> List[bool]: 101 | ans = [] 102 | max_num = max(candies) 103 | for candy in candies: 104 | if candy + extraCandies >= max_num: 105 | ans.append(True) 106 | else: 107 | ans.append(False) 108 | return ans 109 | ``` 110 | 111 | - 执行用时 : 40 ms , 在所有 Python3 提交中击败了 76.94% 的用户 112 | - 内存消耗 : 13.7 MB , 在所有 Python3 提交中击败了 100.00% 的用户 113 | 114 | -------------------------------------------------------------------------------- /problems/0560.subarray-sum-equals-k/README.md: -------------------------------------------------------------------------------- 1 | # 和为K的子数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/subarray-sum-equals-k/) 4 | 5 | ## 题目 6 | 7 | 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 8 | 9 | 示例 1 : 10 | - 输入:nums = [1,1,1], k = 2 11 | - 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。 12 | 13 | 说明 : 14 | - 数组的长度为 [1, 20,000]。 15 | - 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。 16 | 17 | ## 题解 18 | 19 | 能想到的只有两次遍历,类似 20 | 21 | ```python 22 | class Solution: 23 | def subarraySum(self, nums: List[int], k: int) -> int: 24 | n = len(nums) 25 | if n == 0: 26 | return 0 27 | ans = 0 28 | for i in range(n): 29 | tmp = nums[i] 30 | if tmp == k: 31 | ans += 1 32 | for j in range(i + 1, n): 33 | tmp += nums[j] 34 | if tmp == k: 35 | ans += 1 36 | return ans 37 | ``` 38 | 39 | 但是肯定会超时的,怎么也想不出来,去看答案。 40 | 41 | 42 | 1. 建立map表用于存储每个连续子数组sum求和出现的次数,初始化为(0,1),表示和为0的连续子数组出现1次 43 | 2. sum的值是在对nums数组的循环中不断累加当前元素的,res的值则需要查找map中是否已存在`sum-k`的元素,也就是在查找此前所有从0项开始累加的连续子项和中有没有`sum-k` 44 | 3. 如果有的话,则说明从该项到当前项的连续子数组和必定为k,那么res则可以和这个sum的对应值,即这个sum出现的次数,相加得到新的res 45 | 4. 对于当前sum如果已存在与map中则其对应值+1,不存在则添加新项,初始值为1 46 | 47 | 算法思想:前缀和+哈希表 48 | 49 | ## 代码 50 | 51 | ### Java 52 | 53 | ```java 54 | class Solution { 55 | public int subarraySum(int[] nums, int k) { 56 | Map map = new HashMap<>(); 57 | map.put(0, 1); 58 | int sum = 0, ans = 0; 59 | for (int num : nums) { 60 | sum += num; 61 | if (map.containsKey(sum - k)) { 62 | ans += map.get(sum - k); 63 | } 64 | map.put(sum, map.getOrDefault(sum, 0) + 1); 65 | } 66 | return ans; 67 | } 68 | } 69 | ``` 70 | 71 | - 执行用时 : 22 ms , 在所有 Java 提交中击败了 68.49% 的用户 72 | - 内存消耗 : 40.2 MB , 在所有 Java 提交中击败了 11.54% 的用户 73 | 74 | 75 | ### Go 76 | ```go 77 | func subarraySum(nums []int, k int) int { 78 | count := 0 79 | sumMap := map[int]int{} 80 | sum := 0 81 | sumMap[0] = 1 82 | for _, v := range nums { 83 | sum += v 84 | if sumMap[sum - k] > 0 { 85 | count += sumMap[sum - k] 86 | } 87 | sumMap[sum]++ 88 | } 89 | return count 90 | } 91 | ``` 92 | 93 | - 执行用时 : 28 ms , 在所有 Go 提交中击败了 45.19% 的用户 94 | - 内存消耗 : 6.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 95 | 96 | ### Python 97 | 98 | ```python 99 | class Solution: 100 | def subarraySum(self, nums: List[int], k: int) -> int: 101 | d = dict() 102 | d[0] = 1 103 | count, ans = 0, 0 104 | for i in range(len(nums)): 105 | count += nums[i] 106 | if count - k in d: 107 | ans += d[count - k] 108 | d[count] = d.get(count, 0) + 1 109 | return ans 110 | ``` 111 | 112 | - 执行用时 : 72 ms , 在所有 Python3 提交中击败了 96.89% 的用户 113 | - 内存消耗 : 16.1 MB , 在所有 Python3 提交中击败了 11.11% 的用户 114 | 115 | -------------------------------------------------------------------------------- /problems/0009.PalindromeNumber/README.md: -------------------------------------------------------------------------------- 1 | # 回文数 2 | 3 | [问题链接](https://leetcode-cn.com/problems/palindrome-number/description/) 4 | 5 | ## 题目 6 | 7 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入: 121 13 | 输出: true 14 | ``` 15 | 16 | **示例 2:** 17 | 18 | ``` 19 | 输入: -121 20 | 输出: false 21 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 22 | ``` 23 | 24 | **示例 3:** 25 | 26 | ``` 27 | 输入: 10 28 | 输出: false 29 | 解释: 从右向左读, 为 01 。因此它不是一个回文数。 30 | ``` 31 | 32 | **进阶:** 33 | 34 | 你能不将整数转为字符串来解决这个问题吗? 35 | 36 | ## 题解 37 | 38 | 最简单常规的思路当然是转化为字符串,然后双指针判断是否相等,这肯定不是一种好方法。 39 | 40 | 可以新建一个变量去表示数字转换的结果 41 | 42 | ## 代码 43 | 44 | ### Java 45 | 46 | ```java 47 | class Solution { 48 | public boolean isPalindrome(int x) { 49 | if (x < 0) { 50 | return false; 51 | } 52 | int y = 0, tmp = x; 53 | while (tmp != 0) { 54 | y = y * 10 + tmp % 10; 55 | tmp = tmp / 10; 56 | } 57 | return x == y; 58 | } 59 | } 60 | ``` 61 | 62 | - 执行用时 : 9 ms , 在所有 Java 提交中击败了 99.06% 的用户 63 | - 内存消耗 : 39.3 MB , 在所有 Java 提交中击败了 5.14% 的用户 64 | 65 | 提交后发现一种方法,简化了计算步骤 66 | 67 | ```java 68 | class Solution { 69 | public boolean isPalindrome(int x) { 70 | if (x < 0 || x % 10 == 0 && x != 0) { 71 | return false; 72 | } 73 | int reversed = 0; 74 | while (x > reversed) { 75 | reversed = reversed * 10 + x % 10; 76 | x /= 10; 77 | } 78 | return x == reversed || x == reversed / 10; 79 | } 80 | } 81 | ``` 82 | 83 | - 执行用时 : 9 ms , 在所有 Java 提交中击败了 99.06% 的用户 84 | - 内存消耗 : 39.3 MB , 在所有 Java 提交中击败了 5.14% 的用户 炫耀一下: 85 | 86 | ## Go 87 | 88 | ```go 89 | func isPalindrome(x int) bool { 90 | if x < 0 || x%10 == 0 && x != 0 { 91 | return false 92 | } 93 | reversed := 0 94 | for x > reversed { 95 | reversed = reversed*10 + x%10 96 | x /= 10 97 | } 98 | return x == reversed || x == reversed/10 99 | } 100 | ``` 101 | 102 | - 执行用时 : 8 ms , 在所有 Go 提交中击败了 97.86% 的用户 103 | - 内存消耗 : 5.2 MB , 在所有 Go 提交中击败了 88.00% 的用户 104 | 105 | ### Python 106 | 107 | ```python 108 | class Solution: 109 | def isPalindrome(self, x: int) -> bool: 110 | if x < 0 or x % 10 == 0 and x != 0: 111 | return False 112 | reversed_num = 0 113 | while x > reversed_num: 114 | reversed_num = reversed_num * 10 + x % 10 115 | x //= 10 116 | return x == reversed_num or x == reversed_num // 10 117 | ``` 118 | 119 | - 执行用时 : 84 ms , 在所有 Python3 提交中击败了 70.72% 的用户 120 | - 内存消耗 : 13.7 MB , 在所有 Python3 提交中击败了 5.88% 的用户 121 | 122 | 看看这种解法 123 | ```python 124 | class Solution: 125 | def isPalindrome(self, x: int) -> bool: 126 | return str(x) == str(x)[::-1] 127 | ``` 128 | 129 | - 执行用时 : 80 ms , 在所有 Python3 提交中击败了 80.17% 的用户 130 | - 内存消耗 : 13.5 MB , 在所有 Python3 提交中击败了 5.88% 的用户 131 | 132 | > 我不服 133 | -------------------------------------------------------------------------------- /problems/0572.subtree-of-another-tree/README.md: -------------------------------------------------------------------------------- 1 | # 另一个树的子树 2 | 3 | [原题链接](https://leetcode-cn.com/problems/subtree-of-another-tree/) 4 | 5 | ## 题目 6 | 7 | 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 8 | 9 | 示例 1: 10 | 给定的树 s: 11 | ```text 12 | 3 13 | / \ 14 | 4 5 15 | / \ 16 | 1 2 17 | ``` 18 | 19 | 给定的树 t: 20 | ```text 21 | 4 22 | / \ 23 | 1 2 24 | ``` 25 | 26 | 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。 27 | 28 | 示例 2: 29 | 30 | 给定的树 s: 31 | ```text 32 | 3 33 | / \ 34 | 4 5 35 | / \ 36 | 1 2 37 | / 38 | 0 39 | ``` 40 | 41 | 给定的树 t: 42 | ```text 43 | 4 44 | / \ 45 | 1 2 46 | ``` 47 | 48 | 返回 false。 49 | 50 | ## 题解 51 | 52 | 这题肯定与二叉树的遍历有关,正好去学习下二叉树的遍历方法。 53 | 54 | 二叉树定义 55 | 56 | ```java 57 | static class TreeNode { 58 | int val; 59 | TreeNode left; 60 | TreeNode right; 61 | 62 | public TreeNode(int x) { 63 | val = x; 64 | } 65 | } 66 | ``` 67 | 68 | ![a1rbGN](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/a1rbGN.jpg) 69 | 70 | 递归先序遍历 71 | 72 | ```java 73 | public static void recursionPreorderTraversal(TreeNode root) { 74 | if (root != null) { 75 | System.out.print(root.val + " "); 76 | recursionPreorderTraversal(root.left); 77 | recursionPreorderTraversal(root.right); 78 | } 79 | } 80 | ``` 81 | 82 | 递归中序遍历 83 | 84 | ```java 85 | public static void recursionMiddleorderTraversal(TreeNode root) { 86 | if (root != null) { 87 | recursionMiddleorderTraversal(root.left); 88 | System.out.print(root.val + " "); 89 | recursionMiddleorderTraversal(root.right); 90 | } 91 | } 92 | ``` 93 | 94 | 递归后序遍历 95 | 96 | ```java 97 | public static void recursionPostorderTraversal(TreeNode root) { 98 | if (root != null) { 99 | recursionPostorderTraversal(root.left); 100 | recursionPostorderTraversal(root.right); 101 | System.out.print(root.val + " "); 102 | } 103 | } 104 | ``` 105 | 106 | 递归方法比较简单,也好理解。还有非递归办法,利用栈去储存,这种方法以后再说。 107 | 108 | ## 代码 109 | 110 | ### Java 111 | 112 | ```java 113 | class Solution { 114 | public boolean isSubtree(TreeNode s, TreeNode t) { 115 | if (s == null || t == null) { 116 | return false; 117 | } 118 | return hasSubtree(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); 119 | } 120 | 121 | public boolean hasSubtree(TreeNode s, TreeNode t) { 122 | if (s == null && t == null) { 123 | return true; 124 | } 125 | if (s == null || t == null) { 126 | return false; 127 | } 128 | if (s.val == t.val) { 129 | return hasSubtree(s.left, t.left) && hasSubtree(s.right, t.right); 130 | } 131 | return false; 132 | } 133 | } 134 | ``` 135 | 136 | - 执行用时 : 7 ms , 在所有 Java 提交中击败了 93.76% 的用户 137 | - 内存消耗 : 40.3 MB , 在所有 Java 提交中击败了 40.00% 的用户 138 | 139 | https://juejin.im/post/5b8d64346fb9a01a1d4f99fa 140 | 141 | 142 | > 重新定义简单 143 | -------------------------------------------------------------------------------- /problems/0238.product-of-array-except-self/README.md: -------------------------------------------------------------------------------- 1 | # 除自身以外数组的乘积 2 | 3 | [原题链接](https://leetcode-cn.com/problems/product-of-array-except-self/) 4 | 5 | ## 题目 6 | 7 | 给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 8 | 9 | 示例: 10 | ```text 11 | 输入: [1,2,3,4] 12 | 输出: [24,12,8,6] 13 | ``` 14 | 15 | 提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。 16 | 17 | 说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 18 | 19 | 进阶:你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。) 20 | 21 | ## 题解 22 | 23 | 正常人的思路 24 | 25 | ```java 26 | public class Solution { 27 | public int[] productExceptSelf(int[] nums) { 28 | int count = 1; 29 | for (int num : nums) { 30 | count *= num; 31 | } 32 | int[] res = new int[nums.length]; 33 | for (int i = 0; i < nums.length; i++) { 34 | res[i] = count / nums[i]; 35 | } 36 | return res; 37 | } 38 | } 39 | ``` 40 | 41 | 肯定是不符合要求的。 42 | 43 | 看了答案才知道可以使用`左右乘积列表`,对于给定索引 i,我们将使用它左边所有数字的乘积乘以右边所有数字的乘积,就是我们想要的答案。 44 | 45 | 算法 46 | 1. 初始化 answer 数组,对于给定索引 i,answer[i] 代表的是 i 左侧所有数字的乘积。 47 | 2. 构造 answer 作为方法一数组。满足题目要求,空间复杂度 $O(1)$ 48 | 3. 首先遍历nums数组来更新左边乘积 49 | 4. 再次遍历nums数组来更新右边乘积 50 | 51 | ## 题解 52 | 53 | ### Java 54 | 55 | ```java 56 | class Solution { 57 | public int[] productExceptSelf(int[] nums) { 58 | int[] res = new int[nums.length]; 59 | int left = 1, right = 1; 60 | int len = nums.length; 61 | for (int i = 0; i < len; i++) { 62 | // 此时数组存储的是除去当前元素左边的元素乘积 63 | res[i] = left; 64 | left *= nums[i]; 65 | } 66 | for (int j = len - 1; j >= 0; j--) { 67 | // right为该数右边的乘积。 68 | res[j] *= right; 69 | right *= nums[j]; 70 | } 71 | return res; 72 | } 73 | } 74 | ``` 75 | 76 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 100.00% 的用户 77 | - 内存消耗 : 48.3 MB , 在所有 Java 提交中击败了 11.76% 的用户 78 | 79 | ### Go 80 | 81 | ```go 82 | func productExceptSelf(nums []int) []int { 83 | left, right := 1, 1 84 | length := len(nums) 85 | ans := make([]int, length) 86 | for i := 0; i < length; i++ { 87 | ans[i] = left 88 | left *= nums[i] 89 | } 90 | for j := length - 1; j >= 0; j-- { 91 | ans[j] *= right 92 | right *= nums[j] 93 | } 94 | return ans 95 | } 96 | ``` 97 | 98 | - 执行用时 : 12 ms , 在所有 Go 提交中击败了 93.26% 的用户 99 | - 内存消耗 : 6.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 100 | 101 | ### Python 102 | 103 | ```python 104 | class Solution: 105 | def productExceptSelf(self, nums: List[int]) -> List[int]: 106 | left, right = 1, 1 107 | length = len(nums) 108 | ans = [0 for _ in range(length)] 109 | for i in range(length): 110 | ans[i] = left 111 | left *= nums[i] 112 | for j in range(length - 1, -1, -1): 113 | ans[j] *= right 114 | right *= nums[j] 115 | return ans 116 | ``` 117 | 118 | - 执行用时 : 48 ms , 在所有 Python3 提交中击败了 96.80% 的用户 119 | - 内存消耗 : 17.9 MB , 在所有 Python3 提交中击败了 100.00% 的用户 120 | -------------------------------------------------------------------------------- /problems/0046II.ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/README.md: -------------------------------------------------------------------------------- 1 | # 螺旋矩阵 2 | 3 | [原题链接](https://leetcode-cn.com/problems/spiral-matrix/) 4 | 5 | ## 题目 6 | 7 | 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: 12258 12 | 输出: 5 13 | 解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi" 14 | ``` 15 | 16 | 提示:0 <= num < 231 17 | 18 | ## 题解 19 | 20 | ### 动态规划 21 | 22 | 首先我们来通过一个例子理解一下这里「翻译」的过程:我们来尝试翻译「1402」。 23 | 24 | 分成两种情况: 25 | 1. 首先我们可以把每一位单独翻译,即 [1, 4, 0, 2][1,4,0,2],翻译的结果是 beac 26 | 2. 然后我们考虑组合某些连续的两位: 27 | 1. [14, 0, 2][14,0,2],翻译的结果是 oac。 28 | 2. [1, 40, 2][1,40,2],这种情况是不合法的,因为 4040 不能翻译成任何字母。 29 | 3. [1, 4, 02][1,4,02],这种情况也是不合法的,含有前导零的两位数不在题目规定的翻译规则中,那么 [14, 02][14,02] 显然也是不合法的。 30 | 31 | 那么我们可以归纳出翻译的规则,字符串的第 ii 位置: 32 | 1. 可以单独作为一位来翻译 33 | 2. 如果第 i - 1i−1 位和第 ii 位组成的数字在 1010 到 2525 之间,可以把这两位连起来翻译 34 | 35 | 到这里,我们发现它和「198. 打家劫舍」非常相似。我们可以用 f(i) 表示以第 i 位结尾的前缀串翻译的方案数,考虑第 i 位单独翻译和与前一位连接起来再翻译对 f(i) 的贡献。 36 | 单独翻译对 f(i) 的贡献为 f(i - 1);如果第 i−1 位存在,并且第 i−1 位和第 i 位形成的数字 x 满足 10≤x≤25,那么就可以把第 i−1 位和第 i 位连起来一起翻译,对 f(i) 的贡献为 f(i−2),否则为 0。 37 | 38 | 我们可以列出这样的动态规划转移方程: 39 | 40 | `f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]` 41 | 42 | 43 | ## 代码 44 | 45 | ### Java 动态规划 46 | 47 | ```java 48 | class Solution { 49 | public int translateNum(int num) { 50 | String src = String.valueOf(num); 51 | int p = 0, q = 0, r = 1; 52 | for (int i = 0; i < src.length(); i++) { 53 | p = q; 54 | q = r; 55 | r = 0; 56 | r += q; 57 | if (i == 0) { 58 | continue; 59 | } 60 | String pre = src.substring(i - 1, i + 1); 61 | if (pre.compareTo("25") <= 0 && pre.compareTo("10") >= 0) { 62 | r += p; 63 | } 64 | } 65 | return r; 66 | } 67 | } 68 | ``` 69 | 70 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 71 | - 内存消耗 : 36.5 MB , 在所有 Java 提交中击败了 100.00% 的用户 72 | 73 | ### Go 动态规划 74 | ```go 75 | func translateNum(num int) int { 76 | stc := strconv.Itoa(num) 77 | p, q, r := 0, 0, 1 78 | for i := 0; i < len(stc); i++ { 79 | p, q, r = q, r, 0 80 | r += q 81 | if i == 0 { 82 | continue 83 | } 84 | pre := stc[i-1 : i+1] 85 | if pre <= "25" && pre >= "10" { 86 | r += p 87 | } 88 | } 89 | return r 90 | } 91 | ``` 92 | 93 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 94 | - 内存消耗 : 1.9 MB , 在所有 Go 提交中击败了 100.00% 的用户 95 | 96 | 97 | ### Python 动态规划 98 | 99 | ```python 100 | class Solution: 101 | def translateNum(self, num: int) -> int: 102 | string = str(num) 103 | p, q, r = 0, 0, 1 104 | for i in range(len(string)): 105 | p, q, r = q, r, 0 106 | r += q 107 | if i == 0: 108 | continue 109 | pre = string[i - 1:i + 1] 110 | if '10' <= pre <= '25': 111 | r += p 112 | return r 113 | ``` 114 | 115 | - 执行用时 : 36 ms , 在所有 Python3 提交中击败了 83.43% 的用户 116 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 100.00% 的用户 117 | -------------------------------------------------------------------------------- /problems/0152.maximum-product-subarray/README.md: -------------------------------------------------------------------------------- 1 | # 乘积最大子数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/maximum-product-subarray/) 4 | 5 | ## 题目 6 | 7 | 给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 8 | 9 | 示例 1: 10 | 11 | 输入: [2,3,-2,4] 12 | 输出: 6 13 | 解释: 子数组 [2,3] 有最大乘积 6。 14 | 15 | 示例 2: 16 | 17 | 输入: [-2,0,-1] 18 | 输出: 0 19 | 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 20 | 21 | ## 题解 22 | 23 | 本题是 [最大子序列](https://leetcode-cn.com/problems/maximum-subarray) 的加强版本,之前一题是求最大区间和,而本题是求最大区间乘积。 24 | 25 | 两题最本质的区别是在对负数的处理上,求和时,加上一个负数,总和一定变小,而乘上一个负数,结果可能变大也可能变小(负数乘以负数结果会变大)。 26 | 27 | 因此我们需要新建2个变量,一个代表区间内最大乘积max(可能为负),另一个代表区间内最小乘积min(可能为负),首元素区间的最大乘积与最小乘积均为该数字本身。我们从数组第二个数字开始循环,当前数字与前区间能形成的最大乘积,一定在下面三个元素中产生: 28 | 29 | 1. 当前数字num自身 30 | 2. 当前数字num * max 31 | 3. 当前数字num * min 32 | 33 | 用三个数的最大值更新max,最小值更新min,用max更新全局最大区间乘积,然后继续向后循环,找到一个全局最大区间乘积即可。 34 | 35 | ## 代码 36 | 37 | ### Java 38 | 39 | ```java 40 | class Solution { 41 | public int maxProduct(int[] nums) { 42 | int n = nums.length; 43 | if (n == 0) { 44 | return 0; 45 | } 46 | int max = nums[0], min = nums[0]; 47 | int ans = max; 48 | for (int i = 1; i < n; i++) { 49 | if (nums[i] < 0) { 50 | int tmp = max; 51 | max = min; 52 | min = tmp; 53 | } 54 | max = Math.max(max * nums[i], nums[i]); 55 | min = Math.min(min * nums[i], nums[i]); 56 | ans = Math.max(ans, max); 57 | } 58 | return ans; 59 | } 60 | } 61 | ``` 62 | 63 | - 执行用时 : 2 ms , 在所有 Java 提交中击败了 92.56% 的用户 64 | - 内存消耗 : 39.9 MB , 在所有 Java 提交中击败了 6.67% 的用户 65 | 66 | ### Go 67 | 68 | ```go 69 | func maxProduct(nums []int) int { 70 | n := len(nums) 71 | if n == 0 { 72 | return 0 73 | } 74 | ans, tmp_max, tmp_min := nums[0], nums[0], nums[0] 75 | for i := 1; i < n; i++ { 76 | if nums[i] < 0 { 77 | tmp_max, tmp_min = tmp_min, tmp_max 78 | } 79 | tmp_max = max(tmp_max * nums[i], nums[i]) 80 | tmp_min = min(tmp_min * nums[i], nums[i]) 81 | ans = max(ans, tmp_max) 82 | } 83 | return ans 84 | } 85 | 86 | func max(a, b int) int { 87 | if a > b { 88 | return a 89 | } 90 | return b 91 | } 92 | 93 | func min(a, b int) int { 94 | if a > b { 95 | return b 96 | } 97 | return a 98 | } 99 | ``` 100 | 101 | - 执行用时 : 4 ms , 在所有 Go 提交中击败了 88.22% 的用户 102 | - 内存消耗 : 2.7 MB , 在所有 Go 提交中击败了 100.00% 的用户 103 | 104 | ### Python 105 | 106 | ```python 107 | class Solution: 108 | def maxProduct(self, nums: List[int]) -> int: 109 | n = len(nums) 110 | if n == 0: 111 | return 0 112 | ans, tmp_max, tmp_min = nums[0], nums[0], nums[0] 113 | for i in range(1, n): 114 | # 如果为负数、调换位置 115 | if nums[i] < 0: 116 | tmp_max, tmp_min = tmp_min, tmp_max 117 | tmp_max = max(tmp_max * nums[i], nums[i]) 118 | tmp_min = min(tmp_min * nums[i], nums[i]) 119 | ans = max(ans, tmp_max) 120 | return ans 121 | ``` 122 | 123 | - 执行用时 : 48 ms , 在所有 Python3 提交中击败了 83.97% 的用户 124 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 25.00% 的用户 125 | 126 | -------------------------------------------------------------------------------- /problems/0102.binary-tree-level-order-traversal/README.md: -------------------------------------------------------------------------------- 1 | # 二叉树的层序遍历 2 | 3 | [原题链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 4 | 5 | ## 题目 6 | 7 | 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 8 | 9 | 示例: 10 | 二叉树:[3,9,20,null,null,15,7], 11 | ```text 12 | 3 13 | / \ 14 | 9 20 15 | / \ 16 | 15 7 17 | ``` 18 | 19 | 返回其层次遍历结果: 20 | ```text 21 | [ 22 | [3], 23 | [9,20], 24 | [15,7] 25 | ] 26 | ``` 27 | 28 | ## 题解 29 | 30 | 这两天正好在复习二叉树专题,这题很简单,用队列即可实现。 31 | 32 | ## 代码 33 | 34 | ### Java 35 | 36 | ```java 37 | class Solution { 38 | public List> levelOrder(TreeNode root) { 39 | List> ans = new ArrayList<>(); 40 | if (root == null) { 41 | return ans; 42 | } 43 | Queue queue = new LinkedList<>(); 44 | queue.add(root); 45 | TreeNode cur; 46 | int size; 47 | 48 | while (!queue.isEmpty()) { 49 | size = queue.size(); 50 | List tmp = new ArrayList<>(); 51 | for (int i = 0; i < size; i++) { 52 | cur = queue.poll(); 53 | tmp.add(cur.val); 54 | if (cur.left != null) { 55 | queue.add(cur.left); 56 | } 57 | if (cur.right != null) { 58 | queue.add(cur.right); 59 | } 60 | } 61 | ans.add(tmp); 62 | } 63 | return ans; 64 | } 65 | } 66 | ``` 67 | 68 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 91.36% 的用户 69 | - 内存消耗 : 40.1 MB , 在所有 Java 提交中击败了 5.71% 的用户 70 | 71 | ### Go 72 | ```go 73 | 74 | func levelOrder(root *TreeNode) [][]int { 75 | var res [][]int 76 | if root == nil { 77 | return res 78 | } 79 | queue := []*TreeNode{root} 80 | for len(queue) != 0 { 81 | size := len(queue) 82 | var tmp []int 83 | for i := 0; i < size; i++ { 84 | cur := queue[i] 85 | tmp = append(tmp, cur.Val) 86 | if cur.Left != nil { 87 | queue = append(queue, cur.Left) 88 | } 89 | if cur.Right != nil { 90 | queue = append(queue, cur.Right) 91 | } 92 | } 93 | res = append(res, tmp) 94 | queue = queue[size:] 95 | } 96 | return res 97 | } 98 | ``` 99 | 100 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 101 | - 内存消耗 : 2.8 MB , 在所有 Go 提交中击败了 100.00% 的用户 102 | 103 | ### Python 104 | 105 | ```python 106 | 107 | class Solution: 108 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 109 | ans = [] 110 | if root is None: 111 | return ans 112 | queue = [root] 113 | while len(queue) > 0: 114 | size = len(queue) 115 | tmp = [] 116 | for i in range(size): 117 | cur = queue[i] 118 | tmp.append(cur.val) 119 | if cur.left is not None: 120 | queue.append(cur.left) 121 | if cur.right is not None: 122 | queue.append(cur.right) 123 | ans.append(tmp) 124 | queue = queue[size:] 125 | return ans 126 | ``` 127 | 128 | - 执行用时 : 40 ms , 在所有 Python3 提交中击败了 75.19% 的用户 129 | - 内存消耗 : 14 MB , 在所有 Python3 提交中击败了 7.14% 的用户 130 | 131 | -------------------------------------------------------------------------------- /problems/0050.powx-n/README.md: -------------------------------------------------------------------------------- 1 | # Pow(x, n) 2 | 3 | [原题链接](https://leetcode-cn.com/problems/powx-n/) 4 | 5 | ## 题目 6 | 7 | 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: 2.00000, 10 12 | 输出: 1024.00000 13 | ``` 14 | 15 | 示例 2: 16 | ```text 17 | 输入: 2.10000, 3 18 | 输出: 9.26100 19 | ``` 20 | 21 | 示例 3: 22 | ```text 23 | 输入: 2.00000, -2 24 | 输出: 0.25000 25 | 解释: 2-2 = 1/22 = 1/4 = 0.25 26 | ``` 27 | 28 | 说明: 29 | 1. -100.0 < x < 100.0 30 | 2. n 是 32 位有符号整数,其数值范围是 [$−2^31$, $2^31$ − 1] 。 31 | 32 | ## 题解 33 | 34 | 以下内容来自官方题解 35 | 36 | ### 快速幂 + 递归 37 | 38 | 「快速幂算法」的本质是分治算法。举个例子,如果我们要计算 $x^{64}x$ ,我们可以按照: 39 | 40 | x $\to$$ x^2$ $\to$ $x^4$ $\to$ x^8$ \to$ $x^{16}$ $\to$$ x^{32}$$ \to$$ x^{64}$的顺序,从 $x$ 开始,每次直接把上一次的结果进行平方,计算 6 次就可以得到 $x^{64}$的值,而不需要对 x 乘 63 次 x。 41 | 42 | 再举一个例子,如果我们要计算 $x^{77}$ ,我们可以按照: 43 | 44 | $x$$ \to$$ x^2$$ \to$$ x^4$$ \to$$ x^9$$ \to$$ x^{19}$$ \to$$ x^{38}$$ \to$$ x^{77}$的顺序,在 $x$$ \to$$ x^2$,$x^2$$ \to$$ x^4$ ,$x^{19}$$ \to$$ x^{38}$这 些步骤中,我们直接把上一次的结果进行平方,而在 $x^4$$ \to$$ x^9$ ,$x^9$$ \to$$ x^{19}$,$x^{38}$$ \to$$ x^{77}$ 45 | 这些步骤中,我们把上一次的结果进行平方后,还要额外乘一个 x。 46 | 47 | 直接从左到右进行推导看上去很困难,因为在每一步中,我们不知道在将上一次的结果平方之后,还需不需要额外乘 x。但如果我们从右往左看,分治的思想就十分明显了: 48 | 49 | - 当我们要计算 $x^n$ 时,我们可以先递归地计算出$ y = x^{\lfloor n/2 \rfloor}$,其中$ \lfloor a \rfloor$ 表示对 $a$ 进行下取整; 50 | - 根据递归计算的结果,如果 $n$ 为偶数,那么$ x^n = y^2$x ;如果 $n$ 为奇数,那么 $x^n = y^2 * x$; 51 | - 递归的边界为$n=0$,任意数的 0 次方均为 1。 52 | 53 | 由于每次递归都会使得指数减少一半,因此递归的层数为 $O(\log n)$,算法可以在很快的时间内得到结果。 54 | 55 | ## 代码 56 | 57 | ### java 58 | ```java 59 | class Solution { 60 | public double myPow(double x, int n) { 61 | long N = n; 62 | if (n >= 0) { 63 | return quickMul(x, N); 64 | } else { 65 | return 1.0 / quickMul(x, -N); 66 | } 67 | } 68 | 69 | public double quickMul(double x, long N) { 70 | if (N == 0) { 71 | return 1.0; 72 | } 73 | double y = quickMul(x, N / 2); 74 | if (N % 2 == 0) { 75 | return y * y; 76 | } else { 77 | return y * y * x; 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 94.50% 的用户 84 | - 内存消耗 : 37.3 MB , 在所有 Java 提交中击败了 5.88% 的用户 85 | 86 | ### Go 87 | 88 | ```go 89 | func myPow(x float64, n int) float64 { 90 | if n >= 0 { 91 | return quickMul(x, n) 92 | } 93 | return 1.0 / quickMul(x, -n) 94 | } 95 | 96 | func quickMul(x float64, n int) float64 { 97 | if n == 0 { 98 | return 1 99 | } 100 | y := quickMul(x, n/2) 101 | if n%2 == 0 { 102 | return y * y 103 | } 104 | return y * y * x 105 | } 106 | ``` 107 | 108 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 109 | - 内存消耗 : 2 MB , 在所有 Go 提交中击败了 100.00% 的用户 110 | 111 | ### Python 112 | ```python 113 | class Solution: 114 | def myPow(self, x: float, n: int) -> float: 115 | def quickMul(N): 116 | if N == 0: 117 | return 1 118 | y = quickMul(N // 2) 119 | if N % 2 == 0: 120 | return y * y 121 | else: 122 | return y * y * x 123 | 124 | return quickMul(n) if n >= 0 else 1.0 / quickMul(-n) 125 | ``` 126 | 127 | - 执行用时 : 44 ms , 在所有 Python3 提交中击败了 46.34% 的用户 128 | - 内存消耗 : 13.9 MB , 在所有 Python3 提交中击败了 8.33% 的用户 129 | 130 | -------------------------------------------------------------------------------- /problems/0202.happy-number/README.md: -------------------------------------------------------------------------------- 1 | # 快乐数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/happy-number/) 4 | 5 | ## 题目 6 | 7 | 编写一个算法来判断一个数 n 是不是快乐数。 8 | 9 | 「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。 10 | 11 | 如果 n 是快乐数就返回 True ;不是,则返回 False 。 12 | 13 | 示例: 14 | ```text 15 | 输入:19 16 | 输出:true 17 | 解释: 18 | 12 + 92 = 82 19 | 82 + 22 = 68 20 | 62 + 82 = 100 21 | 12 + 02 + 02 = 1 22 | ``` 23 | 24 | ## 题解 25 | 26 | 看到这个题目,没有想多了,递归计算就行了,用map存储已经出现过的数字,如果有重复就不是,然后代码就写出来了 27 | 28 | ```java 29 | class Solution { 30 | public boolean isHappy(int n) { 31 | Map map = new HashMap<>(); 32 | map.put(n, n); 33 | return isHappy(n, map); 34 | } 35 | 36 | private boolean isHappy(int n, Map map) { 37 | String[] strings = String.valueOf(n).split(""); 38 | int ans = 0; 39 | for (String string : strings) { 40 | ans += Integer.parseInt(string) * Integer.parseInt(string); 41 | } 42 | if (ans == 1) { 43 | return true; 44 | } 45 | if (map.containsKey(ans)) { 46 | return false; 47 | } 48 | map.put(ans, ans); 49 | return isHappy(ans, map); 50 | } 51 | } 52 | ``` 53 | 54 | - 执行用时 : 11 ms , 在所有 Java 提交中击败了 5.01% 的用户 55 | - 内存消耗 : 39.9 MB , 在所有 Java 提交中击败了 8.33% 的用户 56 | 57 | 懵逼了,啥情况,然后去群里看,别人都在讨论双指针、环,我就知道这道题最优解法肯定不会这么暴力。难点就在于如何转为一个环,想不出来啊 58 | 59 | 1. 利用快慢指针思想, 慢指针每次做一次转换, 快指针每次做两次转换 60 | 2. 如果出现无限循环, 那么快慢指针一定相遇 61 | 62 | ## 代码 63 | 64 | ### Java 65 | 66 | ```java 67 | class Solution { 68 | public boolean isHappy(int n) { 69 | // 利用快慢指针思想, 慢指针每次做一次转换, 快指针每次做两次转换 70 | // 如果出现无限循环, 那么快慢指针一定相遇 71 | int slow = trans(n), fast = trans(trans(n)); 72 | while (slow != fast) { 73 | slow = trans(slow); 74 | fast = trans(trans(fast)); 75 | } 76 | return slow == 1; 77 | } 78 | 79 | private int trans(int n) { 80 | int res = 0; 81 | while (n != 0) { 82 | int tmp = n % 10; 83 | res += tmp * tmp; 84 | n /= 10; 85 | } 86 | return res; 87 | } 88 | } 89 | ``` 90 | 91 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 99.89% 的用户 92 | - 内存消耗 : 36.2 MB , 在所有 Java 提交中击败了 8.33% 的用户 93 | 94 | 最快乐的算法如下,数学发现只有出现数字1或者4会循环(不推荐) 95 | 96 | ```java 97 | class Solution { 98 | public boolean isHappy(int n) { 99 | while (n != 1 && n != 4) { 100 | int sum = 0; 101 | while (n != 0) { 102 | sum += (n % 10) * (n % 10); 103 | n /= 10; 104 | } 105 | n = sum; 106 | } 107 | return n == 1; 108 | } 109 | } 110 | ``` 111 | 112 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 99.89% 的用户 113 | - 内存消耗 : 36.4 MB , 在所有 Java 提交中击败了 8.33% 的用户 114 | 115 | 还有看到用这种方法的 116 | 117 | ```java 118 | func isHappy(n int) bool { 119 | for i := 0; i < 100; i++ { 120 | ans := 0 121 | for n > 0 { 122 | ans += (n % 10) * (n % 10) 123 | n /= 10 124 | } 125 | n = ans 126 | if n == 1 { 127 | return true 128 | } 129 | } 130 | return false 131 | } 132 | ``` 133 | 134 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 135 | - 内存消耗 : 2 MB , 在所有 Go 提交中击败了 100.00% 的用户 136 | 137 | 果然很快乐 138 | 139 | -------------------------------------------------------------------------------- /problems/0680.valid-palindrome-ii/README.md: -------------------------------------------------------------------------------- 1 | # 验证回文字符串 Ⅱ 2 | 3 | [原题链接](https://leetcode-cn.com/problems/valid-palindrome-ii/) 4 | 5 | ## 题目 6 | 7 | 给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。 8 | 9 | 示例 1: 10 | 11 | - 输入: "aba" 12 | - 输出: True 13 | 14 | 示例 2: 15 | 16 | - 输入: "abca" 17 | - 输出: True 18 | 19 | 解释: 你可以删除c字符。 20 | 21 | 注意: 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。 22 | 23 | ## 题解 24 | 25 | 最开始想到的是遍历每一个字符,去掉该字符,再判断是否回文串。代码 26 | 27 | ```python 28 | class Solution: 29 | def validPalindrome(self, s: str) -> bool: 30 | if self.isPalindrome(s): 31 | return True 32 | for i in range(len(s)): 33 | tmp = s[:i] + s[i + 1:] 34 | if self.isPalindrome(tmp): 35 | return True 36 | return False 37 | 38 | def isPalindrome(self, s): 39 | if len(s) == 0: 40 | return True 41 | i, j = 0, len(s) - 1 42 | while i < j: 43 | if s[i] != s[j]: 44 | return False 45 | i += 1 46 | j -= 1 47 | return True 48 | ``` 49 | 50 | 毫无疑问的,超时了。 51 | 52 | 1. 双指针,用子函数判断当前字符串是否符合回文串。 53 | 2. 两个指针从两头向中间聚拢,如果遇到字母不相同,判断左指针右移一位或者右指针左移一位是否构成回文串。 54 | 55 | ## 代码 56 | 57 | ### Java 58 | 59 | ```java 60 | class Solution { 61 | public boolean validPalindrome(String s) { 62 | int i = 0, j = s.length() - 1; 63 | while (i < j) { 64 | if (s.charAt(i) != s.charAt(j)) { 65 | return valid(s, i + 1, j) || valid(s, i, j - 1); 66 | } 67 | i++; 68 | j--; 69 | } 70 | return true; 71 | } 72 | 73 | private boolean valid(String s, int i, int j) { 74 | while (i < j) { 75 | if (s.charAt(i) != s.charAt(j)) { 76 | return false; 77 | } 78 | i++; 79 | j--; 80 | } 81 | return true; 82 | } 83 | } 84 | ``` 85 | 86 | - 执行用时 : 10 ms , 在所有 Java 提交中击败了 50.20% 的用户 87 | - 内存消耗 : 40.5 MB , 在所有 Java 提交中击败了 6.67% 的用户 88 | 89 | ### Go 90 | 91 | ```go 92 | 93 | func validPalindrome(s string) bool { 94 | i, j := 0, len(s)-1 95 | for i < j { 96 | if s[i] != s[j] { 97 | return valid(s, i, j-1) || valid(s, i+1, j) 98 | } 99 | i++ 100 | j-- 101 | } 102 | return true 103 | } 104 | 105 | func valid(s string, i, j int) bool { 106 | for i < j { 107 | if s[i] != s[j] { 108 | return false 109 | } 110 | i++ 111 | j-- 112 | } 113 | return true 114 | } 115 | ``` 116 | 117 | - 执行用时 : 20 ms , 在所有 Go 提交中击败了 72.02% 的用户 118 | - 内存消耗 : 6.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 119 | 120 | ### Python 121 | 122 | ```python 123 | class Solution: 124 | def validPalindrome(self, s: str) -> bool: 125 | if s == s[::-1]: 126 | return True 127 | i, j = 0, len(s) - 1 128 | while i < j: 129 | if s[i] == s[j]: 130 | i += 1 131 | j -= 1 132 | else: 133 | return self.valid(s, i + 1, j) or self.valid(s, i, j - 1) 134 | 135 | def valid(self, s, i, j): 136 | while i < j: 137 | if s[i] != s[j]: 138 | return False 139 | i += 1 140 | j -= 1 141 | return True 142 | ``` 143 | 144 | - 执行用时 : 164 ms , 在所有 Python3 提交中击败了 49.12% 的用户 145 | - 内存消耗 : 14.1 MB , 在所有 Python3 提交中击败了 36.36% 的用户 146 | 147 | -------------------------------------------------------------------------------- /problems/0221.maximal-square/README.md: -------------------------------------------------------------------------------- 1 | # 最大正方形 2 | 3 | [原题链接](https://leetcode-cn.com/problems/maximal-square/) 4 | 5 | ## 题目 6 | 7 | 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 8 | 9 | 示例: 10 | ~~~~ 11 | 输入: 12 | ```text 13 | 1 0 1 0 0 14 | 1 0 1 1 1 15 | 1 1 1 1 1 16 | 1 0 0 1 0 17 | ``` 18 | 19 | 输出: 4 20 | 21 | ## 题解 22 | 23 | 二维数组`dp[i][j]`表示在`[i][j]`时最大的正方形,如何判断正方形? 24 | 25 | 1. `dp[0][0]` = 0 26 | 2. 如果`dp[i-1][j]`,`dp[i][j-1]`,`dp[i-1][j-1]` 中存在一个等于0,那么就等于`dp[i][j]` 27 | 3. 如果`dp[i-1][j]`,`dp[i][j-1]`,`dp[i-1][j-1]` 中全部等于1,那么就等于`dp[i-1][j-1] + 1` 28 | 29 | 这道题理解上有误区,`[['1']]`返回答案是1,因为1也是正方形,我擦。 30 | 31 | `dp[i][j]`表示以第i行第j列为右下角所能构成的最大正方形边长, 则递推式为: 32 | `dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])` 33 | 34 | ## 代码 35 | 36 | ### Java 37 | 38 | ```java 39 | class Solution { 40 | public int maximalSquare(char[][] matrix) { 41 | int row = matrix.length; 42 | if (row < 1) { 43 | return 0; 44 | } 45 | int high = matrix[0].length; 46 | int[][] dp = new int[row + 1][high + 1]; 47 | dp[0][0] = 0; 48 | int max = 0; 49 | for (int i = 1; i <= row; i++) { 50 | for (int j = 1; j <= high; j++) { 51 | if (matrix[i - 1][j - 1] == '1') { 52 | dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])); 53 | if (dp[i][j] > max) { 54 | max = dp[i][j]; 55 | } 56 | } 57 | } 58 | } 59 | return max * max; 60 | } 61 | } 62 | ``` 63 | 64 | - 执行用时 : 6 ms , 在所有 Java 提交中击败了 87.05% 的用户 65 | - 内存消耗 : 42.3 MB , 在所有 Java 提交中击败了 68.75% 的用户 66 | 67 | ### Go 68 | 69 | ```go 70 | func maximalSquare(matrix [][]byte) int { 71 | row := len(matrix) 72 | if row < 1 { 73 | return 0 74 | } 75 | high := len(matrix[0]) 76 | dp := make([][]int, row+1) 77 | for i := range dp { 78 | dp[i] = make([]int, high+1) 79 | } 80 | max := 0 81 | for i := 1; i <= row; i++ { 82 | for j := 1; j <= high; j++ { 83 | if matrix[i-1][j-1] == '1' { 84 | dp[i][j] = 1 + min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) 85 | if dp[i][j] > max { 86 | max = dp[i][j] 87 | } 88 | } 89 | } 90 | } 91 | return max * max 92 | } 93 | 94 | func min(a, b, c int) int { 95 | if a > b { 96 | if b > c { 97 | return c 98 | } else { 99 | return b 100 | } 101 | } else { 102 | if a > c { 103 | return c 104 | } else { 105 | return a 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 112 | - 内存消耗 : 4.3 MB , 在所有 Go 提交中击败了 11.11% 的用户 113 | 114 | ### Python 115 | 116 | ```python 117 | class Solution: 118 | def maximalSquare(self, matrix: List[List[str]]) -> int: 119 | m = len(matrix) 120 | if m == 0: 121 | return 0 122 | n = len(matrix[0]) 123 | dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] 124 | num = 0 125 | for i in range(1, m + 1): 126 | for j in range(1, n + 1): 127 | if matrix[i - 1][j - 1] == '1': 128 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 129 | num = max(num, dp[i][j]) 130 | return num * num 131 | ``` 132 | 133 | - 执行用时 : 100 ms , 在所有 Python3 提交中击败了 65.58% 的用户 134 | - 内存消耗 : 14.4 MB , 在所有 Python3 提交中击败了 14.29% 的用户 135 | -------------------------------------------------------------------------------- /problems/0022.generate-parentheses/README.md: -------------------------------------------------------------------------------- 1 | # 括号生成 2 | 3 | [原题链接](https://leetcode-cn.com/problems/generate-parentheses/) 4 | 5 | ## 题目 6 | 7 | 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 8 | 9 | 示例: 10 | 11 | 输入:n = 3 12 | 输出: 13 | ```text 14 | [ 15 | "((()))", 16 | "(()())", 17 | "(())()", 18 | "()(())", 19 | "()()()" 20 | ] 21 | ``` 22 | 23 | ## 思路 24 | 25 | DFS + 递归,思考下过程: 26 | 1. 记录左括号与右括号出现的次数,left、right = n 27 | 2. 判断left左括号次数是否大于0,如果是添加"(" 28 | 3. 判断left与right大小,如果left小于right,添加")" 29 | 30 | 递归终止条件: left = 0 or right = 0 31 | 32 | ## 代码 33 | 34 | ```java 35 | package problem; 36 | 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | 40 | public class Solution { 41 | public List generateParenthesis(int n) { 42 | List ans = new ArrayList<>(); 43 | dfs("", n, n, ans); 44 | return ans; 45 | } 46 | 47 | private void dfs(String str, int left, int right, List ans) { 48 | // 左右括号都不剩余了,递归终止 49 | if (left == 0 && right == 0) { 50 | ans.add(str); 51 | return; 52 | } 53 | // 如果左括号还剩余的话,可以拼接左括号 54 | if (left > 0) { 55 | dfs(str + "(", left - 1, right, ans); 56 | } 57 | // 如果右括号剩余多于左括号剩余的话,可以拼接右括号 58 | if (left < right) { 59 | dfs(str + ")", left, right - 1, ans); 60 | } 61 | } 62 | 63 | public static void main(String[] args) { 64 | System.out.println(new Solution().generateParenthesis(3)); 65 | // [ 66 | // "((()))", 67 | // "(()())", 68 | // "(())()", 69 | // "()(())", 70 | // "()()()" 71 | // ] 72 | } 73 | } 74 | ``` 75 | 76 | - 执行用时 : 2 ms , 在所有 Java 提交中击败了 55.37% 的用户 77 | - 内存消耗 : 40.1 MB , 在所有 Java 提交中击败了 5.01% 的用户 78 | 79 | ### Python 80 | 81 | ```python 82 | class Solution: 83 | def generateParenthesis(self, n): 84 | ans = [] 85 | 86 | def dfs(string, left, right): 87 | # 如果左右括号都用完了 88 | if left == 0 and right == 0: 89 | ans.append(string) 90 | return 91 | # 如果还剩下左括号 92 | if left > 0: 93 | dfs(string + "(", left - 1, right) 94 | # 如果左括号小于右括号 95 | if left < right: 96 | dfs(string + ")", left, right - 1) 97 | dfs('', n, n) 98 | return ans 99 | 100 | 101 | if __name__ == '__main__': 102 | print(Solution().generateParenthesis(0)) 103 | ``` 104 | 105 | - 执行用时 : 40 ms , 在所有 Python3 提交中击败了 66.09% 的用户 106 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 5.03% 的用户 107 | 108 | ### Go 109 | 110 | ```go 111 | package main 112 | 113 | import "fmt" 114 | 115 | func generateParenthesis(n int) []string { 116 | var ans []string 117 | dfs("", n, n, &ans) 118 | return ans 119 | } 120 | 121 | func dfs(s string, left int, right int, ans *[]string) { 122 | if left == 0 && right == 0 { 123 | *ans = append(*ans, s) 124 | return 125 | } 126 | if left > 0 { 127 | dfs(s+"(", left-1, right, ans) 128 | } 129 | if left < right { 130 | dfs(s+")", left, right-1, ans) 131 | } 132 | } 133 | 134 | func main() { 135 | fmt.Println(generateParenthesis(3)) 136 | } 137 | 138 | ``` 139 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 140 | - 内存消耗 : 2.8 MB , 在所有 Go 提交中击败了 33.33% 的用户 141 | 142 | 不能使用全局变量,使用指针引用 143 | 144 | -------------------------------------------------------------------------------- /data_structure/binary_tree/手写算法.md: -------------------------------------------------------------------------------- 1 | ```java 2 | 3 | 4 | // 定义 5 | class TreeNode { 6 | int val; 7 | TreeNode left; 8 | TreeNode right; 9 | 10 | public TreeNode(int x) { 11 | val = x; 12 | } 13 | } 14 | 15 | // 递归前序 16 | public List preOrder1(TreeNode root) { 17 | List res = new ArrayList<>(); 18 | if (root == null) { 19 | return res; 20 | } 21 | pre(root, res); 22 | return res; 23 | } 24 | 25 | private void pre(TreeNode root, List res) { 26 | if (root != null) { 27 | res.add(root.val); 28 | pre(root.left); 29 | pre(root.right); 30 | } 31 | } 32 | 33 | // 非递归前序 34 | public List preOrder2(TreeNode root) { 35 | List res = new ArrayList<>(); 36 | if (root == null) { 37 | return res; 38 | } 39 | Stack stack = new Stack<>(); 40 | stack.push(root); 41 | TreeNode cur; 42 | while (!stack.isEmpty()) { 43 | cur = stack.pop(); 44 | res.add(cur.val); 45 | if (cur.right != null) { 46 | stack.push(cur.right); 47 | } 48 | if (cur.left != null) { 49 | stack.push(cur.left); 50 | } 51 | } 52 | return res; 53 | } 54 | 55 | 56 | // 递归中序 57 | public List inOrder1(TreeNode root) { 58 | List res = new ArrayList<>(); 59 | if (root == null) { 60 | return res; 61 | } 62 | in(root, res); 63 | return res; 64 | } 65 | 66 | private void in(TreeNode root, List res) { 67 | if (root != null) { 68 | in(root.left); 69 | res.add(root.val); 70 | in(root.right); 71 | } 72 | } 73 | 74 | // 非递归中序 75 | public List inOrder2(TreeNode root) { 76 | List res = new ArrayList<>(); 77 | if (root == null) { 78 | return res; 79 | } 80 | Stack stack = new Stack<>(); 81 | TreeNode cur; 82 | while (root != null || !stack.isEmpty()) { 83 | if (root != null) { 84 | stack.push(root); 85 | root = root.left; 86 | } else { 87 | cur = stack.pop(); 88 | res.add(cur.val); 89 | root = cur.right; 90 | } 91 | } 92 | return res; 93 | } 94 | 95 | 96 | // 递归后序 97 | public List postOrder1(TreeNode root) { 98 | List res = new ArrayList<>(); 99 | if (root == null) { 100 | return res; 101 | } 102 | post(root, res); 103 | return res; 104 | } 105 | 106 | private void post(TreeNode root, List res) { 107 | if (root != null) { 108 | post(root.left); 109 | post(root.right); 110 | res.add(root.val); 111 | } 112 | } 113 | 114 | // 非递归后序 115 | // 左-右-根 => 根-右-左 116 | public List postOrder2(TreeNode root) { 117 | List res = new ArrayList<>(); 118 | if (root == null) { 119 | return res; 120 | } 121 | Stack s1 = new Stack<>(); 122 | Stack s2 = new Stack<>(); 123 | s1.push(root); 124 | TreeNode cur = root; 125 | while (!s1.isEmpty()) { 126 | cur = s1.pop(); 127 | s2.push(cur); 128 | if (cur.left != null) { 129 | s1.push(cur.left); 130 | } 131 | if (cur.right != null) { 132 | s1.push(cur.right); 133 | } 134 | } 135 | while (!s2.isEmpty()) { 136 | cur = s2.pop(); 137 | res.add(cur.val); 138 | } 139 | return res; 140 | } 141 | ``` 142 | 143 | -------------------------------------------------------------------------------- /problems/0837.new-21-game/README.md: -------------------------------------------------------------------------------- 1 | # 新21点 2 | 3 | [原题链接](https://leetcode-cn.com/problems/new-21-game/) 4 | 5 | ## 题目 6 | 7 | 爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下: 8 | 9 | 爱丽丝以 0 分开始,并在她的得分少于 K 分时抽取数字。 10 | 11 | 抽取时,她从 [1, W] 的范围中随机获得一个整数作为分数进行累计,其中 W 是整数。 每次抽取都是独立的,其结果具有相同的概率。 12 | 13 | 当爱丽丝获得不少于 K 分时,她就停止抽取数字。 爱丽丝的分数不超过 N 的概率是多少? 14 | 15 | 示例 1: 16 | ```text 17 | 输入:N = 10, K = 1, W = 10 18 | 输出:1.00000 19 | 说明:爱丽丝得到一张卡,然后停止。 20 | ``` 21 | 22 | 示例 2: 23 | ```text 24 | 输入:N = 6, K = 1, W = 10 25 | 输出:0.60000 26 | 说明:爱丽丝得到一张卡,然后停止。在 W = 10 的 6 种可能下,她的得分不超过 N = 6 分。 27 | ``` 28 | 29 | 示例 3: 30 | ```text 31 | 输入:N = 21, K = 17, W = 10 32 | 输出:0.73278 33 | ``` 34 | 35 | 提示: 36 | 1. 0 <= K <= N <= 10000 37 | 2. 1 <= W <= 10000 38 | 3. 如果答案与正确答案的误差不超过 $10^-5$,则该答案将被视为正确答案通过。 39 | 4. 此问题的判断限制时间已经减少。 40 | 41 | ## 题解 42 | 43 | 直接去网上找题解 44 | 45 | --- 46 | 47 | 动态规划题目。 48 | 49 | dp[i]表示当目标为i时的概率,由于最后一次的选择的排数的范围为[i-w,i-1],这时可选的牌有[1,w],所以最后的点数和是[i,w+i-1],对应我们是要求大于等于K的数,所以最后的累加范围是[K,K+W-1],所以我们的结果便是返回dp[i](K<=i<=K+W-1)的和。 50 | 51 | 最后还有一个小技巧,由于最后的范围是[K,K+W-1]: 52 | 53 | 如果N>=K+W(意味着在最后一次累加和[K-W,K-1]选择时,无论选择[1,W]中的任何数,最后的累加和一定在[K,N]范围内,在这种条件下[K,K+W-1]是[K,N]的子集,所以直接返回1; 54 | 55 | 如果N= K + W) { 65 | return 1.0; 66 | } 67 | double[] dp = new double[N + 1]; 68 | double res = 0.0, sum = 0.0; 69 | for (int i = 1; i <= N; i++) { 70 | if (i <= W) { 71 | dp[i] = sum / W + 1.0 / W; 72 | } else { 73 | dp[i] = sum / W; 74 | } 75 | if (i < K) { 76 | sum += dp[i]; 77 | } 78 | if (i > W) { 79 | sum -= dp[i - W]; 80 | } 81 | if (i >= K) { 82 | res += dp[i]; 83 | } 84 | } 85 | return res; 86 | } 87 | } 88 | ``` 89 | 90 | - 执行用时 : 6 ms , 在所有 Java 提交中击败了 20.75% 的用户 91 | - 内存消耗 : 38.9 MB , 在所有 Java 提交中击败了 100.00% 的用户 92 | 93 | ### Go 94 | 95 | ```go 96 | func new21Game(N int, K int, W int) float64 { 97 | if K == 0 || N >= K+W { 98 | return 1 99 | } 100 | dp := make([]float64, N+1) 101 | res, sum := 0.0, 0.0 102 | for i := 1; i <= N; i++ { 103 | if i <= W { 104 | dp[i] = sum/float64(W) + 1.0/float64(W) 105 | } else { 106 | dp[i] = sum / float64(W) 107 | } 108 | if i < K { 109 | sum += dp[i] 110 | } 111 | if i > W { 112 | sum -= dp[i-W] 113 | } 114 | if i >= K { 115 | res += dp[i] 116 | } 117 | } 118 | return res 119 | } 120 | ``` 121 | 122 | - 执行用时 : 4 ms , 在所有 Go 提交中击败了 71.43% 的用户 123 | - 内存消耗 : 4.5 MB , 在所有 Go 提交中击败了 100.00% 的用户 124 | 125 | ### Python 126 | 127 | ```python 128 | class Solution: 129 | def new21Game(self, N: int, K: int, W: int) -> float: 130 | if K == 0 or N >= K + W: 131 | return 1 132 | dp = [0.0 for _ in range(N + 1)] 133 | res, cnt = 0.0, 0.0 134 | for i in range(1, N + 1): 135 | dp[i] = cnt / W + 1.0 / W if i <= W else cnt / W 136 | if i < K: 137 | cnt += dp[i] 138 | if i > W: 139 | cnt -= dp[i - W] 140 | if i >= K: 141 | res += dp[i] 142 | return res 143 | ``` 144 | 145 | - 执行用时 : 168 ms , 在所有 Python3 提交中击败了 16.91% 的用户 146 | - ``内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 100.00% 的用户 147 | -------------------------------------------------------------------------------- /problems/1111.maximum-nesting-depth-of-two-valid-parentheses-strings/README.md: -------------------------------------------------------------------------------- 1 | # 有效括号的嵌套深度 2 | 3 | [原题链接](https://leetcode-cn.com/problems/maximum-nesting-depth-of-two-valid-parentheses-strings/) 4 | 5 | ## 题目 6 | 7 | 有效括号字符串 仅由 "(" 和 ")" 构成,并符合下述几个条件之一: 8 | 9 | - 空字符串 10 | - 连接,可以记作 AB(A 与 B 连接),其中 A 和 B 都是有效括号字符串 11 | - 嵌套,可以记作 (A),其中 A 是有效括号字符串 12 | 13 | 类似地,我们可以定义任意有效括号字符串 s 的 嵌套深度 depth(S): 14 | 15 | - s 为空时,depth("") = 0 16 | - s 为 A 与 B 连接时,depth(A + B) = max(depth(A), depth(B)),其中 A 和 B 都是有效括号字符串 17 | - s 为嵌套情况,depth("(" + A + ")") = 1 + depth(A),其中 A 是有效括号字符串 18 | 19 | 例如:"","()()",和 "()(()())" 都是有效括号字符串,嵌套深度分别为 0,1,2,而 ")(" 和 "(()" 都不是有效括号字符串。  20 | 21 | 给你一个有效括号字符串 seq,将其分成两个不相交的子序列 A 和 B,且 A 和 B 满足有效括号字符串的定义(注意:A.length + B.length = seq.length)。 22 | 23 | 现在,你需要从中选出 任意 一组有效括号字符串 A 和 B,使 max(depth(A), depth(B)) 的可能取值最小。 24 | 25 | 返回长度为 seq.length 答案数组 answer ,选择 A 还是 B 的编码规则是:如果 seq[i] 是 A 的一部分,那么 answer[i] = 0。否则,answer[i] = 1。即便有多个满足要求的答案存在,你也只需返回 一个。 26 | 27 | 示例 1: 28 | 29 | 输入:seq = "(()())" 30 | 输出:[0,1,1,1,1,0] 31 | 32 | 示例 2: 33 | 34 | 输入:seq = "()(())()" 35 | 输出:[0,0,0,1,1,0,1,1] 36 | 37 | 提示:1 <= text.size <= 10000 38 | 39 | ## 题解 40 | 41 | 看到这么长的题目,根本就不想做,直接翻评论区,发现都在吐槽,题目看不懂。专门去英文版的看了下,85个👍,417个踩,国内版就让不开放踩的功能,可太真实了。 42 | 43 | 但是在评论区,还是看到有热心人在解释: 44 | 45 | ```text 46 | 尝试解释一下,抛砖引玉: 47 | Seq = ( ( ) ( ( ) ) ( ) ) 48 | 49 | 嵌套深度 = [ 1, 2, 2, 2, 3, 3, 2, 2, 2, 1] 50 | 51 | 分组情况 = [ A, B, B, B, A, A, B, B, B, A] 52 | 53 | 输出 = [ 0, 1, 1, 1, 0, 0, 1, 1, 1, 0] 54 | 55 | 其中把A或B下标的括号单独抽出来,均为合法括号 56 | 57 | 按深度奇偶分组,原理请看题解:) 58 | ``` 59 | 60 | 61 | ## 代码 62 | 63 | ### JAVA 64 | 65 | ```java 66 | package problem; 67 | 68 | import java.util.Arrays; 69 | 70 | public class Solution { 71 | 72 | public int[] maxDepthAfterSplit(String seq) { 73 | int[] ans = new int[seq.length()]; 74 | int d = 0; 75 | for (char c : seq.toCharArray()) { 76 | if (c == '(') { 77 | ans[d++] = d & 1; 78 | } else { 79 | ans[d++] = (d + 1) & 1; 80 | } 81 | } 82 | return ans; 83 | } 84 | 85 | public static void main(String[] args) { 86 | System.out.println(Arrays.toString(new Solution().maxDepthAfterSplit("()(())()"))); 87 | } 88 | } 89 | ``` 90 | 91 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 100.00% 的用户 92 | - 内存消耗 : 40 MB , 在所有 Java 提交中击败了 7.14% 的用户 93 | 94 | ### Python 95 | 96 | ```python 97 | class Solution: 98 | def maxDepthAfterSplit(self, seq: str): 99 | ans = [] 100 | d = 0 101 | for c in seq: 102 | if c == '(': 103 | d += 1 104 | ans.append(d % 2) 105 | if c == ')': 106 | ans.append(d % 2) 107 | d -= 1 108 | return ans 109 | 110 | 111 | if __name__ == '__main__': 112 | print(Solution().maxDepthAfterSplit("()(())()")) 113 | ``` 114 | 115 | - 执行用时 : 56 ms , 在所有 Python3 提交中击败了 52.83% 的用户 116 | - 内存消耗 : 14 MB , 在所有 Python3 提交中击败了 16.67% 的用户 117 | 118 | ### go 119 | 120 | ```go 121 | package main 122 | 123 | import "fmt" 124 | 125 | func maxDepthAfterSplit(seq string) []int { 126 | var ans []int 127 | d := 0 128 | for _, i := range seq { 129 | if i == '(' { 130 | d++ 131 | ans = append(ans, d%2) 132 | } 133 | if i == ')' { 134 | ans = append(ans, d%2) 135 | d-- 136 | } 137 | } 138 | return ans 139 | } 140 | 141 | func main() { 142 | fmt.Println(maxDepthAfterSplit("()(())()")) 143 | } 144 | ``` 145 | 146 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 147 | - 内存消耗 : 3.7 MB , 在所有 Go 提交中击败了 25.00% 的用户 148 | 149 | 150 | --- 151 | 152 | 失败、失败中的失败 153 | -------------------------------------------------------------------------------- /sword_to_offer/1.实现单例模式.md: -------------------------------------------------------------------------------- 1 | # 题目描述 2 | 3 | 设计一个类,我们只能生成该类的一个实例。 4 | 5 | # 解答方法 6 | 7 | ## 1. 饿汉式(线程安全) 8 | 9 | ```java 10 | public class Singleton { 11 | 12 | /** 13 | * 私有静态变量 14 | * 直接实例化 15 | * 丢失了延迟实例化带来的节约资源的优势 16 | */ 17 | private static Singleton singleton = new Singleton(); 18 | 19 | private Singleton() { 20 | } 21 | 22 | public static Singleton getSingleton() { 23 | return singleton; 24 | } 25 | 26 | } 27 | ``` 28 | 29 | ## 2.懒汉式(线程安全) 30 | 31 | ```java 32 | public class Singleton { 33 | 34 | private static Singleton singleton = new Singleton(); 35 | 36 | private Singleton() { 37 | } 38 | 39 | /** 40 | * 在方法级别上加锁 41 | * 当一个线程进入该方法之后,其它线程试图进入该方法都必须等待, 42 | * 因此性能上有一定的损耗 43 | */ 44 | public static synchronized Singleton getSingleton() { 45 | if (singleton == null) { 46 | singleton = new Singleton(); 47 | } 48 | return singleton; 49 | } 50 | 51 | } 52 | ``` 53 | 54 | ## 3.双重检验锁 55 | 56 | ```java 57 | public class Singleton { 58 | 59 | private static Singleton singleton = new Singleton(); 60 | 61 | private Singleton() { 62 | } 63 | 64 | /** 65 | * 加同步锁前后两次判断实例是否已存在 66 | * 缩小的同步代码块 67 | */ 68 | public static Singleton getSingleton() { 69 | if (singleton == null) { 70 | synchronized (Singleton.class) { 71 | if (singleton == null) { 72 | singleton = new Singleton(); 73 | } 74 | } 75 | } 76 | return singleton; 77 | } 78 | 79 | } 80 | ``` 81 | 82 | 这段代码看起来很完美,很可惜,它是有问题。主要在于`instance = new Singleton()`这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。 83 | 84 | 1. 给 `instance` 分配内存 85 | 2. 调用 `Singleton` 的构造函数来初始化成员变量 86 | 3. 将`instance`对象指向分配的内存空间(执行完这步 `instance` 就为非 `null` 了) 87 | 88 | 但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。 89 | 如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。 90 | 91 | 我们只需要将 instance 变量声明成 volatile 就可以了。 92 | 93 | ```java 94 | public class Singleton { 95 | 96 | //声明成 volatile 97 | private volatile static Singleton instance; 98 | 99 | private Singleton() { 100 | } 101 | 102 | public static Singleton getSingleton() { 103 | if (instance == null) { 104 | synchronized (Singleton.class) { 105 | if (instance == null) { 106 | instance = new Singleton(); 107 | } 108 | } 109 | } 110 | return instance; 111 | } 112 | } 113 | ``` 114 | 115 | 有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。 116 | 117 | 但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。 118 | 119 | 比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。 120 | 121 | 从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。 122 | 123 | 124 | ## 4.静态内部类 125 | 126 | ```java 127 | public class Singleton { 128 | 129 | private Singleton() { 130 | } 131 | 132 | private static class Inner { 133 | private static final Singleton instance = new Singleton(); 134 | } 135 | 136 | public Singleton getSingleton() { 137 | return Inner.instance; 138 | } 139 | } 140 | ``` 141 | 142 | 1. 实现代码简洁。和双重检查单例对比,静态内部类单例实现代码真的是太简洁,又清晰明了。 143 | 2. 延迟初始化。调用getSingleton才初始化Singleton对象。 144 | 3. 线程安全。JVM在执行类的初始化阶段,会获得一个可以同步多个线程对同一个类的初始化的锁。 145 | 146 | ## 5.枚举单例模式 147 | 148 | ```java 149 | public enum Singleton { 150 | INSTANCE; 151 | } 152 | ``` 153 | 154 | 没错,就是这么简单。这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。 155 | 156 | 157 | -------------------------------------------------------------------------------- /problems/0006.zigzag-conversion/README.md: -------------------------------------------------------------------------------- 1 | # [6. Z字形变换](https://leetcode-cn.com/problems/zigzag-conversion/description/) 2 | 3 | 将字符串 "PAYPALISHIRING" 以Z字形排列成给定的行数: 4 | ``` 5 | P A H N 6 | A P L S I I G 7 | Y I R 8 | ``` 9 | 之后从左往右,逐行读取字符:"PAHNAPLSIIGYIR" 10 | 11 | 实现一个将字符串进行指定行数变换的函数: 12 | 13 | `string convert(string s, int numRows);` 14 | 15 | 示例 1: 16 | ``` 17 | 输入: s = "PAYPALISHIRING", numRows = 3 18 | 输出: "PAHNAPLSIIGYIR" 19 | ``` 20 | 示例 2: 21 | ``` 22 | 输入: s = "PAYPALISHIRING", numRows = 4 23 | 输出: "PINALSIGYAHRPI" 24 | 解释: 25 | 26 | P I N 27 | A L S I G 28 | Y A H R 29 | P I 30 | ``` 31 | 32 | # 思路 33 | 34 | 发现其实新的字符串的顺序在原字符串中是有规律可循的。 35 | 36 | 重新组合后的形状其实就是一个个N,相邻N对应位置之间的字符个数是固定的,如例子中的P-A-H-N之间的字符都是3个。现在假设行数为n,则对应位置字符间的间距都为2n-3,现在第一排和最后一排的字符顺序就可以确定了. 37 | 38 | 但中间排的字符就比较特殊,在对应位置之间还会夹着一个字符。以例子中第一列的A和第二列的P来说,假设A的下标为i(从0开始),则A和P的间距为2(n-i-1)-1=2n-2i-3,而A和L的间距为2n-3,同时去掉P自己,所以P和L的间距为(2n-3)-(2n-2i-3)-1=2i-1。这样只需遍历第一列的字符,并依次加上对应的间距来获取后面的字符即可。上文的间距是指中间隔了几个字符。 39 | 40 | 41 | ## Python 42 | ```python 43 | class problem.Solution(object): 44 | def convert(self, s, numRows): 45 | """ 46 | :type s: str 47 | :type numRows: int 48 | :rtype: str 49 | """ 50 | if numRows<=1: 51 | return s 52 | result = '' 53 | index = 0 54 | n = len(s) 55 | for i in range(0, numRows): 56 | if i == 0 or i == numRows - 1: 57 | while index < n: 58 | result += s[index] 59 | index += 2 * numRows - 2 60 | index = i + 1 61 | else: 62 | while index < n: 63 | result += s[index] 64 | index += 2 * numRows - 2 * i - 2 65 | if index >= n: 66 | break 67 | result += s[index] 68 | index += 2 * i 69 | index = i + 1 70 | return result 71 | 72 | ``` 73 | 74 | 75 | ```python 76 | class problem.Solution: 77 | # @return a string 78 | def convert(self, s, nRows): 79 | if nRows==1: return s 80 | tmp=['' for i in range(nRows)] 81 | index=-1; step=1 82 | for i in range(len(s)): 83 | index+=step 84 | if index==nRows: 85 | index-=2; step=-1 86 | elif index==-1: 87 | index=1; step=1 88 | tmp[index]+=s[i] 89 | return ''.join(tmp) 90 | 91 | ``` 92 | 93 | ## Golang 94 | 95 | 输入"ABCDEFGHIJKLMNOPQRSTUVWXYZ"和参数5后,得到答案"AGMSYBFHLNRTXZCEIKOQUWDJPV", 按照题目的摆放方法,可得: 96 | ``` 97 | A I Q Y 98 | B HJ PR XZ 99 | C G K O S W 100 | DF LN TV 101 | E M U 102 | ``` 103 | 可以看到,各行字符在原字符串中的索引号为 104 | ``` 105 | 0行,0, 8, 16, 24 106 | 1行,1, 7, 9, 15, 17, 23, 25 107 | 2行,2, 6, 10, 14, 18, 22 108 | 3行,3, 5, 11, 13, 19, 21 109 | 4行,4, 12, 20 110 | ``` 111 | 令p=numRows×2-2,可以总结出以下规律 112 | ``` 113 | 0行, 0×p,1×p,... 114 | r行, r,1×p-r,1×p+r,2×p-r,2×p+r,... 115 | 最后一行, numRow-1, numRow-1+1×p,numRow-1+2×p,... 116 | ``` 117 | 只需编程依次处理各行即可。 118 | 119 | ```golang 120 | func convert(s string, numRows int) string { 121 | if numRows == 1 || len(s) <= numRows { 122 | return s 123 | } 124 | 125 | res := bytes.Buffer{} 126 | // p pace 步距 127 | p := numRows*2 - 2 128 | 129 | // 处理第一行 130 | for i := 0; i < len(s); i += p { 131 | res.WriteByte(s[i]) 132 | } 133 | 134 | // 处理中间的行 135 | for r := 1; r <= numRows-2; r++ { 136 | // 添加r行的第一个字符 137 | res.WriteByte(s[r]) 138 | 139 | for k := p; k-r < len(s); k += p { 140 | res.WriteByte(s[k-r]) 141 | if k+r < len(s) { 142 | res.WriteByte(s[k+r]) 143 | } 144 | } 145 | } 146 | 147 | // 处理最后一行 148 | for i := numRows - 1; i < len(s); i += p { 149 | res.WriteByte(s[i]) 150 | } 151 | 152 | return res.String() 153 | } 154 | ``` 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | target 4 | build 5 | .DS_Store 6 | logs/ 7 | .gradle 8 | classes/ 9 | out 10 | 11 | # Created by https://www.gitignore.io/api/java,macos,gradle,windows,java-web,jetbrains 12 | # Edit at https://www.gitignore.io/?templates=java,macos,gradle,windows,java-web,jetbrains 13 | 14 | ### Java ### 15 | # Compiled class file 16 | *.class 17 | 18 | # Log file 19 | *.log 20 | 21 | # BlueJ files 22 | *.ctxt 23 | 24 | # Mobile Tools for Java (J2ME) 25 | .mtj.tmp/ 26 | 27 | # Package Files # 28 | *.jar 29 | *.war 30 | *.nar 31 | *.ear 32 | *.zip 33 | *.tar.gz 34 | *.rar 35 | 36 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 37 | hs_err_pid* 38 | 39 | ### Java-Web ### 40 | ## ignoring target file 41 | target/ 42 | 43 | ### JetBrains ### 44 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 45 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 46 | 47 | # User-specific stuff 48 | .idea/**/workspace.xml 49 | .idea/**/tasks.xml 50 | .idea/**/usage.statistics.xml 51 | .idea/**/dictionaries 52 | .idea/**/shelf 53 | 54 | # Generated files 55 | .idea/**/contentModel.xml 56 | 57 | # Sensitive or high-churn files 58 | .idea/**/dataSources/ 59 | .idea/**/dataSources.ids 60 | .idea/**/dataSources.local.xml 61 | .idea/**/sqlDataSources.xml 62 | .idea/**/dynamic.xml 63 | .idea/**/uiDesigner.xml 64 | .idea/**/dbnavigator.xml 65 | 66 | # Gradle 67 | .idea/**/gradle.xml 68 | .idea/**/libraries 69 | .idea/ 70 | # Gradle and Maven with auto-import 71 | # When using Gradle or Maven with auto-import, you should exclude module files, 72 | # since they will be recreated, and may cause churn. Uncomment if using 73 | # auto-import. 74 | # .idea/modules.xml 75 | # .idea/*.iml 76 | # .idea/modules 77 | # *.iml 78 | # *.ipr 79 | 80 | # CMake 81 | cmake-build-*/ 82 | 83 | # Mongo Explorer plugin 84 | .idea/**/mongoSettings.xml 85 | 86 | # File-based project format 87 | *.iws 88 | 89 | # IntelliJ 90 | out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Cursive Clojure plugin 99 | .idea/replstate.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | fabric.properties 106 | 107 | # Editor-based Rest Client 108 | .idea/httpRequests 109 | 110 | # Android studio 3.1+ serialized cache file 111 | .idea/caches/build_file_checksums.ser 112 | 113 | ### JetBrains Patch ### 114 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 115 | 116 | # *.iml 117 | # modules.xml 118 | # .idea/misc.xml 119 | # *.ipr 120 | 121 | # Sonarlint plugin 122 | .idea/sonarlint 123 | 124 | ### macOS ### 125 | # General 126 | .DS_Store 127 | .AppleDouble 128 | .LSOverride 129 | 130 | # Icon must end with two \r 131 | Icon 132 | 133 | # Thumbnails 134 | ._* 135 | 136 | # Files that might appear in the root of a volume 137 | .DocumentRevisions-V100 138 | .fseventsd 139 | .Spotlight-V100 140 | .TemporaryItems 141 | .Trashes 142 | .VolumeIcon.icns 143 | .com.apple.timemachine.donotpresent 144 | 145 | # Directories potentially created on remote AFP share 146 | .AppleDB 147 | .AppleDesktop 148 | Network Trash Folder 149 | Temporary Items 150 | .apdisk 151 | 152 | ### Windows ### 153 | # Windows thumbnail cache files 154 | Thumbs.db 155 | Thumbs.db:encryptable 156 | ehthumbs.db 157 | ehthumbs_vista.db 158 | 159 | # Dump file 160 | *.stackdump 161 | 162 | # Folder config file 163 | [Dd]esktop.ini 164 | 165 | # Recycle Bin used on file shares 166 | $RECYCLE.BIN/ 167 | 168 | # Windows Installer files 169 | *.cab 170 | *.msi 171 | *.msix 172 | *.msm 173 | *.msp 174 | 175 | # Windows shortcuts 176 | *.lnk 177 | 178 | ### Gradle ### 179 | .gradle 180 | build/ 181 | 182 | # Ignore Gradle GUI config 183 | gradle-app.setting 184 | 185 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 186 | !gradle-wrapper.jar 187 | 188 | # Cache of project 189 | .gradletasknamecache 190 | 191 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 192 | # gradle/wrapper/gradle-wrapper.properties 193 | 194 | ### Gradle Patch ### 195 | **/build/ 196 | 197 | # End of https://www.gitignore.io/api/java,macos,gradle,windows,java-web,jetbrains 198 | 199 | *.java 200 | *.py 201 | *.go 202 | solution -------------------------------------------------------------------------------- /problems/0146.lru-cache/README.md: -------------------------------------------------------------------------------- 1 | # LRU缓存机制 2 | 3 | [原题链接](https://leetcode-cn.com/problems/lru-cache/) 4 | 5 | ## 题目 6 | 7 | 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 8 | 9 | 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 10 | 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 11 | 12 | 进阶: 13 | 14 | 你是否可以在 O(1) 时间复杂度内完成这两种操作? 15 | 16 | 示例: 17 | ```java 18 | LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); 19 | 20 | cache.put(1, 1); 21 | cache.put(2, 2); 22 | cache.get(1); // 返回 1 23 | cache.put(3, 3); // 该操作会使得关键字 2 作废 24 | cache.get(2); // 返回 -1 (未找到) 25 | cache.put(4, 4); // 该操作会使得关键字 1 作废 26 | cache.get(1); // 返回 -1 (未找到) 27 | cache.get(3); // 返回 3 28 | cache.get(4); // 返回 4 29 | ``` 30 | 31 | ## 设计 32 | 33 | 数据结构 34 | 35 | ![EQu3Bv](https://raw.githubusercontent.com/zhangslob/oss/master/uPic/EQu3Bv.jpg) 36 | 37 | ## 代码 38 | 39 | 首先,我们把双链表的节点类写出来,为了简化,key 和 val 都认为是 int 类型: 40 | 41 | ```java 42 | class Node { 43 | public int key, val; 44 | public Node next, prev; 45 | public Node(int k, int v) { 46 | this.key = k; 47 | this.val = v; 48 | } 49 | } 50 | ``` 51 | 52 | 然后依靠我们的 Node 类型构建一个双链表,实现几个需要的 API(这些操作的时间复杂度均为 $O(1)$: 53 | 54 | ```java 55 | class DoubleList { 56 | // 在链表头部添加节点 x,时间 O(1) 57 | public void addFirst(Node x); 58 | 59 | // 删除链表中的 x 节点(x 一定存在) 60 | // 由于是双链表且给的是目标 Node 节点,时间 O(1) 61 | public void remove(Node x); 62 | 63 | // 删除链表中最后一个节点,并返回该节点,时间 O(1) 64 | public Node removeLast(); 65 | 66 | // 返回链表长度,时间 O(1) 67 | public int size(); 68 | } 69 | ``` 70 | 71 | PS:这就是普通双向链表的实现,为了让读者集中精力理解 LRU 算法的逻辑,就省略链表的具体代码。 72 | 73 | 到这里就能回答刚才“为什么必须要用双向链表”的问题了,因为我们需要删除操作。删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)。 74 | 75 | 有了双向链表的实现,我们只需要在 LRU 算法中把它和哈希表结合起来即可。我们先把逻辑理清楚: 76 | 77 | ```java 78 | // key 映射到 Node(key, val) 79 | HashMap map; 80 | // Node(k1, v1) <-> Node(k2, v2)... 81 | DoubleList cache; 82 | 83 | int get(int key) { 84 | if (key 不存在) { 85 | return -1; 86 | } else { 87 | 将数据 (key, val) 提到开头; 88 | return val; 89 | } 90 | } 91 | 92 | void put(int key, int val) { 93 | Node x = new Node(key, val); 94 | if (key 已存在) { 95 | 把旧的数据删除; 96 | 将新节点 x 插入到开头; 97 | } else { 98 | if (cache 已满) { 99 | 删除链表的最后一个数据腾位置; 100 | 删除 map 中映射到该数据的键; 101 | } 102 | 将新节点 x 插入到开头; 103 | map 中新建 key 对新节点 x 的映射; 104 | } 105 | } 106 | ``` 107 | 108 | 如果能够看懂上述逻辑,翻译成代码就很容易理解了: 109 | 110 | ```java 111 | class LRUCache { 112 | // key -> Node(key, val) 113 | private HashMap map; 114 | // Node(k1, v1) <-> Node(k2, v2)... 115 | private DoubleList cache; 116 | // 最大容量 117 | private int cap; 118 | 119 | public LRUCache(int capacity) { 120 | this.cap = capacity; 121 | map = new HashMap<>(); 122 | cache = new DoubleList(); 123 | } 124 | 125 | public int get(int key) { 126 | if (!map.containsKey(key)) 127 | return -1; 128 | int val = map.get(key).val; 129 | // 利用 put 方法把该数据提前 130 | put(key, val); 131 | return val; 132 | } 133 | 134 | public void put(int key, int val) { 135 | // 先把新节点 x 做出来 136 | Node x = new Node(key, val); 137 | 138 | if (map.containsKey(key)) { 139 | // 删除旧的节点,新的插到头部 140 | cache.remove(map.get(key)); 141 | cache.addFirst(x); 142 | // 更新 map 中对应的数据 143 | map.put(key, x); 144 | } else { 145 | if (cap == cache.size()) { 146 | // 删除链表最后一个数据 147 | Node last = cache.removeLast(); 148 | map.remove(last.key); 149 | } 150 | // 直接添加到头部 151 | cache.addFirst(x); 152 | map.put(key, x); 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | 这里就能回答之前的问答题“为什么要在链表中同时存储 key 和 val,而不是只存储 val”,注意这段代码: 159 | 160 | ```java 161 | if (cap == cache.size()) { 162 | // 删除链表最后一个数据 163 | Node last = cache.removeLast(); 164 | map.remove(last.key); 165 | } 166 | ``` 167 | 168 | 当缓存容量已满,我们不仅仅要删除最后一个 Node 节点,还要把 map 中映射到该节点的 key 同时删除,而这个 key 只能由 Node 得到。如果 Node 结构中只存储 val,那么我们就无法得知 key 是什么,就无法删除 map 中的键,造成错误。 169 | 170 | 至此,你应该已经掌握 LRU 算法的思想和实现了,很容易犯错的一点是:处理链表节点的同时不要忘了更新哈希表中对节点的映射。 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /problems/0107.rotate-matrix-lcci/README.md: -------------------------------------------------------------------------------- 1 | # 旋转矩阵 2 | 3 | [原题链接](https://leetcode-cn.com/problems/rotate-matrix-lcci/) 4 | 5 | ## 题目 6 | 7 | 给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。 8 | 9 | 不占用额外内存空间能否做到? 10 | 11 | 示例 1: 12 | 13 | 给定 matrix = 14 | ```text 15 | [ 16 | [1,2,3], 17 | [4,5,6], 18 | [7,8,9] 19 | ] 20 | ``` 21 | 22 | 原地旋转输入矩阵,使其变为: 23 | ```text 24 | [ 25 | [7,4,1], 26 | [8,5,2], 27 | [9,6,3] 28 | ] 29 | ``` 30 | 31 | 示例 2: 32 | 33 | 给定 matrix = 34 | ```text 35 | [ 36 | [ 5, 1, 9,11], 37 | [ 2, 4, 8,10], 38 | [13, 3, 6, 7], 39 | [15,14,12,16] 40 | ] 41 | ``` 42 | 43 | 原地旋转输入矩阵,使其变为: 44 | ```text 45 | [ 46 | [15,13, 2, 5], 47 | [14, 3, 4, 1], 48 | [12, 6, 8, 9], 49 | [16, 7,10,11] 50 | ] 51 | ``` 52 | 53 | ## 题解 54 | 55 | 拿到题目,想了会,没思路。我发现了一些规律,但是不知道怎么用代码去实现。 56 | 57 | 第一行 58 | - `[0][0]` ==>`[0][N]` 59 | - `[0][1]` ==> `[1][N]` 60 | - `[0][2]` ==> `[2][N]` 61 | 62 | 第二行 63 | - `[1][0]` ==> `[0][N-1]` 64 | - `[1][1]` ==> `[1][N-1]` 65 | - `[1][2]` ==> `[2][N-1]` 66 | 67 | 第三行 68 | - `[2][0]` ==> `[0][N-2]` 69 | - `[2][1]` ==> `[1][N-2]` 70 | - `[2][2]` ==> `[2][N-2]` 71 | 72 | 好像发现规律了,试试如何用代码实现。题目要求不占用额外内存空间,应该就是: `l[a], l[b] = l[b], l[a]` 这种了 73 | 74 | 尝试了,还是失败告终,看题解:用翻转代替旋转 75 | 76 | 规律: 77 | > 对于矩阵中第 ii 行的第 jj 个元素,在旋转后,它出现在倒数第 ii 列的第 jj 个位置。 78 | 79 | 如下图,先由对角线 `[1, 5, 9]` 为轴进行翻转: 80 | 81 | ```text 82 | [ 83 | [1,2,3], 84 | [4,5,6], 85 | [7,8,9] 86 | ] 87 | ``` 88 | 89 | 于是数组变成了: 90 | ```text 91 | [1,4,7] 92 | [2,5,8] 93 | [3,6,9] 94 | ``` 95 | 96 | 再对每一行以中点进行翻转,就得到了 97 | ```text 98 | [7,4,1] 99 | [8,5,2] 100 | [9,6,3] 101 | ``` 102 | 103 | 两次翻转,完成目标,时间复杂度O(n^2),空间复杂度O(1) 104 | 105 | ## 代码 106 | 107 | ### JAVA 108 | 109 | ```java 110 | package problem; 111 | 112 | import java.util.Arrays; 113 | 114 | public class Solution { 115 | public void rotate(int[][] matrix) { 116 | int n = matrix.length; 117 | for (int i = 0; i < n; i++) { 118 | for (int j = i; j < n; j++) { 119 | int tmp = matrix[i][j]; 120 | matrix[i][j] = matrix[j][i]; 121 | matrix[j][i] = tmp; 122 | } 123 | for (int j = 0; j < n / 2; j++) { 124 | int tmp = matrix[i][j]; 125 | matrix[i][j] = matrix[i][n - j - 1]; 126 | matrix[i][n - j - 1] = tmp; 127 | } 128 | } 129 | } 130 | 131 | public static void main(String[] args) { 132 | int[][] matrix = new int[][]{ 133 | {1, 2, 3}, 134 | {4, 5, 6}, 135 | {7, 8, 9} 136 | }; 137 | new Solution().rotate(matrix); 138 | System.out.println(Arrays.deepToString(matrix)); 139 | } 140 | } 141 | ``` 142 | 143 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 144 | - 内存消耗 : 39.6 MB , 在所有 Java 提交中击败了 100.00% 的用户 145 | 146 | > Amazing 147 | 148 | ### Python 149 | 150 | ```python 151 | class Solution: 152 | def rotate(self, matrix) -> None: 153 | """ 154 | Do not return anything, modify matrix in-place instead. 155 | """ 156 | n = len(matrix[0]) 157 | for i in range(n): 158 | for j in range(i, n, 1): 159 | matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] 160 | for i in range(n): 161 | matrix[i].reverse() 162 | 163 | 164 | if __name__ == '__main__': 165 | matrix = [ 166 | [1, 2, 3], 167 | [4, 5, 6], 168 | [7, 8, 9] 169 | ] 170 | Solution().rotate(matrix) 171 | print(matrix) 172 | ``` 173 | - 执行用时 : 36 ms , 在所有 Python3 提交中击败了 80.33% 的用户 174 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 100.00% 的用户 175 | 176 | 看到有这种方法: 177 | ```python 178 | class Solution: 179 | def rotate(self, matrix: List[List[int]]) -> None: 180 | matrix[::] = zip(*matrix[::-1]) 181 | ``` 182 | 183 | ### Go 184 | 185 | ```go 186 | package main 187 | 188 | import "fmt" 189 | 190 | func rotate(matrix [][]int) { 191 | n := len(matrix) 192 | for i := 0; i < n; i++ { 193 | for j := i; j < n; j++ { 194 | matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] 195 | } 196 | for j := 0; j < n/2; j++ { 197 | matrix[i][j], matrix[i][n-j-1] = matrix[i][n-j-1], matrix[i][j] 198 | } 199 | } 200 | } 201 | 202 | func main() { 203 | matrix := [][]int{ 204 | {1, 2, 3}, 205 | {4, 5, 6}, 206 | {7, 8, 9}, 207 | } 208 | rotate(matrix) 209 | fmt.Println(matrix) 210 | } 211 | ``` 212 | 213 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 214 | - 内存消耗 : 2.2 MB , 在所有 Go 提交中击败了 100.00% 的用户 215 | -------------------------------------------------------------------------------- /problems/0200.number-of-islands/README.md: -------------------------------------------------------------------------------- 1 | # 岛屿数量 2 | 3 | [原题链接](https://leetcode-cn.com/problems/number-of-islands/) 4 | 5 | ## 题目 6 | 7 | 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 8 | 9 | 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 10 | 11 | 此外,你可以假设该网格的四条边均被水包围。 12 | 13 | 示例 1: 14 | 15 | 输入: 16 | ```text 17 | 11110 18 | 11010 19 | 11000 20 | 00000 21 | ``` 22 | 23 | 输出: `1` 24 | 25 | 示例 2: 26 | 27 | 输入: 28 | ```text 29 | 11000 30 | 11000 31 | 00100 32 | 00011 33 | ``` 34 | 35 | 输出: `3` 36 | 37 | 解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。 38 | 39 | 40 | 41 | ## 题解 42 | 43 | 牢记二叉树的遍历方法: 44 | 45 | ```java 46 | void traverse(TreeNode root) { 47 | // 判断 bad case 48 | if (root == null) { 49 | return; 50 | } 51 | // 访问两个相邻结点:左子结点、右子结点 52 | traverse(root.left); 53 | traverse(root.right); 54 | } 55 | ``` 56 | 57 | 在岛屿数量这里,左右节点就是上下左右四个节点,bad case就是越界问题。 58 | 59 | 要明白,一个点和一片点是一样的 60 | 61 | 为了防止重复,可以把已经遍历过的点置为——0 62 | 63 | ## 代码 64 | 65 | ### Java 66 | 67 | ```java 68 | public class Solution { 69 | public int numIslands(char[][] grid) { 70 | // empty? 71 | int ans = 0; 72 | for (int i = 0; i < grid.length; i++) { 73 | for (int j = 0; j < grid[0].length; j++) { 74 | if (grid[i][j] == '0') { 75 | continue; 76 | } 77 | ans++; 78 | dfs(grid, i, j); 79 | } 80 | } 81 | return ans; 82 | } 83 | 84 | private void dfs(char[][] grid, int i, int j) { 85 | // 越界 86 | if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length) { 87 | return; 88 | } 89 | if (grid[i][j] == '0') { 90 | return; 91 | } 92 | grid[i][j] = '0'; 93 | dfs(grid, i, j + 1); 94 | dfs(grid, i, j - 1); 95 | dfs(grid, i + 1, j); 96 | dfs(grid, i - 1, j); 97 | } 98 | 99 | public static void main(String[] args) { 100 | char[][] grid = new char[][]{ 101 | {'1', '1', '1', '1', '0'}, 102 | {'1', '1', '0', '1', '0'}, 103 | {'1', '1', '0', '0', '0'}, 104 | {'0', '0', '0', '0', '0'}, 105 | }; 106 | char[][] grid2 = new char[][]{ 107 | {'1', '1', '0', '0', '0'}, 108 | {'1', '1', '0', '0', '0'}, 109 | {'0', '0', '1', '0', '0'}, 110 | {'0', '0', '0', '1', '1'}, 111 | }; 112 | System.out.println(new Solution().numIslands(grid2)); 113 | } 114 | } 115 | ``` 116 | 117 | - 执行用时 : 2 ms , 在所有 Java 提交中击败了 96.16% 的用户 118 | - 内存消耗 : 41.9 MB , 在所有 Java 提交中击败了 6.25% 的用户 119 | 120 | ### Python 121 | 122 | ```python 123 | class Solution: 124 | def numIslands(self, grid: List[List[str]]) -> int: 125 | def dfs(grid, i, j): 126 | if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]): 127 | return 128 | if grid[i][j] == '0': 129 | return 130 | grid[i][j] = '0' 131 | dfs(grid, i, j - 1) 132 | dfs(grid, i, j + 1) 133 | dfs(grid, i + 1, j) 134 | dfs(grid, i - 1, j) 135 | 136 | ans = 0 137 | for i in range(len(grid)): 138 | for j in range(len(grid[0])): 139 | if grid[i][j] == '0': 140 | continue 141 | ans += 1 142 | dfs(grid, i, j) 143 | return ans 144 | ``` 145 | 146 | - 执行用时 : 80 ms , 在所有 Python3 提交中击败了 79.19% 的用户 147 | - 内存消耗 : 14.3 MB , 在所有 Python3 提交中击败了 6.67% 的用户 148 | 149 | 150 | 151 | ### Go 152 | 153 | ```go 154 | func numIslands(grid [][]byte) int { 155 | ans := 0 156 | if len(grid) == 0 { 157 | return ans 158 | } 159 | m, n := len(grid), len(grid[0]) 160 | for i := 0; i < m; i++ { 161 | for j := 0; j < n; j++ { 162 | if grid[i][j] == '0' { 163 | continue 164 | } 165 | ans++ 166 | dfs(grid, i, j) 167 | } 168 | } 169 | return ans 170 | } 171 | 172 | func dfs(grid [][]byte, i int, j int) { 173 | if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) || grid[i][j] == '0' { 174 | return 175 | } 176 | grid[i][j] = '0' 177 | dfs(grid, i, j+1) 178 | dfs(grid, i, j-1) 179 | dfs(grid, i+1, j) 180 | dfs(grid, i-1, j) 181 | } 182 | ``` 183 | 184 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 185 | - 内存消耗 : 2.9 MB , 在所有 Go 提交中击败了 100.00% 的用户 186 | 187 | 188 | 189 | 190 | 191 | 看到一个很好的教程:[岛屿类问题的通用解法、DFS 遍历框架](https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-lei-wen-ti-de-tong-yong-jie-fa-dfs-bian-li-/) 192 | 193 | -------------------------------------------------------------------------------- /problems/0198.house-robber/README.md: -------------------------------------------------------------------------------- 1 | # 打家劫舍 2 | 3 | [原题链接](https://leetcode-cn.com/problems/house-robber/) 4 | 5 | ## 题目 6 | 7 | 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 8 | 9 | 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 10 | 11 | 示例 1: 12 | ```text 13 | 输入: [1,2,3,1] 14 | 输出: 4 15 | 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 16 | 偷窃到的最高金额 = 1 + 3 = 4 。 17 | ``` 18 | 19 | 示例 2: 20 | ```text 21 | 输入: [2,7,9,3,1] 22 | 输出: 12 23 | 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 24 | 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 25 | ``` 26 | 27 | ## 题解 28 | 29 | 不能连续两个元素相加,有个坑:不能一位计算奇偶数就是答案,有这种情况`[8, 1, 2, 9, 1]`,正确解法是动态规划 30 | 31 | dp 方程 dp[i] = max(dp[i-2] + nums[i], dp[i-1]) 32 | 33 | 34 | ## 代码 35 | 36 | ### Java I 37 | 38 | ```java 39 | class Solution { 40 | public int rob(int[] nums) { 41 | int n = nums.length; 42 | if (n == 0) { 43 | return 0; 44 | } 45 | if (n == 1) { 46 | return nums[0]; 47 | } 48 | if (n == 2) { 49 | return Math.max(nums[0], nums[1]); 50 | } 51 | int[] dp = new int[n]; 52 | dp[0] = nums[0]; 53 | dp[1] = Math.max(nums[0], nums[1]); 54 | for (int i = 2; i < n; i++) { 55 | dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); 56 | } 57 | return dp[n - 1]; 58 | } 59 | } 60 | ``` 61 | 62 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 63 | - 内存消耗 : 37.1 MB , 在所有 Java 提交中击败了 6.52% 的用户 64 | 65 | ### Java II 66 | 67 | ```java 68 | class Solution { 69 | public int rob(int[] nums) { 70 | int n = nums.length; 71 | if (n == 0) { 72 | return 0; 73 | } 74 | if (n == 1) { 75 | return nums[0]; 76 | } 77 | if (n == 2) { 78 | return Math.max(nums[0], nums[1]); 79 | } 80 | int n1 = 0, n2 = 0; 81 | for (int num : nums) { 82 | int tmp = n1; 83 | n1 = Math.max(n1, n2 + num); 84 | n2 = tmp; 85 | } 86 | return n1; 87 | } 88 | } 89 | ``` 90 | 91 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 92 | - 内存消耗 : 36.7 MB , 在所有 Java 提交中击败了 6.52% 的用户 93 | 94 | ### Go I 95 | 96 | ```go 97 | func rob(nums []int) int { 98 | n := len(nums) 99 | if n == 0 { 100 | return 0 101 | } 102 | if n == 1 { 103 | return nums[0] 104 | } 105 | if n == 2 { 106 | return max(nums[0], nums[1]) 107 | } 108 | dp := make([]int, n) 109 | dp[0] = nums[0] 110 | dp[1] = max(nums[0], nums[1]) 111 | for i := 2; i < n; i++ { 112 | dp[i] = max(dp[i-2]+nums[i], dp[i-1]) 113 | } 114 | return dp[n-1] 115 | } 116 | 117 | func max(a, b int) int { 118 | if a > b { 119 | return a 120 | } 121 | return b 122 | } 123 | ``` 124 | 125 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 126 | - 内存消耗 : 2 MB , 在所有 Go 提交中击败了 33.33% 的用户 127 | 128 | 129 | ### Go II 130 | 131 | ```go 132 | func rob(nums []int) int { 133 | preMax, curMax := 0, 0 134 | for _, v := range nums { 135 | temp := curMax 136 | if preMax+v > curMax { 137 | curMax = preMax + v 138 | } 139 | preMax = temp 140 | } 141 | return curMax 142 | } 143 | ``` 144 | 145 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 146 | - 内存消耗 : 2 MB , 在所有 Go 提交中击败了 33.33% 的用户 147 | 148 | ### Python I 149 | 150 | ```python 151 | class Solution: 152 | def rob(self, nums: List[int]) -> int: 153 | n = len(nums) 154 | if n == 0: 155 | return 0 156 | if n == 1: 157 | return nums[0] 158 | if n == 2: 159 | return max(nums[0], nums[1]) 160 | dp = [0 for _ in range(n)] 161 | dp[0] = nums[0] 162 | dp[1] = max(nums[0], nums[1]) 163 | for i in range(2, n): 164 | dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) 165 | return dp[n - 1] 166 | ``` 167 | 168 | - 执行用时 : 36 ms , 在所有 Python3 提交中击败了 84.98% 的用户 169 | - 内存消耗 : 13.6 MB , 在所有 Python3 提交中击败了 9.09% 的用户 170 | 171 | ### Python II 172 | 173 | 其实我们并不需要数组来保存所有状态,可以优化空间为$O(N)$ 174 | 175 | ```python 176 | class Solution: 177 | def rob(self, nums: List[int]) -> int: 178 | n = len(nums) 179 | if n == 0: 180 | return 0 181 | if n == 1: 182 | return nums[0] 183 | if n == 2: 184 | return max(nums[0], nums[1]) 185 | n1, n2 = 0, 0 186 | for num in nums: 187 | n1, n2 = n2, max(n1 + num, n2) 188 | return n2 189 | ``` 190 | 191 | - 执行用时 : 32 ms , 在所有 Python3 提交中击败了 95.25% 的用户 192 | - 内存消耗 : 13.7 MB , 在所有 Python3 提交中击败了 9.09% 的用户 193 | 194 | -------------------------------------------------------------------------------- /problems/0054.spiral-matrix/README.md: -------------------------------------------------------------------------------- 1 | # 螺旋矩阵 2 | 3 | [原题链接](https://leetcode-cn.com/problems/spiral-matrix/) 4 | 5 | ## 题目 6 | 7 | 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: 12 | [ 13 | [ 1, 2, 3 ], 14 | [ 4, 5, 6 ], 15 | [ 7, 8, 9 ] 16 | ] 17 | 输出: [1,2,3,6,9,8,7,4,5] 18 | ``` 19 | 20 | 示例 2: 21 | ```text 22 | 输入: 23 | [ 24 | [1, 2, 3, 4], 25 | [5, 6, 7, 8], 26 | [9,10,11,12] 27 | ] 28 | 输出: [1,2,3,4,8,12,11,10,9,5,6,7] 29 | ``` 30 | 31 | ## 题解 32 | 33 | 我们定义四个坐标:上下左右,分别对应逻辑视图上的上下左右。 34 | 35 | 1. 从左往右。到了最右边,然后判断能不能往下走,注意这里要up要先加一在判断,在java里的写法是`++up`,而不是`up++` 36 | 2. 从上往下。到了最下边,然后判断能不能往左走 37 | 3. 从右往左。到了最左边,然后判断能不能往上走 38 | 4. 从下往上。到了最上边,然后判断能不能往右走 39 | 5. 循环1~4步 40 | 41 | 42 | ## 代码 43 | 44 | ### Java 45 | 46 | ```java 47 | class Solution { 48 | public List spiralOrder(int[][] matrix) { 49 | List ans = new ArrayList<>(); 50 | if (matrix.length == 0) { 51 | return ans; 52 | } 53 | // 上下左右 54 | int up = 0, down = matrix.length - 1, left = 0, right = matrix[0].length - 1; 55 | while (true) { 56 | // 从左往右 57 | for (int i = left; i <= right; i++) { 58 | ans.add(matrix[up][i]); 59 | } 60 | if (++up > down) { 61 | break; 62 | } 63 | // 从上往下 64 | for (int i = up; i <= down; i++) { 65 | ans.add(matrix[i][right]); 66 | } 67 | if (--right < left) { 68 | break; 69 | } 70 | // 从右往左 71 | for (int i = right; i >= left; i--) { 72 | ans.add(matrix[down][i]); 73 | } 74 | if (--down < up) { 75 | break; 76 | } 77 | // 从下往上 78 | for (int i = down; i >= up; i--) { 79 | ans.add(matrix[i][left]); 80 | } 81 | if (++left > right) { 82 | break; 83 | } 84 | } 85 | return ans; 86 | } 87 | } 88 | ``` 89 | 90 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 91 | - 内存消耗 : 37.8 MB , 在所有 Java 提交中击败了 5.72% 的用户 92 | 93 | ### go 94 | 95 | ```go 96 | func spiralOrder(matrix [][]int) []int { 97 | ans := []int{} 98 | if len(matrix) == 0 { 99 | return ans 100 | } 101 | up, down, left, right := 0, len(matrix)-1, 0, len(matrix[0])-1 102 | for { 103 | for i := left; i <= right; i++ { 104 | ans = append(ans, matrix[up][i]) 105 | } 106 | up++ 107 | if up > down { 108 | break 109 | } 110 | for i := up; i <= down; i++ { 111 | ans = append(ans, matrix[i][right]) 112 | } 113 | right-- 114 | if right < left { 115 | break 116 | } 117 | for i := right; i >= left; i-- { 118 | ans = append(ans, matrix[down][i]) 119 | } 120 | down-- 121 | if down < up { 122 | break 123 | } 124 | for i := down; i >= up; i-- { 125 | ans = append(ans, matrix[i][left]) 126 | } 127 | left++ 128 | if left > right { 129 | break 130 | } 131 | } 132 | return ans 133 | } 134 | ``` 135 | 136 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 137 | - 内存消耗 : 2 MB , 在所有 Go 提交中击败了 100.00% 的用户 138 | 139 | ### Python 140 | ```python 141 | class Solution: 142 | def spiralOrder(self, matrix: List[List[int]]) -> List[int]: 143 | if len(matrix) == 0: 144 | return [] 145 | up, down, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 146 | ans = [] 147 | while True: 148 | for i in range(left, right + 1): 149 | ans.append(matrix[up][i]) 150 | up += 1 151 | if up > down: 152 | break 153 | for i in range(up, down + 1): 154 | ans.append(matrix[i][right]) 155 | right -= 1 156 | if right < left: 157 | break 158 | for i in range(right, left - 1, -1): 159 | ans.append(matrix[down][i]) 160 | down -= 1 161 | if down < up: 162 | break 163 | for i in range(down, up - 1, -1): 164 | ans.append(matrix[i][left]) 165 | left += 1 166 | if left > right: 167 | break 168 | return ans 169 | ``` 170 | 171 | - 执行用时 : 40 ms , 在所有 Python3 提交中击败了 63.62% 的用户 172 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 6.25% 的用户 173 | 174 | 又见Python黑魔法 175 | 176 | ```python 177 | class Solution: 178 | def spiralOrder(self, matrix: List[List[int]]) -> List[int]: 179 | res = [] 180 | while matrix: 181 | res += matrix.pop(0) 182 | matrix = list(map(list, zip(*matrix)))[::-1] 183 | return res 184 | ``` 185 | -------------------------------------------------------------------------------- /problems/0287.find-the-duplicate-number/README.md: -------------------------------------------------------------------------------- 1 | # 寻找重复数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/find-the-duplicate-number/) 4 | 5 | ## 题目 6 | 7 | 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 8 | 9 | 示例 1: 10 | ```text 11 | 输入: [1,3,4,2,2] 12 | 输出: 2 13 | ``` 14 | 15 | 示例 2: 16 | ```text 17 | 输入: [3,1,3,4,2] 18 | 输出: 3 19 | ``` 20 | 21 | 说明: 22 | 1. 不能更改原数组(假设数组是只读的)。 23 | 2. 只能使用额外的 O(1) 的空间。 24 | 3. 时间复杂度小于 O(n2) 。 25 | 4. 数组中只有一个重复的数字,但它可能不止重复出现一次。 26 | 27 | ## 题解 28 | 29 | 这道题给了我们 n+1 个数,所有的数都在 [1, n] 区域内,首先让证明必定会有一个重复数。 30 | 31 | 这不禁让博主想起了小学华罗庚奥数中的抽屉原理(又叫鸽巢原理),即如果有十个苹果放到九个抽屉里,如果苹果全在抽屉里,则至少有一个抽屉里有两个苹果,这里就不证明了,直接来做题吧。 32 | 33 | 题目要求不能改变原数组,即不能给原数组排序,又不能用多余空间,那么哈希表神马的也就不用考虑了,又说时间小于 $O(n^2)$,也就不能用 brute force 的方法,那也就只能考虑用二分搜索法了 34 | 35 | 1. 在区间 [1, n] 中搜索,首先求出中点 mid,然后遍历整个数组,统计所有小于等于 mid 的数的个数 36 | 2. 如果个数小于等于 mid,则说明重复值在 [mid+1, n] 之间,反之,重复值应在 [1, mid-1] 之间, 37 | 3. 然后依次类推,直到搜索完成,此时的 low 就是我们要求的重复值 38 | 39 | 还有另一种很牛的解法,巧用快慢指针。 40 | 1. 数组的索引与存储的数值之间形成了特殊链表。 41 | 2. 如果存在重复的数,因为数组大小是 n+1,数字范围是1~n,所以该链表存在环。 42 | 3. 环的入口即为结果。 43 | 44 | > 链表有环可以使用快慢指针 45 | 46 | ## 代码 47 | 48 | ### Java I 49 | 50 | ```java 51 | class Solution { 52 | public int findDuplicate(int[] nums) { 53 | int left = 1, right = nums.length; 54 | while (left < right) { 55 | int mid = left + (right - left) / 2, cnt = 0; 56 | for (int num : nums) { 57 | if (num <= mid) { 58 | cnt++; 59 | } 60 | } 61 | if (cnt <= mid) { 62 | left = mid + 1; 63 | } else { 64 | right = mid; 65 | } 66 | } 67 | return left; 68 | } 69 | } 70 | ``` 71 | 72 | - 执行用时 : 3 ms , 在所有 Java 提交中击败了 60.11% 的用户 73 | - 内存消耗 : 39.7 MB , 在所有 Java 提交中击败了 6.67% 的用户 74 | 75 | ### java II 76 | 77 | ```java 78 | class Solution { 79 | public int findDuplicate(int[] nums) { 80 | // 快慢指针 81 | int fast = nums[0]; 82 | int low = nums[0]; 83 | do{ 84 | low = nums[low]; 85 | fast = nums[nums[fast]]; 86 | }while(fast != low); 87 | int step = nums[0]; 88 | // 寻找环链表的入口,即为结果 89 | while(step != low){ 90 | step = nums[step]; 91 | low = nums[low]; 92 | } 93 | return low; 94 | } 95 | } 96 | ``` 97 | 98 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 99 | - 内存消耗 : 39.4 MB , 在所有 Java 提交中击败了 6.67% 的用户 100 | 101 | ### Python I 102 | 103 | ```python 104 | class Solution: 105 | def findDuplicate(self, nums: List[int]) -> int: 106 | left, right = 1, len(nums) - 1 107 | while left < right: 108 | mid = (left + right) // 2 109 | count = sum(i <= mid for i in nums) 110 | if count > mid: 111 | right = mid 112 | else: 113 | left = mid + 1 114 | return right 115 | ``` 116 | 117 | - 执行用时 : 112 ms , 在所有 Python3 提交中击败了 33.26% 的用户 118 | - 内存消耗 : 16 MB , 在所有 Python3 提交中击败了 35.71% 的用户 119 | 120 | ### Python II 121 | 122 | ```python 123 | class Solution: 124 | def findDuplicate(self, nums: List[int]) -> int: 125 | fast, slow = 0, 0 126 | while True: 127 | fast = nums[nums[fast]] 128 | slow = nums[slow] 129 | if slow == fast: 130 | slow = 0 131 | while nums[fast] != nums[slow]: 132 | slow = nums[slow] 133 | fast = nums[fast] 134 | return nums[fast] 135 | ``` 136 | 137 | - 执行用时 : 88 ms , 在所有 Python3 提交中击败了 68.54% 的用户 138 | - 内存消耗 : 16.1 MB , 在所有 Python3 提交中击败了 35.71% 的用户 139 | 140 | ### Go I 141 | 142 | ```go 143 | func findDuplicate(nums []int) int { 144 | left, right := 1, len(nums) 145 | cnt := 0 146 | for left < right { 147 | mid := left + (right - left) / 2 148 | cnt = 0 149 | for _, v := range nums { 150 | if v <= mid { 151 | cnt++ 152 | } 153 | } 154 | if cnt <= mid { 155 | left = mid + 1 156 | } else { 157 | right = mid 158 | } 159 | } 160 | return left 161 | } 162 | ``` 163 | 164 | - 执行用时 : 8 ms , 在所有 Go 提交中击败了 90.36% 的用户 165 | - 内存消耗 : 3.8 MB , 在所有 Go 提交中击败了 100.00% 的用户 166 | 167 | ### Go II 168 | ```go 169 | func findDuplicate(nums []int) int { 170 | slow := nums[0] 171 | fast := nums[slow] 172 | for slow != fast { 173 | slow = nums[slow] 174 | fast = nums[nums[fast]] 175 | } 176 | fast = nums[slow] 177 | slow = nums[0] 178 | for slow != fast { 179 | slow = nums[slow] 180 | fast = nums[fast] 181 | } 182 | return slow 183 | } 184 | ``` 185 | 186 | - 执行用时 : 4 ms , 在所有 Go 提交中击败了 99.60% 的用户 187 | - 内存消耗 : 3.8 MB , 在所有 Go 提交中击败了 100.00% 的用户 188 | -------------------------------------------------------------------------------- /problems/1143.longest-common-subsequence/README.md: -------------------------------------------------------------------------------- 1 | # 最长公共子序列 2 | 3 | [原题链接](https://leetcode-cn.com/problems/longest-common-subsequence/) 4 | 5 | ## 题目 6 | 7 | 给定两个字符串 `text1` 和 `text2`,返回这两个字符串的最长公共子序列。 8 | 9 | 一个字符串的 `子序列` 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 10 | 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 11 | 12 | 若这两个字符串没有公共子序列,则返回 0。 13 | 14 | 示例 1: 15 | ```text 16 | 输入:text1 = "abcde", text2 = "ace" 17 | 输出:3 18 | 解释:最长公共子序列是 "ace",它的长度为 3。 19 | ``` 20 | 21 | 示例 2: 22 | ```text 23 | 输入:text1 = "abc", text2 = "abc" 24 | 输出:3 25 | 解释:最长公共子序列是 "abc",它的长度为 3。 26 | ``` 27 | 28 | 示例 3: 29 | ```text 30 | 输入:text1 = "abc", text2 = "def" 31 | 输出:0 32 | 解释:两个字符串没有公共子序列,返回 0。 33 | ``` 34 | 35 | 提示: 36 | ```text 37 | 1 <= text1.length <= 1000 38 | 1 <= text2.length <= 1000 39 | 输入的字符串只含有小写英文字符。 40 | ``` 41 | 42 | ## 题解 43 | 44 | ### 思路1 45 | 46 | 递归思路。双指针,从后往前遍历,如果当前值相等,说明有一个相同的,两个指针同时-1,value+1;如果不相等,找大的那个 47 | 48 | 代码: 49 | ```python 50 | class Solution: 51 | def longestCommonSubsequence(self, text1: str, text2: str) -> int: 52 | def back(i, j): 53 | if i < 0 or j < 0: 54 | return 0 55 | if text1[i] == text2[j]: 56 | return back(i - 1, j - 1) + 1 57 | else: 58 | return max(back(i - 1, j), back(i, j - 1)) 59 | return back(len(text1) - 1, len(text2) - 1) 60 | ``` 61 | 62 | 输入:`"ylqpejqbalahwr","yrkzavgdmdgtqpg"`,会超时,显然递归不行。 63 | 64 | ### 思路2 65 | 66 | 增加二维数组维护状态,避免重复计算。代码详见下方 67 | 68 | ## 代码 69 | 70 | ```java 71 | package problem; 72 | 73 | import java.util.Arrays; 74 | 75 | public class Solution { 76 | 77 | public int longestCommonSubsequence(String text1, String text2) { 78 | int m = text1.length(), n = text2.length(); 79 | int[][] dp = new int[m + 1][n + 1]; 80 | for (int i = 1; i < m + 1; i++) { 81 | for (int j = 1; j < n + 1; j++) { 82 | if (text1.charAt(i - 1) == text2.charAt(j - 1)) { 83 | dp[i][j] = dp[i - 1][j - 1] + 1; 84 | } else { 85 | dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); 86 | } 87 | } 88 | } 89 | return dp[m][n]; 90 | } 91 | 92 | public static void main(String[] args) { 93 | System.out.println(new Solution().longestCommonSubsequence("abcde", "ace")); 94 | } 95 | } 96 | ``` 97 | 98 | - 执行用时 : 13 ms , 在所有 Java 提交中击败了 62.11% 的用户 99 | - 内存消耗 : 43.5 MB , 在所有 Java 提交中击败了 5.66% 的用户 100 | 101 | ### Python 102 | 103 | ```python 104 | class Solution: 105 | def longestCommonSubsequence(self, text1, text2): 106 | m, n = len(text1), len(text2) 107 | dp = [[0] * (n + 1) for _ in range(m + 1)] 108 | for i in range(1, m + 1): 109 | for j in range(1, n + 1): 110 | if text1[i - 1] == text2[j - 1]: 111 | dp[i][j] = dp[i - 1][j - 1] + 1 112 | else: 113 | dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 114 | return dp[m][n] 115 | 116 | 117 | if __name__ == '__main__': 118 | print(Solution().longestCommonSubsequence("abcde", "ace")) 119 | ``` 120 | 121 | - 执行用时 : 576 ms , 在所有 Python3 提交中击败了 36.38% 的用户 122 | - 内存消耗 : 21.4 MB , 在所有 Python3 提交中击败了 19.15% 的用户 123 | 124 | ### Go 125 | 126 | ```go 127 | package main 128 | 129 | import "fmt" 130 | 131 | func longestCommonSubsequence(text1 string, text2 string) int { 132 | m, n := len(text1), len(text2) 133 | dp := make([][]int, m+1) 134 | for v := range dp { 135 | dp[v] = make([]int, n+1) 136 | } 137 | for i := 1; i < m+1; i++ { 138 | for j := 1; j < n+1; j++ { 139 | if text1[i-1] == text2[j-1] { 140 | dp[i][j] = dp[i-1][j-1] + 1 141 | } else { 142 | dp[i][j] = max(dp[i][j-1], dp[i-1][j]) 143 | } 144 | } 145 | } 146 | return dp[m][n] 147 | } 148 | 149 | func max(i, j int) int { 150 | if i > j { 151 | return i 152 | } 153 | return j 154 | } 155 | 156 | func main() { 157 | fmt.Println(longestCommonSubsequence("abcde", "ace")) 158 | } 159 | ``` 160 | 161 | - 执行用时 : 8 ms , 在所有 Go 提交中击败了 41.30% 的用户 162 | - 内存消耗 : 10.4 MB , 在所有 Go 提交中击败了 57.14% 的用户 163 | 164 | ## 改进 165 | 166 | 使用一维数组改进 167 | 168 | ```go 169 | func longestCommonSubsequence(text1 string, text2 string) int { 170 | m, n := len(text1), len(text2) 171 | dp := make([]int, n+1) 172 | for i := 1; i <= m; i++ { 173 | last := 0 174 | for j := 1; j <= n; j++ { 175 | tmp := dp[j] 176 | if text1[i-1] == text2[j-1] { 177 | dp[j] = last + 1 178 | } else { 179 | dp[j] = max(tmp, dp[j-1]) 180 | } 181 | last = tmp 182 | } 183 | } 184 | return dp[n] 185 | } 186 | 187 | func max(i, j int) int { 188 | if i > j { 189 | return i 190 | } 191 | return j 192 | } 193 | ``` 194 | 195 | 增加临时变量去储存,一般想不到 196 | 197 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 198 | - 内存消耗 : 2.1 MB , 在所有 Go 提交中击败了 100.00% 的用户 199 | 200 | > Amazing 双百 201 | -------------------------------------------------------------------------------- /problems/0002.add-two-numbers/README.md: -------------------------------------------------------------------------------- 1 | # 两数相加 2 | 3 | [原题链接](https://leetcode-cn.com/problems/add-two-numbers/description/) 4 | 5 | ## 题目 6 | 7 | 给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。 8 | 9 | 你可以假设除了数字 0 之外,这两个数字都不会以零开头。 10 | 11 | 示例: 12 | 13 | ```bash 14 | 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 15 | 输出:7 -> 0 -> 8 16 | 原因:342 + 465 = 807 17 | ``` 18 | 19 | ## 解题思路 20 | 21 | ![add_two_numbers](https://pic.leetcode-cn.com/Figures/2/2_add_two_numbers.svg) 22 | 23 | ```bash 24 | 1.因为存储是反过来的,即数字342存成2->4->3,所以要注意进位是向后的; 25 | 2.链表l1或l2为空时,直接返回,这是边界条件,省掉多余的操作; 26 | 3.链表l1和l2长度可能不同,因此要注意处理某个链表剩余的高位; 27 | 4.2个数相加,可能会产生最高位的进位,因此要注意在完成以上1-3的操作后,判断进位是否为0,不为0则需要增加结点存储最高位的进位。 28 | ``` 29 | 30 | * 全部为null时,返回进位值 31 | * 有一个为null时,返回不为null的那个ListNode和进位相加的值 32 | * 都不为null时,返回 两个ListNode和进位相加的值 33 | 34 | ## 代码 35 | 36 | ### JAVA 37 | 38 | ```java 39 | package problem; 40 | 41 | public class Solution { 42 | 43 | public ListNode addTwoNumbers(ListNode l1, ListNode l2) { 44 | ListNode dummy = new ListNode(0); 45 | ListNode p = l1, q = l2, curr = dummy; 46 | int carry = 0; 47 | while (p != null || q != null) { 48 | int x = (p != null) ? p.val : 0; 49 | int y = (q != null) ? q.val : 0; 50 | int sum = carry + x + y; 51 | carry = sum / 10; 52 | curr.next = new ListNode(sum % 10); 53 | curr = curr.next; 54 | if (p != null) { 55 | p = p.next; 56 | } 57 | if (q != null) { 58 | q = q.next; 59 | } 60 | } 61 | if (carry > 0) { 62 | curr.next = new ListNode(carry); 63 | } 64 | return dummy.next; 65 | } 66 | 67 | static class ListNode { 68 | int val; 69 | ListNode next; 70 | 71 | public ListNode(int x) { 72 | val = x; 73 | } 74 | } 75 | 76 | public static void main(String[] args) { 77 | ListNode l1 = new ListNode(2); 78 | l1.next = new ListNode(4); 79 | l1.next.next = new ListNode(3); 80 | ListNode l2 = new ListNode(5); 81 | l2.next = new ListNode(6); 82 | l2.next.next = new ListNode(4); 83 | ListNode ans = new Solution().addTwoNumbers(l1, l2); 84 | while (ans != null) { 85 | System.out.println(ans.val); 86 | ans = ans.next; 87 | } 88 | } 89 | 90 | } 91 | ``` 92 | 93 | ### Python 94 | 95 | ```Python 96 | # Definition for singly-linked list. 97 | class ListNode: 98 | def __init__(self, x): 99 | self.val = x 100 | self.next = None 101 | 102 | 103 | class Solution: 104 | def addTwoNumbers(self, l1, l2): 105 | """ 106 | :type l1: ListNode 107 | :type l2: ListNode 108 | :rtype: ListNode 109 | """ 110 | c = 0 111 | dummy = cur = ListNode(0) 112 | while l1 or l2 or c: 113 | num1 = l1.val if l1 else 0 114 | num2 = l2.val if l2 else 0 115 | (c, val) = divmod(num1 + num2 + c, 10) 116 | cur.next = ListNode(val) 117 | l1 = l1.next if l1 else None 118 | l2 = l2.next if l2 else None 119 | cur = cur.next 120 | return dummy.next 121 | 122 | 123 | if __name__ == "__main__": 124 | l1 = ListNode(2) 125 | l1.next = ListNode(4) 126 | l1.next.next = ListNode(3) 127 | l2 = ListNode(5) 128 | l2.next = ListNode(6) 129 | l2.next.next = ListNode(4) 130 | ans = Solution().addTwoNumbers(l1, l2) 131 | while ans is not None: 132 | print(ans.val) 133 | ans = ans.next 134 | ``` 135 | 136 | 137 | ### Golang 138 | 139 | ```go 140 | package main 141 | 142 | import "fmt" 143 | 144 | type ListNode struct { 145 | Val int 146 | Next *ListNode 147 | } 148 | 149 | func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { 150 | dummy := &ListNode{Val: 0} 151 | p, q, curr := l1, l2, dummy 152 | carry := 0 153 | for p != nil || q != nil { 154 | x, y := 0, 0 155 | if p != nil { 156 | x = p.Val 157 | } 158 | if q != nil { 159 | y = q.Val 160 | } 161 | sum := carry + x + y 162 | carry = sum / 10 163 | curr.Next = &ListNode{Val: sum % 10} 164 | curr = curr.Next 165 | if p != nil { 166 | p = p.Next 167 | } 168 | if q != nil { 169 | q = q.Next 170 | } 171 | } 172 | if carry > 0 { 173 | curr.Next = &ListNode{Val: carry} 174 | } 175 | return dummy.Next 176 | } 177 | 178 | func main() { 179 | l1 := &ListNode{ 180 | Val: 2, 181 | Next: &ListNode{ 182 | Val: 4, 183 | Next: &ListNode{ 184 | Val: 3, 185 | Next: nil, 186 | }, 187 | }, 188 | } 189 | l2 := &ListNode{ 190 | Val: 5, 191 | Next: &ListNode{ 192 | Val: 6, 193 | Next: &ListNode{ 194 | Val: 4, 195 | Next: nil, 196 | }, 197 | }, 198 | } 199 | ans := addTwoNumbers(l1, l2) 200 | for ans != nil { 201 | fmt.Println(ans.Val) 202 | ans = ans.Next 203 | } 204 | } 205 | ``` -------------------------------------------------------------------------------- /problems/0014.LongestCommonPrefix/README.md: -------------------------------------------------------------------------------- 1 | # 最长公共前缀 2 | 3 | [原题链接](https://leetcode-cn.com/problems/longest-common-prefix/) 4 | 5 | ## 题目 6 | 7 | 编写一个函数来查找字符串数组中的最长公共前缀。 8 | 9 | 如果不存在公共前缀,返回空字符串 `""`。 10 | 11 | **示例 1:** 12 | 13 | ``` 14 | 输入: ["flower","flow","flight"] 15 | 输出: "fl" 16 | ``` 17 | 18 | **示例 2:** 19 | 20 | ``` 21 | 输入: ["dog","racecar","car"] 22 | 输出: "" 23 | 解释: 输入不存在公共前缀。 24 | ``` 25 | 26 | **说明:** 27 | 28 | 所有输入只包含小写字母 `a-z` 。 29 | 30 | ## 题解 31 | 32 | 我把这题分为两步: 33 | 1. 需要知道列表中最短的字符串长度是多少 34 | 2. 需要判断每个字符串指定位置的字符是否相等 35 | 36 | 仔细想想后,第一步其实不是必要的,数组中任意一个字符串都可以当做长度遍历对象 37 | 38 | ## 代码 39 | 40 | ### Java I 41 | 42 | ```java 43 | class Solution { 44 | public String longestCommonPrefix(String[] strs) { 45 | StringBuilder sb = new StringBuilder(); 46 | if (strs.length == 0) { 47 | return sb.toString(); 48 | } 49 | int i = 0; 50 | int max = strs[0].length(); 51 | while (i < max) { 52 | if (!same(strs, i)) { 53 | break; 54 | } 55 | sb.append(strs[0].charAt(i)); 56 | i++; 57 | } 58 | return sb.toString(); 59 | } 60 | 61 | private boolean same(String[] strs, int index) { 62 | char c = strs[0].charAt(index); 63 | for (String str : strs) { 64 | if (index >= str.length()) { 65 | return false; 66 | } 67 | if (str.charAt(index) != c) { 68 | return false; 69 | } 70 | } 71 | return true; 72 | } 73 | } 74 | ``` 75 | 76 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 78.40% 的用户 77 | - 内存消耗 : 37.9 MB , 在所有 Java 提交中击败了 10.00% 的用户 78 | 79 | 看了解答,时间复杂度可以优化 80 | 81 | ### Java II 82 | 83 | ```java 84 | class Solution { 85 | public String longestCommonPrefix(String[] strs) { 86 | if (strs.length == 0) { 87 | return ""; 88 | } 89 | String prefix = strs[0]; 90 | for (int i = 1; i < strs.length; i++) { 91 | while (strs[i].indexOf(prefix) != 0) { 92 | prefix = prefix.substring(0, prefix.length() - 1); 93 | if (prefix.isEmpty()) { 94 | return ""; 95 | } 96 | } 97 | } 98 | return prefix; 99 | } 100 | } 101 | ``` 102 | 103 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 104 | - 内存消耗 : 37.7 MB , 在所有 Java 提交中击败了 25.84% 的用户 105 | 106 | ### Go I 107 | 108 | ```go 109 | func longestCommonPrefix(strs []string) string { 110 | if len(strs) == 0 { 111 | return "" 112 | } 113 | ans := "" 114 | i, max := 0, len(strs[0]) 115 | for i < max { 116 | if !same(strs, i) { 117 | break 118 | } 119 | ans += string(strs[0][i]) 120 | i++ 121 | } 122 | return ans 123 | } 124 | 125 | func same(strs []string, index int) bool { 126 | tmp := strs[0][index] 127 | for i := 1; i < len(strs); i++ { 128 | if len(strs[i]) <= index { 129 | return false 130 | } 131 | if strs[i][index] != tmp { 132 | return false 133 | } 134 | } 135 | return true 136 | } 137 | ``` 138 | 139 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 140 | - 内存消耗 : 2.4 MB , 在所有 Go 提交中击败了 11.76% 的用户 141 | 142 | ### Go II 143 | 144 | ```go 145 | func longestCommonPrefix(strs []string) string { 146 | if len(strs) == 0 { 147 | return "" 148 | } 149 | for index, char := range strs[0] { 150 | for _, v := range strs { 151 | if index >= len(v) { 152 | return v 153 | } 154 | if v[index] != byte(char) { 155 | return v[:index] 156 | } 157 | } 158 | } 159 | return strs[0] 160 | } 161 | ``` 162 | 163 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 164 | - 内存消耗 : 2.4 MB , 在所有 Go 提交中击败了 82.35% 的用户 165 | 166 | ### Python I 167 | 168 | ```python 169 | class Solution: 170 | def longestCommonPrefix(self, strs: List[str]) -> str: 171 | if len(strs) == 0: 172 | return "" 173 | 174 | def same(index): 175 | tmp = strs[0][index] 176 | for s in strs: 177 | if index >= len(s): 178 | return False 179 | if s[index] != tmp: 180 | return False 181 | return True 182 | 183 | index, first = 0, len(strs[0]) 184 | ans = "" 185 | while index < first: 186 | if not same(index): 187 | break 188 | ans += strs[0][index] 189 | index += 1 190 | return ans 191 | ``` 192 | 193 | - 执行用时 : 44 ms , 在所有 Python3 提交中击败了 59.27% 的用户 194 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 6.15% 的用户 195 | 196 | ### Python II 197 | 198 | ```python 199 | class Solution: 200 | def longestCommonPrefix(self, strs: List[str]) -> str: 201 | if len(strs) == 0: 202 | return "" 203 | for i in range(len(strs[0])): 204 | for j in range(1, len(strs)): 205 | if i == len(strs[j]) or strs[j][i] != strs[0][i]: 206 | return strs[0][:i] 207 | return strs[0] 208 | ``` 209 | 210 | - 执行用时 : 48 ms , 在所有 Python3 提交中击败了 37.08% 的用户 211 | - 内存消耗 : 13.7 MB , 在所有 Python3 提交中击败了 6.15% 的用户 -------------------------------------------------------------------------------- /problems/0072.edit-distance/README.md: -------------------------------------------------------------------------------- 1 | # 编辑距离 2 | 3 | [原题链接](https://leetcode-cn.com/problems/edit-distance/) 4 | 5 | ## 题目 6 | 7 | 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 8 | 9 | 你可以对一个单词进行如下三种操作: 10 | 1. 插入一个字符 11 | 2. 删除一个字符 12 | 3. 替换一个字符 13 | 14 | 示例 1: 15 | 16 | 输入:word1 = "horse", word2 = "ros" 17 | 输出:3 18 | 19 | 解释: 20 | horse -> rorse (将 'h' 替换为 'r') 21 | rorse -> rose (删除 'r') 22 | rose -> ros (删除 'e') 23 | 24 | 示例 2: 25 | 26 | 输入:word1 = "intention", word2 = "execution" 27 | 输出:5 28 | 29 | 解释: 30 | intention -> inention (删除 't') 31 | inention -> enention (将 'i' 替换为 'e') 32 | enention -> exention (将 'n' 替换为 'x') 33 | exention -> exection (将 'n' 替换为 'c') 34 | exection -> execution (插入 'u') 35 | 36 | ## 题解 37 | 38 | 刚开始拿到题目,我是懵逼的,没有想法,后来我想到一种方法,分为两步:先删减、后排序。想法是先操作 word1,经过一系列添加删除,转换为 word2,然后再排序。可是实际上没法操作。 39 | 40 | 我也想过使用动态规划,可就是想不出状态转移方程,直到看到了答案。 41 | 42 | > 其实我陷入了误区,没有把题目等价的能力。因为操作 word1 和操作 word2 的步骤是一样的,word1 添加元素 = word2 删除元素。 43 | 44 | 状态定义: 45 | `dp[i][j]`表示word1的前i个字母转换成word2的前j个字母所使用的最少操作。 46 | 47 | 状态转移: 48 | - i指向word1,j指向word2 49 | - 若当前字母相同,则`dp[i][j] = dp[i - 1][j - 1]`; 50 | - 否则取增删替三个操作的最小值 + 1, 即: 51 | `dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1` 52 | 53 | ## 代码 54 | 55 | ### JAVA 56 | 57 | ```java 58 | package problem; 59 | 60 | public class Solution { 61 | public int minDistance(String word1, String word2) { 62 | int len1 = word1.length(), len2 = word2.length(); 63 | int[][] dp = new int[len1 + 1][len2 + 1]; 64 | for (int i = 0; i <= len1; i++) { 65 | dp[i][0] = i; 66 | } 67 | for (int j = 0; j <= len2; j++) { 68 | dp[0][j] = j; 69 | } 70 | for (int i = 1; i <= len1; i++) { 71 | for (int j = 1; j <= len2; j++) { 72 | if (word1.charAt(i - 1) == word2.charAt(j - 1)) { 73 | dp[i][j] = dp[i - 1][j - 1]; 74 | } else { 75 | dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1; 76 | } 77 | } 78 | } 79 | return dp[len1][len2]; 80 | } 81 | 82 | public static void main(String[] args) { 83 | System.out.println(new Solution().minDistance("", "a")); 84 | } 85 | } 86 | ``` 87 | 88 | - 执行用时 : 6 ms , 在所有 Java 提交中击败了 92.48% 的用户 89 | - 内存消耗 : 39.3 MB , 在所有 Java 提交中击败了 5.06% 的用户 90 | 91 | ### Python 92 | 93 | ```python 94 | class Solution: 95 | def minDistance(self, word1: str, word2: str) -> int: 96 | n1, n2 = len(word1), len(word2) 97 | dp = [[0] * (n2 + 1) for _ in range(n1 + 1)] 98 | dp[0][0] = 0 99 | for i in range(1, n1 + 1): 100 | dp[i][0] = i 101 | for j in range(1, n2 + 1): 102 | dp[0][j] = j 103 | for i in range(1, n1 + 1): 104 | for j in range(1, n2 + 1): 105 | if word1[i - 1] == word2[j - 1]: 106 | dp[i][j] = dp[i - 1][j - 1] 107 | else: 108 | dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 109 | return dp[n1][n2] 110 | 111 | 112 | if __name__ == '__main__': 113 | print(Solution().minDistance("intention", "execution")) 114 | ``` 115 | 116 | - 执行用时 : 196 ms , 在所有 Python3 提交中击败了 68.32% 的用户 117 | - 内存消耗 : 17.3 MB , 在所有 Python3 提交中击败了 12.88% 的用户 118 | 119 | ### go 120 | 121 | ```go 122 | package main 123 | 124 | import "fmt" 125 | 126 | func minDistance(word1 string, word2 string) int { 127 | l1, l2 := len(word1), len(word2) 128 | dp := make([][]int, l1+1) 129 | for i := 0; i <= l1; i++ { 130 | dp[i] = make([]int, l2+1) 131 | dp[i][0] = i 132 | } 133 | for j := 0; j <= l2; j++ { 134 | dp[0][j] = j 135 | } 136 | dp[0][0] = 0 137 | for i := 1; i <= l1; i++ { 138 | for j := 1; j <= l2; j++ { 139 | if word1[i-1] == word2[j-1] { 140 | dp[i][j] = dp[i-1][j-1] 141 | } else { 142 | tmp := dp[i-1][j] 143 | if dp[i][j-1] < dp[i-1][j] { 144 | tmp = dp[i][j-1] 145 | } 146 | if tmp > dp[i-1][j-1] { 147 | tmp = dp[i-1][j-1] 148 | } 149 | dp[i][j] = tmp + 1 150 | } 151 | } 152 | } 153 | return dp[l1][l2] 154 | } 155 | 156 | func main() { 157 | fmt.Println(minDistance("", "a")) 158 | } 159 | ``` 160 | 161 | - 执行用时 : 4 ms , 在所有 Go 提交中击败了 84.65% 的用户 162 | - 内存消耗 : 5.7 MB , 在所有 Go 提交中击败了 50.00% 的用户 163 | 164 | ## 进阶 165 | 166 | 使用一位数组优化空间 167 | 168 | ```python 169 | class Solution(object): 170 | def minDistance(self, word1, word2): 171 | """ 172 | :type word1: str 173 | :type word2: str 174 | :rtype: int 175 | """ 176 | n1, n2 = len(word1), len(word2) 177 | dp = [0] * (n2 + 1) #保留一行 178 | dp[0] = 0 179 | for j in range(1, n2 + 1): 180 | dp[j] = j 181 | for i in range(1, n1 + 1): 182 | old_dp_j = dp[0] 183 | dp[0] = i 184 | for j in range(1, n2 + 1): 185 | old_dp_j_1, old_dp_j = old_dp_j, dp[j] 186 | if word1[i - 1] == word2[j - 1]: 187 | dp[j] = old_dp_j_1 188 | else: 189 | dp[j] = min(dp[j], dp[j - 1], old_dp_j_1) + 1 190 | return dp[n2] 191 | ``` 192 | 193 | -------------------------------------------------------------------------------- /problems/0542.01-matrix/README.md: -------------------------------------------------------------------------------- 1 | # 01 矩阵 2 | 3 | [原题链接](https://leetcode-cn.com/problems/01-matrix/) 4 | 5 | ## 题目 6 | 7 | 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 8 | 9 | 两个相邻元素间的距离为 1 。 10 | 11 | 示例 1: 12 | 13 | 输入: 14 | ```text 15 | 0 0 0 16 | 0 1 0 17 | 0 0 0 18 | ``` 19 | 20 | 输出: 21 | 22 | ```text 23 | 0 0 0 24 | 0 1 0 25 | 0 0 0 26 | ``` 27 | 28 | 示例 2: 29 | 30 | 输入: 31 | ```text 32 | 0 0 0 33 | 0 1 0 34 | 1 1 1 35 | ``` 36 | 37 | 输出: 38 | ```text 39 | 0 0 0 40 | 0 1 0 41 | 1 2 1 42 | ``` 43 | 44 | 注意: 45 | 1. 给定矩阵的元素个数不超过 10000。 46 | 2. 给定矩阵中至少有一个元素是 0。 47 | 3. 矩阵中的元素只在四个方向上相邻: 上、下、左、右。 48 | 49 | ## 题解 50 | 51 | 看到这种题目就知道会使用DFS或BFS,这两块我需要找时间好好补补,真的不会做。。。 52 | 53 | ### 思路一:BFS 54 | 55 | 下面是甜姨的题解: 56 | 57 | 1. 对于 「Tree 的 BFS」 (典型的「单源 BFS」) 大家都已经轻车熟路了 58 | 1. 首先把 root 节点入队,再一层一层无脑遍历就行了。 59 | 60 | 2. 对于 「图 的 BFS」 (「多源 BFS」) 做法其实也是一样滴~,与 「Tree 的 BFS」的区别注意以下两条就 ok 辣~ 61 | 62 | - Tree 只有 1 个 root,而图可以有多个源点,所以首先需要把多个源点都入队; 63 | - Tree 是有向的,因此不需要标识是否访问过,而对于无向图来说,必须得标志是否访问过哦!并且为了防止某个节点多次入队,需要在其入队之前就将其设置成已访问!【 看见很多人说自己的 BFS 超时了,坑就在这里哈哈哈 64 | - 首先把每个源点 00 入队,然后从各个 00 同时开始一圈一圈的向 11 扩散(每个 11 都是被离它最近的 00 扩散到的 ),扩散的时候可以设置 int[][] dist 来记录距离(即扩散的层次)并同时标志是否访问过。 65 | - 对于本题是可以直接修改原数组 int[][] matrix 来记录距离和标志是否访问的,这里要注意先把 matrix 数组中 1 的位置设置成 -1 (设成Integer.MAX_VALUE啦,m * n啦,10000啦都行,只要是个无效的距离值来标志这个位置的 1 没有被访问过就行辣~) 66 | 67 | 68 | 69 | ### 思路二:动态规划 70 | 71 | 我是真的没想到还能用dp。 72 | 73 | 对于任一点 (i, j)(*i*,*j*),距离 00 的距离为: 74 | 75 | ![](https://cdn.jsdelivr.net/gh/zhangslob/oss@master/uPic/Tt8vCj.png) 76 | 77 | 78 | 因此我们用 `dp[i][j]`来表示该位置距离最近的 0 的距离。 79 | 我们发现 `dp[i][j]` 是由其上下左右四个状态来决定,无法从一个方向开始递推! 80 | 81 | 于是我们尝试将问题分解: 82 | 83 | 1. 距离 (i, j)最近的 0 的位置,是在其 「左上,右上,左下,右下」4个方向之一; 84 | 2. 因此我们分别从四个角开始递推,就分别得到了位于「左上方、右上方、左下方、右下方」距离 (i, j) 的最近的 0 的距离,取 min 即可; 85 | 3. 通过上两步思路,我们可以很容易的写出 44 个双重 forfor 循环,动态规划的解法写到这一步其实已经完全 OKOK 了; 86 | 4. 如果第三步还不满足的话,从四个角开始的 44 次递推,其实还可以优化成从任一组对角开始的 22 次递推,比如只写从左上角、右下角开始递推就行了,为啥这样可以呢?且听我不负责任的草率论证 = = 87 | 1. 首先从左上角开始递推 dp[i][j]dp[i][j] 是由其 「左方」和 「左上方」的最优子状态决定的; 88 | 2. 然后从右下角开始递推 dp[i][j]dp[i][j] 是由其 「右方」和 「右下方」的最优子状态决定的; 89 | 3. 看起来第一次递推的时候,把「右上方」的最优子状态给漏掉了,其实不是的,因为第二次递推的时候「右方」的状态在第一次递推时已经包含了「右上方」的最优子状态了; 90 | 4. 看起来第二次递推的时候,把「左下方」的最优子状态给漏掉了,其实不是的,因为第二次递推的时候「右下方」的状态在第一次递推时已经包含了「左下方」的最优子状态了。 91 | 92 | 93 | 94 | ## 代码 95 | 96 | ### Java 97 | 98 | #### 思路一 99 | 100 | ```java 101 | class Solution { 102 | public int[][] updateMatrix(int[][] matrix) { 103 | // 首先将所有的 0 都入队,并且将 1 的位置设置成 -1,表示该位置是 未被访问过的 1 104 | Queue queue = new LinkedList<>(); 105 | int m = matrix.length, n = matrix[0].length; 106 | for (int i = 0; i < m; i++) { 107 | for (int j = 0; j < n; j++) { 108 | if (matrix[i][j] == 0) { 109 | queue.offer(new int[] {i, j}); 110 | } else { 111 | matrix[i][j] = -1; 112 | } 113 | } 114 | } 115 | 116 | int[] dx = new int[] {-1, 1, 0, 0}; 117 | int[] dy = new int[] {0, 0, -1, 1}; 118 | while (!queue.isEmpty()) { 119 | int[] point = queue.poll(); 120 | int x = point[0], y = point[1]; 121 | for (int i = 0; i < 4; i++) { 122 | int newX = x + dx[i]; 123 | int newY = y + dy[i]; 124 | // 如果四邻域的点是 -1,表示这个点是未被访问过的 1 125 | // 所以这个点到 0 的距离就可以更新成 matrix[x][y] + 1。 126 | if (newX >= 0 && newX < m && newY >= 0 && newY < n 127 | && matrix[newX][newY] == -1) { 128 | matrix[newX][newY] = matrix[x][y] + 1; 129 | queue.offer(new int[] {newX, newY}); 130 | } 131 | } 132 | } 133 | 134 | return matrix; 135 | } 136 | } 137 | ``` 138 | 139 | #### 思路二 140 | 141 | ```java 142 | class Solution { 143 | public int[][] updateMatrix(int[][] matrix) { 144 | int m = matrix.length, n = matrix[0].length; 145 | int[][] dp = new int[m][n]; 146 | for (int i = 0; i < m; i++) { 147 | for (int j = 0; j < n; j++) { 148 | dp[i][j] = matrix[i][j] == 0 ? 0 : 10000; 149 | } 150 | } 151 | 152 | // 从左上角开始 153 | for (int i = 0; i < m; i++) { 154 | for (int j = 0; j < n; j++) { 155 | if (i - 1 >= 0) { 156 | dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1); 157 | } 158 | if (j - 1 >= 0) { 159 | dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1); 160 | } 161 | } 162 | } 163 | // 从右下角开始 164 | for (int i = m - 1; i >= 0; i--) { 165 | for (int j = n - 1; j >= 0; j--) { 166 | if (i + 1 < m) { 167 | dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1); 168 | } 169 | if (j + 1 < n) { 170 | dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1); 171 | } 172 | } 173 | } 174 | return dp; 175 | } 176 | } 177 | ``` 178 | 179 | 180 | 181 | ## 大佬的总结 182 | 183 | 因为要求最近,所以一般会想到用bfs,如果要是求最远,一般会想到用dfs。对dp的感觉还是懵逼中,哈哈哈 184 | 185 | 深度遍历: 186 | 187 | 1. 第一步:明确递归参数 188 | 2. 第二步:明确递归终止条件 189 | 3. 第三步:明确递归函数中的内容 190 | 4. 第四步:明确回溯返回值 191 | 192 | 广度遍历: 193 | 194 | 1. 第一步:设置队列,添加初始节点 195 | 2. 第二步:判断队列是否为空 196 | 3. 第三步:迭代操作 弹出队列元素,进行逻辑处理 当前队列元素的下级元素,入队 197 | 4. 第四步:在此执行步骤三 198 | 199 | -------------------------------------------------------------------------------- /problems/0033.search-in-rotated-sorted-array/README.md: -------------------------------------------------------------------------------- 1 | # 搜索旋转排序数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 4 | 5 | ## 题目 6 | 7 | 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 8 | 9 | ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 10 | 11 | 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 12 | 13 | 你可以假设数组中不存在重复的元素。 14 | 15 | 你的算法时间复杂度必须是 O(log n) 级别。 16 | 17 | 示例 1: 18 | ```text 19 | 输入: nums = [4,5,6,7,0,1,2], target = 0 20 | 输出: 4 21 | ``` 22 | 23 | 示例 2: 24 | ```text 25 | 输入: nums = [4,5,6,7,0,1,2], target = 3 26 | 输出: -1 27 | ``` 28 | 29 | ## 题解 30 | 31 | 数组其实是有序的,比如数组长度是n,前k个数是有序的;从n-k到n也是有序的。 32 | 33 | 我初步的想法是根据双指针往中间缩,具体如下: 34 | 1. 如果target比第一个数大,如果target在这个数组中,那么一定在前k个。不停的从前往后去找 35 | 2. 如果target小于第一个数,那么target一定存在与从n-k到n。不停的从后往前找 36 | 37 | 38 | ```java 39 | class Solution { 40 | public int search(int[] nums, int target) { 41 | if (nums.length == 0) { 42 | return -1; 43 | } 44 | if (target < nums[0] && target > nums[nums.length - 1]) { 45 | return -1; 46 | } 47 | int first = nums[0], len = nums.length; 48 | if (target == first) { 49 | return 0; 50 | } else if (target > first) { 51 | int left = 1; 52 | while (left < len) { 53 | if (nums[left] == target) { 54 | return left; 55 | } 56 | if (nums[left] < nums[left - 1]) { 57 | break; 58 | } 59 | left++; 60 | } 61 | } else { 62 | int right = nums.length - 1; 63 | while (right > 0) { 64 | if (nums[right] == target) { 65 | return right; 66 | } 67 | if (nums[right] < nums[right - 1]) { 68 | break; 69 | } 70 | right--; 71 | } 72 | } 73 | return -1; 74 | } 75 | } 76 | ``` 77 | 78 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 79 | - 内存消耗 : 39.5 MB , 在所有 Java 提交中击败了 17.74% 的用户 80 | 81 | 虽然从结果来说还不错,但是这道题要求的算法时间复杂度必须是 O(log n) 级别,我这个做法并没有达到,最坏情况下依然是O(n)。这道题必须使用二分去解决 82 | 83 | 84 | ## 代码 85 | 86 | ### Java 87 | 88 | ```java 89 | class Solution { 90 | public int search(int[] nums, int target) { 91 | if (nums.length == 0) { 92 | return -1; 93 | } 94 | if (target < nums[0] && target > nums[nums.length - 1]) { 95 | return -1; 96 | } 97 | int left = 0, right = nums.length - 1; 98 | while (left <= right) { 99 | int mid = left + (right - left) / 2; 100 | if (nums[mid] == target) { 101 | return mid; 102 | } else if (nums[mid] >= nums[0]) { 103 | // 在左边 104 | if (nums[0] <= target && target < nums[mid]) { 105 | right = mid - 1; 106 | } else { 107 | left = mid + 1; 108 | } 109 | } else { 110 | // 在右边 111 | if (nums[mid] < target && target <= nums[right]) { 112 | left = mid + 1; 113 | } else { 114 | right = mid - 1; 115 | } 116 | } 117 | } 118 | return -1; 119 | } 120 | } 121 | ``` 122 | 123 | - 执行用时 : 0 ms , 在所有 Java 提交中击败了 100.00% 的用户 124 | - 内存消耗 : 39.4 MB , 在所有 Java 提交中击败了 17.74% 的用户 125 | 126 | 127 | ## Go 128 | 129 | ```go 130 | package main 131 | 132 | import "fmt" 133 | 134 | func search(nums []int, target int) int { 135 | if len(nums) == 0 { 136 | return -1 137 | } 138 | left, right := 0, len(nums)-1 139 | for left <= right { 140 | mid := left + (right - left) / 2 141 | if nums[mid] == target { 142 | return mid 143 | } else if nums[mid] >= nums[0] { 144 | if nums[0] <= target && target < nums[mid] { 145 | right = mid - 1 146 | } else { 147 | left = mid + 1 148 | } 149 | } else { 150 | if nums[right] >= target && target > nums[mid] { 151 | left = mid + 1 152 | } else { 153 | right = mid - 1 154 | } 155 | } 156 | } 157 | return -1 158 | } 159 | 160 | func main() { 161 | nums := []int{4, 5, 6, 7, 0, 1, 2} 162 | fmt.Println(search(nums, 2)) 163 | } 164 | ``` 165 | 166 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 167 | - 内存消耗 : 2.6 MB , 在所有 Go 提交中击败了 100.00% 的用户 168 | 169 | ### Python 170 | 171 | ```python 172 | class Solution: 173 | def search(self, nums: List[int], target: int) -> int: 174 | if len(nums) == 0: 175 | return -1 176 | left, right = 0, len(nums)-1 177 | while left <= right: 178 | mid = left + (right - left) // 2 179 | if nums[mid] == target: 180 | return mid 181 | elif nums[mid] >= nums[0]: 182 | if nums[0] <= target < nums[mid]: 183 | right = mid - 1 184 | else: 185 | left = mid + 1 186 | else: 187 | if nums[mid] < target <= nums[right]: 188 | left = mid + 1 189 | else: 190 | right = mid - 1 191 | return -1 192 | ``` 193 | 194 | - 执行用时 : 44 ms , 在所有 Python3 提交中击败了 56.57% 的用户 195 | - 内存消耗 : 13.6 MB , 在所有 Python3 提交中击败了 7.69% 的用户 196 | 197 | 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Solutions 2 | 3 | ## 算法 - Algorithms 4 | 5 | 1. 排序算法:快速排序、归并排序、计数排序 6 | 2. 搜索算法:回溯、递归、剪枝技巧 7 | 3. 图论:最短路、最小生成树、网络流建模 8 | 4. 动态规划:背包问题、最长子序列、计数问题 9 | 5. 基础技巧:分治、倍增、二分、贪心 10 | 11 | ## 数据结构 - Data Structures 12 | 13 | 1. 数组与链表:单 / 双向链表、跳舞链 14 | 2. 栈与队列 15 | 3. 树与图:最近公共祖先、并查集 16 | 4. 哈希表 17 | 5. 堆:大 / 小根堆、可并堆 18 | 6. 字符串:字典树、后缀树 19 | 7. [二叉树](./data_structure/binary_tree) 20 | 21 | # 动力 22 | 23 | 之前有一个爬虫机会,Airbnb。这可以算是一个超好的公司了,待遇福利都很好,但是我在他们的一面上就挂了,就是算法。给我做的题目是leetcode简单级别的,如果现场面是medium到hard级别的。 24 | 所以我越发意识到,想要去好点的公司,算法是我必须过的关。项目对我来说,应该不算难事,我缺的就是算法,加油。坚持用JAVA和Go刷下来,**两年后,去头条**!! 25 | 26 | 语言我会使用 JAVA && Python && Go。Python是我的入门语言,JAVA是我来带拼多多这边开始学习的,让我感受到了静态语言的魅力,Go是头条那边主力开发的语言,现在先用来刷题。 27 | 28 | Contact Me: zhangslob&gmail 29 | 30 | # 秘诀 31 | 32 | 1. 链表有环有快慢指针 33 | 2. 求子数组和(数组和运算)用前缀和 34 | 3. 动态规划能否优化空间 35 | 36 | # 题目列表 37 | 38 | | NUM | Solution | Difficulty | Time | Tag | 39 | |---| ----- | ---------- | ---- | ---- | 40 | |0001|[两数之和](./problems/0001.two-sum/README.md)|Easy| 2020-03-31 | 数组、哈希表 | 41 | |0002|[两数相加](./problems/0002.add-two-numbers/README.md)|Medium| 2020-03-31 | 链表、数学 | 42 | |0004|[两个排序数组的中位数](./problems/0004.median-of-two-sorted-arrays/README.md)|Hard| 2020-05-26 | 数组、二分查找、分治算法 | 43 | |0005|[最长回文子串](./problems/0005.longest-palindromic-substring/README.md)|Medium| 2020-05-22 | 字符串、动态规划 | 44 | |0009|[回文数](./problems/0009.PalindromeNumber/README.md)|Easy| 2020-06-10 | 数学 | 45 | |0014|[最长公共前缀](./problems/0014.LongestCommonPrefix/README.md)|Easy| 2020-06-15 | 字符串 | 46 | |0022|[括号生成](./problems/0022.generate-parentheses/README.md)|Medium| 2020-04-09 | 字符串、回溯算法 | 47 | |0033|[搜索旋转排序数组](./problems/0033.search-in-rotated-sorted-array/README.md)|Medium| 2020-04-27 | 数组、二分查找 | 48 | |0045|[跳跃游戏 II](./problems/0045.jump-game-ii/README.md)|Hard| 2020-05-04 | 数组、贪心算法 | 49 | |0046II|[螺旋矩阵](./problems/0046II.ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/README.md)|Medium| 2020-06-09 | 数组 | 50 | |0050|[Pow(x, n)](./problems/0050.powx-n/README.md)|Medium| 2020-05-11 | 数学、二分查找 | 51 | |0054|[螺旋矩阵](./problems/0054.spiral-matrix/README.md)|Medium| 2020-06-06 | 数组 | 52 | |0056-1|[数组中数字出现的次数](./problems/0056-1.shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/README.md)|Medium| 2020-04-28 | 数学、位运算 | 53 | |0064|[求1+2+…+n](./problems/0064.qiu-12n-lcof/README.md)|Medium| 2020-06-02 | 奇淫技巧 | 54 | |0070|[爬楼梯](./problems/0070.climbing-stairs/README.md)|Easy| 2020-06-13 | 动态规划 | 55 | |0098|[验证二叉搜索树](./problems/0098.validate-binary-search-tree/README.md)|Medium| 2020-05-05 | 树、深度优先搜索 | 56 | |0101|[对称二叉树](./problems/0101.symmetric-tree/README.md)|Easy| 2020-05-31 | 树、深度优先搜索、广度优先搜索 | 57 | |0102|[二叉树的层序遍历](./problems/0102.binary-tree-level-order-traversal/README.md)|Medium| 2020-05-13 | 树、广度优先搜索 | 58 | |0107|[旋转矩阵](./problems/0107.rotate-matrix-lcci/README.md)|Medium| 2020-04-07 | 数组 | 59 | |0136|[只出现一次的数字](./problems/0136.single-number/README.md)|Easy| 2020-05-14 | 位运算、哈希表 | 60 | |0146|[LRU缓存机制](./problems/0146.lru-cache/README.md)|Medium| 2020-05-26 | 设计 | 61 | |0152|[乘积最大子数组](./problems/0152.maximum-product-subarray/README.md)|Medium| 2020-05-18 | 数组、动态规划 | 62 | |0155|[最小栈](./problems/0155.min-stack/README.md)|Easy| 2020-05-12 | 栈、设计 | 63 | |0198|[打家劫舍](./problems/0198.house-robber/README.md)|Easy| 2020-05-29 | 动态规划 | 64 | |0199|[二叉树的右视图](./problems/0199.binary-tree-right-side-view/README.md)|Medium| 2020-04-22 | 数、深度优先搜索、广度优先搜索 | 65 | |0200|[岛屿数量](./problems/0200.number-of-islands/README.md)|Medium| 2020-04-20 | 深度优先搜索、广度优先搜索、并查集 | 66 | |0202|[快乐数](./problems/0202.happy-number/README.md)|Easy| 2020-04-30 | 哈希表、数学 | 67 | |0221|[最大正方形](./problems/0221.maximal-square/README.md)|Medium| 2020-05-08 | 动态规划 | 68 | |0238|[除自身以外数组的乘积](./problems/0238.product-of-array-except-self/README.md)|Medium| 2020-06-04 | 数组 | 69 | |0287|[寻找重复数](./problems/0287.find-the-duplicate-number/README.md)|Medium| 2020-05-27 | 数组、双指针、二分查找 | 70 | |0322|[零钱兑换](./problems/0322.coin-change/README.md)|Medium| 2020-08-16 | 动态规划 | 71 | |0445|[两数相加 II](./problems/0445.add-two-numbers-ii/README.md)|Medium| 2020-04-15 | 链表 | 72 | |0509|[斐波那契数](./problems/0509.fibonacci-number/README.md)|Easy| 2020-08-16 | 数组 | 73 | |0542|[01 矩阵](./problems/0542.01-matrix/README.md)|Medium| 2020-04-21 | 深度优先搜索、广度优先搜索 | 74 | |0560|[和为K的子数组](./problems/0560.subarray-sum-equals-k/README.md)|Medium| 2020-05-15 | 数组、哈希表 | 75 | |0572|[另一个树的子树](./problems/0572.subtree-of-another-tree/README.md)|Easy| 2020-05-07 | 树 | 76 | |0680|[验证回文字符串 Ⅱ](./problems/0680.valid-palindrome-ii/README.md)|Easy| 2020-05-19 | 字符串 | 77 | |0724|[寻找数组的中心索引](./problems/0724.find-pivot-index/README.md)|Easy| 2020-08-11 | 数组 | 78 | |0837|[新21点](./problems/0837.new-21-game/README.md)|Medium| 2020-06-03 | 动态规划、数学 | 79 | |0974|[和可被 K 整除的子数组](./problems/0974.subarray-sums-divisible-by-k/README.md)|Medium| 2020-05-27 | 数组、哈希表 | 80 | |1095|[山脉数组中查找目标值](./problems/1095.find-in-mountain-array/README.md)|Hard| 2020-04-29 | 二分查找 | 81 | |1111|[有效括号的嵌套深度](./problems/1111.maximum-nesting-depth-of-two-valid-parentheses-strings/README.md)|Medium| 2020-04-01 | 贪心算法、二分查找 | 82 | |1143|[最长公共子序列](./problems/1143.longest-common-subsequence/README.md)|Medium| 2020-04-09 | 动态规划 | 83 | |1248|[统计「优美子数组」](./problems/1248.count-number-of-nice-subarrays/README.md)|Medium| 2020-04-21 | 双指针 | 84 | |1371|[每个元音包含偶数次的最长子字符串](./problems/1371.find-the-longest-substring-containing-vowels-in-even-counts/README.md)|Medium| 2020-05-20 | 字符串 | 85 | |1431|[拥有最多糖果的孩子](./problems/1431.kids-with-the-greatest-number-of-candies/README.md)|Easy| 2020-06-01 | 数组 | 86 | 87 | -------------------------------------------------------------------------------- /problems/0199.binary-tree-right-side-view/README.md: -------------------------------------------------------------------------------- 1 | # 二叉树的右视图 2 | 3 | [原题链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) 4 | 5 | ## 题目 6 | 7 | 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 8 | 9 | 示例: 10 | 11 | 输入: [1,2,3,null,5,null,4] 12 | 13 | 输出: [1, 3, 4] 14 | 15 | 解释: 16 | ```text 17 | 1 <--- 18 | / \ 19 | 2 3 <--- 20 | \ \ 21 | 5 4 <--- 22 | ``` 23 | 24 | ## 题解 25 | 26 | 好好记录下我这个菜鸡是怎么一步步写(cao)出来的。 27 | 28 | 首先拿到题目,我就想到了二叉树的遍历: 29 | ```java 30 | void traverse(TreeNode root) { 31 | if (root == null) { 32 | return; 33 | } 34 | traverse(root.left); 35 | traverse(root.right); 36 | } 37 | ``` 38 | 39 | 然后我就想,这不是很简单吗,我只需要通过BFS,每次拿右边的节点即可,代码如下: 40 | 41 | ```java 42 | public List rightSideView(TreeNode root) { 43 | List ans = new ArrayList<>(); 44 | if (root == null) { 45 | return ans; 46 | } 47 | Queue queue = new LinkedList<>(); 48 | queue.offer(root); 49 | while (!queue.isEmpty()) { 50 | TreeNode node = queue.poll(); 51 | ans.add(node.val); 52 | if (node.right != null) { 53 | queue.offer(node.right); 54 | } 55 | } 56 | return ans; 57 | } 58 | ``` 59 | 60 | 我还暗自得意,哎,这题真简单,然而很苦逼的是,我并没有考虑左变存在,右边不存在的情况,如图: 61 | 62 | ```text 63 | 1 <--- 64 | / \ 65 | 2 3 <--- 66 | / 67 | 4 <--- 68 | ``` 69 | 70 | 这种答案是[1,3,5],我计算的是[1,3]。那我再现,当如果左边存在,右边不存在时入队,然后我就开心的写出来了: 71 | 72 | ```java 73 | public List rightSideView(TreeNode root) { 74 | List ans = new ArrayList<>(); 75 | if (root == null) { 76 | return ans; 77 | } 78 | Queue queue = new LinkedList<>(); 79 | queue.offer(root); 80 | while (!queue.isEmpty()) { 81 | TreeNode node = queue.poll(); 82 | ans.add(node.val); 83 | if (node.right != null) { 84 | queue.offer(node.right); 85 | } 86 | if (node.right == null && node.left != null) { 87 | queue.offer(node.left); 88 | } 89 | } 90 | return ans; 91 | } 92 | ``` 93 | 94 | 但是我没有考虑下面这种情况: 95 | 96 | ```text 97 | 1 <--- 98 | / \ 99 | 2 3 <--- 100 | \ 101 | 5 <--- 102 | ``` 103 | 104 | 哎,我真的菜。看了大神的解法,才知道我离答案就差一步。遍历到每一层时,把所有的节点全部入队,取最后一个。特别注意的是,怎么保证最后一个就是右边的呢,所以需要在入队的时候保证,先入左边的,再入右边的。 105 | 106 | ## 代码 107 | 108 | ### Java 109 | 110 | ```java 111 | /** 112 | * Definition for a binary tree node. 113 | * public class TreeNode { 114 | * int val; 115 | * TreeNode left; 116 | * TreeNode right; 117 | * TreeNode(int x) { val = x; } 118 | * } 119 | */ 120 | class Solution { 121 | public List rightSideView(TreeNode root) { 122 | List ans = new ArrayList<>(); 123 | if (root == null) { 124 | return ans; 125 | } 126 | Queue queue = new LinkedList<>(); 127 | queue.offer(root); 128 | while (!queue.isEmpty()) { 129 | int size = queue.size(); 130 | for (int i = 0; i < size; i++) { 131 | TreeNode node = queue.poll(); 132 | if (node == null) { 133 | continue; 134 | } 135 | // 先入左边的,再入右边的 136 | if (node.left != null) { 137 | queue.offer(node.left); 138 | } 139 | if (node.right != null) { 140 | queue.offer(node.right); 141 | } 142 | // 如果到了最后一位 143 | if (i == size - 1) { 144 | ans.add(node.val); 145 | } 146 | } 147 | } 148 | return ans; 149 | } 150 | } 151 | ``` 152 | 153 | - 执行用时 : 1 ms , 在所有 Java 提交中击败了 97.36% 的用户 154 | - 内存消耗 : 38.2 MB , 在所有 Java 提交中击败了 5.00% 的用户 155 | 156 | ### Python 157 | 158 | ```python 159 | class Solution: 160 | def rightSideView(self, root: TreeNode) -> List[int]: 161 | ans = [] 162 | if root is None: 163 | return ans 164 | queue = [root] 165 | while len(queue) != 0: 166 | for i in range(len(queue)): 167 | node = queue.pop(0) 168 | if i == 0: 169 | ans.append(node.val) 170 | if node.right is not None: 171 | queue.append(node.right) 172 | if node.left is not None: 173 | queue.append(node.left) 174 | return ans 175 | ``` 176 | 177 | - 执行用时 : 32 ms , 在所有 Python3 提交中击败了 91.93% 的用户 178 | - 内存消耗 : 13.8 MB , 在所有 Python3 提交中击败了 14.29% 的用户 179 | 180 | 也可以把入队顺序改下,这样每次就从第一个取 181 | 182 | ### Go 183 | 184 | ```go 185 | func rightSideView(root *TreeNode) []int { 186 | var ans []int 187 | if root == nil { 188 | return ans 189 | } 190 | queue := []*TreeNode{root} 191 | for len(queue) != 0 { 192 | sz := len(queue) 193 | for i := 0; i < sz; i++ { 194 | node := queue[0] 195 | queue = queue[1:] 196 | if i == 0 { 197 | ans = append(ans, node.Val) 198 | } 199 | if node.Right != nil { 200 | queue = append(queue, node.Right) 201 | } 202 | if node.Left != nil { 203 | queue = append(queue, node.Left) 204 | } 205 | } 206 | } 207 | return ans 208 | } 209 | ``` 210 | 211 | - 执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户 212 | - 内存消耗 : 2.3 MB , 在所有 Go 提交中击败了 100.00% 的用户 213 | -------------------------------------------------------------------------------- /problems/1371.find-the-longest-substring-containing-vowels-in-even-counts/README.md: -------------------------------------------------------------------------------- 1 | # 每个元音包含偶数次的最长子字符串 2 | 3 | [原题链接](https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/) 4 | 5 | ## 题目 6 | 7 | 给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 8 | 9 | 示例 1: 10 | - 输入:s = "eleetminicoworoep" 11 | - 输出:13 12 | - 解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。 13 | 14 | 示例 2: 15 | - 输入:s = "leetcodeisgreat" 16 | - 输出:5 17 | - 解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。 18 | 19 | 示例 3: 20 | - 输入:s = "bcbcbc" 21 | - 输出:6 22 | - 解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 23 | 24 | 提示: 25 | - 1 <= s.length <= 5 x 10^5 26 | - s 只包含小写英文字母。 27 | 28 | ## 题解 29 | 30 | 这虽然是一道中等难度的题目,但是如果你之前没有接触过该类型题目的话,可能对你来说会有一些困难。 31 | 1. 首先,本题的数据规模很大,因此暴力解(n平方)肯定行不通。我们应该寻找时间复杂度在O(n)级别的解题思路。 32 | 2. 其实本题的核心是如何记录子串的状态,题目要求求出最长的子串中,所有元音字符的个数都是偶数 33 | 3. 那么我们可以使用一个状态位来表示当前子串中每种元音的状态,其中1代表该元音字符有奇数个,0代表个数为偶数,五个元音我们可以使用5个状态位来分别表示,举几个例子: 34 | 35 | ```text 36 | 00000 // a:偶数 e:偶数 i:偶数 o:偶数 u:偶数 37 | 01000 // a:偶数 e:奇数 i:偶数 o:偶数 u:偶数 38 | 01011 // a:偶数 e:奇数 i:偶数 o:奇数 u:奇数 39 | 00100 // a:偶数 e:偶数 i:奇数 o:偶数 u:偶数 40 | ``` 41 | 42 | 1. 我们从开头开始遍历字符串,查看每个[0, i]区间子串的状态,并将每个状态第一次出现的位置存储到Map中 43 | 2. 接下来是重点,当我们再次遇到相同状态出现时,当前位置与首次出现该状态时的位置后一位所组成的区间即是一个合理区间 44 | 3. 我们举个例子来说明一下。比如状态01000在第x位首次出现,该状态表示字符e出现了奇数次,其他元音字符都是偶数次。 45 | 4. 接下来,我们在位置y再次发现了该状态01000,这能说明在x到y之间可能发生了以下2种情况: 46 | - x到y之间没有任何元音字符,因此,他们之间的状态没有发生改变 47 | - x到y之间有1个或多个元音出现,但是每种元音出现的次数都是偶数个,因此,他们的转态最终又还原为x时的状态。 48 | 49 | 我们再举一个生动的例子,比如我们遍历到字符串的第3位(下标为2),当前子串为: 50 | 51 | ```java 52 | String sub = "bba" // 状态为10000,a是奇数个,其他元音都是偶数个(0个) 53 | ``` 54 | 接下来我们遍历到了第8位(下标为7) 55 | 56 | ```java 57 | String sub = "bbaaaiib" // 状态为10000,a是奇数个,其他元音都是偶数个 58 | ``` 59 | 60 | 从第4位开始到第8位过程中,出现了2个a和2个i,因为出现的次数都是偶数,因此a和i的状态实际都没有发生改变,a依旧是奇数个,i依旧还是偶数个。进而转态也还是10000,没有发生改变。 61 | 62 | 因此我们可以得到结论,当两个点之间的状态相同时,他们之间的区间一定是一个合理区间,即所有元音元素都出现了偶数次(包括0次)。 63 | 64 | 1. 解题时,我们依次循环字符串中每个字符,统计下标0位到当前字符间元音状态,如果该状态第一次出现,我们将它存入Map中,记录下该状态第一次出现的位置。 65 | 2. 如果该状态不是首次出现,我们计算出当前位置与首次出现位置之间的长度,该长度为一个合理区间长度,并用该长度更新全局最大长度。 66 | 67 | 另外还有一种情况,如果当前的状态是0,说明当前位到首位间的子串也是合理区间。 68 | 69 | ## 代码 70 | 71 | ### Java 72 | 73 | ```java 74 | class Solution { 75 | public int findTheLongestSubstring(String s) { 76 | // 记录每种状态首次出现的位置 77 | Map map = new HashMap<>(); 78 | // 返回结果 79 | int res = 0; 80 | // 当前状态 81 | int state = 0; 82 | // 循环 83 | for (int i = 0; i < s.length(); i++) { 84 | // 当前字符 85 | char c = s.charAt(i); 86 | switch (c) { 87 | case 'a': 88 | // 更新a的状态 89 | state = state ^ 16; 90 | break; 91 | case 'e': 92 | // 更新e的状态 93 | state = state ^ 8; 94 | break; 95 | case 'i': 96 | // 更新i的状态 97 | state = state ^ 4; 98 | break; 99 | case 'o': 100 | // 更新o的状态 101 | state = state ^ 2; 102 | break; 103 | case 'u': 104 | // 更新u的状态 105 | state = state ^ 1; 106 | break; 107 | default: 108 | break; 109 | } 110 | // 如果当前状态是0,代表所有元音都是偶数,当前区间是合理解 111 | if (state == 0) { 112 | res = i + 1; 113 | } 114 | // 如果该状态不是首次出现 115 | if (map.containsKey(state)) { 116 | // 首次出现位置到当前位置的区间是一个合理解 117 | int length = i - map.get(state); 118 | // 更新全局最大区间 119 | res = Math.max(res, length); 120 | } else { 121 | // 该状态首次出现时,将当前位置记录到map中 122 | map.put(state, i); 123 | } 124 | } 125 | return res; 126 | } 127 | } 128 | ``` 129 | 130 | - 执行用时 : 47 ms , 在所有 Java 提交中击败了 50.00% 的用户 131 | - 内存消耗 : 44.1 MB , 在所有 Java 提交中击败了 100.00% 的用户 132 | 133 | ### Go 134 | 135 | ```go 136 | func findTheLongestSubstring(s string) int { 137 | m := map[int]int{} 138 | res, state := 0, 0 139 | for i := 0; i < len(s); i++ { 140 | if s[i] == 'a' { 141 | state ^= 16 142 | } else if s[i] == 'e' { 143 | state ^= 8 144 | } else if s[i] == 'i' { 145 | state ^= 4 146 | } else if s[i] == 'o' { 147 | state ^= 2 148 | } else if s[i] == 'u' { 149 | state ^= 1 150 | } 151 | if state == 0 { 152 | res = i + 1 153 | } 154 | if v, f := m[state]; f { 155 | length := i - v 156 | if length > res { 157 | res = length 158 | } 159 | } else { 160 | m[state] = i 161 | } 162 | } 163 | return res 164 | } 165 | ``` 166 | 167 | - 执行用时 : 32 ms , 在所有 Go 提交中击败了 48.48% 的用户 168 | - 内存消耗 : 6.2 MB , 在所有 Go 提交中击败了 100.00% 的用户 169 | 170 | ### Python 171 | 172 | ```python 173 | class Solution: 174 | def findTheLongestSubstring(self, s: str) -> int: 175 | map = dict() 176 | res, state = 0, 0 177 | for i in range(len(s)): 178 | if s[i] == 'a': 179 | state ^= 16 180 | elif s[i] == 'e': 181 | state ^= 8 182 | elif s[i] == 'i': 183 | state ^= 4 184 | elif s[i] == 'o': 185 | state ^= 2 186 | elif s[i] == 'u': 187 | state ^= 1 188 | if state == 0: 189 | res = i + 1 190 | if state in map: 191 | length = i - map[state] 192 | res = max(res, length) 193 | else: 194 | map[state] = i 195 | return res 196 | ``` 197 | 198 | - 执行用时 : 836 ms , 在所有 Python3 提交中击败了 43.75% 的用户 199 | - 内存消耗 : 19.8 MB , 在所有 Python3 提交中击败了 100.00% 的用户 200 | -------------------------------------------------------------------------------- /problems/0974.subarray-sums-divisible-by-k/README.md: -------------------------------------------------------------------------------- 1 | # 和可被 K 整除的子数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/subarray-sums-divisible-by-k/) 4 | 5 | ## 题目 6 | 7 | 给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。 8 | 9 | 示例: 10 | ```text 11 | 输入:A = [4,5,0,-2,-3,1], K = 5 12 | 输出:7 13 | ``` 14 | 15 | 解释: 16 | 有 7 个子数组满足其元素之和可被 K = 5 整除: 17 | [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3] 18 | 19 | 20 | 提示: 21 | 1. 1 <= A.length <= 30000 22 | 2. -10000 <= A[i] <= 10000 23 | 3. 2 <= K <= 10000 24 | 25 | ## 题解 26 | 27 | 首先暴力法 28 | 29 | ```java 30 | class Solution { 31 | public int subarraysDivByK(int[] A, int K) { 32 | int n = A.length; 33 | int ans = 0; 34 | for (int i = 0; i < n; i++) { 35 | int sum = A[i]; 36 | if (sum % K == 0) { 37 | ans++; 38 | } 39 | for (int j = i + 1; j < n; j++) { 40 | sum += A[j]; 41 | if (sum % K == 0) { 42 | ans++; 43 | } 44 | } 45 | } 46 | return ans; 47 | } 48 | } 49 | ``` 50 | 51 | 毫无疑问的超时了. 52 | 53 | 如果任意两个数字除以K得到的余数相同,那么两数之差一定可以被K整除。举个例子: 54 | ``` 55 | int a = 7; 56 | int b = 12; 57 | int K = 5; 58 | 59 | // 因为 60 | a % K = 7 % 5 = 2; 61 | b % K = 12 % 5 = 2; 62 | 63 | // 所以 64 | (b - a) % K = (12 - 7) % 5 = 0; 65 | 66 | // weima 67 | 68 | if (a % k == b % k) { 69 | (a - b) % k = 0 70 | } 71 | 72 | ``` 73 | 74 | 这是一个简单的数学定理,在这就不必展开讨论。那么如果来运用这个定理解开本题呢? 75 | 76 | `preSum[i, j](下标i到j之和)` 77 | `preSum[i, j] = preSum[0, j] - preSum[0, i];` 78 | 79 | 也就是说,只要我们求出下列子数组的和, 80 | 81 | ```text 82 | preSum[0, 0]; 83 | preSum[0, 1]; 84 | preSum[0, 2]; 85 | ... 86 | preSum[0, n]; 87 | ``` 88 | 89 | 那么对于数组中任意一个区间的和,都可以由上面相应的2个sum之差来取得。 90 | 91 | 另外,根据上面提到的余数定理,所有sum分别除以K,余数相同的sum之差是可以被K整除的,所以问题就转化为,寻找余数相同的个数。 92 | 93 | 我们定义一个Map来存储每个余数对应的个数: 94 | ```java 95 | Map map = new HashMap<>(); 96 | ``` 97 | 98 | 举个例子,比如当前map中余数为1的个数是5,当我们又找到一个sum%K的余数为1时,由于当前sum与之前5个的差都可以被K整除,所以结果会增加5。此外还要注意一个问题,当余数为0时,当前sum本身也是一个解,别忘了加入结果中。 99 | 100 | 最后还有一个小坑需要注意一下,在java语言中,负数与正整数的余为负数,所以需要特别转换下。比如: 101 | 102 | `-3 % 5 = -3;` 103 | 104 | 我们应该将其转换为正数: 105 | 106 | `-3 % 5 + 5 = 2;` 107 | 108 | ## 代码 109 | 110 | ### Java I 111 | 112 | 使用map 113 | 114 | ```java 115 | class Solution { 116 | public int subarraysDivByK(int[] A, int K) { 117 | int count = 0, preSum = 0; 118 | Map map = new HashMap<>(); 119 | map.put(0, 1); 120 | for (int num : A) { 121 | preSum = (preSum + num) % K; 122 | if (preSum < 0) { 123 | preSum += K; 124 | } 125 | if (map.containsKey(preSum)) { 126 | count += map.get(preSum); 127 | } 128 | map.put(preSum, map.getOrDefault(preSum, 0) + 1); 129 | } 130 | return count; 131 | } 132 | } 133 | ``` 134 | 135 | - 执行用时 : 32 ms , 在所有 Java 提交中击败了 22.20% 的用户 136 | - 内存消耗 : 43.6 MB , 在所有 Java 提交中击败了 100.00% 的用 137 | 138 | ### Java II 139 | 140 | 优化版,使用数组 141 | 142 | ```java 143 | class Solution { 144 | public int subarraysDivByK(int[] A, int K) { 145 | int count = 0, preSum = 0; 146 | int[] nums = new int[K]; 147 | nums[0] = 1; 148 | for (int a : A) { 149 | preSum = (preSum + a) % K; 150 | if (preSum < 0) { 151 | preSum += K; 152 | } 153 | count += nums[preSum]; 154 | nums[preSum]++; 155 | } 156 | return count; 157 | } 158 | } 159 | ``` 160 | 161 | - 执行用时 : 6 ms , 在所有 Java 提交中击败了 50.47% 的用户 162 | - 内存消耗 : 42.7 MB , 在所有 Java 提交中击败了 100.00% 的用户 163 | 164 | ### Go I 165 | 166 | ```go 167 | func subarraysDivByK(A []int, K int) int { 168 | sumMap := map[int]int{} 169 | sumMap[0] = 1 170 | sum, cnt := 0, 0 171 | for _, v := range A { 172 | sum = (v + sum) % K 173 | if sum < 0 { 174 | sum += K 175 | } 176 | if k, found := sumMap[sum]; found { 177 | cnt += k 178 | } 179 | sumMap[sum]++ 180 | } 181 | return cnt 182 | } 183 | ``` 184 | 185 | - 执行用时 : 60 ms , 在所有 Go 提交中击败了 82.22% 的用户 186 | - 内存消耗 : 6.7 MB , 在所有 Go 提交中击败了 100.00% 的用户 187 | 188 | ### Go II 189 | 190 | ```go 191 | func subarraysDivByK(A []int, K int) int { 192 | nums := make([]int, K) 193 | nums[0] = 1 194 | sum, cnt := 0, 0 195 | for _, v := range A { 196 | sum = (v + sum) % K 197 | if sum < 0 { 198 | sum += K 199 | } 200 | cnt += nums[sum] 201 | nums[sum]++ 202 | } 203 | return cnt 204 | } 205 | ``` 206 | 207 | - 执行用时 : 48 ms , 在所有 Go 提交中击败了 97.78% 的用户 208 | - 内存消耗 : 6.6 MB , 在所有 Go 提交中击败了 100.00% 的用户 209 | 210 | ### Python I 211 | 212 | ```python 213 | class Solution: 214 | def subarraysDivByK(self, A: List[int], K: int) -> int: 215 | m = dict() 216 | m[0] = 1 217 | pre_sum, cnt = 0, 0 218 | for i in A: 219 | pre_sum = (pre_sum + i) % K 220 | if pre_sum < 0: 221 | pre_sum += K 222 | cnt += m.get(pre_sum, 0) 223 | m[pre_sum] = m.get(pre_sum, 0) + 1 224 | return cnt 225 | ``` 226 | 227 | - 执行用时 : 388 ms , 在所有 Python3 提交中击败了 53.41% 的用户 228 | - 内存消耗 : 17.6 MB , 在所有 Python3 提交中击败了 100.00% 的用户 229 | 230 | ### Python II 231 | 232 | ```python 233 | class Solution: 234 | def subarraysDivByK(self, A: List[int], K: int) -> int: 235 | nums = [0 for _ in range(K)] 236 | nums[0] = 1 237 | pre_sum, cnt = 0, 0 238 | for i in A: 239 | pre_sum = (pre_sum + i) % K 240 | if pre_sum < 0: 241 | pre_sum += K 242 | cnt += nums[pre_sum] 243 | nums[pre_sum] += 1 244 | return cnt 245 | ``` 246 | 247 | - 执行用时 : 348 ms , 在所有 Python3 提交中击败了 97.63% 的用户 248 | - 内存消耗 : 17.6 MB , 在所有 Python3 提交中击败了 100.00% 的用户 249 | 250 | -------------------------------------------------------------------------------- /problems/1248.count-number-of-nice-subarrays/README.md: -------------------------------------------------------------------------------- 1 | # 统计「优美子数组」 2 | 3 | [原题链接](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/) 4 | 5 | ## 题目 6 | 7 | 给你一个整数数组 nums 和一个整数 k。 8 | 9 | 如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。 10 | 11 | 请返回这个数组中「优美子数组」的数目。 12 | 13 | 示例 1: 14 | 15 | 输入:nums = [1,1,2,1,1], k = 3 16 | 输出:2 17 | 解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。 18 | 示例 2: 19 | 20 | 输入:nums = [2,4,6], k = 1 21 | 输出:0 22 | 解释:数列中不包含任何奇数,所以不存在优美子数组。 23 | 示例 3: 24 | 25 | 输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2 26 | 输出:16 27 | 28 | 提示: 29 | 30 | - 1 <= nums.length <= 50000 31 | - 1 <= nums[i] <= 10^5 32 | - 1 <= k <= nums.length 33 | 34 | ## 题解 35 | 36 | 看到大佬写的一篇:[前缀和技巧](https://zhuanlan.zhihu.com/p/107778275),优秀。 37 | 38 | 提出了前缀和的观点,前缀和的思路是这样的,对于一个给定的数组 `nums`,我们额外开辟一个前缀和数组进行预处理: 39 | 40 | ```java 41 | int n = nums.length; 42 | // 前缀和数组 43 | int[] preSum = new int[n + 1]; 44 | preSum[0] = 0; 45 | for (int i = 0; i < n; i++) 46 | preSum[i + 1] = preSum[i] + nums[i]; 47 | ``` 48 | 49 | ![preview](https://pic1.zhimg.com/v2-0bef2c46daeca964e2933c6a5bea03d4_r.jpg) 50 | 51 | 这个前缀和数组 `preSum` 的含义也很好理解,`preSum[i]` 就是 `nums[0..i-1]` 的和。那么如果我们想求 `nums[i..j]` 的和,只需要一步操作 `preSum[j+1]-preSum[i]` 即可,而不需要重新去遍历数组了。 52 | 53 | 回到这个子数组问题,我们想求有多少个子数组的和为 k,借助前缀和技巧很容易写出一个解法: 54 | 55 | ```java 56 | int subarraySum(int[] nums, int k) { 57 | int n = nums.length; 58 | // 构造前缀和 59 | int[] sum = new int[n + 1]; 60 | sum[0] = 0; 61 | for (int i = 0; i < n; i++) 62 | sum[i + 1] = sum[i] + nums[i]; 63 | 64 | int ans = 0; 65 | // 穷举所有子数组 66 | for (int i = 1; i <= n; i++) 67 | for (int j = 0; j < i; j++) 68 | // sum of nums[j..i-1] 69 | if (sum[i] - sum[j] == k) 70 | ans++; 71 | 72 | return ans; 73 | } 74 | ``` 75 | 76 | 这个解法的时间复杂度 *O*(*N^*2) 空间复杂度 *O*(*N*),并不是最优的解法。不过通过这个解法理解了前缀和数组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。 77 | 78 | 进一步优化 79 | 80 | 前面的解法有嵌套的 for 循环: 81 | 82 | ```java 83 | for (int i = 1; i <= n; i++) 84 | for (int j = 0; j < i; j++) 85 | if (sum[i] - sum[j] == k) 86 | ans++; 87 | ``` 88 | 89 | 第二层 for 循环在干嘛呢?翻译一下就是,**在计算,有几个 `j` 能够使得 `sum[i]` 和 `sum[j]` 的差为 k。**毎找到一个这样的 `j`,就把结果加一。 90 | 91 | 我们可以把 if 语句里的条件判断移项,这样写: 92 | 93 | ```java 94 | if (sum[j] == sum[i] - k) 95 | ans++; 96 | ``` 97 | 98 | > 这不就是第一题两数之和的思想吗 99 | > 100 | > a + b = k ==> a = k - b 101 | 102 | 103 | 104 | ## 代码 105 | 106 | ### Java 107 | 108 | #### 滑动窗口,双指针 109 | 110 | 这种解法一定要注意左右两边的情况,i,j都是奇数,还要把i左右、j右边的偶数加起来,不然就会少很多 111 | 112 | ```java 113 | class Solution { 114 | public int numberOfSubarrays(int[] nums, int k) { 115 | int left = 0, right = 0, ans = 0, oddCount = 0; 116 | while (right < nums.length) { 117 | if ((nums[right++] & 1) == 1) { 118 | oddCount++; 119 | } 120 | if (oddCount == k) { 121 | int tmp = right; 122 | while (right < nums.length && (nums[right] & 1) == 0) { 123 | right++; 124 | } 125 | int rightCount = right - tmp; 126 | int leftCount = 0; 127 | while ((nums[left] & 1) == 0) { 128 | leftCount++; 129 | left++; 130 | } 131 | ans += (leftCount + 1) * (rightCount + 1); 132 | left++; 133 | oddCount--; 134 | } 135 | } 136 | return ans; 137 | } 138 | } 139 | ``` 140 | 141 | - 太难了,自己根本写不出来 142 | 143 | - 执行用时 : 8 ms , 在所有 Java 提交中击败了 74.71% 的用户 144 | - 内存消耗 : 47.9 MB , 在所有 Java 提交中击败了 100.00% 的用户 145 | 146 | #### 使用map 147 | 148 | ```java 149 | class Solution { 150 | public int numberOfSubarrays(int[] nums, int k) { 151 | int sum = 0, ans = 0; 152 | Map map = new HashMap<>(); 153 | map.put(0, 1); 154 | for (int i = 0; i < nums.length; i++) { 155 | sum += (nums[i] & 1); 156 | if (map.containsKey(sum - k)) { 157 | ans += map.get(sum - k); 158 | } 159 | map.put(sum, map.getOrDefault(sum, 0) + 1); 160 | } 161 | return ans; 162 | } 163 | } 164 | ``` 165 | 166 | - 执行用时 : 90 ms , 在所有 Java 提交中击败了 5.17% 的用户 167 | - 内存消耗 : 48.2 MB , 在所有 Java 提交中击败了 100.00% 的用户 168 | 169 | #### 使用array 170 | 171 | ```java 172 | class Solution { 173 | public int numberOfSubarrays(int[] nums, int k) { 174 | int ans = 0, sum = 0; 175 | int[] arr = new int[nums.length + 1]; 176 | arr[0] = 1; 177 | for (int num : nums) { 178 | sum += num & 1; 179 | arr[sum]++; 180 | if (sum >= k) { 181 | ans += arr[sum - k]; 182 | } 183 | } 184 | return ans; 185 | } 186 | } 187 | ``` 188 | 189 | - 执行用时 : 3 ms , 在所有 Java 提交中击败了 100.00% 的用户 190 | - 内存消耗 : 48.4 MB , 在所有 Java 提交中击败了 100.00% 的用户 191 | 192 | 193 | 194 | ### Python 195 | 196 | ```python 197 | class Solution: 198 | def numberOfSubarrays(self, nums: List[int], k: int) -> int: 199 | # use array to store exists value 200 | arr = [0 for i in range(len(nums)+1)] 201 | arr[0] = 1 202 | ans = sum = 0 203 | for num in nums: 204 | sum += num & 1 205 | # sum + x = k 206 | arr[sum] += 1 207 | if sum >= k: 208 | ans += arr[sum - k] 209 | return ans 210 | ``` 211 | 212 | - 执行用时 : 1020 ms , 在所有 Python3 提交中击败了 69.68% 的用户 213 | - 内存消耗 : 20.4 MB , 在所有 Python3 提交中击败了 25.00% 的用户 214 | 215 | ## Go 216 | 217 | ```go 218 | func numberOfSubarrays(nums []int, k int) int { 219 | arr := make([]int, len(nums)+1) 220 | arr[0] = 1 221 | ans, sum := 0, 0 222 | for i := 0; i < len(nums); i++ { 223 | sum += nums[i] & 1 224 | arr[sum]++ 225 | if sum >= k { 226 | ans += arr[sum-k] 227 | } 228 | } 229 | return ans 230 | } 231 | ``` 232 | 233 | - 执行用时 : 148 ms , 在所有 Go 提交中击败了 60.87% 的用户 234 | - 内存消耗 : 7.6 MB , 在所有 Go 提交中击败了 100.00% 的用户 -------------------------------------------------------------------------------- /problems/0015.3sum/README.md: -------------------------------------------------------------------------------- 1 | # 三数之和 2 | 3 | [原题链接](https://leetcode-cn.com/problems/3sum/description/) 4 | 5 | ## 题目 6 | 7 | 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得a + b + c = 0 ?找出所有满足条件且不重复的三元组。 8 | 9 | 注意:答案中不可以包含重复的三元组。 10 | 11 | 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 12 | 13 | 满足要求的三元组集合为: 14 | [ 15 | [-1, 0, 1], 16 | [-1, -1, 2] 17 | ] 18 | 19 | ## 解题思路 20 | 21 | 1. 数组排序 22 | 2. 定义三个指针,i,j,k 23 | 3. 遍历i,那么这个问题就可以转化为在i之后的数组中寻找`nums[j]+nums[k]=-nums[i]` 24 | 4. 这个问题,也就将三数之和问题转变为二数之和---(可以使用双指针) 25 | 26 | ## 代码 27 | 28 | ### JAVA 29 | 30 | ```java 31 | package problem; 32 | 33 | import java.util.ArrayList; 34 | import java.util.Arrays; 35 | import java.util.List; 36 | 37 | public class Solution { 38 | public List> threeSum(int[] nums) { 39 | Arrays.sort(nums); 40 | List> ans = new ArrayList<>(); 41 | for (int i = 0; i < nums.length - 2; i++) { 42 | if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) { 43 | int l = i + 1, r = nums.length - 1, sum = -nums[i]; 44 | while (l < r) { 45 | if (nums[l] + nums[r] == sum) { 46 | ans.add(Arrays.asList(nums[i], nums[l], nums[r])); 47 | while (l < r && nums[l] == nums[l + 1]) { 48 | l++; 49 | } 50 | while (l < r && nums[r] == nums[r - 1]) { 51 | r--; 52 | } 53 | l++; 54 | r--; 55 | } else if (nums[l] + nums[r] < sum) { 56 | while (l < r && nums[l] == nums[l + 1]) { 57 | l++; 58 | } 59 | l++; 60 | } else { 61 | while (l < r && nums[r] == nums[r - 1]) { 62 | r--; 63 | } 64 | r--; 65 | } 66 | } 67 | } 68 | } 69 | return ans; 70 | } 71 | 72 | public static void main(String[] args) { 73 | System.out.println(new Solution().threeSum(new int[]{0, 0, 0})); 74 | } 75 | } 76 | ``` 77 | 78 | - 执行用时 : 28 ms , 在所有 Java 提交中击败了 73.80% 的用户 79 | - 内存消耗 : 43.5 MB , 在所有 Java 提交中击败了 99.77% 的用户 80 | 81 | ### Python 82 | 83 | ```python 84 | class Solution: 85 | def threeSum(self, nums): 86 | """ 87 | :type nums: List[int] 88 | :rtype: List[List[int]] 89 | """ 90 | s = set() 91 | nums.sort() 92 | length = len(nums) 93 | for i in range(length - 2): 94 | j = i + 1 95 | k = length - 1 96 | if nums[i] > 0 or j > k: 97 | break 98 | while j < k: 99 | if nums[i] + nums[j] + nums[k] == 0: 100 | s.add((nums[i], nums[j], nums[k])) 101 | while j < k and nums[j] == nums[j + 1]: 102 | j += 1 103 | while j < k and nums[k] == nums[k - 1]: 104 | k -= 1 105 | j += 1 106 | k -= 1 107 | continue 108 | if nums[i] + nums[j] + nums[k] > 0: 109 | while j < k and nums[k] == nums[k - 1]: 110 | k -= 1 111 | k -= 1 112 | continue 113 | if nums[i] + nums[j] + nums[k] < 0: 114 | while j < k and nums[j] == nums[j + 1]: 115 | j += 1 116 | j += 1 117 | continue 118 | return sorted(list(s), key=lambda x: (x[0], x[1], x[2])) 119 | 120 | 121 | if __name__ == '__main__': 122 | print(Solution().threeSum([-1, 0, 1, 2, -1, -4])) 123 | ``` 124 | - 执行用时 : 2504 ms , 在所有 Python3 提交中击败了 5.04% 的用户 125 | - 内存消耗 : 16.7 MB , 在所有 Python3 提交中击败了 8.30% 的用户 126 | 127 | ```python 128 | class Solution: 129 | def threeSum(self, nums): 130 | """ 131 | :type nums: List[int] 132 | :rtype: List[List[int]] 133 | """ 134 | if len(nums) < 3: 135 | return [] 136 | nums.sort() 137 | res = set() 138 | for i, v in enumerate(nums[:-2]): 139 | if i >= 1 and v == nums[i-1]: 140 | continue 141 | d = {} 142 | for x in nums[i+1:]: 143 | if x not in d: 144 | d[-v-x] = 1 145 | else: 146 | res.add((v, -v-x, x)) 147 | return list(res) 148 | 149 | 150 | if __name__ == '__main__': 151 | print(Solution().threeSum([-1, 0, 1, 2, -1, -4])) 152 | ``` 153 | 154 | 这种方法真的太难想到了 155 | 156 | - 执行用时 : 764 ms , 在所有 Python3 提交中击败了 88.34% 的用户 157 | - 内存消耗 : 16.4 MB , 在所有 Python3 提交中击败了 12.37% 的用户 158 | 159 | ### Golang 160 | 161 | ```go 162 | package main 163 | 164 | import ( 165 | "fmt" 166 | "sort" 167 | ) 168 | 169 | func threeSum(nums []int) [][]int { 170 | sort.Ints(nums) 171 | var ans = make([][]int, 0) 172 | for lo := 0; lo < len(nums)-2; lo++ { 173 | mid, hi, target := lo+1, len(nums)-1, -nums[lo] 174 | for mid < hi { 175 | sum := nums[mid] + nums[hi] 176 | switch { 177 | case sum < target: 178 | mid++ 179 | case sum > target: 180 | hi-- 181 | default: // find one 182 | ans = append(ans, []int{nums[lo], nums[mid], nums[hi]}) 183 | for mid+1 < hi && nums[mid] == nums[mid+1] { // move next to the right-most same number 184 | mid++ 185 | } 186 | for hi-1 > mid && nums[hi] == nums[hi-1] { // move back to the left-most same number 187 | hi-- 188 | } 189 | mid++ 190 | hi-- 191 | } 192 | } 193 | for lo+1 < len(nums) && nums[lo] == nums[lo+1] { // move next to the right-most same number 194 | lo++ 195 | } 196 | } 197 | return ans 198 | } 199 | 200 | func main() { 201 | fmt.Println(threeSum([]int{-1, 0, 1, 2, -1, -4})) 202 | } 203 | ``` 204 | 205 | - 执行用时 : 36 ms , 在所有 Go 提交中击败了 78.02% 的用户 206 | - 内存消耗 : 6.9 MB , 在所有 Go 提交中击败了 30.29% 的用户 207 | -------------------------------------------------------------------------------- /problems/0912.sort-an-array/README.md: -------------------------------------------------------------------------------- 1 | # 排序数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/sort-an-array/) 4 | 5 | ## 题目 6 | 7 | 给你一个整数数组 nums,将该数组升序排列。 8 | 9 | 示例 1: 10 | 输入:nums = [5,2,3,1] 11 | 输出:[1,2,3,5] 12 | 13 | 示例 2: 14 | 输入:nums = [5,1,1,2,0,0] 15 | 输出:[0,0,1,1,2,5] 16 | 17 | 提示: 18 | 1 <= nums.length <= 50000 19 | -50000 <= nums[i] <= 50000 20 | 21 | ## 解题思路 22 | 23 | 这不就是排序吗,所有排序算法中表现效果最好的就是快排了,快排最重要的就是partition函数。看看自己能不能手写出来。 24 | 25 | ## 代码 26 | 27 | ### JAVA 28 | 29 | ```java 30 | package problem; 31 | 32 | import java.util.Arrays; 33 | 34 | public class Solution { 35 | 36 | public int[] sortArray(int[] nums) { 37 | if (nums.length <= 1) { 38 | return nums; 39 | } 40 | sort(nums, 0, nums.length - 1); 41 | return nums; 42 | } 43 | 44 | private void sort(int[] nums, int i, int j) { 45 | if (i < j) { 46 | int index = partition(nums, i, j); 47 | sort(nums, i, index - 1); 48 | sort(nums, index + 1, j); 49 | } 50 | } 51 | 52 | private int partition(int[] nums, int i, int j) { 53 | int p = nums[i]; 54 | int left = i; 55 | int right = j; 56 | while (left < right) { 57 | while (left < right && nums[right] >= p) { 58 | right--; 59 | } 60 | while (left < right && nums[left] <= p) { 61 | left++; 62 | } 63 | swap(nums, right, left); 64 | } 65 | swap(nums, i, left); 66 | return left; 67 | } 68 | 69 | private void swap(int[] nums, int i, int j) { 70 | int tmp = nums[j]; 71 | nums[j] = nums[i]; 72 | nums[i] = tmp; 73 | } 74 | 75 | public static void main(String[] args) { 76 | int[] nums = new int[]{5, 1, 1, 2, 0, 0}; 77 | System.out.println(Arrays.toString(new Solution().sortArray(nums))); 78 | } 79 | 80 | } 81 | ``` 82 | 83 | - 执行用时 : 5 ms , 在所有 Java 提交中击败了 97.87% 的用户 84 | - 内存消耗 : 47.5 MB , 在所有 Java 提交中击败了 7.38% 的用户 85 | 86 | ### Python 87 | 88 | ```python 89 | class Solution: 90 | def sortArray(self, nums): 91 | self.sort(nums, 0, len(nums) - 1) 92 | return nums 93 | 94 | def sort(self, nums, left, right): 95 | if left < right: 96 | posit = self.partition(nums, left, right) 97 | self.sort(nums, left, posit - 1) 98 | self.sort(nums, posit + 1, right) 99 | 100 | def partition(self, nums, left, right): 101 | p = nums[left] 102 | i, j = left, right 103 | while i < j: 104 | while i < j and nums[j] >= p: 105 | j -= 1 106 | while i < j and nums[i] <= p: 107 | i += 1 108 | nums[i], nums[j] = nums[j], nums[i] 109 | nums[left], nums[i] = nums[i], nums[left] 110 | return i 111 | 112 | 113 | if __name__ == '__main__': 114 | nums = [5, 2, 3, 1] 115 | Solution().sortArray(nums) 116 | print(nums) 117 | ``` 118 | 119 | - 执行用时 : 296 ms , 在所有 Python3 提交中击败了 62.07% 的用户 120 | - 内存消耗 : 19.7 MB , 在所有 Python3 提交中击败了 8.57% 的用户 121 | 122 | 执行用时为 36 ms 的范例 123 | 124 | ```python 125 | class Solution: 126 | def sortArray(self, nums: List[int]) -> List[int]: 127 | return sorted(nums) 128 | ``` 129 | 130 | 这样写有什么意思呢,刷题还是蒙骗自己? 131 | 132 | ### go 133 | 134 | ```go 135 | 136 | func sortArray(nums []int) []int { 137 | if len(nums) <= 1 { 138 | return nums 139 | } 140 | sort(nums, 0, len(nums)-1) 141 | return nums 142 | } 143 | 144 | func sort(nums []int, i int, j int) { 145 | if i < j { 146 | index := partition(nums, i, j) 147 | sort(nums, i, index-1) 148 | sort(nums, index+1, j) 149 | } 150 | } 151 | 152 | func partition(nums []int, i int, j int) int { 153 | p := nums[i] 154 | left, right := i, j 155 | for left < right { 156 | for left < right && nums[right] >= p { 157 | right-- 158 | } 159 | for left < right && nums[left] <= p { 160 | left++ 161 | } 162 | nums[left], nums[right] = nums[right], nums[left] 163 | } 164 | nums[left], nums[i] = nums[i], nums[left] 165 | return left 166 | } 167 | 168 | ``` 169 | 170 | - 执行用时 : 24 ms , 在所有 Go 提交中击败了 90.44% 的用户 171 | - 内存消耗 : 6.5 MB , 在所有 Go 提交中击败了 5.13% 的用户 172 | 173 | 在维基百科上看到另一种方法,学习下 174 | 175 | ```go 176 | package main 177 | 178 | import "fmt" 179 | 180 | func sortArray(nums []int) []int { 181 | if len(nums) <= 1 { 182 | return nums 183 | } 184 | mid := nums[0] 185 | head, tail := 0, len(nums)-1 186 | for i := 1; i <= tail; { 187 | if nums[i] > mid { 188 | nums[i], nums[tail] = nums[tail], nums[i] 189 | tail-- 190 | } else { 191 | nums[i], nums[head] = nums[head], nums[i] 192 | head++ 193 | i++ 194 | } 195 | } 196 | sortArray(nums[:head]) 197 | sortArray(nums[head+1:]) 198 | return nums 199 | } 200 | 201 | func main() { 202 | nums := []int{5, 1, 1, 2, 0, 0} 203 | fmt.Println(sortArray(nums)) 204 | } 205 | ``` 206 | 207 | - 执行用时 : 40 ms , 在所有 Go 提交中击败了 20.75% 的用户 208 | - 内存消耗 : 6.5 MB , 在所有 Go 提交中击败了 5.13% 的用户 209 | 210 | ## 新思路 211 | 212 | 参考:[当我谈排序时,我在谈些什么🤔](https://leetcode-cn.com/problems/sort-an-array/solution/dang-wo-tan-pai-xu-shi-wo-zai-tan-xie-shi-yao-by-s/) 213 | 214 | ```java 215 | package problem; 216 | 217 | import java.util.Arrays; 218 | 219 | public class Solution { 220 | 221 | public int[] sortArray(int[] nums) { 222 | int max = -50001, min = 50001; 223 | for (int num : nums) { 224 | max = Math.max(num, max); 225 | min = Math.min(num, min); 226 | } 227 | int[] counter = new int[max - min + 1]; 228 | for (int num : nums) { 229 | counter[num - min]++; 230 | } 231 | int idx = 0; 232 | for (int num = min; num <= max; num++) { 233 | int cnt = counter[num - min]; 234 | while (cnt-- > 0) { 235 | nums[idx++] = num; 236 | } 237 | } 238 | return nums; 239 | } 240 | 241 | public static void main(String[] args) { 242 | int[] nums = new int[]{5, 1, 1, 2, 0, 0}; 243 | System.out.println(Arrays.toString(new Solution().sortArray(nums))); 244 | } 245 | } 246 | ``` 247 | 248 | - 执行用时 : 2 ms , 在所有 Java 提交中击败了 100.00% 的用户 249 | - 内存消耗 : 47.2 MB , 在所有 Java 提交中击败了 7.79% 的用户 250 | 251 | 计数排序法,感觉和之前做的找出一个数组中出现次数最多的,摩尔投票法 252 | 253 | [169. 多数元素](https://leetcode-cn.com/problems/majority-element/) 254 | --------------------------------------------------------------------------------