草庐IT

java - 查看答案-解码方式

coder 2023-08-28 原文

我正在尝试解决一个问题,而我的问题是,为什么我的解决方案不起作用? 。这是问题,下面是答案。

问题来自leetcode:http://oj.leetcode.com/problems/decode-ways/

使用以下映射将包含A-Z字母的消息编码为数字:

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个包含数字的编码消息,请确定对其进行解码的总数。

例如,给出编码消息“12”,它可以被解码为“AB”(1 2)或“L”(12)。解码“12”的方式数为2。

我的解决方案:

我的解决方案的要点是向后退,如果发现拆分,则将选项数量乘以。拆分是指可以用两种方式解释数字。例如:11可以两种方式解释“aa”或“k”。
public class Solution {
    public int numDecodings(String s) {
        if (s.isEmpty() || s.charAt(0) == '0') return 0;
        int decodings = 1;
        boolean used = false; // Signifies that the prev was already use as a decimal
        for (int index = s.length()-1 ; index > 0 ; index--) {
            char curr = s.charAt(index);
            char prev = s.charAt(index-1);
            if (curr == '0') {
                if (prev != '1' && prev != '2') {
                    return 0;
                }
                index--; // Skip prev because it is part of curr
                used = false;
            } else {
                if (prev == '1' || (prev == '2' && curr <= '6')) {
                    decodings = decodings * 2;
                    if (used) {
                        decodings = decodings - 1;
                    }
                    used = true;
                } else {
                    used = false;
                }
            }
        }
        return decodings;
    }
}

失败在以下输入上:
Input:"4757562545844617494555774581341211511296816786586787755257741178599337186486723247528324612117156948"
Output: 3274568
Expected: 589824

最佳答案

这是一个非常有趣的问题。首先,我将展示如何解决这个问题。我们将看到使用递归时并不会那么复杂,并且可以使用动态编程来解决该问题。我们将提供一种通用解决方案,该解决方案不对每个代码点的26上限进行硬编码。

关于术语的注释:我将使用术语代码点(CP)并不是Unicode的意思,而是通过1来指代其中一个代码编号26。每个代码点表示为可变数量的字符。我还将使用术语编码文本(ET)和明文(CT)的明显含义。当谈论序列或数组时,第一个元素称为head。其余元素是尾部。

