java - 使用动态编程计算给定字符串中的组合

标签 java string algorithm encoding dynamic-programming

我在“算法和数据结构”书中发现了一个我无法解决的练习。

从字符编码表开始:

| left | center |
|:---- |:-------|
| A    | 0      |
| B    | 00     |
| C    | 001    |
| D    | 010    |
| E    | 0010   |
| F    | 0100   |
| G    | 0110   |
| H    | 0001   |

这意味着从给定字符串 S = 00100 开始,有 5 个可能的序列可以解码:ADAAFCAACBEA

Obviously not all strings can be decoded (e.g. 1111).

编写一个程序(Java),通过动态编程计算可以编码S的序列数。

另一个例子:字符串0001001000100100001001000010011005567个可能的序列。

Hint: the subproblems are the prefixes of S

我的尝试:

/**
 * this is the main part of the algorithm. I feel like this is way too slow
 * and it doesn't use dynamic programming.
 * 'encodings' is just an ArrayList with the specified values (e.g 00).
 */
private void count(String str) {
  for (String c : encodings) {
    if (str.length() > c.length()) {
      String subStringCodLength = str.substring(0, c.length());
      if (subStringCodLength.equals(c)) {
        String substring = str.substring(c.length());
        count(substring);
      }
    } else if (str.length() == c.length()) {
      if (encodings.contains(str)) {
        this.numOfDecodings++;
        break;
      }
    }
  }
}

最佳答案

动态编程有两种方法:记忆化 制表

两者都基于并存储和重用先前计算的子问题结果。正如提示所示,根据给定字符串的前缀将问题划分为子问题

内存

记忆化技术与递归一起使用。 Map 是存储中间结果的一个不错的选择。

在实现递归时,我们需要描述两种情况:

  • 基本情况 - 表示一个简单的边缘情况(或一组边缘情况),其结果是预先已知的。对于这个问题,这样的边缘情况是:

    • 给定的字符串为空,结果为 1(空的二进制字符串 "" 结果为空字母字符串 "" ),
    • 另一种情况是无法解码给定的二进制字符串,结果将为 0(在下面的解决方案中,当执行递归情况时,它会自然解析),
    • 给定字符串的结果已计算并包含在 map 中。
  • 递归情况 - 解决方案的一部分,其中递归调用以及主逻辑所在的位置。在递归情况中,我们需要找到字符串开头的每个二进制“二进制字母”,然后通过传递子字符串(不带>"letter") 作为参数。这些递归调用的结果需要累积、存储在映射中,然后作为返回值提供。

它可能看起来像这样:

public static int count(String str, List<String> letters, Map<String, Integer> vocab) {
    if (str.isEmpty()) { // base case - a combination was found
        return 1;
    }
    if (vocab.containsKey(str)) { // result was already computed and present in the map 
        return vocab.get(str);
    }

    int count = 0;

    for (String letter: letters) {
        if (str.startsWith(letter)) {
            count += count(str.substring(letter.length()), letters, vocab);
        }
    }
    vocab.put(str, count); // storing the total `count` into the map

    return count;
}

制表

制表技术不是基于递归,而是利用循环。

这使得这种方法更加可靠,因为递归有一些限制,尤其是在 Java 中。制表允许处理大量输入,这些输入可能会通过记忆化产生StackOverflowError

通常,数组用于在实现制表时存储中间结果。

为了解决这个问题,我们需要创建一个数组,其长度为给定字符串+ 1。数组的每个元素将表示使用一组“二进制字母”来收缩从索引 0 到当前元素索引的子字符串的方法数。最终结果将存储在数组的最后一个位置。

要初始化数组,我们需要为数组中对应“二进制字母”的每个元素设置值1,其中恰好是给定字符串的前缀。它是在 populate() 方法中完成的。然后我们需要根据已经找到的组合(即根据非 0 的数组元素)迭代数组搜索可以联系到的可能组合。

public static int count(String str, List<String> letters) {
    int[] tab = new int[str.length() + 1];
    
    populate(str,  letters,  tab);
    
    for (int i = 1; i < tab.length; i++) {
        if (tab[i] == 0) continue;
        for (String letter: letters) {
            if (i + letter.length() >= tab.length) continue;
            
            if (str.substring(i, i + letter.length()).equals(letter)) {
                tab[i + letter.length()] += tab[i];
            }
        }
    }
    return tab[tab.length - 1];
}

public static void populate(String str, List<String> letters, int[] tab) {

    for (String letter: letters) {
        if (letter.length() >= tab.length) continue;
    
        if (str.startsWith(letter)) {
            tab[letter.length()] += 1;
        }
    }
}

演示

main()

public static void main(String[] args) {
    
    List<String> letters = List.of("0", "00", "001", "010", "0010", "0100", "0110", "0001"); // binary letters

    System.out.println(count("000100100010010000100100001001100", letters, new HashMap<>())); // Memoization
    System.out.println(count("000100100010010000100100001001100", letters)); // Tabulation
}

输出:

5567   // Memoization
5567   // Tabulation

A link to Online Demo

关于java - 使用动态编程计算给定字符串中的组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72578991/

相关文章:

c - C 中 [String][1] 类型的数组

c++ - std:find()用于排序与未排序

c++ - 如何使用内在函数将两个 char 数组按元素相乘并将乘法相加为 int?

java - 如何修复在另一个类中修改的私有(private)列表

java - 如何在 Java 8 中打印唯一的数字平方?

与字符数组连接的 C++ 字符串

algorithm - 实现求根功能

c# - ushort 等价物

Java jTable 排序百分比值不起作用

java - 如何不仅获取字符串的所有子字符串,还获取字符串的所有可能的子字符串?