【回溯 字典树(前缀树)】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 _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> 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++**实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android studio 打开Device Mirroring方便调试

巧合下发现一个很好用的工具&#xff0c;在平时调试真机的时候在每次run app后都要低头找找手机看看效果。但是&#xff0c;用了AS上的Device Mirroring&#xff0c;你会发现根本不需要再低头点手机&#xff0c;调试方便一万倍啊。 话不多说&#xff0c;上图。直接就可以在电脑…

【初级数据结构】队列

目录 前言队列的概念及结构队列的实现队列的结构队列的初始化队列的销毁入队出队取队头元素取队尾元素判断队列是否为空取出队列中元素个数代码测试 完整代码Queue.hQueue.ctest.c 前言 前面我们已经学习了栈&#xff0c;栈是一种后进先出的结构&#xff0c;即LIFO&#xff0c;…

从JSON数据到Pandas DataFrame:如何解析出所需字段

目录 一、引言 二、JSON数据的基本结构 三、使用Pandas从JSON数据中读取数据 四、从DataFrame中解析出所需字段 解析对象字段 解析嵌套对象字段 解析数组字段 五、案例与代码示例 六、总结 一、引言 在数据分析和处理的日常工作中&#xff0c;我们经常需要从各种…

【Qt 学习笔记】Qt常用控件 | 多元素控件 | Table Widget的说明及介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 多元素控件 | Table Widget的说明及介绍 文章编号&#…

【JS红宝书学习笔记】第3章 语言基础

第3章 语言基础 1. 语法 标识符&#xff08;变量、函数、属性或函数参数的名称&#xff09;&#xff1a;一般使用驼峰法命名&#xff0c;关键字、保留字、true、false 和 null 不能作为标识符。 标识符的第一个字符必须是一个字母、下划线&#xff08;_&#xff09;或美元符号…

乡村振兴与数字乡村建设:加强农村信息化建设,推动数字乡村发展,提升乡村治理和服务水平,构建智慧化的美丽乡村

目录 一、引言 二、数字乡村建设的必要性 1、推动农村经济转型升级 2、提升乡村治理水平 3、改善乡村民生福祉 三、数字乡村建设的现状与挑战 1、现状 2、挑战 四、数字乡村建设的未来发展路径 1、加强农村信息化基础设施建设 2、提升农民信息素养和技能水平 3、制…

软件设计师笔记和错题

笔记截图 数据库 模式是概念模式 模式/内模式 存在概念级和内部级之间&#xff0c;实现了概念模式和内模式的互相转换 外模式/模式映像 存在外部级和概念级之间&#xff0c;实现了外模式和概念模式的互相转换。 数据的物理独立性&#xff0c; 概念模式和内模式之间的映像…

JAVA抽象类,接口与内部类,常用API知识总结

文章目录 抽象类和抽象方法抽象类的定义格式抽象方法的定义格式注意事项 接口定义和使用成员特点和类之间的关系新增JDK8新增方法JDK9新增方法 总结设计模式 内部类使用场景分类成员内部类获取内部类对象访问成员变量 静态内部类局部内部类匿名内部类格式使用场景 示例 常用API…

QT C++(QWidget类及其常见的属性)

文章目录 1. QWidget类及其常见的属性 1. QWidget类及其常见的属性 QT各种控件都是继承自QWidget类&#xff0c;QWidget类是QT控件体系中通用的部分。 QWidget属性如下图 常见的QT属性为&#xff1a; enabled&#xff1a;描述控件是否处于可用状态&#xff08;禁用状态这个…

民航电子数据库:select查询时部分字段缺失

目录 前言异常排查原因解决使用systemPath标签引入本地Jar包后无法打包 前言 1、对接民航电子数据库 2、框架为shardingsphere caedb mybatis 3、部分SQL查询时&#xff0c;会出现字段缺失的情况 4、查看日志打印出来的SQL&#xff0c;字段并未缺失 异常 这里省略SQL语句…

