Problem: Longest Unequal Adjacent Groups Subsequence II

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.


Python solution:
import math
import itertools
import bisect
import collections
import string
import heapq
import functools
import sortedcontainers
from typing import List, Dict, Tuple, Iterator

class Solution:
  def getWordsInLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:
    ans = []
    n=len(words)
    dp = [1] * n
    prev = [-1] * n

    for i in range(1, n):
      for j in range(i):
        if groups[i] == groups[j]:
          continue
        if len(words[i]) != len(words[j]):
          continue
        if sum(a != b for a, b in zip(words[i], words[j])) != 1:
          continue
        if dp[i] < dp[j] + 1:
          dp[i] = dp[j] + 1
          prev[i] = j

    index = dp.index(max(dp))
    while index != -1:
      ans.append(words[index])
      index = prev[index]

    return ans[::-1]


Java solution:
class Solution {
  public List<String> getWordsInLongestSubsequence(int n, String[] words, int[] groups) {
    List<String> ans = new ArrayList<>();
    // dp[i] := the length of the longest subsequence ending in `words[i]`
    int[] dp = new int[n];
    Arrays.fill(dp, 1);
    // prev[i] := the best index of words[i]
    int[] prev = new int[n];
    Arrays.fill(prev, -1);

    for (int i = 1; i < n; ++i)
      for (int j = 0; j < i; ++j) {
        if (groups[i] == groups[j])
          continue;
        if (words[i].length() != words[j].length())
          continue;
        if (hammingDist(words[i], words[j]) != 1)
          continue;
        if (dp[i] < dp[j] + 1) {
          dp[i] = dp[j] + 1;
          prev[i] = j;
        }
      }

    // Find the last index of the subsequence.
    int index = getMaxIndex(dp);
    while (index != -1) {
      ans.add(words[index]);
      index = prev[index];
    }

    Collections.reverse(ans);
    return ans;
  }

  private int hammingDist(final String s1, final String s2) {
    int dist = 0;
    for (int i = 0; i < s1.length(); ++i)
      if (s1.charAt(i) != s2.charAt(i))
        ++dist;
    return dist;
  }

  private int getMaxIndex(int[] dp) {
    int maxIndex = 0;
    for (int i = 0; i < dp.length; ++i)
      if (dp[i] > dp[maxIndex])
        maxIndex = i;
    return maxIndex;
  }
}


C++ solution:
class Solution {
 public:
  vector<string> getWordsInLongestSubsequence(int n, vector<string>& words,
                                              vector<int>& groups) {
    vector<string> ans;
    // dp[i] := the length of the longest subsequence ending in `words[i]`
    vector<int> dp(n, 1);
    // prev[i] := the best index of words[i]
    vector<int> prev(n, -1);

    for (int i = 1; i < n; ++i)
      for (int j = 0; j < i; ++j) {
        if (groups[i] == groups[j])
          continue;
        if (words[i].length() != words[j].length())
          continue;
        if (hammingDist(words[i], words[j]) != 1)
          continue;
        if (dp[i] < dp[j] + 1) {
          dp[i] = dp[j] + 1;
          prev[i] = j;
        }
      }

    // Find the last index of the subsequence.
    int index = ranges::max_element(dp) - dp.begin();
    while (index != -1) {
      ans.push_back(words[index]);
      index = prev[index];
    }

    ranges::reverse(ans);
    return ans;
  }

 private:
  int hammingDist(const string& s1, const string& s2) {
    int dist = 0;
    for (int i = 0; i < s1.length(); ++i)
      if (s1[i] != s2[i])
        ++dist;
    return dist;
  }
};