Leetcode:Substring with Concatenation of All Words分析和实现
题目大意是传入一个字符串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分析和实现相关推荐
- LeetCode 30. Substring with Concatenation of All Words
LeetCode 30. Substring with Concatenation of All Words Solution1: 转载自:http://www.cnblogs.com/grandya ...
- LeetCode——1834. 单线程 CPU(Single-Threaded CPU)[中等]——分析及代码(Java)
LeetCode--1834. 单线程 CPU[Single-Threaded CPU][中等]--分析及代码[Java] 一.题目 二.分析及代码 1. 优先队列 (1)思路 (2)代码 (3)结果 ...
- LeetCode——223. 矩形面积(Rectangle Area)[中等]——分析及代码(C++)
LeetCode--223. 矩形面积[Rectangle Area][中等]--分析及代码[C++] 一.题目 二.分析及代码 1. 几何计算 (1)思路 (2)代码 (3)结果 三.其他 一.题目 ...
- LeetCode——765. 情侣牵手(Couples Holding Hands)——分析及代码(Java)
LeetCode--765. 情侣牵手[Couples Holding Hands]--分析及代码[Java] 一.题目 二.分析及代码 1. 并查集 (1)思路 (2)代码 (3)结果 三.其他 一 ...
- leetcode 30. Substring with Concatenation of All Words 与所有单词相关联的字串 滑动窗口法
题目描述 给定一个字符串 s 和一些长度相同的单词 words.在 s 中找出可以恰好串联 words 中所有单词的子串的起始位置. You are given a string, s, and a ...
- 【leetcode】30. Substring with Concatenation of All Words
题目如下: 解题思路:本题题干中有一个非常关键的前提,就是words中的所有word的长度一样,并且都要被使用到.所以,我们可以把输入的s按word的长度进行等分,以s = "barfoot ...
- **LeetCode 30. Substring with Concatenation of All Words
https://leetcode.com/problems/substring-with-concatenation-of-all-words/ 滑动窗口法.解析看这里: http://www.2ct ...
- 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. ...
- 030 Substring with Concatenation of All Words 与所有单词相关联的字串
给定一个字符串 s 和一些长度相同的单词 words,找出 s 与 words 中所有单词(words 每个单词只出现一次)串联一起(words中组成串联串的单词的顺序随意)的字符串匹配的所有起始索引 ...
最新文章
- 用python结束exe进程
- 量子计算机新科技未来,能够“预测多个未来”的量子计算机诞生
- 《Java8实战》笔记(15):面向对象和函数式编程的混合-Java 8和Scala的比较
- c语言指针灵活性管窥
- 如何改变对话或窗体视窗的背景颜色
- gRPC入门教程汇总
- Redis(一)面试总结精讲
- vue将经纬度转换成地理名称_新武汉北,红安有了一个新的地理名称,恒大项目将对标上海迪士尼...
- 自定义View(四) ViewGroup 动态添加变长Tag标签 支持自动换行
- 1 linux中解决文件已rm删除但空间不释放的案例
- C++实现多级时间轮定时器
- flex-builder编译方法
- linux 查看java_opts_Linux Tomcat 设置 JAVA_OPTS 异常
- html 输入框 大于0,【前端】input输入框只能输入大于等于0的正数
- Android动画完全解析--属性动画
- 设置HTML为桌面壁纸
- office之word开启参考线对齐
- 将Android项目发布到Jcenter
- UltraISO制作大于4G文件的光盘映像可启动U盘
- 字节面试:谈谈索引为什么能提高查询性能?
热门文章
- 【深度学习】CNN在大规模图像数据集上的应用(基于keras和MNIST)
- java chunked 解码_模拟http请求 带 chunked解析办法一
- 驱动人生(离线网卡版)_驱动人生8.0版正式发布,最新功能速看
- 哪个厂家的监控平台用的云服务器_哪个品牌的云服务器最好用?
- 如何优化才能赢得搜索引擎“欢心”,提升抓取量?
- 企业网络推广专员浅析不同阶段下要有不同的企业网络推广方式
- 浅析网站域名申请注册的四种常见方式
- java已知一个二叉树_Day58:对称的二叉树
- android 内置app,android9.0内置APP
- JAVA中方法的类型转换_Java中几种常用数据类型之间转换的方法