理论前奏

  • EC ""具有一种解码方式:CT ""
  • 可以将EC的"3"分解为'3' + "",并进行一次解码。
  • 可以将EC的"23"分解为'2' + "3"'23' + ""。每个尾部都有一个解码,因此整个EC都有两个解码。
  • 可以将EC的"123"分解为'1' + "23"'12' + "3"。尾部分别具有两个和一个解码。整个EC具有三个解码。解构'123' + ""无效,因为123 > 26是我们的上限。
  • …等等,用于任何长度的EC。

  • 因此,给定类似"123"的字符串,我们可以通过在开头查找所有有效CP并对每个尾部的解码次数求和来获得解码次数。

    最困难的部分是找到有效的头。通过查看上限的字符串表示形式,我们可以获得头的最大长度。在我们的情况下,头的长度最多为两个字符。但是并不是所有适当长度的头都有效,因为它们也必须是≤ 26

    天真的递归实现

    现在,我们已经完成了一个简单(但可行的)递归实现的所有必要工作:
    static final int upperLimit  = 26;
    static final int maxHeadSize = ("" + upperLimit).length();
    
    static int numDecodings(String encodedText) {
        // check base case for the recursion
        if (encodedText.length() == 0) {
            return 1;
        }
    
        // sum all tails
        int sum = 0;
        for (int headSize = 1; headSize <= maxHeadSize && headSize <= encodedText.length(); headSize++) {
            String head = encodedText.substring(0, headSize);
            String tail = encodedText.substring(headSize);
            if (Integer.parseInt(head) > upperLimit) {
                break;
            }
            sum += numDecodings(tail);
        }
    
        return sum;
    }
    

    缓存递归实现

    显然这不是很有效,因为(对于更长的ET),同一尾部将被分析多次。另外,我们创建了许多临时字符串,但现在让我们继续。我们可以轻松地做的一件事就是记住特定尾部的解码次数。为此,我们使用长度与输入字符串相同的数组:
    static final int upperLimit  = 26;
    static final int maxHeadSize = ("" + upperLimit).length();
    
    static int numDecodings(String encodedText) {
        return numDecodings(encodedText, new Integer[1 + encodedText.length()]);
    }
    
    static int numDecodings(String encodedText, Integer[] cache) {
        // check base case for the recursion
        if (encodedText.length() == 0) {
            return 1;
        }
    
        // check if this tail is already known in the cache
        if (cache[encodedText.length()] != null) {
            return cache[encodedText.length()];
        }
    
        // cache miss -- sum all tails
        int sum = 0;
        for (int headSize = 1; headSize <= maxHeadSize && headSize <= encodedText.length(); headSize++) {
            String head = encodedText.substring(0, headSize);
            String tail = encodedText.substring(headSize);
            if (Integer.parseInt(head) > upperLimit) {
                break;
            }
            sum += numDecodings(tail, cache);  // pass the cache through
        }
    
        // update the cache
        cache[encodedText.length()] = sum;
        return sum;
    }
    

    请注意,我们使用的是Integer[],而不是int[]。这样,我们可以使用null测试来检查不存在的条目。该解决方案不仅是正确的,而且还非常舒适-天真的递归以O(解码次数)时间运行,而备忘录版本以O(字符串长度)时间运行。

    迈向DP解决方案

    当您在头上运行上述代码时,您会注意到,使用整个字符串进行的第一次调用将出现缓存未命中,然后计算第一尾的解码次数,这也会每次都丢失缓存。我们可以通过从输入的末尾开始首先评估尾部来避免这种情况。因为将在整个字符串之前对所有尾部进行求值,所以我们可以删除对缓存未命中的检查。现在,我们也没有任何递归理由,因为所有先前的结果已经在缓存中。
    static final int upperLimit  = 26;
    static final int maxHeadSize = ("" + upperLimit).length();
    
    static int numDecodings(String encodedText) {
        int[] cache = new int[encodedText.length() + 1];
    
        // base case: the empty string at encodedText.length() is 1:
        cache[encodedText.length()] = 1;
    
        for (int position = encodedText.length() - 1; position >= 0; position--) {
            // sum directly into the cache
            for (int headSize = 1; headSize <= maxHeadSize && headSize + position <= encodedText.length(); headSize++) {
                String head = encodedText.substring(position, position + headSize);
                if (Integer.parseInt(head) > upperLimit) {
                    break;
                }
                cache[position] += cache[position + headSize];
            }
        }
    
        return cache[0];
    }
    

    注意到我们只查询缓存中的最后maxHeadSize元素,可以进一步优化该算法。因此,我们可以使用固定大小的队列来代替数组。到那时,我们将拥有一个在* O(输入长度​​)时间和O(maxHeadSize)空间中运行的动态编程解决方案。
    upperLimit = 26的特化

    上面的算法尽可能保持通用,但是我们可以手动将其专用于特定的upperLimit。这很有用,因为它允许我们进行各种优化。但是,这引入了“魔数(Magic Number)”,使代码难以维护。因此,应该在非关键软件中避免这种手动特化(并且上面的算法已经尽可能快了)。
    static int numDecodings(String encodedText) {
        // initialize the cache
        int[] cache = {1, 0, 0};
    
        for (int position = encodedText.length() - 1; position >= 0; position--) {
            // rotate the cache
            cache[2] = cache[1];
            cache[1] = cache[0];
            cache[0] = 0;
    
            // headSize == 1
            if (position + 0 < encodedText.length()) {
                char c = encodedText.charAt(position + 0);
    
                // 1 .. 9
                if ('1' <= c && c <= '9') {
                    cache[0] += cache[1];
                }
            }
    
            // headSize == 2
            if (position + 1 < encodedText.length()) {
                char c1 = encodedText.charAt(position + 0);
                char c2 = encodedText.charAt(position + 1);
    
                // 10 .. 19
                if ('1' == c1) {
                    cache[0] += cache[2];
                }
                // 20 .. 26
                else if ('2' == c1 && '0' <= c2 && c2 <= '6') {
                    cache[0] += cache[2];
                }
            }
        }
    
        return cache[0];
    }
    

    与您的代码比较

    该代码在表面上是相似的。但是,围绕字符的解析比较复杂。您已经引入了used变量,如果设置了该变量,该变量将减少解码计数,以便考虑双字符CP。这是错误的,但是我不确定为什么。主要问题是您几乎在每一步都将计数加倍。如我们所见,以前的计数是相加的,可能会有所不同。

    这表明您没有适当准备就编写了代码。您可以写很多种软件而不必考虑太多,但是在设计算法时必须仔分割析。对我而言,在纸上设计算法并绘制每个步骤的图表(沿该答案的“理论前奏”的路线)通常会很有帮助。当您过多考虑将要实现的语言而对可能的错误假设考虑得太少时,这特别有用。

    我建议您阅读“proofs by induction”以了解如何编写正确的递归算法。一旦有了递归解决方案,就可以随时将其转换为迭代版本。

    关于java - 查看答案-解码方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20342462/

    有关java - 查看答案-解码方式的更多相关文章

    1. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

      我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    4. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

      question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

    5. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    6. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

      在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

    9. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    10. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

      HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

    随机推荐