【问题标题】:Concatenate array and return substring with longest repeating character连接数组并返回具有最长重复字符的子字符串
【发布时间】:2021-10-22 21:16:08
【问题描述】:

让我以“我是新手”作为序言,所以请放轻松,哈哈。

我需要帮助编写一个函数,该函数接受一个单词数组并返回一个包含字母和该字母最长子字符串长度的对象。数组中的每个单词都由从 a 到 z 的小写字母组成,并且至少有 1 个字符。您需要将数组中的单词连接起来,以获得由一个特定字母组成的最长子字符串的单个单词。

示例:

输入数组是 ["ccdd", "bbbb", "bbab"] 函数应该返回一个 6 和 "b" 的对象。最好的连接之一是 words[1] + words[0] + words[2] = "ccddbbbbbbab"

输入:

longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]);

输出:

{ "字母": "b", "长度": 6 }

我对这个问题的问题是能够排列数组的元素,然后循环遍历结果以找到具有最多重复字符的子字符串。当我从示例代码返回结果时,我不断收到“abcbundefined”。任何帮助都会很棒,谢谢!

function longestSingleCharSubstring(arr) {
let result = [];
let cur_count = 1;     

if (arr.length === 0) return "";
if (arr.length === 1) return arr;

for (let i = 0; i < arr.length; i++) {
let currentChar = arr[i];

let remainingChar = arr.slice(0,i) + arr.slice(i + 1);

for (let j = 0; j < remainingChar.length; j++) {
result.push(currentChar + longestSubstring(remainingChar[j]);
}

let len = result.length;
let count = 0;

let finalRes = result[0];
for (let m=0; m<len; m++)  
{

for (let k = 0; k < result[m].length; k++) 
{
for (let l=k+1; l < result[m].length; l++)  
    if (k != l) 
        break;
    cur_count++;
}
if (cur_count > count) {
  count = cur_count;
  finalRes = result[m];
}
}
 return finalRes;
 }
}

console.log(longestSingleCharSubstring(['abcb','bbbc','aaaa']));

【问题讨论】:

  • 这里的答案似乎是在假设我认为您的文本和代码尝试不支持。您不是简单地尝试按照它们已经存在的相同顺序组合输入中的单词,然后找到最长的单字符运行,对吗?排列的讨论是说你需要尝试各种组合,对吧?所以在['aa', 'ba', 'ac'] 中,您需要尝试'aabaac''aaacba''baaaac''baacaa''acaaba''acbaaa' 以发现'baaaac' 有四个a。这是正确的吗?

标签: javascript arrays algorithm longest-substring


【解决方案1】:

这是一个使用String.join 将原始数组连接成一个长字符串的实现。然后立即调用String.split 将长字符串拆分为一个char 数组。接下来,我们为循环设置一些变量,然后循环遍历 char 数组。在循环中,我们在必要时使用新字符和计数 0 重置 current 对象。在增加当前计数后,如果current.countlongest.count 长,我们更新longest 结果。

const longestSingleCharSubstring = (arr) => {
  const charArray = arr.join('').split('')

  let i = 0
  let longest = {
    char: '',
    count: 0
  }
  let current = {
    char: '',
    count: 0
  }
  while (i < charArray.length) {
    char = charArray[i]
    if (current.char != char) {
      current = {
        char,
        count: 0
      };
    }
    current.count += 1;
    if (current.count > longest.count) {
      longest = { ...current }
    }
    i += 1;
  }
  return longest
}

console.log(longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]))

【讨论】:

  • 不使用reduce连接字符串,也可以使用arr.join("")
  • 更新为使用来自@decpk 的建议
【解决方案2】:

或者,您也可以在这里使用正则表达式,它干净且易于阅读和维护。

/([a-z])\1+/g

const longestSingleCharSubstring = (arr) => {
  const result = { char: "", count: 0 };

  return arr
    .join("")
    .match(/([a-z])\1+/g)
    .reduce((acc, curr) => {
      if (curr.length > acc.count) {
        acc.char = curr[0];
        acc.count = curr.length;
      }
      return acc;
    }, result);
};

console.log(longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]));

【讨论】:

  • 非常好!你用什么来生成那个正则表达式图?
  • 另一个问题:为什么单独的accresult 引用同一个对象?为什么不删除result,使用acc 代替它,并将.reduceresult 参数替换为{ char: "", count: 0 }
  • @ScottSauyet 我用过regulex
  • 是的,你是对的,但两者都是完全相同的 AFAIK,如果我错了,请纠正我。为了简化事情,我通常先声明,然后传递变量。通常 reduce 语法对于新语法来说已经很复杂了,所以为了使其更具可读性,我这样做了。
  • 单独声明没什么大不了的,你可以将它传递给.reduce回调没有问题。但是在回调函数中更改它似乎很奇怪。但看起来你解决了这个问题。再次,一个非常好的答案!我想知道是否有其他人认为我在我的答案中抓住了一个否则会错过的真正要求:考虑原始数组的所有排列。必须等待 OP,但这样做似乎更有意义。
