《超级有效子字符串计数》和《端点石子移动问题》| 豆包MarsCode AI刷题

85 阅读4分钟

今天我们将在豆包MarsCode AI刷题平台上,完成《超级有效子字符串计数》与《端点石子移动问题》这两个算法问题,通过这些练习提升用户解决此类问题的能力

《超级有效子字符串计数》题面如下:

image.png

问题理解

题目要求我们计算一个字符串中包含的超级有效子字符串的数量。超级有效子字符串是由不同的有效字符串连接而成的字符串。有效字符串的定义是:由 X< 开头,接着一个 |,最后是 X> 组成的字符串。

数据结构选择

我们选择了两个数组 preleftnumprerightnum 来分别记录每个位置左侧连续 < 的数量和右侧连续 > 的数量。

算法步骤

  1. 预处理左侧连续 < 的数量

    • 遍历字符串,如果当前字符是 <,则 preleftnum[i] 记录从当前位置向左连续 < 的数量。
    • 如果当前字符不是 <,则 preleftnum[i] 置为 0。
  2. 预处理右侧连续 > 的数量

    • 由右向左遍历字符串,如果当前字符是 >,则 prerightnum[i] 记录由当前位置向右连续 > 的数量。
    • 如果当前字符不是 >,则 prerightnum[i] 置为 0。
  3. 计算超级有效子字符串的数量

    • 遍历字符串,找到每个 | 的位置。
    • 对于每个 |,计算其左侧连续 < 的数量 leftnum 和右侧连续 > 的数量 rightnum
    • 计算 leftnumrightnum 的最小值,并累加到结果 ret 中。
    • 如果 leftnumrightnum 相等且大于 0,则说明当前 | 可以与之前的 | 形成更多的超级有效子字符串,累加 pre 的值到 ret,并更新 pre

具体实现

int solution(int N, const std::string& S) {
    // write code here
    int ret=0;
    int pre=0;
    vector<int> preleftnum(N,0), prerightnum(N,0);
    // 预处理左侧连续 < 的数量
    for(int i=0;i<N;i++){
        if(S[i] == '<'){
            preleftnum[i] = i-1>=0 ? preleftnum[i-1]+1 : 1;
        } else {
            preleftnum[i] = 0;
        }
    }
    // 预处理右侧连续 > 的数量
    for(int i=N-1;i>=0;i--){
        if(S[i] == '>'){
            prerightnum[i] = i+1<N ? prerightnum[i+1]+1 : 1;
        } else {
            prerightnum[i] = 0;
        }
    }
    // 计算超级有效子字符串的数量
    for(int i=0;i<N;i++){
        if(S[i] == '|'){
            int leftnum=i-1>=0 ? preleftnum[i-1] : 0, rightnum=i+1<N ? prerightnum[i+1] : 0;
            ret += min(leftnum, rightnum);
            
            if(leftnum == rightnum && leftnum>0){
                ret += pre;
                pre++;
            } else {
                pre=0;
            }
        }
    }
    return ret;
}
  • 时间复杂度O(N)

  • 空间复杂度O(N)

  • 其中 N 是字符串的长度。

《端点石子移动问题》题面如下:

问题理解

题目要求我们计算将所有端点石子移动到 非端点位置 所需的最小次数。端点石子是指位于数组 stones 中最小或最大位置的石子。如果石子的位置是连续的,则游戏结束,因为没有可以进行的移动操作。

数据结构选择

  • 由于我们需要对石子的位置进行排序和查找操作,因此选择使用 vector 来存储石子的位置。
  • 首先对 stones 进行排序,以便后续处理。

算法步骤

  1. 排序:首先对 stones 进行排序,这样我们可以更容易地找到端点石子。
  2. 计算移动次数:遍历排序后的 stones,计算每个位置作为端点石子时,需要移动的次数。
  3. 特殊情况处理:在遍历过程中,因为要求端点石子移动之后要位于非端点位置,所以要对特殊情况进行处理。

具体实现

int solution(vector<int>& stones) {
    int n = stones.size();
    int ret = n;
    sort(stones.begin(), stones.end());
    
    for(int i = 0; i < n; i++) {
        // 计算由当前位置 stones[i] 开始,有多少个石子在 stones[i] + n 之后
        int rightNum = distance(lower_bound(stones.begin(), stones.end(), stones[i] + n), stones.end());
        // 计算当前位置作为左端点石子时,需要移动的次数,i指的是有多少个石子在 stones[i]  之前
        int moveNum = i + rightNum;
        // 特殊情况处理
        // 如果当前位置是第一个石子,并且只有一个石子在 stones[i] + n 之后,则需要移动两次
        if(i == 0 && rightNum == 1) {
            moveNum = 2;
        }
        // 如果当前位置是第二个石子,并且后面所有石子都是连续的,则需要移动两次
        if(i == 1 && stones[n-1] - stones[1] + 1 == n - 1) {
            moveNum = 2;
        }
        
        ret = min(ret, moveNum);
    }
    return ret;
}

借助豆包MarsCode AI刷题平台,我们不仅高效地解决了《超级有效子字符串计数》和《端点石子移动问题》,还加深了对相关算法和数据结构的理解,后续会借助豆包MarsCode AI给大家展示更多题目的解法。