题目大意是传入一个字符串s和一个字符串数组words,其中words中的所有字符串均等长。要在s中找所有的索引index,使得以s[index]为起始字符的长为words中字符串总长的s的子串是由words中的所有字符串(每个出现一次)拼接而成。


这个题目有点恶趣味,而且也很难找到特别有效的优化方案。下面说说我的思路:

首先记s的长度为m,而words的长度为k,words中字符串的长度为n。显然当n*k>m时只需要返回一个空集即可,因此可以认为n*k<=m。

首先我们将所有的words中的元素插入到红黑树中,并将words[i]映射为words[i]被插入的次数(即words[i]在words中出现的个数)。这所花费的时间为插入比较次数*每次比较时间=O(log2(k))*n=O(nlog2(k))。现在我们所需的数据结构也就建立完毕了。

map = empty-red-black-tree

for(i = 0; i < words.length; i = i + 1)

  times = map.get(words[i])

  if times ==  NIL

    times = 0

  times = times + 1

  map.put(words[i], times)

接着我们需要利用一种有趣的想法。先以0~nk作为起始匹配子串,并每次向右移动k位,即下一次判断k~(n+1)k是否可行。同时用一个remain变量记录尚未被匹配的words中字符串的数目。一旦remain为0,则意味着当前扫描的子串出现了所有words中的字符串。并且次数map中记录的映射值应该表示当前扫描的字串中对于各个字符串所缺少出现的次数。

每次子串右移k位时,我们只需要做类似移除头部k个字符所代表的字符串并加入后续k个字符所代表字符串的工作。移除首部长为k的子串,需要修改map中的值,其费用为O(nlog2(k)),而插入尾部长为k的子串,同样需要修改map中的值,其费用同样为O(nlog2(k))。故一次右移动所花费的总时间为O(nlog2(k)),还有一些O(1)的简单操作,比如修改remain的值,以及判断当前子串是否满足要求。

在子串尾部触及s的结尾时,停止当前循环。并展开下一次循环,其以1~nk+1作为起始匹配子串。外部循环的结束条件是t~nk+t的起始情况都已经被考虑过了,其中0<=t<n。这样我们就扫描了所有有可能满足条件的s的子串,分别以0,1,...,m-nk作为起始。按照前面所说对map的操作发生的次数应该为m-nk,但是实际上对map的操作发生的次数应该是m次,不要忘记了对起始子串也存在着修正map和remain的工作,其次还存在着恢复map和remain的操作时间,为k次。因此上面的循环总共的时间复杂度为O(m+k)*O(nlog2(k))=O((m+k)nlog2(k))=O(mnlog2(k))。

remove(subs)  //从当前扫描子串中移除n长字符串subs

  times = map.get(subs)

  if times == NIL

    return

  times = times+1

  map.put(subs, times)

  if(times > 0)

    remain = remain+1

append(subs) //向当前扫描子串中加入n长字符串subs

  times = map.get(subs)

  if times == NIL

    return

  times = times-1

  map.put(subs, times)

  if(times >= 0)

    remain = remain+1

.............

result = empty-list

remain = n

for(i = 0; i < n; i = i+1)

  for(j = i; j < m; j = j+n)

    start = j - n*k

    end = j

    if(start >= 0)

      startStr = s.substring(start, start  + n)

      remove(startStr)

    if(end + k <= m)

      endStr = s.substring(end, end + n)

      append(endStr)

    if(remain == 0)

      result.add(begin+n)

  reset map values and remain

这就是上述想法的实现代码。综合初始化操作,总的时间复杂度为O(nlog2(k))+O(mnlog2(k))=O(mnlog2(k))<O(m^2)。

稍微说一下优化前面一些步骤的想法,首先从map中取值修改后插回map的操作,可以利用一个包装器包装整数,之后取回后只需修改包装器中的整数,而不需重新插回。还有就是substring的算法,也可以利用包装器来直接包装s,并限定其有效范围,这样就可以将创建子字符串的费用优化到O(1)。当然这些优化的都不是必须的,因为它们是否优化都无法改变整体的时间复杂度。而整体的时间复杂度取决于利用字符串向map取值操作上,利用散列以及缓存散列码的技术可以真正对上述过程产生优化。假如散列足够优化,即所有的字符串都会被散列到不同的槽中,那么为所有字符串计算散列码的时间复杂度为O(n*k)+O(m*n)=O(mn),而每次取值并插回的时间复杂度为O(n),而计算返回值时的双重循环共执行m次,故时间复杂度为O(n)*O(m)=O(nm),因此结果的时间复杂度为O(mn)+O(nm)=O(mn),当然这只是理想状态而已。

