题目大意是传入一个字符串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代码:

  1 package cn.dalt.leetcode;
  2
  3 import org.hibernate.internal.util.ValueHolder;
  4
  5 import java.util.*;
  6
  7 /**
  8  * Created by dalt on 2017/6/22.
  9  */
 10 public class SubstringwithConcatenationofAllWords {
 11     private static final class Substring {
 12         private char[] data;
 13         private int from;
 14         private int length;
 15
 16         public Substring(char[] data, int from, int length) {
 17             this.data = data;
 18             this.from = from;
 19             this.length = length;
 20         }
 21
 22         public Substring substring(int from, int length) {
 23             return new Substring(data, this.from + from, length);
 24         }
 25
 26         Integer cachedHashCode;
 27
 28         @Override
 29         public int hashCode() {
 30             if (cachedHashCode == cachedHashCode) {
 31                 int value = 0;
 32                 for (int i = from, bound = from + length; i < bound; i++) {
 33                     value = (value << 5) - value + data[i];
 34                 }
 35                 cachedHashCode = Integer.valueOf(value);
 36             }
 37             return cachedHashCode.intValue();
 38         }
 39
 40         public char charAt(int i) {
 41             return data[i + from];
 42         }
 43
 44         public int size() {
 45             return length;
 46         }
 47
 48         @Override
 49         public boolean equals(Object obj) {
 50             if (obj == null)
 51                 return false;
 52             if (obj.getClass() != Substring.class)
 53                 return false;
 54             Substring other = (Substring) obj;
 55             if (hashCode() != other.hashCode() || length != other.length)
 56                 return false;
 57             for (int i = 0; i < length; i++) {
 58                 if (charAt(i) != other.charAt(i))
 59                     return false;
 60             }
 61             return true;
 62         }
 63
 64         @Override
 65         public String toString() {
 66             return String.valueOf(data, from, length);
 67         }
 68     }
 69
 70     private static final class IntHolder {
 71         private int value;
 72         private int storedValue;
 73
 74         public IntHolder(int initValue) {
 75             value = initValue;
 76         }
 77
 78         public void inc() {
 79             value++;
 80         }
 81
 82         public void dec() {
 83             value--;
 84         }
 85
 86         public void store() {
 87             storedValue = value;
 88         }
 89
 90         public void restore() {
 91             value = storedValue;
 92         }
 93
 94         public int getValue() {
 95             return value;
 96         }
 97
 98         @Override
 99         public int hashCode() {
100             return value;
101         }
102
103         @Override
104         public String toString() {
105             return value + "(" + storedValue + ")";
106         }
107
108         @Override
109         public boolean equals(Object obj) {
110             if (obj == null)
111                 return false;
112             if (obj.getClass() == IntHolder.class) {
113                 return ((IntHolder) obj).value == value;
114             }
115             return false;
116         }
117     }
118
119     public List<Integer> findSubstring(String s, String[] words) {
120         if (words.length == 0) {
121             List<Integer> result = new ArrayList<>(s.length());
122             for (int i = 0, bound = s.length(); i < bound; i++) {
123                 result.add(Integer.valueOf(i));
124             }
125             return result;
126         }
127         int m = s.length();
128         int n = words[0].length();
129         int k = words.length;
130
131         Map<Substring, IntHolder> map = new HashMap<>(k);
132         for (String word : words) {
133             Substring pack = new Substring(word.toCharArray(), 0, word.length());
134             IntHolder holder = map.get(pack);
135             if (holder == null) {
136                 holder = new IntHolder(0);
137                 map.put(pack, holder);
138             }
139             holder.inc();
140         }
141
142         List<IntHolder> holders = new ArrayList<IntHolder>(map.values());
143         for (IntHolder holder : holders) {
144             holder.store();
145         }
146         List<Integer> result = new LinkedList<>();
147         char[] sarray = s.toCharArray();
148         for (int i = 0; i < n; i++) {
149             for (IntHolder holder : holders) {
150                 holder.restore();
151             }
152             int remain = words.length;
153             for (int j = i; j < m; j = j + n) {
154                 int start = j - n * k;
155                 int end = j;
156                 if (start >= 0) {
157                     Substring sub = new Substring(sarray, start, n);
158                     IntHolder times = map.get(sub);
159                     if (times != null) {
160                         times.inc();
161                         if (times.getValue() > 0) {
162                             remain++;
163                         }
164                     }
165                 }
166                 if (end + n <= m) {
167                     Substring sub = new Substring(sarray, end, n);
168                     IntHolder times = map.get(sub);
169                     if (times != null) {
170                         times.dec();
171                         if (times.getValue() >= 0) {
172                             remain--;
173                         }
174                     }
175                 }
176                 if (remain == 0) {
177                     result.add(start + n);
178                 }
179             }
180         }
181         return result;
182     }
183 }

