[算法练习]leetcode-6050. 字符串的总引力

该博客探讨了如何计算字符串所有子字符串的引力,即不同字符的数量之和。通过动态规划的方法,实现了从已知子串引力推导出后续子串引力的过程,有效地解决了O(N^2)的时间复杂度问题。示例中详细解释了算法实现,并提供了两个不同思路的代码示例,一个是求引力总和,另一个是寻找特定数量相同字符的子串。此外,还提到了处理子数组问题时可以考虑使用单调栈策略。
摘要由CSDN通过智能技术生成

题目链接

力扣

字符串的 引力 定义为:字符串中 不同 字符的数量。

例如,"abbca" 的引力为 3 ,因为其中有 3 个不同字符 'a'、'b' 和 'c' 。
给你一个字符串 s ,返回 其所有子字符串的总引力 。

子字符串 定义为:字符串中的一个连续字符序列。

示例 1:

输入:s = "abbca"
输出:28
解释:"abbca" 的子字符串有:
- 长度为 1 的子字符串:"a"、"b"、"b"、"c"、"a" 的引力分别为 1、1、1、1、1,总和为 5 。
- 长度为 2 的子字符串:"ab"、"bb"、"bc"、"ca" 的引力分别为 2、1、2、2 ,总和为 7 。
- 长度为 3 的子字符串:"abb"、"bbc"、"bca" 的引力分别为 2、2、3 ,总和为 7 。
- 长度为 4 的子字符串:"abbc"、"bbca" 的引力分别为 3、3 ,总和为 6 。
- 长度为 5 的子字符串:"abbca" 的引力为 3 ,总和为 3 。
引力总和为 5 + 7 + 7 + 6 + 3 = 28 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/total-appeal-of-a-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

所有的连续子数组是[i, j]  i <= j 的组合,直接枚举所有情况,是O(N^2)的时间复杂度,

可以考虑使用动态规划来分析,假设f(i)是以第i个元素为起始点的所有情况,

f(0) = [0, 0], [0,1], [0,2], [0,3] …… 

f(1) =  [1,1], [1,2], [1,3] ……

可以看到,f(1)的结果可以从f(0)推导出来,分为a[0] == a[1]和a[0] != a[1]的情况,

按照这个思路,可以这样写

class Solution {
public:
    long long appealSum(string s) {
        //init, get sum and each pos
        long long f = 0;
        map<int, vector<int>> pos;
        int len = s.size();
        for (int i = 0; i < len; i++) {
            int k = s[i];
            pos[k].push_back(i);
            f += pos.size();
        }

        long long ans = f;
        vector<int> dt(len);
        vector<long long> fs(len);
        dt[0] = 0;
        fs[0] = f;
        for (int i = 0; i < len-1; i++) {
            //f1 = f0 - dt
            int c = s[i];
            if (s[i] == s[i+1]) {
                fs[i+1] = fs[i] - 1;
                ans += fs[i+1];
                continue;
            }
            //find the pos of i
            vector<int>& arr = pos[c];
            int b = 0;
            int e = arr.size()-1;
            int p = e+1;
            while (b <= e) {
                int m = (b+e)/2;
                if (arr[m] > i) {
                    e = m-1;
                    p = m;
                } else {
                    b = m+1;
                }
            }
            int d = len - i;
            if (p <= arr.size()-1) {
                d = arr[p] - i;
            }
            fs[i+1] = fs[i] - d;
            ans += fs[i+1];
        }     
        return ans;   
    }
};

对于这个题目,也可以假设f(i)是以a[i]为末尾的情况,这样处理,程序写起来更清楚一些,

上面的题目是求>=1的个数,下面的题目比较相似,是求==1的数量,采用f(i)是以a[i]为末尾的情况来处理,

力扣

class Solution {
public:
    int uniqueLetterString(string s) {
        const int n = s.size();
        int tt = 1000000000+7;

        //1.
        // dp[i]: 以s[i]结尾的子字符串的总引力
        // pre_idx[i]: 记录字符 'a'+i 之前出现的位置,如果之前没有出现,就 = -1
        long long dp[n];
        vector<vector<int>> pre_idx(26, vector<int>(2, -1));
    //    int pre_idx[26][2] = {-1};//just the first one is -1

        // 2. initial
        dp[0] = 1;
        int c = s[0] - 'A';
        pre_idx[c][1] = 0;
        
        // 3. 
        long long ans = 1;
        for (int i = 1; i < n; ++i) {
            c = s[i] - 'A'; char of pos i
        //    int len = i - pre_idx[s[i] - 'A'] - 1;
            int dt = 0;
            //not appeared begfore
            if (pre_idx[c][1] == -1) {
                dt = i+1;
            } else {
                dt = (i-pre_idx[c][1])
                 - (pre_idx[c][1]-pre_idx[c][0]); 
                // (pre_idx[c][1]-pre_idx[c][0]) //new
               // - (i-pre_idx[c][0]);//old
            }
            dp[i] = dp[i - 1] + dt;

            pre_idx[c][0] = pre_idx[c][1];
            pre_idx[c][1] = i;

            //
            if (dp[i] > tt) {
                dp[i] = dp[i]%tt;
            }

            ans += dp[i];
            
        }

        // 4. 
        return ans%tt;
    
    }
};

另外子数组的题目,需要考虑使用单调栈找左右区间,

力扣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值