【问题标题】:Recursion: keeping track of a variable in all recursion paths when multiple sub paths are possible递归:当可能有多个子路径时,跟踪所有递归路径中的变量
【发布时间】:2019-11-02 13:12:39
【问题描述】:

我试图计算模式作为字符串的子序列出现的次数,并保留匹配发生的索引。

使用递归调用很容易计数。

function count(str, pattern, strInd, patternInd) {
    if (patternInd === 0) {
      return 1;
    }

    if (strInd === 0) {
      return 0;
    }

    if (str.charAt(strInd - 1) === pattern.charAt(patternInd - 1)) {
      const count1 = count(str, pattern, strInd - 1, patternInd - 1);
      const count2 = count(str, pattern, strInd - 1, patternInd);
      return count1 + count2;
    } else {
      return count(str, pattern, strInd - 1, patternInd);
    }
  }

为了保持索引,我的逻辑是当模式字符与字符串字符匹配时,将 str 的当前索引推送到递归调用中的“本地索引数组”,一旦模式完成,推送“本地索引数组”索引”到“全局索引”并为下一个递归路径重置“本地索引”。

重置本地索引是我面临的问题:

function count(str, pattern, strInd, patternInd) {
    if (patternInd === 0) {
      // when this path ends, add to list the indices found on this path
      globalIndices.push(localIndices);
      // reset the local indices
      localIndices = [];
      console.log("found");
      return 1;
    }

    if (strInd === 0) {
      return 0;
    }

    if (str.charAt(strInd - 1) === pattern.charAt(patternInd - 1)) {
      localIndices.push(strInd);
      const count1 = count(str, pattern, strInd - 1, patternInd - 1);
      const count2 = count(str, pattern, strInd - 1, patternInd);
      return count1 + count2;
    } else {
      return count(str, pattern, strInd - 1, patternInd);
    }
  }

这样它会在每次分叉后丢失之前的路径信息,因为一旦匹配的子路径被消耗,它就会从 localIndices 中删除,并且 localIndices 在分叉发生后开始跟踪匹配。

例如,str 是“abab”,模式是“ab” 然后我想 globalIndices = [[4,3], [4,1], [2,1]] 但相反我会得到 [[4,3],[1],[2,1]]

我想将“本地索引”重置为之前的分叉。

我的方向是否正确,或者这类问题是否需要完全不同的实现?

【问题讨论】:

    标签: javascript recursion dynamic-programming lcs


    【解决方案1】:

    首先,当您收集索引时,您不需要保留计数,因为最终数组的长度将是计数:每个数组元素将对应一个匹配项,并且是相关索引的列表.

    您可以使函数的返回值成为(部分)匹配的数组,并在回溯时为每个数组扩展一个额外的索引(当该字符被匹配时):

    function count(str, pattern, strInd = str.length, patternInd = pattern.length) {
        if (patternInd === 0) {
            return [[]]; // A match. Provide an array with an empty array for that match
        }
    
        if (strInd === 0) {
            return []; // No match. Provide empty array.
        }
    
        if (str.charAt(strInd - 1) === pattern.charAt(patternInd - 1)) {
            const matches1 = count(str, pattern, strInd - 1, patternInd - 1);
            const matches2 = count(str, pattern, strInd - 1, patternInd);
            // For the first case, add the current string index to the partial matches:
            return [...matches1.map(indices => [...indices, strInd-1]), ...matches2];
        } else {
            return count(str, pattern, strInd - 1, patternInd);
        }
    }
    
    console.log(count("abab", "ab")); 

    请注意,索引是从零开始的,因此它们比您提到的预期输出少一。此外,索引是从左到右排序的,这似乎更有用。

    总体思路

    一般来说你最好避免使用全局变量,尽可能使用递归函数的返回值。您从中得到的仅涉及递归调用访问的“子树”。在上述情况下,该子树是字符串和模式的较短版本。递归函数返回的内容应该与传递的参数一致(应该是那些参数的“解决方案”)。

    返回值可能很复杂:当您需要返回多个“一件事”时,您可以将不同的部分放在一个对象或数组中并返回。然后,调用者可以再次将其解包到各个部分。例如,如果我们在上面的代码中也返回了计数,我们会这样做:

    function count(str, pattern, strInd = str.length, patternInd = pattern.length) {
        if (patternInd === 0) {
            return { count: 1, matches: [[]] };
        }
    
        if (strInd === 0) {
            return { count: 0, matches: [] };
        }
    
        if (str.charAt(strInd - 1) === pattern.charAt(patternInd - 1)) {
            const { count: count1, matches: matches1 }  = 
                 count(str, pattern, strInd - 1, patternInd - 1);
            const { count: count2, matches: matches2 } = 
                 count(str, pattern, strInd - 1, patternInd);
            // For the first case, add the current string index to the partial matches:
            return {
                count: count1 + count2,
                matches: [...matches1.map(indices => [...indices, strInd-1]), ...matches2]
            };
        } else {
            return count(str, pattern, strInd - 1, patternInd);
        }
    }
    

    应该总是可以解决这样的递归问题,但如果证明太难,您可以作为替代方案,传递一个额外的对象变量(或数组),递归调用会将其结果添加到其中:它就像一个收集器,逐渐成长到最终的解决方案。缺点是不让函数产生副作用违反了最佳实践,其次,该函数的调用者必须已经准备一个空对象并传递它以获取结果。

    最后,不要试图使用全局变量来收集此类数据。如果这样的“全局”变量实际上是闭包中的局部变量,那就更好了。但是,其他选项仍然是首选。

    【讨论】:

    • 为什么我们要将 strInd - 1 添加到匹配 1 的所有子数组中?
    • 还有为什么只用于matches1而不用于matches2
    • 因为这些子数组都表示应该包含该索引的匹配项。您在该递归调用中减少了模式索引,这表明您“使用”了该字符,因此它必须包含在所有匹配项中。
    • 不适用于matches2,因为在那里进行递归调用时没有考虑字符串字符与模式字符匹配,即使它们是相同的字符。请注意在第二次递归调用中如何不减少模式索引,这意味着您仍然希望匹配该模式字符(与不同索引处的字符串字符)
    • 我添加了更多信息,但本质上我相信这种工作方式(完全依赖递归函数的返回值)将始终有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多