递归:当可能有多个子路径时,在所有递归路径中跟踪变量

时间:2019-06-19 18:21:48

标签: javascript recursion dynamic-programming lcs

我正在尝试计算模式作为字符串的子序列出现的次数,并且还将匹配发生的位置保留在索引中。

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

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的当前索引推送到“局部索引数组”,并且一旦模式完成,就将“ local索引”设置为“全局索引”,并为下一个递归路径重置“本地索引”。

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

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]]

我想将“本地索引”重设为上一个分支。

我是朝着正确的方向前进吗,还是这些问题需要完全不同的实现方式?

1 个答案:

答案 0 :(得分: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")); 

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

一般想法

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

返回值可能很复杂:当您需要返回多个“一件事”时,您只需将不同部分放入对象或数组中并返回即可。然后,调用者可以再次将其拆包到各个部分。例如,如果我们还要在上面的代码中返回count,那么我们将完成:

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);
    }
}

总是应该可以解决这样的递归问题,但是如果证明它太困难,您可以选择传递一个额外的对象变量(或数组),递归调用将在其上添加结果:就像一个收集器,逐渐成长为最终解决方案。不利的一面是,不让函数产生副作用与最佳实践背道而驰,其次,该函数的调用者必须已经准备好一个空对象并将其传递以获得结果。

最后,不要试图使用全局变量进行此类数据收集。如果这样的“全局”变量实际上是闭包中的局部变量,那会更好。但是,另一个选择是首选。