LeetCode 第 30 题:串联所有单词的子串

题目:

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

在这道题中,你需要在一个字符串中找到所有子串,这些子串由一组单词的串联组成,每个单词的长度相同。下面是一个 Java 的解题示例,带有详细的注释:

import java.util.*;

public class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        // 初始化返回结果列表
        List<Integer> result = new ArrayList<>();
        
        // 检查输入字符串和单词数组是否为空
        if (s == null || s.length() == 0 || words == null || words.length == 0) {
            return result; // 如果任何一个为空,返回空结果
        }
        
        // 每个单词的长度
        int wordLength = words[0].length();
        // 单词的总数量
        int wordCount = words.length;
        // 所有单词连接后的总长度
        int totalLength = wordLength * wordCount;
        
        // 如果字符串长度小于所有单词的总长度,直接返回结果
        if (s.length() < totalLength) {
            return result;
        }
        
        // 统计每个单词出现的次数
        Map<String, Integer> wordMap = new HashMap<>();
        for (String word : words) {
            wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);
        }
        
        // 遍历起始点,最多 wordLength 次,以处理不同的偏移量
        for (int i = 0; i < wordLength; i++) {
            // 初始化滑动窗口
            int left = i; // 左指针,初始化为当前偏移量
            int count = 0; // 当前窗口中匹配的单词数量
            Map<String, Integer> seenWords = new HashMap<>(); // 当前窗口内的单词计数
            
            // 从起始点开始,每次移动一个单词长度
            for (int j = i; j <= s.length() - wordLength; j += wordLength) {
                // 提取当前单词
                String word = s.substring(j, j + wordLength);
                
                // 检查单词是否在字典中
                if (wordMap.containsKey(word)) {
                    // 更新当前窗口内的单词出现次数
                    seenWords.put(word, seenWords.getOrDefault(word, 0) + 1);
                    count++; // 增加匹配到的单词数量
                    
                    // 如果当前单词出现次数超过需要的次数
                    while (seenWords.get(word) > wordMap.get(word)) {
                        // 移动左指针,缩小窗口
                        String leftWord = s.substring(left, left + wordLength);
                        seenWords.put(leftWord, seenWords.get(leftWord) - 1);
                        count--; // 减少匹配的单词数量
                        left += wordLength; // 移动左指针
                    }
                    
                    // 如果窗口内的单词数与 wordCount 相同,记录结果
                    if (count == wordCount) {
                        result.add(left);
                    }
                } else {
                    // 如果遇到不在字典中的单词,重置窗口
                    seenWords.clear();
                    count = 0;
                    left = j + wordLength; // 移动左指针到当前单词后
                }
            }
        }
        
        return result; // 返回所有符合条件的起始索引
    }
}

详细说明:

  1. 输入检查:首先检查输入字符串和单词数组是否有效。如果无效,立即返回。

  2. 初始化变量

    • wordLength:每个单词的长度。
    • wordCount:单词的总数。
    • totalLength:所有单词连接后的总长度。
  3. 单词计数:使用 HashMap 记录每个单词在数组中的出现次数。

  4. 多次起始点遍历:从 0 到 wordLength - 1 共进行 wordLength 次遍历,以处理不同的偏移量。

  5. 滑动窗口技巧:使用 left 和 j 指针来动态维护一个窗口,seenWords 用来记录窗口内的单词出现次数。

  6. 窗口调整:如果窗口内某个单词出现次数超过需要,则通过移动 left 指针来缩小窗口。

  7. 结果记录:如果窗口内的单词数与单词数组中的单词数相同,则将当前窗口的起始位置 left 加入结果。

这种方法通过更高效地管理窗口的内容来减少重复计算,提高了解题效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值