散列表中有一种变形,称为完全散列,其在最坏情况下依旧有着O(1)的查询时间复杂度。有兴趣的童鞋可以去自己去找找资料,利用完全散列就可以保证不同的字符串被散列到不同的槽中。上面所提及的优化也就有可能实现了。


最后提供AC代码:

 package cn.dalt.leetcode;

 import org.hibernate.internal.util.ValueHolder;

 import java.util.*;

 /**
  * Created by dalt on 2017/6/22.
  */
 public class SubstringwithConcatenationofAllWords {
     private static final class Substring {
         private char[] data;
         private int from;
         private int length;

         public Substring(char[] data, int from, int length) {
             this.data = data;
             this.from = from;
             this.length = length;
         }

         public Substring substring(int from, int length) {
             return new Substring(data, this.from + from, length);
         }

         Integer cachedHashCode;

         @Override
         public int hashCode() {
             if (cachedHashCode == cachedHashCode) {
                 int value = 0;
                 for (int i = from, bound = from + length; i < bound; i++) {
                     value = (value << 5) - value + data[i];
                 }
                 cachedHashCode = Integer.valueOf(value);
             }
             return cachedHashCode.intValue();
         }

         public char charAt(int i) {
             return data[i + from];
         }

         public int size() {
             return length;
         }

         @Override
         public boolean equals(Object obj) {
             if (obj == null)
                 return false;
             if (obj.getClass() != Substring.class)
                 return false;
             Substring other = (Substring) obj;
             if (hashCode() != other.hashCode() || length != other.length)
                 return false;
             for (int i = 0; i < length; i++) {
                 if (charAt(i) != other.charAt(i))
                     return false;
             }
             return true;
         }

         @Override
         public String toString() {
             return String.valueOf(data, from, length);
         }
     }

     private static final class IntHolder {
         private int value;
         private int storedValue;

         public IntHolder(int initValue) {
             value = initValue;
         }

         public void inc() {
             value++;
         }

         public void dec() {
             value--;
         }

         public void store() {
             storedValue = value;
         }

         public void restore() {
             value = storedValue;
         }

         public int getValue() {
             return value;
         }

         @Override
         public int hashCode() {
             return value;
         }

         @Override
         public String toString() {
             return value + "(" + storedValue + ")";
         }

         @Override
         public boolean equals(Object obj) {
             if (obj == null)
                 return false;
             if (obj.getClass() == IntHolder.class) {
                 return ((IntHolder) obj).value == value;
             }
             return false;
         }
     }

     public List<Integer> findSubstring(String s, String[] words) {
         if (words.length == 0) {
             List<Integer> result = new ArrayList<>(s.length());
             for (int i = 0, bound = s.length(); i < bound; i++) {
                 result.add(Integer.valueOf(i));
             }
             return result;
         }
         int m = s.length();
         int n = words[0].length();
         int k = words.length;

         Map<Substring, IntHolder> map = new HashMap<>(k);
         for (String word : words) {
             Substring pack = new Substring(word.toCharArray(), 0, word.length());
             IntHolder holder = map.get(pack);
             if (holder == null) {
                 holder = new IntHolder(0);
                 map.put(pack, holder);
             }
             holder.inc();
         }

         List<IntHolder> holders = new ArrayList<IntHolder>(map.values());
         for (IntHolder holder : holders) {
             holder.store();
         }
         List<Integer> result = new LinkedList<>();
         char[] sarray = s.toCharArray();
         for (int i = 0; i < n; i++) {
             for (IntHolder holder : holders) {
                 holder.restore();
             }
             int remain = words.length;
             for (int j = i; j < m; j = j + n) {
                 int start = j - n * k;
                 int end = j;
                 if (start >= 0) {
                     Substring sub = new Substring(sarray, start, n);
                     IntHolder times = map.get(sub);
                     if (times != null) {
                         times.inc();
                         if (times.getValue() > 0) {
                             remain++;
                         }
                     }
                 }
                 if (end + n <= m) {
                     Substring sub = new Substring(sarray, end, n);
                     IntHolder times = map.get(sub);
                     if (times != null) {
                         times.dec();
                         if (times.getValue() >= 0) {
                             remain--;
                         }
                     }
                 }
                 if (remain == 0) {
                     result.add(start + n);
                 }
             }
         }
         return result;
     }
 }

