返回介绍

solution / 2900-2999 / 2901.Longest Unequal Adjacent Groups Subsequence II / README_EN

发布于 2024-06-17 01:02:59 字数 14123 浏览 0 评论 0 收藏 0

2901. Longest Unequal Adjacent Groups Subsequence II

中文文档

Description

You are given a string array words, and an array groups, both arrays having length n.

The hamming distance between two strings of equal length is the number of positions at which the corresponding characters are different.

You need to select the longest subsequence from an array of indices [0, 1, ..., n - 1], such that for the subsequence denoted as [i0, i1, ..., ik-1] having length k, the following holds:

  • For adjacent indices in the subsequence, their corresponding groups are unequal, i.e., groups[ij] != groups[ij+1], for each j where 0 < j + 1 < k.
  • words[ij] and words[ij+1] are equal in length, and the hamming distance between them is 1, where 0 < j + 1 < k, for all indices in the subsequence.

Return _a string array containing the words corresponding to the indices (in order) in the selected subsequence_. If there are multiple answers, return _any of them_.

Note: strings in words may be unequal in length.

 

Example 1:

Input: words = ["bab","dab","cab"], groups = [1,2,2]

Output: ["bab","cab"]

Explanation: A subsequence that can be selected is [0,2].

  • groups[0] != groups[2]
  • words[0].length == words[2].length, and the hamming distance between them is 1.

So, a valid answer is [words[0],words[2]] = ["bab","cab"].

Another subsequence that can be selected is [0,1].

  • groups[0] != groups[1]
  • words[0].length == words[1].length, and the hamming distance between them is 1.

So, another valid answer is [words[0],words[1]] = ["bab","dab"].

It can be shown that the length of the longest subsequence of indices that satisfies the conditions is 2.

Example 2:

Input: words = ["a","b","c","d"], groups = [1,2,3,4]

Output: ["a","b","c","d"]

Explanation: We can select the subsequence [0,1,2,3].

It satisfies both conditions.

Hence, the answer is [words[0],words[1],words[2],words[3]] = ["a","b","c","d"].

It has the longest length among all subsequences of indices that satisfy the conditions.

Hence, it is the only answer.

 

Constraints:

  • 1 <= n == words.length == groups.length <= 1000
  • 1 <= words[i].length <= 10
  • 1 <= groups[i] <= n
  • words consists of distinct strings.
  • words[i] consists of lowercase English letters.

Solutions

Solution 1: Dynamic Programming

We define $f[i]$ as the length of the longest adjacent non-equal subsequence ending with the $i$-th word, and $g[i]$ as the predecessor index of the longest adjacent non-equal subsequence ending with the $i$-th word. Initially, we set $f[i] = 1$ and $g[i] = -1$.

In addition, we define a variable $mx$ to represent the length of the longest adjacent non-equal subsequence.

We traverse $i$ and $j \in [0, i)$, and if $groups[i] \neq groups[j]$, $f[i] \lt f[j] + 1$, and the Hamming distance between $words[i]$ and $words[j]$ is $1$, we update $f[i] = f[j] + 1$, $g[i] = j$, and update $mx = \max(mx, f[i])$.

Finally, we find the index $i$ corresponding to the maximum value in the $f$ array, and then continuously search backwards from $i$ until we find $g[i] = -1$, which gives us the longest adjacent non-equal subsequence.

The time complexity is $O(n^2 \times L)$, and the space complexity is $O(n)$. Here, $L$ represents the maximum length of a word.

Optimization: Space for Time

In Solution 1, we need to enumerate all $i$ and $j$ combinations, a step that can be optimized by maintaining a wildcard hash table. For each string $word[i]$, we enumerate each character, replace it with a wildcard, and then use the replaced string as the key and add its subscript to the list which is the value in the hash table. This allows us to find all $word[j]$ with a Hamming distance of 1 from $word[i]$ in $O(L)$ time. Although the time complexity is still $O(n^2 \times L)$, the average complexity is reduced.

