KK's blog

每天积累多一些

0%

LeetCode

<div>

Given an integer array nums, return the length of the longest strictly increasing subsequence.

A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements. For example, [3,6,2,7] is a subsequence of the array [0,3,1,6,2,2,7].

Example 1:

<pre>Input: nums = [10,9,2,5,3,7,101,18] Output: 4 Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. </pre>

Example 2:

<pre>Input: nums = [0,1,0,3,2,3] Output: 4 </pre>

Example 3:

<pre>Input: nums = [7,7,7,7,7,7,7] Output: 1 </pre>

Constraints:

  • 1 <= nums.length <= 2500
  • -10<sup>4</sup> <= nums[i] <= 10<sup>4</sup>

Follow up: Can you come up with an algorithm that runs in O(n log(n)) time complexity?

</div>

DP算法思路:

N/A

注意事项:

  1. 初始化从1开始,因为一个数是递增序列
  2. 返回值不是dp[-1],而是所有dp的最大值

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[3, 5, 1, 3, 9, 5, 6] 1, 3, 5, 6
[1, 0]
dp[i] = max(dp[j]) + 1, if nums[i-1] > nums[j-1], 1 <= j < i

def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
res = 1
# dp = [0, 1, 0]
dp = [1] * (len(nums) + 1)
for i in range(1, len(dp)):
# i = 2
# j = 1
for j in range(1, i):
if nums[i - 1] > nums[j - 1]:
dp[i] = max(dp[i], dp[j] + 1)
res = max(res, dp[i])
return res

算法分析:

时间复杂度为<code>O(n<sup>2</sup>)</code>,空间复杂度<code>O(n<sup>2</sup>)</code>.


打印路径算法思路:

N/A

注意事项:

  1. path数组大小和原数组一样,因为path数组的下标和值都是下标,这样不用-1,直接nums[pos],不容易错
  2. 多了Line 11 - 12和14 - 15,需要记录产生最大值的下标
  3. 打印的时候path数组的下标和值都是下标,而值是前一个下标,所以是pos = positions[pos],循环次数为dp值,开始下标为dp值对应的下标path[:biggest_pos + 1]。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def lengthOfLIS_with_path(self, nums: List[int]) -> int:
if not nums:
return 0
res, path = 1, [0] * len(nums) # remember no + 1
biggest_pos = -1
dp = [1] * (len(nums) + 1)
for i in range(1, len(dp)):
for j in range(1, i):
if nums[i - 1] > nums[j - 1]:
dp[i] = max(dp[i], dp[j] + 1)
if dp[i] == dp[j] + 1:
path[i - 1] = j - 1
res = max(res, dp[i])
if res == dp[i]:
biggest_pos = i - 1
path_list = self.print_path(nums, path[:biggest_pos + 1], res)
return path_list

def print_path(self, nums, path, dp_value):
pos, res = len(path) - 1, []
for _ in range(dp_value):
res.append(nums[pos])
pos = path[pos]
return res[::-1]


bisect算法II解题思路:

N/A

注意事项:

  1. bisect_left类似于greater_or_equal_position但若equal时,取第一个,但greater_or_equal_position是取最后一个,此题没关系,因为相等的数会被替换掉,递增序列中不会存在相等的数

Python代码:

1
2
3
4
5
6
7
8
9
10
11
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
res = []
for i in range(len(nums)):
pos = bisect.bisect_left(res, nums[i])
if pos < len(res):
res[pos] = nums[i]
else:
res.append(nums[i])
return len(res)

算法分析:

时间复杂度为O(nlogn),空间复杂度O(n).

LeetCode

<div>

Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

  • For example, "ace" is a subsequence of "abcde".

A common subsequence of two strings is a subsequence that is common to both strings.

Example 1:

<pre>Input: text1 = "abcde", text2 = "ace" Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3. </pre>

Example 2:

<pre>Input: text1 = "abc", text2 = "abc" Output: 3 Explanation: The longest common subsequence is "abc" and its length is 3. </pre>

Example 3:

<pre>Input: text1 = "abc", text2 = "def" Output: 0 Explanation: There is no such common subsequence, so the result is 0. </pre>

Constraints:

  • 1 <= text1.length, text2.length <= 1000
  • text1 and text2 consist of only lowercase English characters.

</div>

题目大意:

