(B卷,100分)- 关联子串(Java & JS & Python & C)

题目描述

给定两个字符串str1和str2,如果字符串str1中的字符,经过排列组合后的字符串中,只要有一个字符串是str2的子串,则认为str1是str2的关联子串。

若str1是str2的关联子串,请返回子串在str2的起始位置;

若不是关联子串,则返回-1。

输入描述

输入两个字符串,分别为题目中描述的str1、str2。

输出描述

若str1是str2的关联子串,请返回子串在str2的起始位置;

若不是关联子串,则返回-1。

若str2中有多个str1的组合子串,请返回最小的起始位置。

备注

  • 输入的字符串只包含小写字母;
  • 两个字符串的长度范围[1, 100000]之间;

用例

输入 abc efghicbaiii
输出 5
说明 str2包含str1的一种排列组合("cab"),此组合在str2的字符串起始位置为5(从0开始计数)
输入 abc efghiccaiii
输出 -1
说明 “abc”字符串中三个字母的各种组合(abc、acb、bac、bca、cab、cba),str2中均不包含,因此返回-1

题目解析

本题看上去是要求解全排列,但是题目描述数量级为:两个字符串的长度范围[1, 100000]之间;

因此str1的全排列肯定会超时。

本题可以使用尺取法来求解,尺取法其实就是高级一点的滑动窗口,常用于解决最小覆盖子串问题,比如

大家可以认真看下上面这个博客,对尺取法有个大致了解。

尺取法,通常是有一个子串str1,和一个父串str2,我们需要在父串str2中找到包含str1子串所有字符的目标串target,不在意字符顺序。

解决方案很固定:

预处理动作:

  • 统计出子串str1中各字符的数量,记录到count中,这里的count容器通常选择128长度的数组,因为字符串中的字符通常都是ASCII码表的字符,而ASCII码只有128个,因此选择128长度的数组就可以涵盖到所有字符。
  • 定义一个total变量,来记录str1字符的总数(即str1的长度)

滑窗动作:

  • 初始滑窗,即父串str2的0~str1.length-1范围的滑窗。此时滑窗只有新增字符的过程,即滑窗左边界保持在0,而滑窗右边界从0运动到str1.length-1位置。
  • 后续滑窗,即父串str2的 i ~ i + str1.length – 1,其中 i >= 1,每次滑窗整体向右移动一格,即相较于上一个滑窗,会失去 str2[i-1]字符,以及新增 str2[i + str1.length – 1] 字符。

滑窗的意义:

  • 滑窗其实就是一个str2的子串,我们可以基于滑窗来统计滑窗内子串各字符的数量,如果滑窗内各字符的数量与str1各字符数量一致,则说明滑窗内子串就是一个目标子串

滑窗处理新增字符:

  • 当滑窗新增一个字符c时,我们应该count[c] -= 1,表示滑窗子串和str1的差别缩小了,此时total是否也需要-=1吗?total是str1的字符总数,此时需要细化讨论
  1. 如果字符c不是str1内的字符,则total不需要-=1
  2. 如果字符c是str1内的字符,此时又需要细化讨论,字符c虽然是str1内的字符,但是滑窗内c字符的数量完全有可能超过str1内的字符数量,即滑窗内存在多余的字符c,那么此时滑窗新增了一个c,并不会缩小和str1的差距,即total不需要-=1。

那么此时就需要搞清楚,如何判断滑窗内字符c是否是str1的字符?以及是str1字符的情况下,是否为超标字符?

上面两个判断,可以用一句话实现:count[c] > 0

  • 如果滑窗新增的字符c不是str1字符,则必然count[c] <= 0
  • 如果滑窗新增的字符c是str1字符,但是超标了,则经过count[c]–动作,超过的c字符,必然count[c] <= 0

滑窗处理移除字符:

当滑窗移除一个字符c是,我们应该count[c]++,表示滑窗子串和str1的差别扩大了,此时total是否也需要+=1吗?total是str1的字符总数,此时需要细化讨论:

  • 如果字符c不是str1内的字符,则total不需要+=1
  • 如果字符c是str1内的字符,此时又需要细化讨论,字符c虽然是str1内的字符,但是滑窗内c字符的数量完全有可能超过str1内的字符数量,即滑窗内存在多余的字符c,那么此时滑窗移除一个c,并不会扩大和str1的差距,即不需要total+=1

那么此时就需要搞清楚,如何判断滑窗内字符c是否是str1的字符?以及是str1字符的情况下,是否为超标字符?

上面两个判断,可以用一句话实现:count[c] >= 0

  • 如果滑窗移除的字符c不是str1字符,则移除之前必然count[c] < 0
  • 如果滑窗移除的字符c是str1字符,但是超标了,则移除之前必然count[c] < 0

