Skip to content

Commit 52f8002

Browse files
committed
feat(python): solutions
1 parent d8b33b1 commit 52f8002

8 files changed

+437
-58
lines changed

JavaScript/0005. Longest Palindromic Substring.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
// This yields a straight forward DP solution, which we first initialize the one and two letters palindromes, and work
4141
// our way up finding all three letters palindromes, and so on.
4242

43-
/** 3) Expand around center */
43+
/** 3) Expand from center */
4444
// https://www.youtube.com/watch?v=m2Mk9JN5T4A
4545
//
4646
// Time O(n^2). Expanding a palindrome around its center takes O(n) time, so the overall complexity is O(n^2)

JavaScript/0010. Regular Expression Matching.js

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const isMatch3 = (s, p) => {
147147
};
148148

149149
/** 4) Dynamic programming */
150-
// https://www.youtube.com/watch?v=qza1UKNHAys
150+
// https://leetcode.com/problems/regular-expression-matching/discuss/5651/Easy-DP-Java-Solution-with-detailed-Explanation/238767
151151
//
152152
// dp[i][j] denotes whether s[0 : i - 1] matches p[0 : j - 1]
153153
//
@@ -157,47 +157,54 @@ const isMatch3 = (s, p) => {
157157
// 2. if p[j] === '.'
158158
// dp[i][j] = dp[i - 1][j - 1]
159159
//
160-
// 3. if p[j] === '*'
160+
// 3. if p[j] === '*', also need consider p[j - 1]
161161
// 1) if p[j - 1] !== s[i] and p[j - 1] !== '.' // a ab* and not a a.*
162162
// dp[i][j] = dp[i][j - 2] // e.g. a ab* -> p remove 'b*' which is j - 2
163163
//
164-
// 2) if p[i - 1] === s[i] or p[i - 1] == '.'
165-
// a) dp[i][j] = dp[i][j - 2] // c* - empty, e.g. ab ab.*
166-
// b) dp[i][j] = dp[i][j - 1] // c* - single c, e.g. abc abc*
167-
// c) dp[i][j] = dp[i - 1][j] // c* - multiple c, e.g. abccc abc*
164+
// 2) if p[j - 1] === s[i] or p[j - 1] === '.'
165+
// a) dp[i][j] = dp[i][j - 2] // c* - no 'c', e.g. ab ab.*
166+
// b) dp[i][j] = dp[i][j - 1] // c* - single 'c', e.g. abc abc*
167+
// c) dp[i][j] = dp[i - 1][j] // c* - multiple 'c', e.g. abccc abc*
168168
//
169169
// Example 1
170170
// s = 'abcd', p = 'a*.cd'
171171
//
172-
// p 0 1 2 3 4
173-
// 0 1 2 3 4 5
174-
// s 0 a * . c d
175-
// 0 1 T F T F F F
176-
// 1 2 a F T T T F F
177-
// 2 3 b F F F T F F
178-
// 3 4 c F F F F T F
179-
// 4 5 d F F F F F T
172+
// p 0 1 2 3 4
173+
// s a * . c d
174+
// 0 T F T F F F
175+
// 1 a F T T T F F
176+
// 2 b F F F T F F
177+
// 3 c F F F F T F
178+
// 4 d F F F F F T
180179
//
181180
// Example 2
182181
// s = 'abaa', p = 'ab.*'
183182
//
184-
// p 0 1 2 3 4
185-
// 0 1 2 3 4 5
186-
// s 0 a b . *
187-
// 0 1 T F F F F
188-
// 1 2 a F T F F F
189-
// 2 3 b F F T F T
190-
// 3 4 a F F F T T
191-
// 4 5 a F F F F T
183+
// p 0 1 2 3 4
184+
// s a b . *
185+
// 0 T F F F F
186+
// 1 a F T F F F
187+
// 2 b F F T F T
188+
// 3 a F F F T T
189+
// 4 a F F F F T
192190

193191
const isMatch = (s, p) => {
194192
const dp = [...Array(s.length + 1)].map(() => Array(p.length + 1).fill(false));
193+
194+
// Initialization
195+
// 1) empty string matches empty pattern
195196
dp[0][0] = true;
196197

197-
// init dp[0][i] to true if p[i] is *
198-
for (let i = 1; i < p.length; i++) {
199-
if (p[i] === '*' && dp[0][i - 1]) {
200-
dp[0][i + 1] = true;
198+
// 2) dp[i][0] = false (which is default value of the boolean array) since empty pattern cannot match non-empty string
199+
// 3) dp[0][j]: what pattern matches empty string ""? It should be #*#*#*#*..., or (#*)* if allow me to represent regex using regex :P,
200+
// and for this case we need to check manually:
201+
// as we can see, the length of pattern should be even && the character at the even position should be *,
202+
// thus for odd length, dp[0][j] = false which is default. So we can just skip the odd position, i.e. j starts from 2, the interval of j is also 2.
203+
// and notice that the length of repeat sub-pattern #* is only 2, we can just make use of dp[0][j - 2] rather than scanning j length each time
204+
// for checking if it matches #*#*#*#*.
205+
for (let j = 2; j < p.length; j += 2) {
206+
if (p[j - 1] === '*' && dp[0][j - 2]) {
207+
dp[0][j] = true;
201208
}
202209
}
203210

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Given a string s, find the length of the longest substring without repeating characters.
2+
#
3+
# Example 1:
4+
#
5+
# Input: s = "abcabcbb"
6+
# Output: 3
7+
# Explanation: The answer is "abc", with the length of 3.
8+
#
9+
# Example 2:
10+
#
11+
# Input: s = "bbbbb"
12+
# Output: 1
13+
# Explanation: The answer is "b", with the length of 1.
14+
#
15+
# Example 3:
16+
#
17+
# Input: s = "pwwkew"
18+
# Output: 3
19+
# Explanation: The answer is "wke", with the length of 3.
20+
# Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
21+
#
22+
# Constraints:
23+
#
24+
# 0 <= s.length <= 5 * 10^4
25+
# s consists of English letters, digits, symbols and spaces.
26+
27+
28+
# 1) Sliding window + hash map
29+
# Similar
30+
# 3. Longest Substring Without Repeating Characters
31+
# 904. Fruit Into Baskets
32+
# 992. Subarrays with K Different Integers
33+
#
34+
# Time O(2n) = O(n). In the worst case each character will be visited twice by l and r.
35+
# Space O(min(m, n)). We need O(k) space for the sliding window, where k is the size of the Set. The size of the Set is
36+
# upper bounded by the size of the string nn and the size of the charset/alphabet m.
37+
class Solution:
38+
def lengthOfLongestSubstring(self, s: str) -> int:
39+
dic = dict()
40+
max_len = 0
41+
l = 0
42+
r = 0
43+
while l < len(s) and r < len(s):
44+
if s[r] not in dic:
45+
dic[s[r]] = True
46+
r += 1
47+
max_len = max(max_len, r - l)
48+
else:
49+
dic.pop(s[l])
50+
l += 1
51+
return max_len
52+
53+
54+
# 2) Sliding window (optimized)
55+
# Time O(n)
56+
# Space O(min(m, n)), m is the size of the hash map
57+
#
58+
# The above solution requires at most 2n steps. In fact, it could be optimized to require only n steps. Instead of
59+
# using a set to tell if a character exists or not, we could define a mapping of the characters to its index. Then
60+
# we can skip the characters immediately when we found a repeated character.
61+
# The reason is that if s[r] have a duplicate in the range [l, r) with index r', we don't need to increase l
62+
# little by little. We can skip all the elements in the range [l, r'] and let l to be r' + 1 directly.
63+
#
64+
# e.g. pwwkew
65+
# l = 0, r = 0, dic = { p: 1 }
66+
# l = 0, r = 1, dic = { p: 1, w: 2 }
67+
# l = 2, r = 2, dic = { p: 1, w: 3 }
68+
# l = 2, r = 3, dic = { p: 1, w: 3, k: 4 }
69+
# l = 2, r = 4, dic = { p: 1, w: 3, k: 4, e: 5 }
70+
# l = 3, r = 5, dic = { p: 1, w: 6, k: 4, e: 5 }
71+
class Solution:
72+
def lengthOfLongestSubstring(self, s: str) -> int:
73+
dic = dict()
74+
max_len = 0
75+
l = 0
76+
for r, c in enumerate(s):
77+
if c in dic:
78+
# not l = dic[c], because max makes sure l always increase
79+
l = max(l, dic[c])
80+
dic[c] = r + 1 # dic[c] saves next start point for l
81+
max_len = max(max_len, r - l)
82+
return max_len
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.
2+
# The overall run time complexity should be O(log (m+n)).
3+
#
4+
# Example 1:
5+
#
6+
# Input: nums1 = [1,3], nums2 = [2]
7+
# Output: 2.00000
8+
# Explanation: merged array = [1,2,3] and median is 2.
9+
#
10+
# Example 2:
11+
#
12+
# Input: nums1 = [1,2], nums2 = [3,4]
13+
# Output: 2.50000
14+
# Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
15+
#
16+
# Constraints:
17+
#
18+
# nums1.length == m
19+
# nums2.length == n
20+
# 0 <= m <= 1000
21+
# 0 <= n <= 1000
22+
# 1 <= m + n <= 2000
23+
# -10^6 <= nums1[i], nums2[i] <= 10^6
24+
25+
# 1) Sorting
26+
# Time O((m + n)log(m + n))
27+
# Space O(m + n)
28+
class Solution:
29+
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
30+
nums = nums1 + nums2
31+
nums.sort()
32+
if len(nums) % 2 != 0:
33+
return nums[len(nums) // 2]
34+
else:
35+
return (nums[len(nums) // 2 - 1] + nums[len(nums) // 2]) / 2
36+
37+
38+
# Binary Search
39+
# https://zxi.mytechroad.com/blog/algorithms/binary-search/leetcode-4-median-of-two-sorted-arrays/
40+
#
41+
# Time O(log(m + n))
42+
# Space O(1)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Given a string s, return the longest palindromic substring in s.
2+
#
3+
# Example 1:
4+
#
5+
# Input: s = "babad"
6+
# Output: "bab"
7+
# Explanation: "aba" is also a valid answer.
8+
9+
# Example 2:
10+
#
11+
# Input: s = "cbbd"
12+
# Output: "bb"
13+
#
14+
# Constraints:
15+
#
16+
# 1 <= s.length <= 1000
17+
# s consist of only digits and English letters.
18+
19+
# 1) Brute force
20+
# Time O(n^3)
21+
# Space O(1)
22+
#
23+
# Pick all possible starting and ending positions for a substring, and verify if it is a palindrome
24+
25+
# 2) Dynamic programming
26+
# Time O(n^2)
27+
# Space O(n^2)
28+
#
29+
# To improve over the brute force solution, we first observe how we can avoid unnecessary re-computation while
30+
# validating palindromes. Consider the case "ababa". If we already knew that "bab" is a palindrome, it is obvious
31+
# that "ababa" must be a palindrome since the two left and right end letters are the same.
32+
#
33+
# Therefore,
34+
# dp(i, j) = dp(i + 1, j − 1) and s(i) == s(j)
35+
#
36+
# The base cases are:
37+
# dp(i, i) = true
38+
# dp(i, i + 1) = s(i) == s(j + 1)
39+
#
40+
# This yields a straight forward DP solution, which we first initialize the one and two letters palindromes, and work
41+
# our way up finding all three letters palindromes, and so on.
42+
43+
# 3) Expand from center
44+
# https://www.youtube.com/watch?v=m2Mk9JN5T4A
45+
#
46+
# Time O(n^2). Expanding a palindrome from its center takes O(n) time, so the overall complexity is O(n^2)
47+
# Space O(1)
48+
class Solution:
49+
def longestPalindrome(self, s: str) -> str:
50+
res = ""
51+
for i in range(len(s)):
52+
s1 = self.expend_from_center(s, i, i)
53+
if len(s1) > len(res):
54+
res = s1
55+
s2 = self.expend_from_center(s, i, i + 1)
56+
if len(s2) > len(res):
57+
res = s2
58+
return res
59+
60+
@staticmethod
61+
def expend_from_center(s: str, l: int, r: int) -> str:
62+
while l >= 0 and r < len(s) and s[l] == s[r]:
63+
l -= 1
64+
r += 1
65+
return s[l + 1 : r]
66+
67+
68+
# Manacher's algorithm
69+
# Time O(n)
70+
#
71+
# It is a non-trivial algorithm

Python/0007. Reverse Integer.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-231, 231 - 1], then return 0.
2+
# Assume the environment does not allow you to store 64-bit integers (signed or unsigned).
3+
#
4+
# Example 1:
5+
# Input: x = 123
6+
# Output: 321
7+
#
8+
# Example 2:
9+
# Input: x = -123
10+
# Output: -321
11+
#
12+
# Example 3:
13+
# Input: x = 120
14+
# Output: 21
15+
#
16+
# Constraints:
17+
# -2^31 <= x <= 2^31 - 1
18+
19+
20+
# Pop and push
21+
# Time O(n)
22+
# Space O(1)
23+
#
24+
# e.g. 123
25+
# d: 3, x: 12, n: 3
26+
# d: 2, x: 1, n: 32
27+
# d: 1, x: 0, n: 321
28+
class Solution:
29+
def reverse(self, x: int) -> int:
30+
if x < 0:
31+
return -self.reverse(-x)
32+
33+
n = 0
34+
while x > 0:
35+
# pop
36+
d = x % 10
37+
x = x // 10
38+
39+
# push
40+
n = n * 10 + d
41+
42+
# Above codes can be combined to these, but hard to understand
43+
# n = n * 10 + x % 10
44+
# x = x // 10
45+
46+
if n > 2 ** 31 - 1:
47+
return 0
48+
return n

0 commit comments

Comments
 (0)