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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值