17.18. 最短超串

题目

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入:

big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]

输出: [7,10]

示例 2:

输入:

big = [1,2,3]
small = [4]

输出: []

提示:

  • big.length <= 100000
  • 1 <= small.length <= 100000

代码

完整代码

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

void dfs(int* minHeadToTail, int** appearIndex, int* appearIndexIndex, int smallIndex, int nowSmallestIndex, int nowBiggestIndex, int smallSize, int* res, int* big, int bigSize) {
    if (smallIndex == smallSize) {
        int nowHeadToTail = nowBiggestIndex - nowSmallestIndex + 1;
        if (nowHeadToTail < (*minHeadToTail) && nowHeadToTail > 0) {
            (*minHeadToTail) = nowHeadToTail;
            res[0] = nowSmallestIndex;
            res[1] = nowBiggestIndex;
        }
        return;
    }
    for (int i = 0; i < appearIndexIndex[smallIndex]; i++) {
        int index = appearIndex[smallIndex][i];
        int tmpnowSmallestIndex = nowSmallestIndex;
        int tmpnowBiggestIndex = nowBiggestIndex;
        if (nowSmallestIndex == bigSize) {
            nowSmallestIndex = index;
        }
        if (nowBiggestIndex == -1) {
            nowBiggestIndex = index;
        }
        if (index > nowBiggestIndex) {
            nowBiggestIndex = index;
        } else if (index < nowSmallestIndex) {
            nowSmallestIndex = index;
        }
        dfs(minHeadToTail, appearIndex, appearIndexIndex, smallIndex + 1, nowSmallestIndex, nowBiggestIndex, smallSize, res, big, bigSize);
        nowSmallestIndex = tmpnowSmallestIndex;
        nowBiggestIndex = tmpnowBiggestIndex;
    }
}

int* shortestSeq(int* big, int bigSize, int* small, int smallSize, int* returnSize) {
    int** appearIndex = (int**)calloc(smallSize, sizeof(int*));
    int* appearIndexIndex = (int*)calloc(smallSize, sizeof(int));
    for (int i = 0; i < smallSize; i++) {
        appearIndex[i] = (int*)calloc(bigSize, sizeof(int));
        for (int j = 0; j < bigSize; j++) {
            appearIndex[i][j] = -1;
        }
    }
    for (int i = 0; i < bigSize; i++) {
        for (int j = 0; j < smallSize; j++) {
            if (big[i] == small[j]) {
                appearIndex[j][appearIndexIndex[j]++] = i;
                break;
            }
        }
    }
    
    int* res = (int*)calloc(2, sizeof(int));
    int minHeadToTail = bigSize + 1;
    dfs(&minHeadToTail, appearIndex, appearIndexIndex, 0, bigSize, -1, smallSize, res, big, bigSize);
    (*returnSize) = 2;
    
    for (int i = 0; i < smallSize; i++) {
        free(appearIndex[i]);
    }
    free(appearIndex);
    free(appearIndexIndex);
    
    if (minHeadToTail == bigSize + 1) {
        (*returnSize) = 0;
        return res;
    }
    
    return res;
}

思路分析

这套代码使用深度优先搜索(DFS)方法来解决这个问题。它首先记录每个 small 数组元素在 big 数组中出现的位置,然后通过DFS遍历所有可能的组合,找出包含所有 small 数组元素的最短子数组。

拆解分析

  1. dfs 函数
void dfs(int* minHeadToTail, int** appearIndex, int* appearIndexIndex, int smallIndex, int nowSmallestIndex, int nowBiggestIndex, int smallSize, int* res, int* big, int bigSize) {
    if (smallIndex == smallSize) {
        int nowHeadToTail = nowBiggestIndex - nowSmallestIndex + 1;
        if (nowHeadToTail < (*minHeadToTail) && nowHeadToTail > 0) {
            (*minHeadToTail) = nowHeadToTail;
            res[0] = nowSmallestIndex;
            res[1] = nowBiggestIndex;
        }
        return;
    }
    for (int i = 0; i < appearIndexIndex[smallIndex]; i++) {
        int index = appearIndex[smallIndex][i];
        int tmpnowSmallestIndex = nowSmallestIndex;
        int tmpnowBiggestIndex = nowBiggestIndex;
        if (nowSmallestIndex == bigSize) {
            nowSmallestIndex = index;
        }
        if (nowBiggestIndex == -1) {
            nowBiggestIndex = index;
        }
        if (index > nowBiggestIndex) {
            nowBiggestIndex = index;
        } else if (index < nowSmallestIndex) {
            nowSmallestIndex = index;
        }
        dfs(minHeadToTail, appearIndex, appearIndexIndex, smallIndex + 1, nowSmallestIndex, nowBiggestIndex, smallSize, res, big, bigSize);
        nowSmallestIndex = tmpnowSmallestIndex;
        nowBiggestIndex = tmpnowBiggestIndex;
    }
}
  • dfs 函数用于递归搜索所有可能的子数组。
  • 当所有 small 数组元素都找到后,计算当前子数组的长度并更新最短长度及其起始和结束位置。
  • 递归遍历 appearIndex 中每个元素的位置,更新最小和最大索引。
  1. shortestSeq 函数
