所有子字符串中的元音 - 力扣 (LeetCode) 竞赛 (leetcode-cn.com)

301 阅读3分钟

题目:

给你一个字符串 word ,返回 word 的所有子字符串中 元音的总数 ,元音是指 'a''e''i''o''u'

子字符串 是字符串中一个连续(非空)的字符序列。

**注意:**由于对 word 长度的限制比较宽松,答案可能超过有符号 32 位整数的范围。计算时需当心。

  • 1 <= word.length <= 10^5
  • word 由小写英文字母组成

暴力方法:

确定起始和结束位置,再检查子字符串的元音总数。复杂度是O(n^3)。 肯定超时

DP优化:

为什么会联想到用DP呢?因为看这道题比较类似于求“回文子字符串”,所以就想能不能用DP记录下起始和结束位置两个维度。即dp[i][j]表示原字符串中下标i到下标j的子字符串所含有的元音字符的总数。然后观察题目的用例,输入字符aba,画出下面这个矩阵先观察了一下

aba
a112
b01
a1

然后把所有dp[i][j] = 1 + 1 + 2 + 1 + 1 = 6。刚好就是结果。其中dp[i][j]表示:子序列s[i:j]中元音字符的数量

因此可以推出dp[i][j] = dp[i][j - 1] + 0或者1.其中当s[j:j]是元音的时候是加1,否则是0。最终的答案是:

i=0,j=indp[i][j]\sum_{i=0,j=i}^ndp[i][j]

另外,公式中的“尾巴” 其实就是表示dp[j][j],即

dp[i][j] = dp[i][j - 1] + dp[j][j]

DP优化 + 公式推导:

但是就算是利用dp[i][j] = dp[i][j - 1] + dp[i + 1][j]公式也还是O(n^2)的复杂度,显然还是超时。于是,继续观察这个dp递推式。就在想:

dp[i][j - 1]也可以继续往下推,即dp[i][j - 1]= dp[i][j - 2] + dp[j-1][j-1],所以看到的是,每一个dp[i][j]都是往左递推的,最后肯定是由dp[1][1],dp[2][2].....dp[n][n]这些原始值表示出来的。

当然比赛的时间有限,我也只是用aba这个例子推导了一下,没有完整的证明,大概思路如下,对于输入aba:

3行是:dp[3][3] = 1 * dp[3][3]2行是:dp[2][2] + dp[2][3] = dp[2][2] + (dp[2][2] + dp[3][3]) = 2 * dp[2][2] + 1 * dp[3][3]1行是:dp[1][1] + dp[1][2] + dp[1][3] = dp[1][1] + (dp[1][1] + dp[2][2]) + (dp[1][2] + dp[2][3]) = 
dp[1][1] + (dp[1][1] + dp[2][2]) + (dp[1][1] + dp[2][2] + dp[2][2] + dp[3][3]) = 3 * dp[1][1] + 2 * dp[2][2] + 1 * dp[3][3]3行的和加起来的系数矩阵就是:
      1
    2 1
  3 2 1
  
所以3行矩阵加起来就是:
3 * 1 * dp[1][1] + 2 * 2 * dp[2][2] + 1 * 3 * dp[1][1]
  
所以重点就是每个dp[i][i]前面的系数,dp[i][i]根据是否是元音字符赋值为01.
  

具体代码可以看下面,比赛时的代码略挫,见笑

public long countVowels(String word) {
    int[] single = new int[word.length() + 1];
    for (int i = 0; i < word.length(); i++) {
        single[i + 1] = (isYuanyin(word.charAt(i))) ? 1 : 0;
    }
    long result = 0;
    int xishu = word.length();
    for (long i = 1; i <= word.length(); i++) {
        result += (i * xishu * single[xishu]);
        xishu--;
    }
    return result;
}

private boolean isYuanyin(char c) {
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}