求两字符串的最大公共字符序列,不一定需要连续

解题思路:

两字符串最值问题用DP dp[i][j]为最大公共字符序列,最后一位不需要相等。递归式为:

1
2
dp[i][j] = dp[i - 1][j - 1] + 1 if text1[i - 1] == text2[j - 1]
= max(dp[i - 1][j], dp[i][j - 1])

解题步骤:

DP五点注意事项

注意事项:

  1. 不相等时候不需要dp[i - 1][j - 1],因为已经包含在dp[i - 1][j]或dp[i][j - 1]中, DP属于累计DP

Python代码:

1
2
3
4
5
6
7
8
9
10
11
# dp[i][j] = dp[i - 1][j - 1] + 1 if text1[i - 1] == text2[j - 1]
# = max(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
dp = [[0 for _ in range(len(text2) + 1)] for _ in range(len(text1) + 1)]
for i in range(1, len(dp)):
for j in range(1, len(dp[0])):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # no dp[i - 1][j - 1] but no impact
return dp[-1][-1]

打印路径

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
longest, res = dp[-1][-1], ''
while m >= 0 and n >= 0:
if dp[m - 1][n] == longest:
m -= 1
elif dp[m][n - 1] == longest:
n -= 1
else:
res += text1[m - 1]
longest -= 1
m -= 1
n -= 1
return res[::-1]

算法分析:

时间复杂度为O(nm),空间复杂度O(nm)

LeetCode 124 Binary Tree Maximum Path Sum

Given a binary tree, find the maximum path sum.

For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root.

For example:
Given the below binary tree,

<pre>
1 /
2 3 </pre>

Return 6.

题目大意:

给定一棵二叉树,寻找最大路径和。路径指的是从任意起始节点出发沿着父亲-孩子链接到达某个终止节点的节点序列。路径不一定要穿过根节点。

DFS解题思路(推荐):

DFS搜索,二叉树的题。步骤主要是考虑

  1. 空指针
  2. 一个node情况或多个node情况(可合并)
    多个node情况下(比如3个节点),有4种情况下含根节点的和:左子树+根节点,右子树+跟节点,根节点,左子树+根节点+右子树。这些情况包含了所有可能的和的情况。但值得注意的是,前三种
    情况可以是子问题的解,也就是它返回值将成为此根节点父亲的左或右子树的解,但第四种情况例外,因为此情况形成的路径与根节点父亲并不在一条路径上。所以此情况应在全局变量中比较
    并不能作为返回值。 gmax = max {return max{val,left+val,right+val} or left+val+right}

注意事项:

  1. 4种情况: root, root + left, root + right, left + root + right, 不要漏掉最后一种left_sum + root.val + right_sum
  2. 全局最大值作为返回值,初始值为负无穷float('-inf')。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
def maxPathSum(self, root: TreeNode) -> int:
_, path_gsum = self.dfs(root)
return path_gsum

def dfs(self, root: TreeNode) -> int:
if not root:
return float('-inf'), float('-inf')
left_sum, left_gsum = self.dfs(root.left)
right_sum, right_gsum = self.dfs(root.right)
max_path_sum = max(left_sum + root.val, right_sum + root.val, root.val)
max_path_gsum = max(max_path_sum, left_gsum, right_gsum, left_sum + root.val + right_sum)
return max_path_sum, max_path_gsum


算法II迭代解题思路:

由上述算法看出,属于后序遍历,因为先left, right, 再计算root。所以用后序遍历模板。然后定义dp[root]为以root为结束点的最大路径值,这与上题也一样。 递归式为

1
dp[node] = max(dp[node.left] + node.val, dp[node.right] + node.val, node.val)
res为全局最大值,也和上述一致
max_path_sum = dp[node]
res = left_gsum, right_gsum

注意事项:

  1. occurrence先加再比较

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def maxPathSum2(self, root: TreeNode) -> int:
it, stack, res = root, [], float('-inf')
dp = collections.defaultdict(int)
while it:
stack.append((it, 0))
it = it.left

while stack:
node, occurrence = stack.pop()
occurrence += 1 # remember
if occurrence == 2:
dp[node] = max(dp[node.left] + node.val, dp[node.right] + node.val, node.val)
res = max(res, dp[node], dp[node.left] + node.val + dp[node.right])
continue
else:
stack.append((node, occurrence))
if node.right:
n = node.right
while n:
stack.append((n, 0))
n = n.left
return res