【解决方案3】:

一种完全基于reduce 的方法,它处理splitted 的单个字符列表,同时跟踪最长的可用单个字符序列,可能如下所示...

function longestSingleCharSequence(value) {
  const {

    char,
    length,

  } = String(value) // e.g. 'ccddbbbbbbab'

    .split('') // ... ["c","c","d","d","b","b","b","b","b","b","a","b"]
    .reduce((collector, char, idx, arr) => {

      const { registry } = collector;
      const listOfCharSequences = registry[char] || (registry[char] = []);

      // if current character and the one before are not equal...
      if (char !== arr[idx - 1]) {

        // ...start a new char sequence.
        listOfCharSequences.push(char);
      } else {
        // ...continue concatenation of the most recent sequence.
        const recentIdx = listOfCharSequences.length - 1;
        const sequence = listOfCharSequences[recentIdx] + char;
        
        listOfCharSequences[recentIdx] = sequence;

        // ...`char`/`length` statistics is in need of an update.
        if (sequence.length >= collector.length) {

          collector.length = sequence.length;
          collector.char = char;
        }
      }
      return collector;

    }, { registry: {}, char: null, length: -1 });

  return { char, length };
}

console.log(
  "longestSingleCharSequence(['ccdd', 'bbbb', 'bbab'].join('')) ...",
  longestSingleCharSequence(['ccdd', 'bbbb', 'bbab'].join(''))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

一个基于正则表达式的解决方案,可将字母序列与来自任何语言的任何类型的字母匹配,基于Unicode Property Escapes ...

function longestSingleLetterSequence(value) {
  // // [https://regex101.com/r/QFJKr5/1/]
  // const regXSingleCharSequence = (/(\w)\1{1,}/g);

  // [https://regex101.com/r/QFJKr5/2/]
  const regXSingleLetterSequence = (/(\p{L})\1{1,}/gu);

  const [ sequence ] = String(value)
    .match(regXSingleLetterSequence)
    .sort((a, b) => b.length - a.length);

  return {
    letter: sequence[0],
    length: sequence.length,
  };
}

console.log(
  "longestSingleLetterSequence(['ccdd', 'bbbb', 'bbab'].join('')) ...",
  longestSingleLetterSequence(['ccdd', 'bbbb', 'bbab'].join(''))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

【讨论】:

  • 这种正则表达式方法非常有趣!我不会想到为此使用正则表达式。当然,这可能是一个玩具问题,因此性能无关紧要,但请注意sort / head 技术是找到最大值的低效方法。
  • @ScottSauyet ...我同时会在生产中使用的第二种方法简短易读。但就迭代周期数量而言,这主要归结为我最喜欢的瑞士刀reduce 方法。加上额外的split 的成本,最终结果在仅迭代一次字符列表结束时就已经可用了。
  • 确实,这里的大多数答案都有reduce-for-maximum 技术的一些变体。我的使用它作为我认为实际更大问题的一部分。对于这部分问题,我认为 decpk 的答案是最优雅的,尽管我会在此处发布一个警告。
【解决方案4】:

此答案假设我在 cmets 中的建议是正确的,即您想要检查输入数组的 all 排列以查找一系列字母。也就是说,如果给定['aa', 'ba', 'ac'],那么我们要检查'aabaac''aaacba''baaaac''baacaa''acaaba''acbaaa' 以发现'baaaac' 包含一个运行四个a 的。如果不是这种情况,请随意忽略这个。

打破它

为此,我想将其分解为多个部分。我想写一个函数,它接受输入字符串,找到这些字符串的所有排列,将每个排列中的字符串连接成一个字符串,找到每个字符串中最长的单字母条纹,然后选择最长的结果连胜。

我想要一个 main 函数,看起来像这样:

const longestSingleLetterSequence = (arr) =>
  maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))

会这样使用:

longestSingleLetterSequence (['aa', 'ba', 'ac'])
//=> {"char": "a", "streak": 4, "word": "baaaac"}

但这意味着我们需要编写三个辅助函数,longestStreakpermutationsmaximumBy。请注意,最后两个可能是真正可重用的函数。

让我们看看创建这些函数。我们可以这样写permutations

const permutations = (xs) => 
  xs .length == 0 
    ? [[]] 
    : xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => x + p))

使用简单的帮助器excluding,它接受一个索引和一个数组,并返回该数组的副本,不包括该索引处的值。

permutations firsts 检查输入数组是否为空。如果是,我们只需返回一个仅包含空数组的数组。否则,对于每个元素,我们将其从列表中删除,递归排列剩余元素,并将初始元素添加到每个结果中。

我们的助手很简单。它可以内联到permutations,但我认为如果它们以这种方式分开,它们会更干净。它可能看起来像这样:

const excluding = (i) => (xs) => 
  [... xs .slice (0, i), ... xs .slice (i + 1)]

然后我们可以写maximumBy,它接受一个函数并返回一个函数,该函数接受一个数组并返回该函数返回最大值的元素。它通过一个简单的reduce 调用来做到这一点:

const maximumBy = (fn) => (xs) => 
  xs .reduce (({val, max}, x, i, _, xVal = fn (x)) => 
    (i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
    {}
  ) .val

值得注意的是,这并不特定于数字。它适用于任何可以与&lt; 进行比较的值,例如数字、字符串、日期或具有valueOf 方法的对象。

到目前为止,这些函数都是通用的实用函数,我们可以很容易地想象跨项目重用。下一个,longestStreak,更具体地针对这个项目:

const longestStreak = ([... chars]) => {
  let {streakChar, streak} = chars .reduce (
    ({currChar, count, streakChar, streak}, char) => ({
      currChar: char, 
      count: char == currChar ? count + 1 : 1,
      streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
      streak: Math .max (char == currChar ? count + 1 : 1, streak),
    }),
    {streak: -1}
  )
  return {char: streakChar, streak, word: chars .join ('')} 
}

像许多极大值问题一样,我们可以使用.reduce 来解决这个问题。这里我们将输入字符串重构为一个字符数组,参数为[... cs]。然后当我们折叠我们的数组时,我们不断返回一个具有{currChar, count, streakChar, streak} 结构的对象,它包含我们正在跟踪的 currentChar、到目前为止看到的它们的计数,以及迄今为止看到的最长连击的字符和那个计数。我们从 {streak: -1} 的初始值开始。

当我们遍历字符串时,我们返回条纹字符及其长度(以及包含它的单词;这里不需要,但似乎对更大的问题很有用。)

这是它的整体外观:

// Utility functions
const excluding = (i) => (xs) => 
  [... xs .slice (0, i), ... xs .slice (i + 1)]

const permutations = (xs) => 
  xs .length == 0 
    ? [[]] 
    : xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))

const maximumBy = (fn) => (xs) => 
  xs .reduce (({val, max}, x, i, _, xVal = fn (x)) => 
    (i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
    {}
  ) .val


// Helper function
const longestStreak = ([... chars]) => {
  let {streakChar, streak} = chars .reduce (
    ({currChar, count, streakChar, streak}, char) => ({
      currChar: char, 
      count: char == currChar ? count + 1 : 1,
      streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
      streak: Math .max (char == currChar ? count + 1 : 1, streak),
    }),
    {streak: -1}
  )
  return {char: streakChar, streak, word: chars .join ('')} 
}


// Main function
const longestSingleLetterSequence = (arr) =>
  maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))


// Demos
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}

使用额外的帮助器进行清理

我真的不喜欢 main 函数的一件事。您需要非常仔细地阅读以了解操作顺序:

const longestSingleLetterSequence = (arr) =>
  maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
//`-------- step 4 --------'  `---- step 1 ---'  `--------- step 2 -------' `------ step 3 ------'

我是Ramda 的主要作者之一,它提供了一些非常有用的工具来帮助管理这种复杂性。但是这些工具很容易自己编写。所以有了一些额外的助手,我实际上会这样写 main 函数:

const longestSingleLetterSequence = pipe (
  permutations,
  map (join ('')),
  map (longestStreak),
  maximumBy (prop ('streak'))
)

这里的步骤只是逐行按顺序运行。我不会详细介绍这些辅助函数,但如果你想看到它的实际效果,你可以展开这个 sn-p:

// Utility functions
const pipe = (...fns) => (...args) => 
  fns .slice (1) .reduce ((a, fn) => fn (a), fns[0] (...args))
const map = (fn) => (xs) => xs .map (x => fn (x))
const prop = (p) => (o) => o [p]
const join = (sep) => (xs) => xs .join (sep)
const excluding = (i) => (xs) => [... xs .slice (0, i), ... xs .slice (i + 1)]

const permutations = (xs) => 
  xs .length == 0 
    ? [[]] 
    : xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))

const maximumBy = (fn) => (xs) => 
  xs .reduce (({val, max}, x, i, _, xVal = fn (x)) => 
    (i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
    {}
  ) .val


// Helper function
const longestStreak = ([... chars]) => {
  let {streakChar, streak} = chars .reduce (
    ({currChar, count, streakChar, streak}, char) => ({
      currChar: char, 
      count: char == currChar ? count + 1 : 1,
      streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
      streak: Math .max (char == currChar ? count + 1 : 1, streak),
    }),
    {streak: -1}
  )
  return {char: streakChar, streak, word: chars .join ('')} 
}


// Main function
const longestSingleLetterSequence = pipe (
  permutations,
  map (join ('')),
  map (longestStreak),
  maximumBy (prop ('streak'))
)


// Demo
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}

【讨论】:

  • 漂亮,我花了一些时间通过代码/方法自己工作了几次。
猜你喜欢
  • 2021-08-02
  • 2018-02-17
  • 1970-01-01
  • 1970-01-01
  • 2019-09-11
  • 2022-08-14
  • 2018-03-26
  • 1970-01-01
相关资源
最近更新 更多