class Solution:
  def getWordsInLongestSubsequence(
    self, n: int, words: List[str], groups: List[int]
  ) -> List[str]:
    def check(s: str, t: str) -> bool:
      return len(s) == len(t) and sum(a != b for a, b in zip(s, t)) == 1

    f = [1] * n
    g = [-1] * n
    mx = 1
    for i, x in enumerate(groups):
      for j, y in enumerate(groups[:i]):
        if x != y and f[i] < f[j] + 1 and check(words[i], words[j]):
          f[i] = f[j] + 1
          g[i] = j
          mx = max(mx, f[i])
    ans = []
    for i in range(n):
      if f[i] == mx:
        j = i
        while j >= 0:
          ans.append(words[j])
          j = g[j]
        break
    return ans[::-1]
class Solution {
  public List<String> getWordsInLongestSubsequence(int n, String[] words, int[] groups) {
    int[] f = new int[n];
    int[] g = new int[n];
    Arrays.fill(f, 1);
    Arrays.fill(g, -1);
    int mx = 1;
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < i; ++j) {
        if (groups[i] != groups[j] && f[i] < f[j] + 1 && check(words[i], words[j])) {
          f[i] = f[j] + 1;
          g[i] = j;
          mx = Math.max(mx, f[i]);
        }
      }
    }
    List<String> ans = new ArrayList<>();
    for (int i = 0; i < n; ++i) {
      if (f[i] == mx) {
        for (int j = i; j >= 0; j = g[j]) {
          ans.add(words[j]);
        }
        break;
      }
    }
    Collections.reverse(ans);
    return ans;
  }

  private boolean check(String s, String t) {
    if (s.length() != t.length()) {
      return false;
    }
    int cnt = 0;
    for (int i = 0; i < s.length(); ++i) {
      if (s.charAt(i) != t.charAt(i)) {
        ++cnt;
      }
    }
    return cnt == 1;
  }
}
class Solution {
public:
  vector<string> getWordsInLongestSubsequence(int n, vector<string>& words, vector<int>& groups) {
    auto check = [](string& s, string& t) {
      if (s.size() != t.size()) {
        return false;
      }
      int cnt = 0;
      for (int i = 0; i < s.size(); ++i) {
        cnt += s[i] != t[i];
      }
      return cnt == 1;
    };
    vector<int> f(n, 1);
    vector<int> g(n, -1);
    int mx = 1;
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < i; ++j) {
        if (groups[i] != groups[j] && f[i] < f[j] + 1 && check(words[i], words[j])) {
          f[i] = f[j] + 1;
          g[i] = j;
          mx = max(mx, f[i]);
        }
      }
    }
    vector<string> ans;
    for (int i = 0; i < n; ++i) {
      if (f[i] == mx) {
        for (int j = i; ~j; j = g[j]) {
          ans.emplace_back(words[j]);
        }
        break;
      }
    }
    reverse(ans.begin(), ans.end());
    return ans;
  }
};
func getWordsInLongestSubsequence(n int, words []string, groups []int) []string {
  check := func(s, t string) bool {
    if len(s) != len(t) {
      return false
    }
    cnt := 0
    for i := range s {
      if s[i] != t[i] {
        cnt++
      }
    }
    return cnt == 1
  }
  f := make([]int, n)
  g := make([]int, n)
  for i := range f {
    f[i] = 1
    g[i] = -1
  }
  mx := 1
  for i, x := range groups {
    for j, y := range groups[:i] {
      if x != y && f[i] < f[j]+1 && check(words[i], words[j]) {
        f[i] = f[j] + 1
        g[i] = j
        if mx < f[i] {
          mx = f[i]
        }
      }
    }
  }
  ans := make([]string, 0, mx)
  for i, x := range f {
    if x == mx {
      for j := i; j >= 0; j = g[j] {
        ans = append(ans, words[j])
      }
      break
    }
  }
  for i, j := 0, len(ans)-1; i < j; i, j = i+1, j-1 {
    ans[i], ans[j] = ans[j], ans[i]
  }
  return ans
}
function getWordsInLongestSubsequence(n: number, words: string[], groups: number[]): string[] {
  const f: number[] = Array(n).fill(1);
  const g: number[] = Array(n).fill(-1);
  let mx = 1;
  const check = (s: string, t: string) => {
    if (s.length !== t.length) {
      return false;
    }
    let cnt = 0;
    for (let i = 0; i < s.length; ++i) {
      if (s[i] !== t[i]) {
        ++cnt;
      }
    }
    return cnt === 1;
  };
  for (let i = 0; i < n; ++i) {
    for (let j = 0; j < i; ++j) {
      if (groups[i] !== groups[j] && f[i] < f[j] + 1 && check(words[i], words[j])) {
        f[i] = f[j] + 1;
        g[i] = j;
        mx = Math.max(mx, f[i]);
      }
    }
  }
  const ans: string[] = [];
  for (let i = 0; i < n; ++i) {
    if (f[i] === mx) {
      for (let j = i; ~j; j = g[j]) {
        ans.push(words[j]);
      }
      break;
    }
  }
  return ans.reverse();
}
impl Solution {
  pub fn get_words_in_longest_subsequence(
    n: i32,
    words: Vec<String>,
    groups: Vec<i32>
  ) -> Vec<String> {
    fn check(s: &str, t: &str) -> bool {
      s.len() == t.len() &&
        s
          .chars()
          .zip(t.chars())
          .filter(|(a, b)| a != b)
          .count() == 1
    }

    let n = n as usize;

    let mut f = vec![1; n];
    let mut g = vec![-1; n];

    let mut mx = 1;

    for i in 0..n {
      let x = groups[i] as usize;
      for j in 0..i {
        let y = groups[j] as usize;
        if x != y && f[i] < f[j] + 1 && check(&words[i], &words[j]) {
          f[i] = f[j] + 1;
          g[i] = j as i32;
          mx = mx.max(f[i]);
        }
      }
    }

    let mut ans = vec![];
    let mut i = n - 1;

    while f[i] != mx {
      i -= 1;
    }

    let mut j = i as i32;
    while j >= 0 {
      ans.push(words[j as usize].clone());
      j = g[j as usize];
    }

    ans.reverse();
    ans
  }
}