Follow-up

若path的起始点均为叶子节点

解题思路:

叶子节点要返回值,gsum为无穷小。修改max_path_sum和max_path_gsum公式,不含单独root以及gsum不含以root为终点的路径。
还有路径可能不存在

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def maxPathSum_fromleaf2leaf(self, root: TreeNode) -> int:
path_sum, path_gsum = self.dfs_leaf(root)
return path_gsum if path_gsum != float('-inf') else None

def dfs_leaf(self, root: TreeNode) -> int:
if not root:
return float('-inf'), float('-inf')
if not root.left and not root.right:
return root.val, float('-inf') # remember no gsum
left_sum, left_gsum = self.dfs_leaf(root.left)
right_sum, right_gsum = self.dfs_leaf(root.right)
max_path_sum = max(left_sum + root.val, right_sum + root.val)
max_path_gsum = max(left_gsum, right_gsum, left_sum + root.val + right_sum)
return max_path_sum, max_path_gsum

Follow-up 2

若path的起始点均为叶子节点且只能从某些特定的叶子节点出发和结束

解题思路:

同上,只不过加入条件root.val in endnodes

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def maxPathSum_fromSpecificNodes(self, root: TreeNode, endnodes) -> int:
path_sum, path_gsum = self.dfs_end_nodes(root, set(endnodes))
return path_gsum if path_gsum != float('-inf') else None

def dfs_end_nodes(self, root: TreeNode, endnodes) -> int:
if not root:
return float('-inf'), float('-inf')
if not root.left and not root.right and root.val in endnodes:
return root.val, float('-inf') # remember no gsum
left_sum, left_gsum = self.dfs_end_nodes(root.left, endnodes)
right_sum, right_gsum = self.dfs_end_nodes(root.right, endnodes)
max_path_sum = max(left_sum + root.val, right_sum + root.val)
max_path_gsum = max(left_gsum, right_gsum, left_sum + root.val + right_sum)
return max_path_sum, max_path_gsum

Follow-up 3

若path的起始点可为任意节点且只能从某些特定的叶子节点出发和结束

解题思路:

若起始节点为非叶子节点,注意将表达式修改为原始表达式。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def maxPathSum_fromSpecificNodes2(self, root: TreeNode, endnodes) -> int:
path_sum, path_gsum = self.dfs_end_nodes2(root, set(endnodes))
return path_gsum if path_gsum != float('-inf') else None

def dfs_end_nodes2(self, root: TreeNode, endnodes) -> int:
if not root:
return float('-inf'), float('-inf')
if not root.left and not root.right and root.val in endnodes:
return root.val, float('-inf') # remember no gsum
left_sum, left_gsum = self.dfs_end_nodes2(root.left, endnodes)
right_sum, right_gsum = self.dfs_end_nodes2(root.right, endnodes)
max_path_sum = max(left_sum + root.val, right_sum + root.val)
if root.val in endnodes:
max_path_sum = max(max_path_sum, root.val)
max_path_gsum = max(left_gsum, right_gsum, left_sum + root.val + right_sum)
if root.val in endnodes:
max_path_gsum = max(max_path_gsum, max_path_sum)
return max_path_sum, max_path_gsum

注意事项:

  1. 4种情况
  2. 定义全局变量来比较最大值,因为左到右情况不能返回。全局变量初始值为负无穷。

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int gsum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root){
gsum = 0;
int a = max(root);
return gsum;
}
public int max(TreeNode root){
if(root==null)
return 0;
int lmax = max(root.left);
int rmax = max(root.right);
int sum = Math.max(Math.max(lmax, rmax)+root.val,root.val);
gsum = Math.max(Math.max(gsum, sum), lmax+root.val+rmax);
return sum;
}

算法分析:

两种方法时间复杂度为O(n),n为节点数。空间复杂度为O(h),h为二叉树高度。

相关题目:

LeetCode 112 Path Sum LeetCode 124 Binary Tree Maximum Path Sum

Input - ("mon 10:00 am", mon 11:00 am) Output - [11005, 11010, 11015...11100] Output starts with 1 if the day is monday, 2 if tuesday and so on till 7 for sunday Append 5 min interval times to that till the end time So here it is 10:05 as first case, so its written as 11005 2nd is 10:10 so its written as 11010