Java算法源码

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    String str1 = sc.next();
    String str2 = sc.next();

    System.out.println(getResult(str1, str2));
  }

  public static int getResult(String str1, String str2) {
    // count用于统计str1中各字符的数量
    int[] count = new int[128];
    for (int i = 0; i < str1.length(); i++) {
      char c = str1.charAt(i);
      count[c]++;
    }

    // total统计str1总共字符个数
    int total = str1.length();

    // 初始滑窗,滑窗范围内容,就是一个子串
    for (int i = 0; i < str1.length(); i++) {
      // 滑窗子串新增的字符
      char add = str2.charAt(i);

      // 如果新增字符是str1的字符,即count[add] > 0时,则说明滑窗子串已找到一个目标字符,此时剩余add字符数量count[add]--,剩余目标字符总数total--
      if (count[add]-- > 0) {
        total--;
      }
    }

    // 如果total == 0,则说明滑窗内所有字符都是str1内的字符,由于限定了滑窗的长度就是str1的长度,因此滑窗内字符和str1完全匹配
    if (total == 0) return 0;

    // 下一个滑窗范围是 i ~ i + str1.length() - 1
    for (int i = 1; i + str1.length() - 1 < str2.length(); i++) {
      // 相较于上一个滑窗失去的字符remove
      char remove = str2.charAt(i - 1);
      // 相较于上一个滑窗新增的字符add
      char add = str2.charAt(i + str1.length() - 1);

      // 本轮滑窗remove字符,在上一轮是被add的字符,我们假设此字符为c,此时可以分两种情况讨论:
      // 1、c是str1的字符,则初始统计时count[c]>0,上一轮滑窗加入c字符,则count[c]--,此轮count[c]是有可能>=0或者<0的,
      //    1.1、如果本轮count[c]>=0,则说明上轮滑窗并没有找到所有的c字符,因此本轮移除的c字符可以还原total数量
      //
      // 1.2、如果本轮count[c]<0,这说明上轮滑窗内的c字符超标了,即c字符是目标字符,但是滑窗内包含的c字符数量超过了str1中c字符的数量,因此本轮移除c字符是超标部分,不会还原total
      // 2、c不是str1的字符,则初始统计时count[c]==0,上一轮滑窗加入c字符,则count[c]--,此轮必然count[c]<0,因此c字符的移除,并不会还原total
      if (count[remove]++ >= 0) {
        total++;
      }

      if (count[add]-- > 0) {
        total--;
      }

      if (total == 0) {
        return i;
      }
    }

    return -1;
  }
}

JS算法源码

/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.on("line", (line) => {
  let [str1, str2] = line.split(" ");
  console.log(getResult(str1, str2));
});

function getResult(str1, str2) {
  // count用于统计str1中各字符的数量
  const count = {};
  for (let i = 0; i < str1.length; i++) {
    const c = str1[i];
    count[c] = (count[c] ?? 0) + 1;
  }

  // total统计str1总共字符个数
  let total = str1.length;

  // 初始滑窗,滑窗范围内容,就是一个子串
  for (let i = 0; i < str1.length; i++) {
    // 滑窗子串新增的字符
    const add = str2[i];

    // 如果新增字符是str1的字符,即count[add] > 0时,则说明滑窗子串已找到一个目标字符,此时剩余add字符数量count[add]--,剩余目标字符总数total--
    if (count[add]-- > 0) {
      total--;
    }
  }

  // 如果total == 0,则说明滑窗内所有字符都是str1内的字符,由于限定了滑窗的长度就是str1的长度,因此滑窗内字符和str1完全匹配
  if (total == 0) return 0;

  // 下一个滑窗范围是 i ~ i + str1.length() - 1
  for (let i = 1; i + str1.length - 1 < str2.length; i++) {
    // 相较于上一个滑窗失去的字符remove
    const remove = str2[i - 1];
    // 相较于上一个滑窗新增的字符add
    const add = str2[i + str1.length - 1];

    // 本轮滑窗remove字符,在上一轮是被add的字符,我们假设此字符为c,此时可以分两种情况讨论:
    // 1、c是str1的字符,则初始统计时count[c]>0,上一轮滑窗加入c字符,则count[c]--,此轮count[c]是有可能>=0或者<0的,
    //    1.1、如果本轮count[c]>=0,则说明上轮滑窗并没有找到所有的c字符,因此本轮移除的c字符可以还原total数量
    //    1.2、如果本轮count[c]<0,这说明上轮滑窗内的c字符超标了,即c字符是目标字符,但是滑窗内包含的c字符数量超过了str1中c字符的数量,因此本轮移除c字符是超标部分,不会还原total
    // 2、c不是str1的字符,则初始统计时count[c]==0,上一轮滑窗加入c字符,则count[c]--,此轮必然count[c]<0,因此c字符的移除,并不会还原total
    if (count[remove]++ >= 0) {
      total++;
    }

    if (count[add]-- > 0) {
      total--;
    }

    if (total == 0) return i;
  }

  return -1;
}

Python算法源码

# 输入获取
str1, str2 = input().split()