Solution 2

class Solution {
  public List<String> getWordsInLongestSubsequence(int n, String[] words, int[] groups) {
    int[] dp = new int[n];
    int[] next = new int[n];
    Map<String, List<Integer>> strToIdxMap = new HashMap<>();
    int maxIdx = n;
    for (int i = n - 1; i >= 0; i--) {
      int prevIdx = n;
      char[] word = words[i].toCharArray();
      for (int j = 0; j < word.length; j++) {
        // convert word to pattern with '*'.
        char temp = word[j];
        word[j] = '*';
        String curr = new String(word);

        // search matches and update dp.
        List<Integer> prevList = strToIdxMap.getOrDefault(curr, List.of());
        for (int prev : prevList) {
          if (groups[prev] == groups[i] || dp[prev] < dp[i]) {
            continue;
          }
          dp[i] = dp[prev] + 1;
          prevIdx = prev;
        }

        // append current pattern to dictionary.
        strToIdxMap.computeIfAbsent(curr, k -> new ArrayList<>()).add(i);

        // restore pattern to orignal word.
        word[j] = temp;
      }
      if (maxIdx >= n || dp[i] > dp[maxIdx]) {
        maxIdx = i;
      }
      next[i] = prevIdx;
    }
    int curr = maxIdx;
    List<String> ans = new ArrayList<>();
    while (curr < n) {
      ans.add(words[curr]);
      curr = next[curr];
    }
    return ans;
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文