题目大意:

DD的面经题,给定开始时间和结束时间,求5分钟的间隔时间,注意要round to 5min

解题思路:

由于非10进制,所以开一个类来计算进制

解题步骤:

N/A

注意事项:

  1. 实现lt函数
  2. 12am, 12pm要mod 12
  3. (0 if parts[2] == 'am' else 12)加括号
  4. 开始时间到到5分钟端点,结束时间加1分钟,由于只实现了lt

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
DAY_DICT = {'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6, 'sun': 7}
class Solution(TestCases):

def get_intervals(self, start, end) -> List:
start_time = Time(start)
end_time = Time(end)
if start_time.min % 5 > 0:
start_time.add(5 - start_time.min % 5)
end_time.add(1)
res = []
while start_time < end_time:
res.append(start_time.get_numeric())
start_time.add(5)
return res


class Time:

def __init__(self, time):
parts = time.split(' ')
day = DAY_DICT[parts[0]]
time_parts = parts[1].split(':')
hour = int(time_parts[0]) % 12 + (0 if parts[2] == 'am' else 12) # remember paren (0 ...12), and % 12
self.day = day
self.hour = hour
self.min = int(time_parts[1])

def __lt__(self, other):
if self.day < other.day or (self.day == other.day and self.hour < other.hour) or \
(self.day == other.day and self.hour == other.hour and self.min < other.min):
return True
else:
return False

def get_numeric(self):
return self.day * 10000 + self.hour * 100 + self.min

def add(self, mins):
self.min += mins
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
self.day += 1
if self.day == 7:
self.day = 0

算法分析:

时间复杂度为O(n),空间复杂度O(1)

[ ["3234.html", "xys.html", "7hsaa.html"], // user1 ["3234.html", "sdhsfjdsh.html", "xys.html", "7hsaa.html"] // user2 ]

输出两个user的最长连续且相同的访问记录。

题目大意:

求连续最长子数组

解题思路:

类似于LeetCode 1143先求最长公共子字符串。

LeetCode 1143 Longest Common Subsequence, 求最长公共子字符串 Karat 002 Longest Common Continuous Subarray 一样的题目,结果类型不同:最长长度和结果

不同之处在于:

  1. 由于是连续,所以递归只有相同的情况,其他情况为0。
  2. 答案不是最后一位,而是全局最值

递归式为

1
2
dp[i][j] = dp[i - 1][j - 1] + 1 if text1[i - 1] == text2[j - 1]
= 0

解题步骤:

N/A

注意事项:

  1. 递归只有一种情况
  2. 答案需求全局

Python代码:

1
2
3
4
5
6
7
8
9
10
11
# dp[i][j] = dp[i - 1][j - 1] + 1 if text1[i - 1] == text2[j - 1]
# = 0
def longestCommonContinuous(self, text1: str, text2: str) -> int:
dp = [[0 for _ in range(len(text2) + 1)] for _ in range(len(text1) + 1)]
res = 0
for i in range(1, len(dp)):
for j in range(1, len(dp[0])):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
res = max(res, dp[i][j])
return res

优化空间:

1
2
3
4
5
6
7
8
9
def longestCommonContinuous(self, text1: str, text2: str) -> int:
dp = [[0 for _ in range(len(text2) + 1)] for _ in range(2)]
res = 0
for i in range(1, len(text1) + 1):
for j in range(1, len(dp[0])):
if text1[i - 1] == text2[j - 1]:
dp[i % 2][j] = dp[(i - 1) % 2][j - 1] + 1
res = max(res, dp[i % 2][j])
return res

回到原题,输入是列表而不是字符串,但原理一样。还有需要输出公共结果,而不是数字

Python代码:

1
2
3
4
5
6
7
8
9
10
11
def longestCommonContinuousSubarray(self, history1, history2):
dp = [[0 for _ in range(len(history2) + 1)] for _ in range(len(history1) + 1)]
max_len, res = 0, []
for i in range(1, len(dp)):
for j in range(1, len(dp[0])):
if history1[i - 1] == history2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
if dp[i][j] > max_len:
max_len = dp[i][j]
res = history1[i - dp[i][j]:i]
return res

算法分析:

时间复杂度为O(nm),空间复杂度O(nm)

Free mock interview