从矩阵中路径题目思考回溯题目思路

最近做《剑指offer》矩阵中路径的题目,是一道典型的回溯法题目。 过程中犯了一些错误,记录一下。
1.迭代过程中对访问每个节点函数功能没想清除,违反了单一职责元素。
2.没利用或运算符短路求值的特性导致进行了多余运算

下面是例题以及改进过程

题目

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

主函数:遍历每个点,如果找到一条路径就返回true

bool exist(vector<vector<char>>& board, string word) 
{
	vector<vector<bool>> ischecked(board.size(), vector<bool>(board[0].size(), false));
	for (int i = 0; i < board.size(); ++i)
		for (int j = 0; j < board[0].size(); ++j)
			if (dfs(board, word, 0, ischecked, i, j))
				return true;
	return false;
}

递归函数:

//版本一 求得所有能遍历结果再返回与运算结果,这样的坏处是如果b1为true,后面的其实不用算,要利用好或运算符的最短路径求法。
bool dfs(vector<vector<char>>& board, string& word, int checkindex, vector<vector<bool>>& ischecked, int row, int column)
{
	if (checkindex == word.size() - 1 && word[checkindex] == board[row][column]) return true;
	if (board[row][column] != word[checkindex])	return false;

	ischecked[row][column] = true;
	bool b1 = false, b2 = false, b3 = false, b4 = false;
	if (row + 1 < board.size() && !ischecked[row + 1][column]) b1 = dfs(board, word, checkindex + 1, ischecked, row + 1, column);
	if (row - 1 >= 0 && !ischecked[row - 1][column]) b2 = dfs(board, word, checkindex + 1, ischecked, row - 1, column);
	if (column + 1 < board[0].size() && !ischecked[row][column + 1]) b3 = dfs(board, word, checkindex + 1, ischecked, row, column + 1);
	if (column - 1 >= 0 && !ischecked[row][column - 1]) b4 = dfs(board, word, checkindex + 1, ischecked, row, column - 1);
	ischecked[row][column] = false;
	return b1 || b2 || b3 || b4;
}
//版本二 有点冗余
bool dfs(vector<vector<char>>& board, string& word, int checkindex, vector<vector<bool>>& ischecked, int row, int column)
{
	if (checkindex == word.size() - 1 && word[checkindex] == board[row][column]) return true;
	if (board[row][column] != word[checkindex])	return false;

	ischecked[row][column] = true;
	bool b1 = false, b2 = false, b3 = false, b4 = false;
	if (row + 1 < board.size() && !ischecked[row + 1][column]) b1 = dfs(board, word, checkindex + 1, ischecked, row + 1, column);
    if(b1 == true) return b1;
	if (row - 1 >= 0 && !ischecked[row - 1][column]) b2 = dfs(board, word, checkindex + 1, ischecked, row - 1, column);
    if(b2 == true) return b2;
	if (column + 1 < board[0].size() && !ischecked[row][column + 1]) b3 = dfs(board, word, checkindex + 1, ischecked, row, column + 1);
    if(b3 == true) return b3;
	if (column - 1 >= 0 && !ischecked[row][column - 1]) b4 = dfs(board, word, checkindex + 1, ischecked, row, column - 1);
    if(b4 == true) return b4;
	ischecked[row][column] = false;
    return false;
}
//版本三 对于一个格子,总是访问它相邻四个格子,至于能不能访问,由进入那个格子的函数进行判断。 
//本质上,版本二的错误是功能划分失败,违反单一元素:一个格子只应该检查是否到达结束条件或者它能否访问,不应该去判断其他格子能否访问。
bool dfs(vector<vector<char>>& board, string& word, int checkindex, vector<vector<bool>>& ischecked, int row, int column)
{
	if (checkindex == word.size()) return true;		

	bool result = false;
	if (row >= 0 && row < board.size() && column >= 0 && column < board[0].size() && word[checkindex] == board[row][column] && !ischecked[row][column])
	{
		ischecked[row][column] = true;
		result = dfs(board, word, checkindex + 1, ischecked, row + 1, column) ||
			dfs(board, word, checkindex + 1, ischecked, row - 1, column) ||
			dfs(board, word, checkindex + 1, ischecked, row, column + 1) ||
			dfs(board, word, checkindex + 1, ischecked, row, column - 1);
		ischecked[row][column] = false;
	}
	return result;
}

解答有关回溯题目的思路

通常用在二维方格运动的问题上。

  1. 确认递归结束条件的最简形式和返回值。
    一点优化:通常递归状态可以分为递归过程中,递归结束。递归结束又可以分成达到完成条件结束和未达到完成条件结束。
    通过排列组合这些递归状态可以减少代码量,
    比如上面的例题先处理递归还没到结束的状态,然后默认返回false,而不是先处理未达成完成条件结束的代码;而机器人的运动范围这道题目,则是将递归结束两种情况统一处理。
  2. 根据单一职责原则想好递归函数应该干些什么,而哪些又不归它干。
    比如上面一道题目版本二犯的错误,一个格子只应该检查是否到达结束条件或者它能否访问,不应该去判断其他格子能否访问。
  3. 对于只要求一条有效路径的题目,利用好或运算符短路求值特性。 见递归函数注释。

补充:回溯和深度优先区别

深度优先对于所有节点只访问一次,用一个visited标记每个点以前被访问过没有即可。 回溯对于一个节点可能访问多次,也可能一次都不访问,所以要记录这次遍历是否被访问,而不是记录以前有没有被访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值