数字水印 | 数字水印技术原理入门

&#x1f34d;原文&#xff1a; 基于小波变换的数字水印技术 &#x1f34d;写在前面&#xff1a; 本文属搬运博客&#xff0c;自己留存学习。虽然原文标题聚焦于 “小波变换”&#xff0c;但实际上原文介绍了数字水印技术的整体情况。 前言 离散小波变换不仅可以较好地匹配人…

各种行业里的副业项目,你适合哪一类

你希望在周末能够请自己吃一顿豪华大餐嘛&#xff1f;哈哈&#xff0c;但问题来了&#xff0c;自己的收入勉强够支付生活开销&#xff0c;不足以让自己有额外的消费&#xff0c;这样的生活小调调怎么满足呢&#xff0c;那就一起通过副业来实现吧&#xff01; 面对五花八门的副业…

第⼀个SpringBoot程序

Spring Boot介绍 Spring让Java程序更加快速, 简单和安全. Spring对于速度、简单性和⽣产⼒的关注使其成为 世界上最流⾏的Java框架。 Spring Boot 的诞⽣是为了简化 Spring 项目而诞生的 创建Spring Boot项目 File->New Project->Spring Initializr 选择2.多的版本 创建…

群辉虚拟机安装openWRT作旁路由

最近在整活旁路由&#xff0c;基本就是要实现adguard和出国留学。openwrt这个的安装比较简单&#xff0c;就是先去找个镜像&#xff0c;然后导入即可。 我这里最后是去github上找了个大佬每天编译的地址链接。我用的是这个版本 1.下载解压得到img 下载完之后解压会得到一个…

YOLOv5改进 | 注意力机制 | 通道和空间的双重作用的CBAM注意力机制

在深度学习目标检测领域&#xff0c;YOLOv5成为了备受关注的模型之一。本文给大家带来的是通道和空间的双重作用的CBAM注意力机制。文章在介绍主要的原理后&#xff0c;将手把手教学如何进行模块的代码添加和修改&#xff0c;并将修改后的完整代码放在文章的最后&#xff0c;方…

【云原生】 Kubernetes核心概念

目录 引言 一、部署方式回溯 &#xff08;一&#xff09;传统部署时代 &#xff08;二&#xff09;虚拟化部署时代 &#xff08;三&#xff09;容器部署时代 二、Kubernetes基本介绍 &#xff08;一&#xff09;为什么使用k8s &#xff08;二&#xff09;主要功能 &am…

【JVM】Class文件的格式

目录 概述 Class文件的格式 概述 Class文件是JVM的输入&#xff0c;Java虚拟机规范中定义了Class文件的结构。Class文件是JVM实现平台无关、技术无关的基础。 1:Class文件是一组以8字节为单位的字节流&#xff0c;各个数据项目按顺序紧凑排列 2:对于占用空间大于8字节的数据…

BGP学习一:关于对等体建立和状态组改变

目录 一.BGP基本概念 &#xff08;1&#xff09;.BGP即是协议也是分类 1.早期EGP 2.BGP满足不同需求 3.BGP区域间传输的优势 &#xff08;1&#xff09;安全性——只传递路由信息 &#xff08;2&#xff09;跨网段建立邻居 4.BGP总结 5.BGP的应用 &#xff08;1&#…

力扣HOT100 - 295. 数据流的中位数

解题思路&#xff1a; 小顶堆 大顶堆 class MedianFinder {Queue<Integer> A, B;public MedianFinder() {A new PriorityQueue<>();B new PriorityQueue<>((x, y) -> (y - x));}public void addNum(int num) {if (A.size() ! B.size()) {A.add(num);B…

如何在Mac 电脑上安装 Homebrew

1、打开终端应用程序 在终端中输入以下命令并回车: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 这个命令会自动下载并运行 Homebrew 的安装脚本。 系统可能会提示您输入管理员密码,请输入您的 Mac 登录…