leetcode-串联所有单词子串

在leetcode做的第二道困难题,题目要求: 给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1 
输入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoor" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:
输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]
复制代码

解题思路

(1)记录给定单词出现的次数,因为给定的单词可能有重复的,所以需要先记录出现次数,在之后有用。

Map<String, Integer> counts = new HashMap<>(words.length);
for(int i = 0; i < words.length; i++){
    counts.put(words[i], counts.getOrDefault(words[i], 0) + 1);
}
复制代码

(2)记录完成后就可以遍历字符串了:因为给的每一个单词长度相等,可以利用单词长度和单词数量创建一个窗口,在窗口中寻找单词,之后再向后滑动窗口。

可以定义一个窗口最左端为 l = 0
定义最右端为 r = l + 单词长度 * 单词数量,并且 r < 字符串长度
然后l++
我是这样实现的,只需要定义一个窗口最左端,最右端在循环中计算出来再使用。
for(int i = 0; i < s.length()-num*len+1; i++)
复制代码

(3)创建好窗口之后就从最左端开始向右截取字符串,截取长度为单词长度,截取字符串在第一步的记录中存在的话我们就将他记录下来,不存在的话就向后滑动窗口。

(4)因为可能有重复的单词或者窗口中有重复的字串,所以我们需要记录窗口中字串出现的次数,若窗口中出现字串的次数比我们第一步中的多的话就说明必定有一个单词不再这个窗口中,所以我们消除这一次记录,向后滑动窗口继续判断。

(5)如果我们在记录窗口中字串出现次数后将窗口遍历完毕的话,说明所有单词都出现在了窗口中,所以我们记录这一次的窗口最左端。

HashMap<String, Integer> seen = new HashMap<>(words.length);
int index = 0;
while(index < num){
    String word = s.substring(i+index*len, i+(index+1)*len);
    if(counts.containsKey(word)){
        seen.put(word, seen.getOrDefault(word, 0)+1);
        if(seen.get(word) > counts.get(word))
            break;
    }else
        break;
    index++;
}
复制代码

(6)当窗口滑动至字符串最右端,程序结束,我们返回所有记录到的窗口最左端。

所有代码实现: package 算法.string;

import java.util.*;

/**
 * 我在提交时出错遇到的测试数据
 * "lingmindraboofooowingdingbarrwingmonkeypoundcake"
 * ["fooo","barr","wing","ding","wing"]
 *
 * "ababaab"
 * ["ab","ba","ba"]
 *
 * "wordgoodgoodgoodbestword"
 * ["word","good","best","good"]
 *
 * @author mojiayi
 * @date 2019-04-09 21:05
 */
public class 串联所有单词字串 {
    public static List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        if(s.length() == 0 || words.length == 0)
            return res;
        if((words.length*words[0].length()) > s.length())
            return res;
        Map<String, Integer> counts = new HashMap<>(words.length);
        for(int i = 0; i < words.length; i++){
            counts.put(words[i], counts.getOrDefault(words[i], 0) + 1);
        }

        int num = words.length, len = words[0].length();
        for(int i = 0; i < s.length()-num*len+1; i++){
            HashMap<String, Integer> seen = new HashMap<>(words.length);
            int index = 0;
            while(index < num){
                String word = s.substring(i+index*len, i+(index+1)*len);
                if(counts.containsKey(word)){
                    seen.put(word, seen.getOrDefault(word, 0)+1);
                    if(seen.get(word) > counts.get(word))
                        break;
                }else
                    break;
                index++;
            }
            if(index == num)
                res.add(i);
        }
        return res;
    }

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    String s = scanner.nextLine();
    int n = scanner.nextInt();
    String[] words = new String[n];
    for(int i = 0; i < n; i++) {
        words[i] = scanner.next();
    }
    System.out.println(findSubstring(s, words));
}
}
复制代码

转载于:https://juejin.im/post/5cadf4d8e51d456e5d3dabfa

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值