# 算法入口
def getResult():
    # count用于统计str1中各字符的数量
    count = {}
    for c in str1:
        count[c] = count.get(c, 0) + 1

    # total统计str1总共字符个数
    total = len(str1)

    # 初始滑窗,滑窗范围内容,就是一个子串
    for i in range(len(str1)):
        # 滑窗子串新增的字符
        add = str2[i]

        # 如果新增字符是str1的字符,即count[add] > 0时,则说明滑窗子串已找到一个目标字符,此时剩余add字符数量count[add]--,剩余目标字符总数total--
        if count.get(add) is not None:
            if count[add] > 0:
                total -= 1
            count[add] -= 1

    # 如果total == 0,则说明滑窗内所有字符都是str1内的字符,由于限定了滑窗的长度就是str1的长度,因此滑窗内字符和str1完全匹配
    if total == 0:
        return 0

    # 下一个滑窗范围是 i ~ i + str1.length() - 1
    for i in range(1, len(str2) - len(str1) + 1):
        # 相较于上一个滑窗失去的字符remove
        remove = str2[i-1]
        # 相较于上一个滑窗新增的字符add
        add = str2[i + len(str1) - 1]

        # 本轮滑窗remove字符,在上一轮是被add的字符,我们假设此字符为c:
        # 1、c是str1的字符,则初始统计时count[c]>0,上一轮滑窗加入c字符,则count[c]--,此轮count[c]是有可能>=0或者<0的,
        #    1.1、如果本轮count[c]>=0,则说明上轮滑窗并没有找到所有的c字符,因此本轮移除的c字符可以还原total数量
        #    1.2、如果本轮count[c]<0,这说明上轮滑窗内的c字符超标了,即c字符是目标字符,但是滑窗内包含的c字符数量超过了str1中c字符的数量,因此本轮移除c字符是超标部分,不会还原total
        if count.get(remove) is not None:
            if count[remove] >= 0:
                total += 1
            count[remove] += 1

        if count.get(add) is not None:
            if count[add] > 0:
                total -= 1
            count[add] -= 1

        if total == 0:
            return i

    return -1


# 算法调用
print(getResult())

C算法源码

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

#define MAX_LEN 100000

int getResult(char *str1, char *str2);

int main() {
    char str1[MAX_LEN], str2[MAX_LEN];
    scanf("%s %s", str1, str2);

    printf("%dn", getResult(str1, str2));

    return 0;
}

int getResult(char *str1, char *str2) {
    // count用于统计str1中各字符的数量
    int count[128] = {0};

    for (int i = 0; i < strlen(str1); i++) {
        char c = str1[i];
        count[c]++;
    }

    // total统计str1总共字符个数
    int total = strlen(str1);

    // 初始滑窗,滑窗范围内容,就是一个子串
    for (int i = 0; i < strlen(str1); i++) {
        // 滑窗子串新增的字符
        char add = str2[i];

        // 如果新增字符是str1的字符,即count[add] > 0时,则说明滑窗子串已找到一个目标字符,此时剩余add字符数量count[add]--,剩余目标字符总数total--
        if (count[add]-- > 0) {
            total--;
        }
    }

    // 如果total == 0,则说明滑窗内所有字符都是str1内的字符,由于限定了滑窗的长度就是str1的长度,因此滑窗内字符和str1完全匹配
    if (total == 0) return 0;

    // 下一个滑窗范围是 i ~ i + str1.length() - 1
    for (int i = 1; i + strlen(str1) - 1 < strlen(str2); i++) {
        // 相较于上一个滑窗失去的字符remove
        char remove = str2[i - 1];
        // 相较于上一个滑窗新增的字符add
        char add = str2[i + strlen(str1) - 1];

        // 本轮滑窗remove字符,在上一轮是被add的字符,我们假设此字符为c,此时可以分两种情况讨论:
        // 1、c是str1的字符,则初始统计时count[c]>0,上一轮滑窗加入c字符,则count[c]--,此轮count[c]是有可能>=0或者<0的,
        //    1.1、如果本轮count[c]>=0,则说明上轮滑窗并没有找到所有的c字符,因此本轮移除的c字符可以还原total数量
        //
        // 1.2、如果本轮count[c]<0,这说明上轮滑窗内的c字符超标了,即c字符是目标字符,但是滑窗内包含的c字符数量超过了str1中c字符的数量,因此本轮移除c字符是超标部分,不会还原total
        // 2、c不是str1的字符,则初始统计时count[c]==0,上一轮滑窗加入c字符,则count[c]--,此轮必然count[c]<0,因此c字符的移除,并不会还原total
        if (count[remove]++ >= 0) {
            total++;
        }

        if (count[add]-- > 0) {
            total--;
        }

        if (total == 0) {
            return i;
        }
    }

    return -1;
}

免责声明:

1、IT资源小站为非营利性网站,全站所有资料仅供网友个人学习使用,禁止商用
2、本站所有文档、视频、书籍等资料均由网友分享,本站只负责收集不承担任何技术及版权问题
3、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除下载链接并致以最深的歉意
4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
5、一经注册为本站会员,一律视为同意网站规定,本站管理员及版主有权禁止违规用户
6、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和IT资源小站的同意
7、IT资源小站管理员和版主有权不事先通知发贴者而删除本文

0

评论0

站点公告

没有账号?注册  忘记密码?