【字典树】 【哈希表】 【字符串】100251. 数组中的最短非公共子字符串

简介: 【字典树】 【哈希表】 【字符串】100251. 数组中的最短非公共子字符串

本文涉及知识点

字典树 哈希表 字符串

LeetCode 100251. 数组中的最短非公共子字符串

给你一个数组 arr ,数组中有 n 个 非空 字符串。

请你求出一个长度为 n 的字符串 answer ,满足:

answer[i] 是 arr[i] 最短 的子字符串,且它不是 arr 中其他任何字符串的子字符串。如果有多个这样的子字符串存在,answer[i] 应该是它们中字典序最小的一个。如果不存在这样的子字符串,answer[i] 为空字符串。

请你返回数组 answer 。

示例 1:

输入:arr = [“cab”,“ad”,“bad”,“c”]

输出:[“ab”,“”,“ba”,“”]

解释:求解过程如下:

  • 对于字符串 “cab” ,最短没有在其他字符串中出现过的子字符串是 “ca” 或者 “ab” ,我们选择字典序更小的子字符串,也就是 “ab” 。
  • 对于字符串 “ad” ,不存在没有在其他字符串中出现过的子字符串。
  • 对于字符串 “bad” ,最短没有在其他字符串中出现过的子字符串是 “ba” 。
  • 对于字符串 “c” ,不存在没有在其他字符串中出现过的子字符串。
    示例 2:
    输入:arr = [“abc”,“bcd”,“abcd”]
    输出:[“”,“”,“abcd”]
    解释:求解过程如下:
  • 对于字符串 “abc” ,不存在没有在其他字符串中出现过的子字符串。
  • 对于字符串 “bcd” ,不存在没有在其他字符串中出现过的子字符串。
  • 对于字符串 “abcd” ,最短没有在其他字符串中出现过的子字符串是 “abcd” 。
    提示:
    n == arr.length
    2 <= n <= 100
    1 <= arr[i].length <= 20
    arr[i] 只包含小写英文字母。

字典树

用字典树,给个子串编码。记录各编码在那些字符串出现过。如果在两个或更多字符串中出现过,则不是答案。

用 哈希映射更简单,但性能略差。

代码

