合并两个字符串并生成字典最小的合并字符串

时间:2016-06-15 17:07:57

标签: string algorithm greedy

当我遇到一个相当简单的子问题时,我正在解决问题。给定两个字符串S1S2merge(S1,S2)表示通过散布两个字符串S1S2获得的任何字符串,同时保持两个字符串中的字符顺序结果字符串在词典上最小。

示例

S1 = abad
S2 = bbde
merge(S1, S2) = ababbdde

现在,我尝试通过从字符串的第一个元素开始应用贪婪技术然后查找最小元素并将其添加到结果中来解决问题。但是,很快我发现这并不总能带来最佳解决方案。代码看起来如下所示。

int n = a.size(), m = b.size();
int i =0, j=0, k=0; char array[n+m];
for(; i< n && j<n;) {
    if(a[i] < b[j]) {
        array[k] = a[i];
        ++i;
    }
    else {
        array[k] = b[j];
        ++j;
    }
    ++k;
}

while(i<n) {
    array[k] = a[i];
    ++i;
    ++k;
}
while(j<m) {

    array[k] = b[j];
    ++j;
    ++k;
}
for (int i = 0; i < n+m; ++i) {
     cout<<array[i];
}
cout<<endl;

我想到了向后遍历并选择了最大的角色并开始从后面添加它。经过有限的测试,我表现得很好。

int n = a.size(), m = b.size();
int i =n-1, j=m-1, k=n+m-1; char array[n+m];
for(; i>=0  && j>=0;) {
    if(a[i] > b[j]) {
        array[k] = a[i];
        --i;
    }
    else {
        array[k] = b[j];
        --j;
    }
    --k;
}
while(i>=0) {
    array[k] = a[i];
    --i;
    --k;
}
while(j>=0) {
    array[k] = b[j];
    --j;
    --k;
}
for (int i = 0; i < n + m; ++i) {
    cout<<array[i];
}
cout<<endl;

但是,我不确定这是否始终能提供最佳解决方案。

这个解决方案首先是正确的吗?如果是的话,有人可以给我一个小小的证据,说明为什么这也会产生最佳解决方案。

4 个答案:

答案 0 :(得分:1)

贪婪将解决问题。要解决这个问题,你必须要访问这两个字符串。

在您的代码中,您错过了一个地方,即您的第一个for循环if(a[i] < b[j]),它应该是if(a[i] <= b[j])。 检查代码here

答案 1 :(得分:1)

这是基于我之前评论的完整解决方案。

import string
import random

global brute_force_lowest
global almost_greedy_lowest

global brute_force_calls
global almost_greedy_calls

def brute_force(p, a, b):
  global brute_force_lowest
  global brute_force_calls

  brute_force_calls += 1

  if len(a) > 0: brute_force(p + a[0], a[1:], b)
  if len(b) > 0: brute_force(p + b[0], a, b[1:])

  if len(a) == 0 and len(b) == 0:
    if p < brute_force_lowest: brute_force_lowest = p


def almost_greedy(p, a, b):
  global almost_greedy_lowest
  global almost_greedy_calls

  almost_greedy_calls += 1

  if len(a) == 0 and len(b) == 0:
    if p < almost_greedy_lowest: almost_greedy_lowest = p
  elif len(b) == 0:
    almost_greedy(p + a, '', '')
  elif len(a) == 0:
    almost_greedy(p + b, '', '')
  elif a[0] < b[0]:
    almost_greedy(p + a[0], a[1:], b)
  elif a[0] > b[0]:
    almost_greedy(p + b[0], a, b[1:])
  else:
    almost_greedy(p + a[0], a[1:], b)
    almost_greedy(p + b[0], a, b[1:])


