【力扣题解】使用拓扑排序的思想,图解(936.戳印序列)(C++)

本文介绍了如何使用拓扑排序思想解决LeetCode问题936——戳印序列,通过构建有向图并分析窗口、入度和邻接表,找出印章覆盖字符的最优路径。
摘要由CSDN通过智能技术生成

使用拓扑排序的思想,图解【936.戳印序列】(C++)

Problem: 936. 戳印序列

题目:936. 戳印序列

/**
 * 拥有一个印章stamp,和一个目标字符串target
 * 现在提供一个空白格子,格子长度与target一样
 * 每轮我们可以使用印章在格子任意位置改下印章,新轮的印章会覆盖上一轮的字符
 * 需要注意的是,每次盖下印章,我们必须保证印章不会出格子界
 * 如果我们最后能够通过印章获取target字符,则返回每轮盖下印章,印章最左侧在格子上的位置的数组
 * 如:
 * stamp = abc , target = ababc  表格 = ?????
 * turn one : abc??   [0]
 * turn two: ababc   [0,2]
 * 答案即为  [0,2]
 * 如果无法达成目标,则返回空数组
 * 限定时间复杂度:10 * target.length 
 */

思路

题解参考:Python3 & C++ | 拓扑排序——by:xxgj

我们先明确一些概念。

记stamp长度为m , target 长度为n。

【窗口】:每次盖下stamp,将覆盖长度为stamp.size()的字符。我们将每次覆盖的这个长度看做一个窗口。由于本题要求了,盖下印章不能超出边界,所以在目标字符中,存在 n -m + 1个窗口。

image-20240428163628040

【入度】:一般指有向图中,某个顶点入边的数量。如有图v1 -> v2 。那么v2的入度就为1。而在本题中,我们将每个窗口字符视作一个顶点,而其入度为——以该字符为窗首时,与stamp位置不符的字符数量。

【边】:在本题中,当以i位置为窗首字母,窗口长度为m。 i <= j < i + m -1,当target[j] != stamp[j -i]时,存在j - > i这样一条边。

image-20240428164432144

【拓扑排序】:拓扑排序是有向无环图顶点特殊的序列,是对DAG(有向无环图)的顶点的一种排序。

  • 每个顶点只出现一次
  • A在B前,则不存在B->A

AOV(有向无环网)-> 拓扑序列

  1. 选择无前驱结点(即入度 = 0)的点,out
  2. 从网中删除该点及其以该点为顶点的有向边(弧尾)
  3. 重复(1)(2)步骤,直到AOV为空或者网中不存在无前驱的点(即存在环)。

我们每轮out的点,排列出来就是拓扑序列。

而在本题中,我们将获得的拓扑序列逆置,便为结果。

解题方法

描述你的解题方法

通过以上概念,我们现在得到一个有向图。现在来说明解题细节。

1、设置一个windows数组,其含义为,以windows[i]为窗首时,该窗口中有多少【入度】(即有多少字符与stamp对应位置字符不同)。初始化时,假设所有窗口都不符,即windows[i] = m

2、 用一个二维数组存储一个邻接表neighborsneighbors[j] = {pos_x....}(pos_x <= j < pos_x + m),其含义是,当pos_x作为窗首字符时,target[j] != stamp[j - pos_x]。也就是说,存在一条 j -> pos_x的边。

2、遍历一遍所有窗口,

  • 当遍历某窗口i时,存在某个窗口字符与stamp字符不同,则添加一条该位置朝向窗首字符i的边。否则,windows[i]--

  • 如果windows[i] == 0,代表这个点的入度为0 ,我们加入队列(即拓扑排序中的out)。

3、因为我们out了该点(记为 i),可视作target[i, i + m)中的元素都已匹配正确(归位)。故而我们删除所有以target[i, i+m)为出度的边,如下图所示(这个图演示的是,我们以1位置为窗首的节点out)

image-20240428170537405 image-20240428170246542

4、删除所有边后,我们又获得了一些入度=0的节点,将这些节点入队(一旦归位的元素,不要再次去掉其边)。

5、循环3、4、步,如果最后windows中所有元素值都为0,则代表成功获取拓扑序列,我们将获取的拓扑序列反转,则获得答案。否则,答案不存在,返回空数组。

复杂度

时间复杂度:

添加时间复杂度, 示例: O ( m ( n − m + 1 ) ) O(m(n - m + 1)) O(m(nm+1))

空间复杂度:

添加空间复杂度, 示例: O ( n ( n − m + 1 ) ) O(n(n-m+1)) O(n(nm+1))

Code

class Solution
{
public:
    vector<int> movesToStamp(string stamp, string target)
    {
        // 如果长度相等
        if(stamp.size() == target.size()){
            if(stamp == target){
                return {0};
            }
            return {};
        }else if(stamp[0] != target[0]){
            return {};      // 因为印章不可以越界,所以第一个字符必须相等
        }
        int m = stamp.size(), n = target.size();
        vector<int> windows(n - m + 1, m);
        vector<vector<int>> neighbors(n);
        queue<int> nodes;
        for(int i = 0; i < windows.size(); i++){
            for(int j = 0; j < m; j++){
                if(target[i+j] == stamp[j]){
                    // 代表以i为窗首时,其后有一个字符的位置对应正确
                    windows[i]--;
                    if(windows[i] == 0){
                        // 代表以i为窗首时,target上的窗口字符与stamp完全对应
                        nodes.emplace(i);
                    }
                }else{
                    // 代表以i为窗首时, i + j位置上的字符stamp对应位置的字符不相等
                    neighbors[i + j].emplace_back(i);
                }
            }
        }
        vector<int> ans;
        // 判断该节点的出度是否删除完
        vector<bool> seen(n, false);
        while(!nodes.empty()){
            // cur窗口已经可以匹配
            int cur = nodes.front();
            nodes.pop();
            ans.push_back(cur);
            for(int i= 0; i < m; i++){
                if(!seen[cur + i]){
                    seen[cur + i] = true;
                    for(auto&& ngb: neighbors[cur + i]){
                        if(--windows[ngb] == 0){
                            nodes.emplace(ngb);
                        }
                    }
                }
            }
        }
        int sum = std::accumulate(windows.begin(), windows.end(),0);
        std::reverse(ans.begin(), ans.end());
        return sum == 0 ? ans : vector<int>();
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值