【回溯 字典树(前缀树)】212. 单词搜索 II

本文涉及知识点

回溯 字典树(前缀树)

LeetCode212. 单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
在这里插入图片描述

输入:board = [[“o”,“a”,“a”,“n”],[“e”,“t”,“a”,“e”],[“i”,“h”,“k”,“r”],[“i”,“f”,“l”,“v”]], words = [“oath”,“pea”,“eat”,“rain”]
输出:[“eat”,“oath”]
示例 2:
在这里插入图片描述

输入:board = [[“a”,“b”],[“c”,“d”]], words = [“abcb”]
输出:[]

提示:

m == board.length
n == board[i].length
1 <= m, n <= 12
board[i][j] 是一个小写英文字母
1 <= words.length <= 3 * 104
1 <= words[i].length <= 10
words[i] 由小写英文字母组成
words 中的所有字符串互不相同

回溯

用字典树存储所有words。
枚举起点,时间复杂度O(nm)。
最长的words[i],除去第一个字符后,最多9个字符。后续字符4个。故处理每个起点的时间复杂是O(9n)
总时间复杂度 ≈ \approx 3$\times$107 超时边缘。
回溯部分一定要反复优化。

代码

核心代码

struct CVec
{int r;int c;CVec operator*(const int len)const{return { r * len,c * len };}
};struct CPos
{	int r = 0, c = 0;CPos(int tr, int tc) {r = tr;c = tc;}int ToMask()const { return s_MaxC * r + c; };bool operator==(const CPos& o)const{return (r == o.r) && (c == o.c);}CPos operator+(const CVec& v)const{return { r + v.r, c + v.c };}CPos operator-(const CVec& v)const{return{ r - v.r, c - v.c };}CVec operator-(const CPos& o)const{return {r - o.r,c- o.c};}inline static  int s_MaxC = 10'000;
};struct SPosHash {// 哈希函数的操作符重载,接受一个指针作为参数size_t operator()(const CPos& pos) const {// 简单的哈希函数示例,将指针地址转换为哈希值return (long long)100'000 * pos.r + pos.c;;}
};class CRange
{
public:CRange(int rCount, int cCount, std::function<bool(int, int)> funVilidCur):m_r(rCount),m_c(cCount), m_funVilidCur(funVilidCur){}bool Vilid(CPos pos)const{return (pos.r >= 0) && (pos.r < m_r) && (pos.c >= 0) && (pos.c < m_c) && m_funVilidCur(pos.r, pos.c);}const int m_r, m_c;
protected:std::function<bool(int, int)> m_funVilidCur;
};template<class TData = char, int iTypeNum = 26, TData cBegin = 'a'>
class CTrieNode
{
public:CTrieNode* AddChar(TData ele, int& iMaxID){
#ifdef _DEBUGif ((ele < cBegin) || (ele >= cBegin + iTypeNum)){return nullptr;}
#endifconst int index = ele - cBegin;auto ptr = m_vPChilds[ele - cBegin];if (!ptr){m_vPChilds[index] = new CTrieNode();
#ifdef _DEBUGm_vPChilds[index]->m_iID = ++iMaxID;m_childForDebug[ele] = m_vPChilds[index];
#endif}return m_vPChilds[index];}CTrieNode* GetChild(TData ele)const{
#ifdef _DEBUGif ((ele < cBegin) || (ele >= cBegin + iTypeNum)){return nullptr;}
#endifreturn m_vPChilds[ele - cBegin];}
protected:
#ifdef _DEBUGint 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> findWords(vector<vector<char>>& board, vector<string>& words) {const int R = board.size();const int C = board[0].size();for (const auto& s : words) {m_trie.Add(s.begin(), s.end());}		vector<bool> vHas(m_trie.GetLeadCount());int hasVisit[12][12] ;memset(hasVisit, 0, sizeof(hasVisit));CRange rang(R, C, [&](int r, int c) {return !hasVisit[r][c]; });int move[][2] = { {0,1},{0,-1},{1,0},{-1,0}};std::function<void(CTrieNode<>* par, int r, int c)> BackTrack = [&](CTrieNode<>* par, int r, int c){auto p = par->GetChild(board[r][c]);if (nullptr == p) { return; }if (-1 != p->m_iLeafIndex) {vHas[p->m_iLeafIndex] = true;}hasVisit[r][c]=true;for (int i = 0; i < 4; i++) {const int r1 = r + move[i][0];const int c1 = c + move[i][1];if(!rang.Vilid(CPos(r1,c1 ))){continue;}BackTrack(p, r1, c1);}hasVisit[r][c] = false;};for (int r = 0; r < R; r++) {for (int c = 0; c < C; c++) {BackTrack(&m_trie.m_root, r, c);}}vector<string> vRet;for (const auto& s : words) {auto p = m_trie.Search(s.begin(), s.end());if (vHas[p->m_iLeafIndex]) {vRet.emplace_back(s);}}return vRet;}	CTrie<> m_trie;
};

测试用例

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]);}
}template<class T>
void Assert(const T& t1, const T& t2)
{assert(t1 == t2);
}int main()
{vector<vector<char>> board;vector<string> words;{Solution slu;board= { {'o','a','a','n'},{'e','t','a','e'},{'i','h','k','r'},{'i','f','l','v'} },words= { "oath","pea","eat","rain" };auto res = slu.findWords(board, words);Assert({ "oath","eat" }, res);}{Solution slu;board = { {'a','b'},{'c','d'} }, words = { "abcb" };auto res = slu.findWords(board, words);Assert({  }, res);}	}