for j in range(10000):
  a = ''.join(random.choice(string.ascii_lowercase) for _ in range(random.randint(2, 10)))
  b = ''.join(random.choice(string.ascii_lowercase) for _ in range(random.randint(2, 10)))
  brute_force_lowest = a + b
  brute_force_calls = 0
  brute_force('', a, b)
  almost_greedy_calls = 0
  almost_greedy_lowest = a + b
  almost_greedy('', a, b)
  print('%s, %s -> %s vs. %s (%.3f)' % (a, b, brute_force_lowest, almost_greedy_lowest, float(almost_greedy_calls) / brute_force_calls))
  if almost_greedy_lowest != brute_force_lowest: print 'ERROR'

enter image description here 一个有趣的统计数据是,如果我们将字母表限制为'ab',那么此算法的平均速度比蛮力算法快十倍。

更新一些优化:

def prefix_length(a):
  for i in range(len(a)):
    if a[i] != a[0]: return i
  return len(a)

def almost_greedy(p, a, b):
  global almost_greedy_lowest
  global almost_greedy_calls

  almost_greedy_calls += 1

  if p > almost_greedy_lowest: return

  if len(a) == 0 and len(b) == 0:
    if p < almost_greedy_lowest: almost_greedy_lowest = p
  elif len(b) == 0:
    almost_greedy(p + a, '', '')
  elif len(a) == 0:
    almost_greedy(p + b, '', '')
  elif a[0] < b[0]:
    almost_greedy(p + a[0], a[1:], b)
  elif a[0] > b[0]:
    almost_greedy(p + b[0], a, b[1:])
  else:
    la = prefix_length(a)
    almost_greedy(p + a[0] * la, a[la:], b)
    lb = prefix_length(b)
    almost_greedy(p + b[0] * lb, a, b[lb:])

答案 2 :(得分:0)

Greedy将无法提供正确的解决方案,并且分叉将增加算法的时间复杂度,因为您将不得不不断地重新访问两个字符串中相同的子字符串。取而代之的是,使用出队队列来存储常见的字符,并比较要从出局队列或第一个字符串或第二个字符串中选择的下一个字符

查看下面的解决方案(用字符串中的char替换整数,并切换比较符号)

def maxNumber(self, nums1, nums2):
    """
    :type nums1: List[int]
    :type nums2: List[int]
    :type k: int
    :rtype: List[int]
    """
    d = deque()
    arr = []
    i, j = 0, 0
    while i < len(nums1) and j < len(nums2):
        if len(d) and d[0] > nums1[i] and d[0] > nums2[j]:
            arr.append(d.popleft())
        else:
            if nums1[i] > nums2[j]:
                arr.append(nums1[i])
                i += 1
            elif nums1[i] < nums2[j]:
                arr.append(nums2[j])
                j += 1
            else:
                arr.append(nums1[i])
                d.append(nums2[j])
                i += 1
                j += 1
    while i < len(nums1):
        if len(d) and d[0] > nums1[i]:
            arr.append(d.popleft())
        else:
            arr.append(nums1[i])
            i += 1
    while j < len(nums2):
        if len(d) and d[0] > nums2[j]:
            arr.append(d.popleft())
        else:
            arr.append(nums2[j])
            j += 1
    while len(d):
        arr.append(d.popleft())
    return arr

答案 3 :(得分:0)

然而,贪婪的方法是有效的,

if(a[i] < b[j]) {
        array[k] = a[i];
        ++i;
    }
    else {
        array[k] = b[j];
        ++j;
    }

这部分不正确,因为当 a[i] == b[j] 时,您不能简单地将 b[j] 分配给 array[k]

相反,您需要在a[i:]时比较子串b[j:]a[i] == b[j],并且您可以对std::string本身进行操作:

if(s1[i] < s2[j]) 
{
    array[k] = s1[i];
    ++i;
}
else if (s1[i] == s2[j] && s1.substr(i) < s2.substr(j))
{
    array[k] = s1[i];
    ++i;
}
else 
{
    array[k] = s2[j];
    ++j;
}

时间复杂度将是二次的 (O(n^2)),因为 substr 操作需要 O(n)

相关问题