AC
给你两个字符串 word1 和 word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。
返回 合并后的字符串 。
示例 1:
输入:word1 = "abc", word2 = "pqr"
输出:"apbqcr"
解释:字符串合并情况如下所示:
word1: a b c
word2: p q r
合并后: a p b q c r
示例 2:
输入:word1 = "ab", word2 = "pqrs"
输出:"apbqrs"
解释:注意,word2 比 word1 长,"rs" 需要追加到合并后字符串的末尾。
word1: a b
word2: p q r s
合并后: a p b q r s
示例 3:
输入:word1 = "abcd", word2 = "pq"
输出:"apbqcd"
解释:注意,word1 比 word2 长,"cd" 需要追加到合并后字符串的末尾。
word1: a b c d
word2: p q
合并后: a p b q c d
提示:
1 <= word1.length, word2.length <= 100
word1 和 word2 由小写英文字母组成
没什么好说的了,类似合并2个有序数组,时间复杂度 o ( m a x ( w o r d 1. l e n g h t , w o r d 2. l e n g t h ) ) o(max(word1.lenght,word2.length)) o(max(word1.lenght,word2.length))
class Solution:
def mergeAlternately(self, word1: str, word2: str) -> str:
ret_str = ''
index1, index2 = 0, 0
while index1 < len(word1) and index2 < len(word2):
ret_str += word1[index1]
index1 += 1
ret_str += word2[index2]
index2 += 1
if index1 < len(word1):
ret_str += word1[index1:]
if index2 < len(word2):
ret_str += word2[index2:]
return ret_str
AC
有 n 个盒子。给你一个长度为 n 的二进制字符串 boxes ,其中 boxes[i] 的值为 ‘0’ 表示第 i 个盒子是 空 的,而 boxes[i] 的值为 ‘1’ 表示盒子里有 一个 小球。
在一步操作中,你可以将 一个 小球从某个盒子移动到一个与之相邻的盒子中。第 i 个盒子和第 j 个盒子相邻需满足 abs(i - j) == 1 。注意,操作执行后,某些盒子中可能会存在不止一个小球。
返回一个长度为 n 的数组 answer ,其中 answer[i] 是将所有小球移动到第 i 个盒子所需的 最小 操作数。
每个 answer[i] 都需要根据盒子的 初始状态 进行计算。
示例 1:
输入:boxes = "110"
输出:[1,1,3]
解释:每个盒子对应的最小操作数如下:
1) 第 1 个盒子:将一个小球从第 2 个盒子移动到第 1 个盒子,需要 1 步操作。
2) 第 2 个盒子:将一个小球从第 1 个盒子移动到第 2 个盒子,需要 1 步操作。
3) 第 3 个盒子:将一个小球从第 1 个盒子移动到第 3 个盒子,需要 2 步操作。将一个小球从第 2 个盒子移动到第 3 个盒子,需要 1 步操作。共计 3 步操作。
示例 2:
输入:boxes = "001011"
输出:[11,8,5,4,3,4]
提示:
n == boxes.length
1 <= n <= 2000
boxes[i] 为 '0' 或 '1'
蛮简单的题目,首先记录有小球的盒子位置,然后从前向后遍历,对每个位置计算把其他小球挪过来的步骤,具体来说就是小球的位置和当前位置的差。时间复杂度 o ( n ) o(n) o(n)
class Solution:
def minOperations(self, boxes: str) -> List[int]:
ball_indexs = [index for index, item in enumerate(boxes) if item == '1']
ans = [0] * len(boxes)
for index in range(len(ans)):
ans[index] = sum([abs(ball_i - index) for ball_i in ball_indexs])
return ans
TLE
这道题赛中写得差不多了,一直TLE,赛后修改完才通过……
给你两个长度分别 n 和 m 的整数数组 nums 和 multipliers ,其中 n >= m ,数组下标 从 1 开始 计数。
初始时,你的分数为 0 。你需要执行恰好 m 步操作。在第 i 步操作(从 1 开始 计数)中,需要:
选择数组 nums 开头处或者末尾处 的整数 x 。
你获得 multipliers[i] * x 分,并累加到你的分数中。
将 x 从数组 nums 中移除。
在执行 m 步操作后,返回 最大 分数。
示例 1:
输入:nums = [1,2,3], multipliers = [3,2,1]
输出:14
解释:一种最优解决方案如下:
- 选择末尾处的整数 3 ,[1,2,3] ,得 3 * 3 = 9 分,累加到分数中。
- 选择末尾处的整数 2 ,[1,2] ,得 2 * 2 = 4 分,累加到分数中。
- 选择末尾处的整数 1 ,[1] ,得 1 * 1 = 1 分,累加到分数中。
总分数为 9 + 4 + 1 = 14 。
示例 2:
输入:nums = [-5,-3,-3,-2,7,1], multipliers = [-10,-5,3,4,6]
输出:102
解释:一种最优解决方案如下:
- 选择开头处的整数 -5 ,[-5,-3,-3,-2,7,1] ,得 -5 * -10 = 50 分,累加到分数中。
- 选择开头处的整数 -3 ,[-3,-3,-2,7,1] ,得 -3 * -5 = 15 分,累加到分数中。
- 选择开头处的整数 -3 ,[-3,-2,7,1] ,得 -3 * 3 = -9 分,累加到分数中。
- 选择末尾处的整数 1 ,[-2,7,1] ,得 1 * 4 = 4 分,累加到分数中。
- 选择末尾处的整数 7 ,[-2,7] ,得 7 * 6 = 42 分,累加到分数中。
总分数为 50 + 15 - 9 + 4 + 42 = 102 。
提示:
n == nums.length
m == multipliers.length
1 <= m <= 10^3
m <= n <= 10^5
-1000 <= nums[i], multipliers[i] <= 1000
v1
最直观的思路,记f(N, M)
为输出结果的函数,则可以递归地:
f(N, M) = max(N[0] * M[0] + f(N[1:], M[1:]), N[-1] * M[0] + f(N[:-1], M[1:])
这样的话可以直接写出递归版代码1,但是会超时。
v2
考虑用memo保存中间状态,也即以(N, M)
作为key
保存一个字典,然后发现这样会超内存。
v3
发现由于nums[i]
在[-1000,1000]
之间,考虑用位存储。但是这样只能把每个数字占的空间缩小,仍然无法解决超出内存的问题
v4
发现可以用dp来解决。用dp[i][j]
表示nums[i:j]
与multipliers
相乘的最大结果,则:
d p [ i ] [ j ] = m a x { d p [ i + 1 ] [ j ] + n u m s [ i ] ∗ m u l t i p l i e r s [ i + n u m s . l e n − j − 1 ] d p [ i ] [ j − 1 ] + n u m s [ j ] ∗ m u l t i p l i e r s [ i + n u m s . l e n − j − 1 ] dp[i][j] = max \begin{cases} dp[i+1][j] + nums[i] * multipliers[i + nums.len - j - 1] \\ dp[i][j - 1] + nums[j] * multipliers[i + nums.len - j - 1] \end{cases} dp[i][j]=max{ dp[i+1][j]+nums[i]∗multipliers[i+nums.len−j−1]dp[i][j−1]+nums[j]∗multipliers[i+nums.len−j−1]
其中:
i
是左边的数字索引,所以左边用了i
个数字,右边数字索引为j
,右边用了nums.len - j - 1
个数字,这次multipliers
应该选第“已经用了的数字个数+1”个数字,数字个数比索引多1,所以其索引恰好就是i + nums.len - j - 1
;dp[i][j]
,有 i ≤ j i \leq j i≤j,也即矩阵只有上三角;转移方程有了,接下来是初始化。由于矩阵的每个位置只和左边、下面有关,所以初始化的时候应该从对角线开始
首先考虑n == m
的情况,dp[i][i] = nums[i] * multiplliers[-1]
,填充矩阵时,从下到上,从左到右
接下来再考虑n > m
的情况,当m < n
时,nums
中有一部分数字是完全排不上队和multipliers
相乘的,最后一个能和multipliers
相乘的数字,其索引应该恰好对应的multipliers
的最后一个数字索引,也即i + nums.len - j - 1 == multipliers.len - 1
所以初始化的时候,代码写为:
for i in range(len(nums)):
for j in range(len(nums)):
if i + len(nums) - j - 1 == len(multipliers) - 1:
dp[i][j] = max(nums[i] * multipliers[-1], nums[j] * multipliers[-1])
这样可以写出dp的代码1。
代码的时间复杂度为 o ( n 2 ) o(n^2) o(n2),会超时。
v5
超时了之后我觉得实在没法优化了,就看了看pycharm里dp数组的内容,发现当m < n
时,其实dp数组有很大一部分没有填满,只填满了m
的部分。
在初始化的时候,只需要初始化对角线,所以初始化可以改成:
# init
for i in range(len(multipliers)):
j = i + len(nums) - len(multipliers)
dp[i][j] = max(nums[i] * multipliers[-1], nums[j] * multipliers[-1])
在实际填充时,从初始化的地方开始填充,填充改为:
for i in range(len(multipliers) - 1, -1, -1):
for j in range(i + len(nums) - len(multipliers) + 1, len(nums)):
cur_mul = multipliers[i + len(nums) - j - 1]
dp[i][j] = max(dp[i + 1][j] + nums[i] * cur_mul, dp[i][j - 1] + nums[j] * cur_mul)
这样初始化和填充dp数组的复杂度,都只与m
有关,时间复杂度降为 o ( m 2 ) o(m^2) o(m2)
在实际执行时,v5
的方案仍然会超时,试了用c++也会超时……可能是因为空间复杂度仍然是 o ( n 2 ) o(n^2) o(n2),理论上来讲,空间复杂度也可以优化到 o ( m 2 ) o(m^2) o(m2),但是我脑子不够用,感觉没法优化了……
倒是发现一个,可以把nums
里面排不上队的那些元素去掉,也就是对于nums
而言,最多只有左边m
个元素和右边m
个元素能用上,所以可以把nums
中间的那些元素去掉,仅保留左右两边m
个元素,这时 n = 2 ∗ m n = 2 * m n=2∗m,空间复杂度就变成 o ( m 2 ) o(m^2) o(m2)了!
看了大家的做法,其实v1的方法可以稍改一下,也能过
v1
class Solution:
def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
@lru_cache(None)
def helper(nums: tuple, multipliers: tuple) -> int:
if not multipliers:
return 0
return max(nums[0] * multipliers[0] + helper(nums[1:], multipliers[1:]), nums[-1] * multipliers[0] + helper(nums[:-1], multipliers[1:]))
return helper(tuple(nums), tuple(multipliers))
v4
class Solution:
def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
dp = [[0] * len(nums) for _ in range(len(nums))]
# init
for i in range(len(nums)):
for j in range(len(nums)):
if i + len(nums) - j - 1 == len(multipliers) - 1:
dp[i][j] = max(nums[i] * multipliers[-1], nums[j] * multipliers[-1])
for row in range(len(multipliers) - 1, -1, -1):
for col in range(row + 1, len(nums)):
cur_mul = multipliers[row + len(nums) - col - 1] if row + len(nums) - col - 1 < len(multipliers) else 0
dp[row][col] = max(dp[row + 1][col] + nums[row] * cur_mul, dp[row][col - 1] + nums[col] * cur_mul)
return dp[0][-1]
v5
class Solution:
def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
nums = nums[:len(multipliers)] + nums[-len(multipliers):]
dp = [[0] * len(nums) for _ in range(len(nums))]
# init
for i in range(len(multipliers)):
j = i + len(nums) - len(multipliers)
dp[i][j] = max(nums[i] * multipliers[-1], nums[j] * multipliers[-1])
for i in range(len(multipliers) - 1, -1, -1):
for j in range(i + len(nums) - len(multipliers) + 1, len(nums)):
cur_mul = multipliers[i + len(nums) - j - 1]
dp[i][j] = max(dp[i + 1][j] + nums[i] * cur_mul, dp[i][j - 1] + nums[j] * cur_mul)
return dp[0][-1]
v1修改版
class Solution:
def maximumScore(self, nums: List[int], multipliers: List[int]) -> int:
n, m = len(nums), len(multipliers)
@lru_cache(n)
def helper(left: int, right: int, mul_index: int) -> int:
if mul_index == m:
return 0
a1 = nums[left] * multipliers[mul_index] + helper(left + 1, right, mul_index + 1)
a2 = nums[right] * multipliers[mul_index] + helper(left, right - 1, mul_index + 1)
return max(a1, a2)
return helper(0, n - 1, 0)
这道题没做出来,赛后做的
给你两个字符串 word1 和 word2 ,请你按下述方法构造一个字符串:
从 word1 中选出某个 非空 子序列 subsequence1 。
从 word2 中选出某个 非空 子序列 subsequence2 。
连接两个子序列 subsequence1 + subsequence2 ,得到字符串。
返回可按上述方法构造的最长 回文串 的 长度 。如果无法构造回文串,返回 0 。
字符串 s 的一个 子序列 是通过从 s 中删除一些(也可能不删除)字符而不更改其余字符的顺序生成的字符串。
回文串 是正着读和反着读结果一致的字符串。
示例 1:
输入:word1 = "cacb", word2 = "cbba"
输出:5
解释:从 word1 中选出 "ab" ,从 word2 中选出 "cba" ,得到回文串 "abcba" 。
示例 2:
输入:word1 = "ab", word2 = "ab"
输出:3
解释:从 word1 中选出 "ab" ,从 word2 中选出 "a" ,得到回文串 "aba" 。
示例 3:
输入:word1 = "aa", word2 = "bb"
输出:0
解释:无法按题面所述方法构造回文串,所以返回 0 。
提示:
1 <= word1.length, word2.length <= 1000
word1 和 word2 由小写英文字母组成
参考:https://leetcode-cn.com/problems/maximize-palindrome-length-from-subsequences/solution/zui-chang-hui-wen-zi-xu-lie-xian-zhi-tia-g6nx/
类似最长回文子序列
把word1
和word2
合并到一起,记合并完的字符串为word
,用dp[i][j]
表示word[i:j]
的回文子序列长度,则转移方程:
d p [ i ] [ j ] = { 2 + d p [ i + 1 ] [ j − 1 ] , i f w o r d [ i ] = = w o r d [ j ] m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , e l s e . \begin{aligned} dp[i][j] = \begin{cases} 2 + dp[i + 1][j - 1], &if \;\; word[i] == word[j] \\ max(dp[i + 1][j], dp[i][j - 1]), &else. \end{cases} \end{aligned} dp[i][j]={ 2+dp[i+1][j−1],max(dp[i+1][j],dp[i][j−1]),ifword[i]==word[j]else.
在更新答案时,需要保证i
属于word1
长度范围内,j
属于word2
长度范围内,并且word[i] == word[j]
。最后一个条件可以保证word1
和word2
都至少有字符被取到了
class Solution:
def longestPalindrome(self, word1: str, word2: str) -> int:
l1, l2 = len(word1), len(word2)
new_word = word1 + word2
l = l1 + l2
dp = [[0] * l for _ in range(l)]
# init
for i in range(l):
dp[i][i] = 1
# dp
ans = 0
for i in range(l - 1, -1, -1):
for j in range(i + 1, l):
if new_word[i] == new_word[j]:
dp[i][j] = 2 + (dp[i + 1][j - 1] if i <= j - 2 else 0)
if i < l1 <= j:
ans = max(ans, dp[i][j])
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
return ans