面试问题 - 根据规则将文本拆分为子字符串

时间:2010-07-11 13:32:52

标签: algorithm

根据以下规则将文本拆分为子字符串:

  • a)每个子串的长度应小于或等于M
  • b)如果子字符串包含任何数字字符,则子字符串的长度应小于或等于N(N
  • c)子串的总数应尽可能小

我不知道如何解决这个问题,我想这与“动态编程”有关。 任何人都可以帮我用C#或Java实现它吗?非常感谢。

3 个答案:

答案 0 :(得分:11)

贪婪的方法是要走的路:

  • 如果当前文字为空,则表示您已完成。
  • 取前N个字符。如果它们中的任何一个是数字,那么这是一个新的子字符串。把它砍掉然后开始。
  • 否则,将无数字段扩展到最多M个字符。这是一个新的子字符串。把它砍掉然后开始。

证明

这是一个reductio-ad-absurdum证明,以上产生了最佳解决方案。 假设有比贪婪分裂更好的分裂。让我们跳到两个分裂开始不同的点,并在此之前删除所有内容。

案例1)前N个字符中的一个数字。

假设有一个输入,切断前N个字符无法产生最佳解决方案。

Greedy split:   |--N--|...
A better split: |---|--...
                      ^
                      +---- this segment can be shortened from the left side

然而,推定的更好的解决方案的第二部分可以始终从左侧缩短,第一部分扩展到N个字符,而不改变段的数量。因此,矛盾:这种分裂并不比贪婪的分裂更好。

情况2)第一个K(N

假设有一个输入,切断前K个字符无法产生最佳解决方案。

Greedy split:   |--K--|...
A better split: |---|--...
                      ^
                      +---- this segment can be shortened from the left side

同样,“更好”的分裂可以在不改变分段数的情况下转换为贪婪分割,这与最初的假设相矛盾,即最好的分裂比贪婪分裂更好。

因此,贪婪的分裂是最佳的。 Q.E.D。

实施(Python)

import sys

m, n, text = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]
textLen, isDigit = len(text), [c in '0123456789' for c in text]

chunks, i, j = [], 0, 0
while j < textLen:
   i, j = j, min(textLen, j + n) 
   if not any(isDigit[i:j]):
      while j < textLen and j - i < m and not isDigit[j]:
         j += 1
   chunks += [text[i:j]]
print chunks

实施(Java)

public class SO {
   public List<String> go(int m, int n, String text) {
      if (text == null)
         return Collections.emptyList();
      List<String> chunks = new ArrayList<String>();

      int i = 0;
      int j = 0;
      while (j < text.length()) {
         i = j;         
         j = Math.min(text.length(), j + n);
         boolean ok = true;
         for (int k = i; k < j; k++) 
            if (Character.isDigit(text.charAt(k))) {
               ok = false;              
               break;                   
            }                   
         if (ok)        
            while (j < text.length() && j - i < m && !Character.isDigit(text.charAt(j)))
               j++;                     
         chunks.add(text.substring(i, j));
      }         
      return chunks;
   }    

   @Test
   public void testIt() {
      Assert.assertEquals(
         Arrays.asList("asdas", "d332", "4asd", "fsdxf", "23"),
         go(5, 4, "asdasd3324asdfsdxf23"));
   }    
}

答案 1 :(得分:4)

Bolo 在他的回答中提供了一个贪婪的算法,并要求一个反例。嗯,没有反例,因为这是完全正确的方法。这是证明。虽然它有点罗嗦,但证据通常比算法本身更长:)

让我们假设我们输入了长度为L并使用我们的算法构建了答案A。现在,假设有更好的答案B。即,B的分段数少于A

比方说,A中的第一个段的长度为laB - lbla&gt; = lb因为我们选择了A中的第一个段,以获得最大可能的长度。如果lb&lt; la,我们可以在B中增加第一个细分的长度,而不会增加B中细分的总数。它会为我们提供一些其他最佳解决方案B',与A具有相同的第一个分段。

现在,从AB'删除第一个细分,然后重复长度L'&lt; L。直到没有任何片段为止。这意味着,答案A等于某种最佳解决方案。

答案 2 :(得分:0)

计算的结果将是将给定文本划分为包含数字和不包含数字的长子串的短子字符串。 (你已经知道了很多)。

你将基本上分割数字周围的短子,然后根据需要经常将其他所有内容分解为长子,以满足长度标准。

您的自由,即您可以操作以改善结果的方式,就是选择要包含数字的字符。如果N = 3,则对于每个数字,您可以选择XXNXNXNXX。如果M是5并且你的第一个数字之前有6个字符,那么你需要在短子中包含至少一个这样的字符,这样你就不会在你的“short”左边有两个“长”字符串“当你只有一个而不是一个。

作为第一个近似值,我会将你的“短”字符串向左延伸到足以避免多余的“长”字符串。这是一种典型的“贪婪”方法,贪婪方法通常会产生最佳或几乎最佳的结果。要想做得更好也不容易,我不打算弄清楚如何去做。