template<class TData = char, int iTypeNum = 26, TData cBegin = 'a'>
class CTrieNode
{
public:
  CTrieNode* AddChar(TData ele, int& iMaxID)
  {
#ifdef _DEBUG
    if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
    {
      return nullptr;
    }
#endif
    const int index = ele - cBegin;
    auto ptr = m_vPChilds[ele - cBegin];
    if (!ptr)
    {
      m_vPChilds[index] = new CTrieNode();
#ifdef _DEBUG
      m_vPChilds[index]->m_iID = ++iMaxID;
      m_childForDebug[ele] = m_vPChilds[index];
#endif
    }
    return m_vPChilds[index];
  }
  CTrieNode* GetChild(TData ele)const
  {
#ifdef _DEBUG
    if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
    {
      return nullptr;
    }
#endif
    return m_vPChilds[ele - cBegin];
  }
protected:
#ifdef _DEBUG
  int m_iID = -1;
  std::unordered_map<TData, CTrieNode*> m_childForDebug;
#endif
public:
  int m_iLeafIndex = -1;
protected:
  CTrieNode* m_vPChilds[iTypeNum] = { nullptr };
};
template<class TData = char, int iTypeNum = 26, TData cBegin = 'a'>
class CTrie
{
public:
  int GetLeadCount()
  {
    return m_iLeafCount;
  }
  CTrieNode<TData, iTypeNum, cBegin>* AddA(CTrieNode<TData, iTypeNum, cBegin>* par,TData curValue)
  {
    auto curNode =par->AddChar(curValue, m_iMaxID);
    FreshLeafIndex(curNode);
    return curNode;
  }
  template<class IT>
  int Add(IT begin, IT end)
  {
    auto pNode = &m_root;
    for (; begin != end; ++begin)
    {
      pNode = pNode->AddChar(*begin, m_iMaxID);
    }
    FreshLeafIndex(pNode);
    return pNode->m_iLeafIndex;
  } 
  template<class IT>
  CTrieNode<TData, iTypeNum, cBegin>* Search(IT begin, IT end)
  {
    auto ptr = &m_root;
    for (; begin != end; ++begin)
    {
      ptr = ptr->GetChild(begin);
      if (nullptr == ptr)
      {
        return nullptr;
      }
    }
    return ptr;
  }
  CTrieNode<TData, iTypeNum, cBegin> m_root;
protected:
  void FreshLeafIndex(CTrieNode<TData, iTypeNum, cBegin>* pNode)
  {
    if (-1 == pNode->m_iLeafIndex)
    {
      pNode->m_iLeafIndex = m_iLeafCount++;
    }
  }
  int m_iMaxID = 0;
  int m_iLeafCount = 0;
};
class Solution {
public:
  vector<string> shortestSubstrings(vector<string>& arr) {
    CTrie<> trie;
    for (const auto& s : arr)
    {
      const int n = s.size();
      m_vSubIndex.emplace_back(n + 1, vector<int>(n, -1));
      for (int left = 0; left < n; left++)
      {
        auto ptr = &trie.m_root;
        for (int len = 0; left + len < n; len++)
        {
          ptr = trie.AddA(ptr,s[left+len]);
          m_vSubIndex.back()[len + 1][left] = ptr->m_iLeafIndex;
          m_mIndexCount[ptr->m_iLeafIndex].emplace(m_vSubIndex.size());
        }
      }
    }
    vector<string> vRet;
    for (int i = 0; i < arr.size(); i++)
    {
      vRet.emplace_back(Cal(arr[i], i));
    }
    return vRet;
  }
  string Cal(const string& s, int index)
  {
    auto& v = m_vSubIndex[index];
    const int n = s.size();
    for (int len = 1; len <= n; len++)
    {
      set<string> sets;
      for (int left = 0; left < n; left++)
      {
        const int index = v[len][left];
        if (1 == m_mIndexCount[index].size())
        {
          sets.insert(s.substr(left, len));
          if (sets.size() > 1)
          {
            sets.erase(std::prev(sets.end()));
          }
        }
      }
      if (sets.size())
      {
        return *sets.begin();
      }
    }
    return "";
  }
  vector<vector<vector<int>>> m_vSubIndex;
  unordered_map<int, unordered_set<int>> m_mIndexCount;
  int m_r, m_c;
};

测试用例

template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  vector<string> arr;
  {
    Solution sln;
    arr = { "vbb", "grg", "lexn", "oklqe", "yxav" };
    auto res = sln.shortestSubstrings(arr);
    Assert({ "b","g","n","k","a" }, res);
  }
  {
    Solution sln;
    arr = { "cab", "ad", "bad", "c" };
    auto res = sln.shortestSubstrings(arr);
    Assert({ "ab","","ba","" }, res);
  }
  {
    Solution sln;
    arr = { "abc", "bcd", "abcd" };
    auto res = sln.shortestSubstrings(arr);
    Assert({ "","","abcd" }, res);
  }
}

旧版代码