2023年4月版

class Solution {
public:vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {m_r = board.size();m_c = board[0].size();m_iMaskNum = m_r*m_c;m_vMove.resize(m_iMaskNum);for (int r = 0; r < m_r; r++){for (int c = 0; c < m_c; c++){int iMask = m_c*r + c;if (r > 0){m_vMove[iMask].emplace_back(iMask - m_c);}if (r + 1 < m_r){m_vMove[iMask].emplace_back(iMask + m_c);}if ( c > 0 ){m_vMove[iMask].emplace_back(iMask - 1);}if (c + 1 < m_c){m_vMove[iMask].emplace_back(iMask + 1);}}}for (const auto& s : words){long long llRet = 0;for (const auto& ch : s){llRet = llRet * 27 + ch - 'a'+1;m_setNeedSub.emplace(llRet);}m_setNeed.emplace(llRet);}int v[12] = { 0 };bool bPre[12 * 12] = { 0 };for (int i = 0; i < m_iMaskNum; i++){			const char& ch = board[i / m_c][i%m_c];v[0] = i;bPre[i] = true;dfs(v, 1,bPre, ch - 'a' + 1, board);bPre[i] = false;}vector<string> vRet;for (const auto& s : words){const long long llMask = Mask(s);if (!m_setNeed.count(llMask)){vRet.emplace_back(s);}}return vRet;}void dfs(int* v,int iSize, bool* vPre, const long long llMask, vector<vector<char>>& board){if (iSize > 10){return;}if (m_setNeed.count(llMask)){m_setNeed.erase(llMask);auto tmp = llMask;while (tmp > 0){auto it = m_setNeedSub.find(tmp);m_setNeedSub.erase(it);tmp /= 27;}}if (!m_setNeedSub.count(llMask)){return;}//const int iSize = v.size();const int iCur = v[iSize-1];for (const auto& next : m_vMove[iCur]){if (vPre[next]){continue;}v[iSize] = next;vPre[next] = true;dfs(v, iSize+1,vPre, llMask * 27 + board[next / m_c][next%m_c] - 'a' + 1, board);vPre[next] = false;}}long long Mask(const std::string& str){long long llRet = 0;for (const auto& ch : str){llRet = llRet * 27 + ch - 'a'+1;}return llRet;}int m_r, m_c;int m_iMaskNum;vector<vector<int>> m_vMove;std::unordered_multiset<long long> m_setNeedSub;std::unordered_set<long long> m_setNeed;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/692142.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【全开源】排队叫号系统基于FastAdmin+GatewayWorker(源码搭建/上线/运营/售后/维护更新)

一款基于FastAdminGatewayWorker开发的多项目多场景排队叫号系统&#xff0c;支持大屏幕投屏&#xff0c;语音播报叫号&#xff0c;可用于餐厅排队取餐、美甲店排队取号、排队领取、排队就诊、排队办理业务等诸多场景&#xff0c;助你轻松应对各种排队取号叫号场景。 功能简介…

springboot中mybatisplus注意事项

使用代码生成工具CodeGenerator 需要修改的内容 dsc.setUsername(“root”); mysql账号dsc.setPassword(“root”); mysql密码strategy.setInclude(“crm_edu”); 表名pc.setModuleName(“eduservice”); //模块名 package com.test.demo;import com.baomidou.mybatisplus.a…

Array.map解析

map方法会创建一个新数组。该方法会循环数组中的每个值&#xff0c;如果仅仅是想循环数组不需要返回值使用数组的forEach方法就可以。原数组中的每个元素都调用一次提供的函数后的返回值组成。Array.map 它接收一个函数 这个函数可以接收三个参数 数组的每个值item 这个值的索引…

【WebGPU】WebGPU 中的反应扩散计算着色器

在本教程中&#xff0c;我们将使用 WebGPU 技术中的计算着色器实现图像效果。更多精彩内容尽在数字孪生平台。 程序结构 主要构建两个 WebGPU 管道&#xff1a; 运行反应扩散算法多次迭代的计算管道&#xff08;js/rd-compute.js 和 js/shader/rd-compute-shader.js&#xff…

李飞飞首次创业!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 最近AI又有啥进展&#xff1f;一起看看吧~ 中国独角兽企业已达369家&#xff0c;六成以上与AI、芯片等硬科技赛道有关 2024中关村论坛“全球独角兽企业大会”上发布全新《中国独角兽企业发展报告&am…

数据结构-二叉树-红黑树

一、红黑树的概念 红黑树是一种二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是Red或者BLACK&#xff0c;通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出两倍&#xff0c;…

相同的树——java

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&…

网安面经之文件包含漏洞

一、文件包含漏洞 1、文件包含漏洞原理&#xff1f;危害&#xff1f;修复&#xff1f; 原理&#xff1a;开发⼈员⼀般希望代码更灵活&#xff0c;所以将被包含的⽂件设置为变量&#xff0c;⽤来进⾏动态调⽤&#xff0c;但是由于⽂件包含函数加载的参数没有经过过滤或者严格的…

HTML表单创建学习

文章目录 1、创建HTML框架2.body标签CSS3.表单创建3.1、添加fieldset与label标签3.2、为label标签添加css样式3.3、添加input标签3.4、添加提交按钮3.5、在input标签中添加required3.6、添加minlength属性3.7、pattern属性3.8、设置表单单选按钮无法同时选中3.9、添加链接3.10、…

三、RocketMQ应用

RocketMQ应用 一、测试环境工程准备二、消息响应1.消息发送的状态-SendStatus2.producer消息Id-msgId3.broker消息Id-offsetMsgId 三、普通消息1.消息发送分类1.1 同步发送消息1.2 异步发送消息1.3 单向发送消息 2.代码举例2.1 同步消息发送生产者2.2 异步消息发送生产者2.3 单…

AI应用案例:新闻文本分类

随着科学技术的不断发展&#xff0c;互联网技术得以快速的发展和普及&#xff0c;并已在各行各业得到了广泛的应用&#xff0c;从中致使了网络上的信息呈现出爆炸式的增长状态&#xff0c;达到了“足不出户&#xff0c;万事皆知”的境况&#xff0c;充分体现了互联网新闻给生活…

半小时搞懂STM32面经知识点——IIC

1.IIC 1.1什么是IIC&#xff1f; 同步半双工通信协议&#xff0c;适用于小数据和短距离传输。 1.2 IIC需要几条线&#xff1f; IIC总共有2条通信总线&#xff08;SDA,SCL&#xff09;&#xff0c;SCL为时钟同步线&#xff0c;用于主机和从机间数据同步操作&#xff1b;SDA为…