View Code

转载于:https://www.cnblogs.com/dalt/p/7066990.html

Leetcode:Substring with Concatenation of All Words分析和实现相关推荐

  1. LeetCode 30. Substring with Concatenation of All Words

    LeetCode 30. Substring with Concatenation of All Words Solution1: 转载自:http://www.cnblogs.com/grandya ...

  2. LeetCode——1834. 单线程 CPU(Single-Threaded CPU)[中等]——分析及代码(Java)

    LeetCode--1834. 单线程 CPU[Single-Threaded CPU][中等]--分析及代码[Java] 一.题目 二.分析及代码 1. 优先队列 (1)思路 (2)代码 (3)结果 ...

  3. LeetCode——223. 矩形面积(Rectangle Area)[中等]——分析及代码(C++)

    LeetCode--223. 矩形面积[Rectangle Area][中等]--分析及代码[C++] 一.题目 二.分析及代码 1. 几何计算 (1)思路 (2)代码 (3)结果 三.其他 一.题目 ...

  4. LeetCode——765. 情侣牵手(Couples Holding Hands)——分析及代码(Java)

    LeetCode--765. 情侣牵手[Couples Holding Hands]--分析及代码[Java] 一.题目 二.分析及代码 1. 并查集 (1)思路 (2)代码 (3)结果 三.其他 一 ...

  5. leetcode 30. Substring with Concatenation of All Words 与所有单词相关联的字串 滑动窗口法

    题目描述 给定一个字符串 s 和一些长度相同的单词 words.在 s 中找出可以恰好串联 words 中所有单词的子串的起始位置. You are given a string, s, and a ...

  6. 【leetcode】30. Substring with Concatenation of All Words

    题目如下: 解题思路:本题题干中有一个非常关键的前提,就是words中的所有word的长度一样,并且都要被使用到.所以,我们可以把输入的s按word的长度进行等分,以s = "barfoot ...

  7. **LeetCode 30. Substring with Concatenation of All Words

    https://leetcode.com/problems/substring-with-concatenation-of-all-words/ 滑动窗口法.解析看这里: http://www.2ct ...

  8. 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. ...

  9. 030 Substring with Concatenation of All Words 与所有单词相关联的字串

    给定一个字符串 s 和一些长度相同的单词 words,找出 s 与 words 中所有单词(words 每个单词只出现一次)串联一起(words中组成串联串的单词的顺序随意)的字符串匹配的所有起始索引 ...

最新文章

  1. 用python结束exe进程
  2. 量子计算机新科技未来,能够“预测多个未来”的量子计算机诞生
  3. 《Java8实战》笔记(15):面向对象和函数式编程的混合-Java 8和Scala的比较
  4. c语言指针灵活性管窥
  5. 如何改变对话或窗体视窗的背景颜色
  6. gRPC入门教程汇总
  7. Redis(一)面试总结精讲
  8. vue将经纬度转换成地理名称_新武汉北,红安有了一个新的地理名称,恒大项目将对标上海迪士尼...
  9. 自定义View(四) ViewGroup 动态添加变长Tag标签 支持自动换行
  10. 1 linux中解决文件已rm删除但空间不释放的案例
  11. C++实现多级时间轮定时器
  12. flex-builder编译方法
  13. linux 查看java_opts_Linux Tomcat 设置 JAVA_OPTS 异常
  14. html 输入框 大于0,【前端】input输入框只能输入大于等于0的正数
  15. Android动画完全解析--属性动画
  16. 设置HTML为桌面壁纸
  17. office之word开启参考线对齐
  18. 将Android项目发布到Jcenter
  19. UltraISO制作大于4G文件的光盘映像可启动U盘
  20. 字节面试:谈谈索引为什么能提高查询性能?

热门文章

  1. 【深度学习】CNN在大规模图像数据集上的应用(基于keras和MNIST)
  2. java chunked 解码_模拟http请求 带 chunked解析办法一
  3. 驱动人生(离线网卡版)_驱动人生8.0版正式发布,最新功能速看
  4. 哪个厂家的监控平台用的云服务器_哪个品牌的云服务器最好用?
  5. 如何优化才能赢得搜索引擎“欢心”,提升抓取量?
  6. 企业网络推广专员浅析不同阶段下要有不同的企业网络推广方式
  7. 浅析网站域名申请注册的四种常见方式
  8. java已知一个二叉树_Day58:对称的二叉树
  9. android 内置app,android9.0内置APP
  10. JAVA中方法的类型转换_Java中几种常用数据类型之间转换的方法