template
class CTrieNode
{
public:
CTrieNode* AddChar(TData ele, int& iMaxID)
{
#ifdef _DEBUG
if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
{
return nullptr;
}
#endif
const int index = ele - cBegin;
auto ptr = m_vPChilds[ele - cBegin];
if (!ptr)
{
m_vPChilds[index] = new CTrieNode();
#ifdef _DEBUG
m_vPChilds[index]->m_iID = ++iMaxID;
m_childForDebug[ele] = m_vPChilds[index];
#endif
}
return m_vPChilds[index];
}
CTrieNode* GetChild(TData ele)const
{
#ifdef _DEBUG
if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
{
return nullptr;
}
#endif
return m_vPChilds[ele - cBegin];
}
protected:
#ifdef _DEBUG
int m_iID = -1;
std::unordered_map<TData, CTrieNode*> m_childForDebug;
#endif
public:
int m_iLeafIndex = -1;
protected:
CTrieNode* m_vPChilds[iTypeNum] = { nullptr };
};
template
class CTrie
{
public:
int GetLeadCount()
{
return m_iLeafCount;
}
template
int Add(IT begin, IT end)
{
auto pNode = &m_root;
for (; begin != end; ++begin)
{
pNode = pNode->AddChar(begin, m_iMaxID);
}
if (-1 == pNode->m_iLeafIndex)
{
pNode->m_iLeafIndex = m_iLeafCount++;
}
return pNode->m_iLeafIndex;
}
template
CTrieNode<TData, iTypeNum, cBegin> Search(IT begin, IT end)
{
auto ptr = &m_root;
for (; begin != end; ++begin)
{
ptr = ptr->GetChild(begin);
if (nullptr == ptr)
{
return nullptr;
}
}
return ptr;
}
CTrieNode<TData, iTypeNum, cBegin> m_root;
protected:
int m_iMaxID = 0;
int m_iLeafCount = 0;
};
class Solution {
public:
vector shortestSubstrings(vector& arr) {
CTrie<> trie;
for (const auto& s : arr)
{
const int n = s.size();
m_vSubIndex.emplace_back(n+1, vector(n, -1));
for (int left = 0; left < n; left++)
{
for (int len = 0; left+len < n; len++)
{
const int index = trie.Add(s.data() + left, s.data() + left + len + 1);
m_vSubIndex.back()[len + 1][left] = index;
m_mIndexCount[index].emplace(m_vSubIndex.size());
}
}
}
vector vRet;
for (int i = 0 ; i < arr.size();i++)
{
vRet.emplace_back(Cal(arr[i], i));
}
return vRet;
}
string Cal(const string& s ,int index)
{
auto& v = m_vSubIndex[index];
const int n = s.size();
for (int len = 1; len <= n ;len++)
{
set sets;
for (int left = 0; left < n; left++)
{
const int index = v[len][left];
if (1 == m_mIndexCount[index].size())
{
sets.insert(s.substr(left, len));
if (sets.size() > 1)
{
sets.erase(std::prev(sets.end()));
}
}
}
if (sets.size())
{
return *sets.begin();
}
}
return “”;
}
vector<vector<vector>> m_vSubIndex;
unordered_map<int, unordered_set> m_mIndexCount;
int m_r, m_c;
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关文章
|
3月前
|
存储 算法 索引
|
6月前
|
存储
数据结构---串(赋值,求子串,比较,定位)
数据结构---串(赋值,求子串,比较,定位)
61 4
数据结构---串(赋值,求子串,比较,定位)
|
6月前
|
存储 索引
DAY-2 | 哈希思想:求字符串包含的字符集合
这是一个关于代码实现的问题,主要展示了两种利用哈希思想去除字符串中重复字符的方法。第一种方法使用了`boolean[] flg`数组来标记字符是否出现过,遍历字符串时,如果字符未出现则添加到结果并标记为已出现。第二种方法使用`char[] ch`数组直接存储字符出现状态,先遍历一次字符串记录出现过的字符,再遍历一次输出未标记的字符。
31 0
|
6月前
|
索引 容器
06-数据容器str(字符串)-字符串的下标索引/字符串无法修改/查找字符串下标初始值/字符串的替换/字符串的分割/字符串去除前后空格/统计字符串的数量/字符串的循环遍历/对字符串进行分割
06-数据容器str(字符串)-字符串的下标索引/字符串无法修改/查找字符串下标初始值/字符串的替换/字符串的分割/字符串去除前后空格/统计字符串的数量/字符串的循环遍历/对字符串进行分割
|
6月前
|
算法 测试技术 C#
【字典树】【字符串】【 前缀】100268. 最长公共后缀查询
【字典树】【字符串】【 前缀】100268. 最长公共后缀查询
|
6月前
|
算法 测试技术 C#
【字典树】【KMP】【C++算法】3045统计前后缀下标对 II
【字典树】【KMP】【C++算法】3045统计前后缀下标对 II
|
6月前
【每日一题Day203】LC1016子串能表示从 1 到 N 数字的二进制串 | 枚举 哈希表
【每日一题Day203】LC1016子串能表示从 1 到 N 数字的二进制串 | 枚举 哈希表
48 2
|
存储 索引
数组与字符串的关系【了解一下】
数组与字符串的关系【了解一下】
124 0
|
JSON 数据格式
将字符串按指定的符号分割为集合或数组
将字符串按指定的符号分割为集合或数组
192 0
将字符串按指定的符号分割为集合或数组
|
存储 算法
数据结构 | 串的存储结构之顺序串【重要的边界判断】
数据结构中对于串的存储结构之顺序串,了解其基本实现及算法
221 0
数据结构 | 串的存储结构之顺序串【重要的边界判断】