旧瓶装新酒
300. 最长递增子序列 是一个经典n^2复杂度的dp题目。673.最长递增子序列的个数
在此基础上加大难度。解题思路也变难。
双数组dp
673. 最长递增子序列的个数 解题需要在普通dp数组基础上再加一个计数数组cnt,可算作双数组dp类题目。一般来说,dp分类里并无此类题目,不过这个题目很有发散性,故加以强调,可做板子题:
public boolean isFlipedString(String s1, String s2) {
class Solution {
public int findNumberOfLIS(int[] nums) {
int len = nums.length;
int[] dp = new int[len]; //最长上升子序列
int[] cnt = new int[len+1];//最长上生子序列数量
int max = 1;
int ans = 1;
dp[0]=1;
cnt[0]=1;
for(int i=1;i<len;i++){
dp[i]=1;
cnt[i]=1;
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
if(dp[i]<dp[j]+1){//这里不是判断dp[i]与max的关系,只考虑dp[i]的情况
dp[i] = dp[j]+1;
cnt[i] = cnt[j];//重制,关键,这里不是重制为1,而是从j继承
}else if (dp[i]==dp[j]+1){
cnt[i] += cnt[j];
}
}
}
if(dp[i]>max){
max=dp[i];
ans = cnt[i];//与上面同理
}else if (dp[i]==max) ans+= cnt[i];
}
return ans;
}
}
python版本语法要更加简单:
class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
length, ans, max_val = len(nums), 0, 0
dp = [0] * length
cnt = [0] * length
for i, x in enumerate(nums):
dp[i] = 1
cnt[i] = 1
for j in range(i):
if x > nums[j]:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
cnt[i] = cnt[j]
elif dp[j] + 1 == dp[i]:
cnt[i] += cnt[j]
if dp[i] > max_val:
max_val = dp[i]
ans = cnt[i]
elif dp[i] == max_val:
ans += cnt[i]
return ans
贪食蛇
792. 匹配子序列的单词数 这个题目的官方解法非常精巧,先根据首字母构建索引,然后每一次遍历字符串都重建一次索引,索引指向的数据被消耗完,说明子序列匹配结束,ans++;另一巧妙点为每一轮先把命中这一个数组拿出来,重新处理这一索引。 反复处理的过程,很像贪食蛇的感觉。Java版本:
class Solution {
public int numMatchingSubseq(String s, String[] words) {
int ans = 0;
List<Node>[] dict = new ArrayList[26];
for (int i = 0; i < 26; i++) {
dict[i] = new ArrayList<>();
}
for (String word : words) {
int index = word.charAt(0) - 'a';
dict[index].add(new Node(word,word.length(),0));
}
char[] chars = s.toCharArray();
for (char aChar : chars) {
int index = aChar - 'a';
List<Node> available = dict[index];//每一轮先把命中这一个数组拿出来,重新处理这一索引
dict[index] = new ArrayList<>();
for (Node obj : available) {
obj.index++;
if(obj.index==obj.len){
ans++;
}else {
dict[obj.str.charAt(obj.index)-'a'].add(obj);
}
}
}
return ans;
}
static class Node{
Node(String s,int l,int i){
str = s;
index = i;
len = l;
}
String str;
int index;
int len;
}
}
用Python则更加简单,用迭代iter充当Node对象:
class Solution:
def numMatchingSubseq(self, s: str, words: List[str]) -> int:
ans = 0
dictionary = [[] for _ in range(26)]
for w in words:
it = iter(w)
dictionary[ord(next(it)) - ord('a')].append(it)
for le in s:
cur = ord(le) - ord('a')
old = dictionary[cur]
dictionary[cur] = []
for it in old:
nex = next(it, None)
if nex:
dictionary[ord(nex) - ord('a')].append(it)
else:
ans += 1
return ans
一塔与一城
双数组类算法题目的变种有很多,比如list+set、list+map,一般来说,一个数据结构用来保存数据,一个数据结构用来记录数据特性(去重、第一次出现的下标,等等)。可以算作一塔与一城,而非双塔。
一塔与一城类题目最广泛的应用是在前缀和,前缀和求目标区间,一般的解法需要O(n^2)的复杂度,利用hashmap进行字典存储优化,可以降低到O(n).一个典型的题目如字母与数字 。常规 O(n^2) 的解法:
def findLongestSubarray(array: List[str]) -> List[str]:
length = len(array)
if length <= 1:
return []
prefix = [0] * (length + 1)
for index, s in enumerate(array):
temp = 1 if ord(s[0]) > ord('9') else -1
prefix[index + 1] = prefix[index] + temp
left, right = 0, 0
for index in range(length + 1):
for j in range(length, -1, -1):
if prefix[j] - prefix[index] == 0:
if j - index > right - left:
left = index
right = j
break
return array[left:right]
虽然前缀和已经降低了复杂度,但是在大数据量下,此种解法依然会超时。
优化方案为借助一个map(dict)进行记录操作:
def findLongestSubarray(self, array: List[str]) -> List[str]:
length = len(array)
if length <= 1:
return []
prefix = [0] * (length + 1)
dict = {0: 0}
left, right = 0, 0
for index, s in enumerate(array):
temp = 1 if ord(s[0]) > ord('9') else -1
pref_sum = prefix[index] + temp
prefix[index + 1] = pref_sum
if pref_sum in dict:
prev = dict[pref_sum]
if index + 1 - prev > right - left:
left = prev
right = index + 1
else:
dict[pref_sum] = index + 1
return array[left:right]
其他类似的 prefix+hashmap
题目:
325 和等于 k 的最长子数组长度
930 和相同的二元子数组
974 和可被 K 整除的子数组
1371 每个元音包含偶数次的最长子字符串
1542 找出最长的超赞子字符串
1590 使数组和能被 P 整除
Is this article useful to you? How about buy me a coffee ☕ ?