int* shortestSeq(int* big, int bigSize, int* small, int smallSize, int* returnSize) {
    int** appearIndex = (int**)calloc(smallSize, sizeof(int*));
    int* appearIndexIndex = (int*)calloc(smallSize, sizeof(int));
    for (int i = 0; i < smallSize; i++) {
        appearIndex[i] = (int*)calloc(bigSize, sizeof(int));
        for (int j = 0; j < bigSize; j++) {
            appearIndex[i][j] = -1;
        }
    }
    for (int i = 0; i < bigSize; i++) {
        for (int j = 0; j < smallSize; j++) {
            if (big[i] == small[j]) {
                appearIndex[j][appearIndexIndex[j]++] = i;
                break;
            }
        }
    }
    
    int* res = (int*)calloc(2, sizeof(int));
    int minHeadToTail = bigSize + 1;
    dfs(&minHeadToTail, appearIndex, appearIndexIndex, 0, bigSize, -1, smallSize, res, big, bigSize);
    (*returnSize) = 2;
    
    for (int i = 0; i < smallSize; i++) {
        free(appearIndex[i]);
    }
    free(appearIndex);
    free(appearIndexIndex);
    
    if (minHeadToTail == bigSize + 1) {
        (*returnSize) = 0;
        return res;
    }
    
    return res;
}
  • shortestSeq 函数首先初始化并填充 appearIndex,记录 small 数组中每个元素在 big 数组中出现的位置。
  • 然后调用 dfs 函数来搜索包含所有 small 数组元素的最短子数组。
  • 最后释放分配的内存,并返回结果。

复杂度分析

  • 时间复杂度:O(n * m),其中 nbig 数组的长度,msmall 数组的长度。最坏情况下需要遍历所有可能的子数组组合。
  • 空间复杂度:O(n * m),用于存储 appearIndex 和递归调用栈的空间。

一题多解 滑动窗口

滑动窗口代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int count;
    int required;
} ElementInfo;

int* shortestSeq(int* big, int bigSize, int* small, int smallSize, int* returnSize) {
    int* result = (int*)malloc(2 * sizeof(int));
    *returnSize = 0;
    
    if (smallSize > bigSize) {
        return result;
    }
    
    ElementInfo* smallMap = (ElementInfo*)calloc(100000, sizeof(ElementInfo));
    int requiredCount = 0;
    
    for (int i = 0; i < smallSize; i++) {
        if (smallMap[small[i]].required == 0) {
            requiredCount++;
        }
        smallMap[small[i]].required++;
    }
    
    int left = 0;
    int minLength = bigSize + 1;
    int currentCount = 0;
    
    for (int right = 0; right < bigSize; right++) {
        if (smallMap[big[right]].required > 0) {
            smallMap[big[right]].count++;
            if (smallMap[big[right]].count == smallMap[big[right]].required) {
                currentCount++;
            }
        }
        
        while (currentCount == requiredCount && left <= right) {
            if (right - left + 1 < minLength) {
                minLength = right - left + 1;
                result[0] = left;
                result[1] = right;
                *returnSize = 2;
            }
            
            if (smallMap[big[left]].required > 0) {
                if (smallMap[big[left]].count == smallMap[big[left]].required) {
                    currentCount--;
                }
                smallMap[big[left]].count--;
            }
            left++;
        }
    }
    
    free(smallMap);
    
    if (*returnSize == 0) {
        result[0] = -1;
        result[1] = -1;
    }
    
    return result;
}

思路分析

这套代码使用滑动窗口方法来解决问题。通过维护两个指针 leftright,动态调整窗口大小以找到包含所有 small 数组元素的最短子数组。

拆解分析

  1. 初始化

    • smallMap 记录 small 数组中每个元素的出现次数和当前计数。
    • requiredCount 记录 small 数组中不同元素的个数。
  2. 滑动窗口

    • 右指针 right 从左到右遍历 big 数组。
    • 当窗口包含所有 small 数组元素时,移动左指针 left 以缩小窗口并更新最短子数组的起始和结束位置。
  3. 返回结果

    • 如果没有找到包含所有 small 数组元素的子数组,返回 [-1, -1]

复杂度分析

  • 时间复杂度:O(n + m),其中 nbig 数组的长度,msmall 数组的长度。滑动窗口方法需要遍历 big 数组一次,同时更新 smallMap 数组。
  • 空间复杂度:O(m),用于存储 smallMap 数组。

结果

DFS:

在这里插入图片描述

滑动窗口

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值