Leetcode:Substring with Concatenation of All Words分析和实现的更多相关文章

  1. LeetCode: Substring with Concatenation of All Words 解题报告

    Substring with Concatenation of All Words You are given a string, S, and a list of words, L, that ar ...

  2. [LeetCode] Substring with Concatenation of All Words 串联所有单词的子串

    You are given a string, s, and a list of words, words, that are all of the same length. Find all sta ...

  3. LeetCode:Substring with Concatenation of All Words (summarize)

    题目链接 You are given a string, S, and a list of words, L, that are all of the same length. Find all st ...

  4. [leetcode]Substring with Concatenation of All Words @ Python

    原题地址:https://oj.leetcode.com/problems/substring-with-concatenation-of-all-words/ 题意: You are given a ...

  5. Leetcode Substring with Concatenation of All Words

    You are given a string, S, and a list of words, L, that are all of the same length. Find all startin ...

  6. [LeetCode] Substring with Concatenation of All Words(good)

    You are given a string, S, and a list of words, L, that are all of the same length. Find all startin ...

  7. LeetCode()Substring with Concatenation of All Words 为什么我的超时呢?找不到原因了!!!

    超时代码 class Solution { public: vector<int> findSubstring(string s, vector<string>& wo ...

  8. LeetCode HashTable 30 Substring with Concatenation of All Words

    You are given a string, s, and a list of words, words, that are all of the same length. Find all sta ...

  9. leetcode面试准备: Substring with Concatenation of All Words

    leetcode面试准备: Substring with Concatenation of All Words 1 题目 You are given a string, s, and a list o ...

随机推荐

  1. RAD Studio Mobile Roadmap updated,XE5 will released on next month, Andriod will be supported.

    RAD Studio Mobile Roadmap updated   Embarcadero updated his RAD Studio Mobile Roadmap. This concern ...

  2. 用java网络编程中的TCP方式上传文本文件及出现的小问题

    自己今天刚学java网络编程中的TCP传输,要用TCP传输文件时,自己也是遇到了一些问题,抽空把它整理了一下,供自己以后参考使用. 首先在这个程序中,我用一个客户端,一个服务端,从客户端上传一个文本文 ...

  3. 定时框架quartz的一些问题总结

    1 什么是Quartz Quartz是OpenSymphony开源组织在Job scheduling领域的开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单 ...

  4. 接口测试基础——第4篇logging模块

    Logging:日志记录是为了跟踪记录软件运行时,发生的事件,包括出错,提示信息等等. log日志级别:日志级别大小关系为:CRITICAL > ERROR > WARNING > ...

  5. SCARA——OpenGL入门学习四(颜色)

    OpenGL入门学习[四] 本次学习的是颜色的选择.终于要走出黑白的世界了~~ OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. 无论哪种颜色模式,计算机都必须为每一个像素保存一些数 ...

  6. CLR值类型和引用类型

    知识点:引用类型.值类型.装箱.拆箱 CLR支持两种类型:引用类型和值类型.引用类型在堆上分配内存,值类型在线程栈上分配内存.值类型与引用类型如下所示: 值类型对象有两种表示形式:未装箱和已装箱.将一 ...

  7. 调用 SSPI 失败,请参见内部异常 解决方法

    2017-11-12 12:49:53:706] OnServerConnectionAvailable error : System.Security.Authentication.Authenti ...

  8. 使用appassembler-maven-plugin插件生成启动脚本

    appassembler-maven-plugin可以自动生成跨平台的启动脚本,省去了手工写脚本的麻烦,而且还可以生成jsw的后台运行程序. 首先pom引入相关依赖 <build> < ...

  9. 前端mvc mvp mvvm 架构介绍(vue重构项目一)

    首先 我们为什么重构这个项目 1:我们现有的技术是前后台不分离,页面上采用esayUI+jq构成的单页面,每个所谓的单页面都是从后台胜场的唯一Id 与前端绑定,即使你找到了那个页面元素,也找不到所在的 ...

  10. dx工具(android将jar包转成dex格式二进制jar包工具)

    博客分类: android 时钟 dx工具二进制jar包  好吧,不得不承认这个工具真心难找,也不知道自己sdk里以前怎么就有了,还好给了师傅一份,现在重装系统从网上找这个工具都找不到. 将platf ...