classSolution: defreverseVowels(self, s: str) -> str: ss = [ x for x in s ] i = 0 l = len(ss) j = l - 1 vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'] while i < j: while i < l and ss[i] notin vowels: i += 1 while j > 0and ss[j] notin vowels: j -= 1
if i <= j: ss[i], ss[j] = ss[j], ss[i] i += 1 j -= 1 return"".join(ss)
classSolution: deftranspose(self, matrix: List[List[int]]) -> List[List[int]]: m = len( matrix ) n = len( matrix[0] ) result = [] for i inrange( n ): result.append( [0] * m ) for i inrange( m ): for j inrange( n ): result[j][i] = matrix[i][j] return result
classSolution: defremoveDuplicates(self, nums: List[int]) -> int: l = len(nums) if l == 0or l == 1: return l r = 0 p = r + 1 while p < l: if nums[p] != nums[r]: nums[r+1] = nums[p] r += 1 p += 1 return r + 1
第一个信封选择,理论上有 N 种,而第二个信封选择,理论上有 N - 1 种。只有 w 值相等时候才能剪枝。这就看出来,上面的算法,时间复杂度是 O( n! )。果然是无法 AC 的,超出了时间限制。
我的智慧估计就到这里了,能写出来不错了,还没有能力想到更短的算法。然后看了官方解法。我们设想,w 有序后,我们彻底摒弃 w 这个维度的影响,单纯看 h 这个维度的影响,应该怎么做呢?其实就是求剩下 h 的序列里,最长严格递增子序列。但是,这里其实有个问题,就是如果在 w 值相等的情况下,h 的最长严格递增子序列长度是错的。解决方法也很简单,在 w 相等的时候,h 按降序排列,就好了,这样就无法形成严格递增子序列了,只能在 w 不同的情况下增加子序列长度。
到了这一步,就可以设计一个动态规划的算法,其实,这里也是递推法。状态怎么转换的呢?第 i 个状态,就是前 i 个数构成的最长严格递增子序列长度。这时候,新来的第 i + 1 个数,就要去和前 i 个数里的每个数去比,如果更大,就能得到一个比当前这个状态长 1 的子序列,这里最大的一个,就是第 i + 1 个状态的值。太绕了,举个例子:
再来看最后一个解法,官方解法的说明,说实在的太抽象了,都是用的代数,我看不懂。我自己想了个。我们朴素来想想,如果我们想要套得更多层,到底怎么挑选信封比较有效呢?比如第一个信封是 (w:1, h:1),这时候第二个信封有两个选择(w:2, h:5)和 (w:2, h:2),你选哪个呢?不傻的话,都会选后者吧,因为 h 更小,意味着将来可以被下一个信封套进去的概率更大。
这个新的动态规划算法,状态这样设计的,假如,我想组成一个层数为 i 的套娃,那么用到的最小的那个信封高度,记为第 i 个状态值。注意这个算法仍然建立在 w 已经是升序,而 h 是降序的基础上。这时候,新来一个信封。如果它的高度,比 i 大,那只能套在 i 的外面,其高度变成第 i + 1 个状态。如果其高度比 i 小的时候呢?注意我们的策略,组成一个套娃的时候,尽可能选满足条件最小的一个信封,将来套入新信封的概率才会变大。所以,如果其高度比 i 小的话,理论上我们应该把第 i 个状态的值调小,也即没有扩展新状态。不过,我们真应该更新第 i 个么?如果前面还有比这个值大的呢?那这个肯定是套不上去的,所以得往前找一个位置,正好是前一个状态比它小,后一个状态比它大,然后把比它大的那个给更新了。这个位置可以用 二分查找 来搜索。
f = [envelopes[0][1]] for i inrange(1, n): if (num := envelopes[i][1]) > f[-1]: f.append(num) else: index = bisect.bisect_left(f, num) f[index] = num returnlen(f)
于是,我们得到了这个算法,这是我抄的,不是我自己手撸的。我们可以看看这个算法的复杂度是什么,排序是 O( n logn ),二分查找是 O( logn ),所以动态规划的过程,复杂度还是 O( n logn ),总的时间复杂度是 O( n logn )。比上一个算法又进步了。
classSolution: deflongestCommonPrefix(self, strs: List[str]) -> str: l = len(strs) if l == 0 : return"" if l == 1 : return strs[0] common = "" for i inrange(len(strs[0])): for j inrange (len(strs)) : if i >= len(strs[j]) or strs[j][i] != strs[0][i] : return common common += strs[0][i] return common
看到横向比较的算法,我就很容易发现这个题目的递归结构,这个问题可以递归解决的。N 个字符串的最长公共前缀,是 N - 1 个字符串的最长公共前缀和最后一个字符串的公共前缀。于是我们可以写个递归算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution: deflongestCommonPrefix(self, strs: List[str]) -> str: l = len(strs) if l == 0 : return"" if l == 1 : return strs[0] common = "" sub = self.longestCommonPrefix(strs[1::]) for i inrange(len(sub)): if i >= len(strs[0]) or strs[0][i] != sub[i]: return common common += sub[i] return common
classSolution: defromanToInt(self, s: str) -> int: mapping = {'I':1, 'V': 5, 'X': 10, 'L': 50, 'C':100, 'D': 500, 'M': 1000} i = 0 result = 0 l = len(s) while i < l : if s[i] == 'I' : if i + 1 < l and s[i+1] == 'V' : result += 4 i += 1 elif i + 1 < l and s[i+1] == 'X': result += 9 i += 1 else: result += 1 elif s[i] == 'X': if i + 1 < l and s[i+1] == 'L': result += 40 i += 1 elif i + 1 < l and s[i+1] == 'C': result += 90 i += 1 else: result += 10
elif s[i] == 'C': if i + 1 < l and s[i+1] == 'D': result += 400 i += 1 elif i + 1 < l and s[i+1] == 'M': result += 900 i += 1 else: result += 100 else: result += mapping[s[i]] i += 1 return result
我就写了上面的代码,果然极其乏味,我以此写对了,不用 mapping 也是可以的,用了也没节省很多。从中,我学到了一个点,就是 Python 是没有 switch 语句的,只能用 if 来代替,那也没什么。
然后我还是看了看评论和题解,果然,就是这么无聊的题目,有人也能给你写出一朵花来:
1 2 3 4
classSolution: defromanToInt(self, s: str) -> int: d = {'I':1, 'IV':3, 'V':5, 'IX':8, 'X':10, 'XL':30, 'L':50, 'XC':80, 'C':100, 'CD':300, 'D':500, 'CM':800, 'M':1000} returnsum(d.get(s[max(i-1, 0):i+1], d[n]) for i, n inenumerate(s))
来赏析一个老哥的作品,Python,两行搞定。简直了。同样是使用 dict 数据结构做了个映射,看看这个老哥的功力,对 api 熟悉到了一定的境界,让我叹为观止。这里用到了 dict 的一个 api,get 方法,这个方法允许输入第二个参数,参数的作用是一旦没有取到,就用第二个参数作为缺省值来返回。老哥利用了这一点,每次迭代都尝试从串里取两位出来,如果取到了合法值,就查表取得数值,如果没有取到合法值,就用第二个位的字母的值代替,真是巧妙,省掉了多少个 if 条件啊,妙,实在是妙!
再看 max 函数的用法,迭代的过程中,每次需要取两个出来,但是第一个字符的时候,只能取一个,用了一个 max,当下标为负数的时候,就只取一位数字,又是妙到颠毫。还展示了一个 enumerate 方法迭代 dict 的用法,就相对来说比较平淡了。