「双指针/kmp」通过连接另一个数组的子数组得到一个数组(力扣第1764题)

本题为12月17日力扣每日一题

题目来源:力扣第1764题

题目tag:双指针 kmp

题面

题目描述

给你一个长度为n的二维整数数组groups,同时给你一个整数数组nums。

你是否可以从nums中选出n个不相交的子数组,使得第i个子数组与groups[i](下标从0开始)完全相同,且如果i>0,那么第(i-1)个子数组在nums中出现的位置在第i个子数组前面。(也就是说,这些子数组在nums中出现的顺序需要与groups顺序相同)

如果你可以找出这样的n个子数组,请你返回true,否则返回false。

如果不存在下标为k的元素nums[k]属于不止一个子数组,就称这些子数组是不相交的。子数组指的是原数组中连续元素组成的一个序列。

示例

示例 1

输入:

groups = [[1,-1,-1],[3,-2,0]], nums = [1,-1,0,1,-1,-1,3,-2,0]

输出:

true

解释:

你可以分别在 nums 中选出第 0 个子数组 [1,-1,0,1,-1,-1,3,-2,0] 和第 1 个子数组 [1,-1,0,1,-1,-1,3,-2,0] 。

这两个子数组是不相交的,因为它们没有任何共同的元素。

示例 2

输入:

groups = [[10,-2],[1,2,3,4]], nums = [1,2,3,4,10,-2]

输出:

false

解释:

选择子数组 [1,2,3,4,10,-2] 和 [1,2,3,4,10,-2] 是不正确的,因为它们出现的顺序与 groups 中顺序不同。

[10,-2] 必须出现在 [1,2,3,4] 之前。

示例 3

输入:

groups = [[1,2,3],[3,4]], nums = [7,7,1,2,3,4,7,7]

输出:

false

解释:

选择子数组 [7,7,1,2,3,4,7,7] 和 [7,7,1,2,3,4,7,7] 是不正确的,因为它们不是不相交子数组。

它们有一个共同的元素 nums[4] (下标从 0 开始)。

提示

groups.length == n
1 <= n <= $ 10^3 $
1 <= groups[i].length, sum(groups[i].length) <= $ 10^3 $
1 <= nums.length <= $ 10^3 \(
\)
-10^7 $ <= groups[i][j], nums[k] <= $ 10^7 $


思路分析

这题的题目翻译过来就是,对nums数组,挨个匹配二维数组groups中的每一行,如果能全部匹配就是匹配成功,如果不能则为失败.

再简单一点说就是对nums数组进行多次子串匹配,而子串匹配显然可以直接使用kmp算法来完成.由于本篇博客仅为题解,所以这里不对kmp算法展开讨论,如感兴趣可以自行搜索学习,下面的参考代码也不会对kmp算法的部分进行注释.

那如果不会kmp算法怎么办呢?这道题的数据量比较小,所以直接使用双指针暴力比较也是可以的.kmp其实就是对下面这样的暴力匹配的一种优化方案.

我们使用i指针指向nums,j指针指向groups,看看nums中以i指向的元素为开头的子串,是否能和当前j指向的那行groups(下用group指代)匹配:

  • 如果当前匹配,那就把i移动group的长度,然后j指向下一个group,继续匹配.
  • 如果不匹配,那就把i后移,匹配下一个子串.

如果最终j成功移到groups末尾,那证明groups中的所有group成功匹配,返回true,否则证明其中存在group匹配失败,那就返回false.

参考代码

双指针暴力比较

class Solution
{
public:
    bool canChoose(vector<vector<int>> &groups, vector<int> &nums)
    {
        int i = 0, j = 0; // i为nums上的指针,j为groups上的指针
        while (i < nums.size() && j < groups.size())
        {
            if (equal(groups[j], nums, i)) // 如果当前从i开始的子串与当前对应的group匹配
            {
                i += groups[j].size(); // 跳过这些已匹配部分
                j++; // 匹配下一个group
            }
            else // 当前未匹配
            {
                i++; // 从下一个开始继续尝试比较
            }
        }
        return j == groups.size(); // 如果是因为j遍历完毕而退出的循环,就是匹配成功,否者就是匹配失败
    }

private:
    // 比较nums中以start开头的子串是否和group匹配
    bool equal(vector<int> group, vector<int> nums, int start)
    {
        if (group.size() > nums.size() - start) // 当前nums子串的长度已不够,必然不匹配
        {
            return false;
        }
        for (int i = 0; i < group.size(); i++) // 逐个遍历即可
        {
            if (group[i] != nums[start + i])
            {
                return false;
            }
        }
        return true;
    }
};

kmp

class Solution
{
public:
    bool canChoose(vector<vector<int>> &groups, vector<int> &nums)
    {
        int i = 0; // i为nums上的指针
        for (int k = 0; k < groups.size(); k++)
        {
            int j = 0; // j为当前group上的指针

            ///////// kmp核心代码 /////////
            vector<int> next = getNext(groups[k]);
            while (i < nums.size() && j < groups[k].size())
            {
                if (nums[i] == groups[k][j])
                {
                    i++;
                    j++;
                }
                else if (next[j] != -1)
                {
                    j = next[j];
                }
                else
                {
                    i++;
                }
            }
            ///////// kmp核心代码 /////////

            if (j != groups[k].size()) // 当前串匹配失败,则一定不匹配
            {
                return false;
            }
        }
        return true;
    }

private:
    ///////// kmp核心代码 /////////
    vector<int> getNext(vector<int> group)
    {
        vector<int> next(group.size(), -1);
        if (group.size() == 1)
        {
            return next;
        }
        next[1] = 0;
        int i = 2, cn = 0;
        while (i < group.size())
        {
            if (group[i - 1] == group[cn])
            {
                next[i++] = ++cn;
            }
            else if (cn > 0)
            {
                cn = next[cn];
            }
            else
            {
                next[i++] = 0;
            }
        }
        return next;
    }
    ///////// kmp核心代码 /////////
};

“正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯” —亚里士多德