《面试算法 LeetCode 刷题班》——8. 搜索

本文内容是基于小象学院——林沐 《面试算法 LeetCode 刷题班》,后期仍将对相关内容进行不定期更新!

8.搜索

LeetCode 200 岛屿数量(M)

问题描述:

用一个二维数组代表一张地图,全由“0”和“1”组成,其中“0”代表水域,“1”代表小岛,小岛“1”被水域“0”所包围,当小岛土地“1”在水平和垂直方向相连接时,认为是同一块土地。求这张地图中小岛的数量。

算法思路1: DFS

1.标记当前搜索位置已被搜索

2.按照方向数组的4个方向,拓展4个新位置 newx,newy

3.若新位置不在地图范围内,则忽略。

4.如果新位置未曾到达过、且是陆地,继续DFS该位置。

class Solution {
public:

	void DFS(vector<vector<int>> &mark, vector<vector<char>> &grid, int x, int y)
	{
		mark[x][y] = 1;
		static const int dx[] = { -1,1,0,0 };
		static const int dy[] = { 0,0,-1,1 };
		for (int i = 0; i < 4; i++)
		{
			int newx = dx[i] + x;
			int newy = dy[i] + y;
			if (newx<0 || newx >= mark.size() ||
				newy<0 || newy >= mark[newx].size())
			{
				continue;
			}
			if (mark[newx][newy] == 0 && grid[newx][newy] == '1')
			{
				DFS(mark, grid, newx, newy);
			}
		}
	}

	int numIslands(vector<vector<char>>& grid) {
		int island_num = 0;
		vector<vector<int>> mark;
		for (int i = 0; i < grid.size(); i++)
		{
			mark.push_back(vector<int>());
			for (int j = 0; j < grid[i].size(); j++)
			{
				mark[i].push_back(0);
			}
		}


		for (int i = 0; i < grid.size(); i++)
		{
			for (int j = 0; j < grid[i].size(); j++) {
				if (grid[i][j] == '1' && mark[i][j] == 0)
				{
					DFS(mark, grid, i, j);
					island_num++;
				}
			}
		}
		return island_num;
	}
};

算法思路2: BFS

1.设置搜索队列Q,标记 mark[x][y] = 1, 并将待搜索的位置 (x,y) push 进队列Q

2.只要队列不为空,即取队头元素,按照方向数组的4个方向,拓展4个新位置,newx,newy

3.若新位置不在地图范围内,则忽略

4.如果新位置未曾到达过、且是陆地,将该位置push 进入队列,并标记 mark[newx][newy] = 1

class Solution {
public:

	void BFS(vector<vector<int>> &mark, vector<vector<char>> &grid, int x, int y)
	{
		static const int dx[] = { -1,1,0,0 };
		static const int dy[] = { 0,0,-1,1 };
		queue<pair<int, int>> Q;
		Q.push(make_pair(x, y));
		mark[x][y] = 1;
		while (!Q.empty())
		{
			x = Q.front().first;
			y = Q.front().second;
			Q.pop();
			for (int i = 0; i < 4; i++)
			{
				int newx = dx[i] + x;
				int newy = dy[i] + y;
				if (newx<0 || newx >= mark.size() ||
					newy<0 || newy >= mark[newx].size())
				{
					continue;
				}
				if (mark[newx][newy] == 0 && grid[newx][newy] == '1')
				{
					Q.push(make_pair(newx, newy));
					BFS(mark, grid, newx, newy);
				}
			}
		}
	}

	int numIslands(vector<vector<char>>& grid) {
		int island_num = 0;
		vector<vector<int>> mark;
		for (int i = 0; i < grid.size(); i++)
		{
			mark.push_back(vector<int>());
			for (int j = 0; j < grid[i].size(); j++)
			{
				mark[i].push_back(0);
			}
		}


		for (int i = 0; i < grid.size(); i++)
		{
			for (int j = 0; j < grid[i].size(); j++) {
				if (grid[i][j] == '1' && mark[i][j] == 0)
				{
					BFS(mark, grid, i, j);
					island_num++;
				}
			}
		}
		return island_num;
	}
};
LeetCode 127 词语阶梯 (M)

问题描述:

已知两个单词(一个为起始单词与结束单词),一个单词词典,根据转换规则计算从起始单词到结束单词最短转换步数:

转化规则如下:

1.在转化时,只能转换单词中的1个字符

2.转换得到的新词必须在单词词典中

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
             return its length 5.

算法思路

单词和单词之间的转换,可以理解为一张,图的顶点是单词,若两个单词之间可以相互转换,则这两个单词所代表的顶点间有一条边,求图中节点hit(开始单词)到节点cog(结束单词)的所有路径中,最少包括对少个节点。即图的宽度优先搜索

可以先基于上述关系使用 map 构建邻接表表示的图,将开始单词作为key,对任意两个单词,若他们只差一个字符,则将其相连。

首先构建图的邻接表:

bool connect(const string &word1, const string &word2)
{
	int cnt = 0;
	for (int i = 0; i < word1.length(); i++)
	{
		if (word1[i] != word2[i]) {
			cnt++;
		}	
	}
	return cnt == 1;
}

void connect_graph(string &beginWord, vector<string> &wordList,map<string,vector<string>> &graph)
{
	wordList.push_back(beginWord);
	for (int i = 0; i < wordList.size(); i++)
	{
		graph[wordList[i]] = vector<string>();
	}
	for (int i = 0; i < wordList.size(); i++)
	{
		for (int j = i+1; j < wordList.size(); j++)
		{
			if (connect(wordList[i],wordList[j]))
			{
				graph[wordList[i]].push_back(wordList[j]);
				graph[wordList[j]].push_back(wordList[i]);
			}
		}
	}
}

给定了起始单词,终点单词,图,从开始单词宽度优先搜索图graph,搜索过程中记录到达步数:

  1. 设置搜索队列Q,队列节点为 pair<顶点,步数>;设置集合 visit,记录搜索过的顶点;将<beginWord,1>添加到队列;
  2. 只要队列不空,取出队列头部元素,若等于结束单词则返回步数,否则拓展该节点,将与之相邻的未添加到visit中的节点与步数同时添加到队列Q,并将其拓展节点加入 visit;
  3. 若最终都无法搜索到endWord,返回0;

实现图的宽度优先搜索:

int BFS_graph(string &amp;beginWord, string &amp;endWord,map&lt;string,vector&lt;string&gt;&gt;&amp;graph)
{
	queue&lt;pair&lt;string, int&gt;&gt; Q;
	std::set&lt;string&gt; visit;
	Q.push(make_pair(beginWord, 1));
	visit.insert(beginWord);
	while (!Q.empty())
	{
		string node = Q.front().first;
		int step = Q.front().second;
		Q.pop();
		if (node == endWord)
		{
			return step;
		}
		const vector&lt;string&gt; &amp;neighbors = graph[node];
		for (int i = 0; i &lt; neighbors.size(); i++)
		{
			if (visit.find(neighbors[i]) == visit.end())
			{
				Q.push(make_pair(neighbors[i], ++step));
				visit.insert(neighbors[i]);
			}
		}
	}
	return 0;
}

最终提交代码:

bool connect(const string &amp;word1, const string &amp;word2)
{
	int cnt = 0;
	for (int i = 0; i &lt; word1.length(); i++)
	{
		if (word1[i] != word2[i]) {
			cnt++;
		}	
	}
	return cnt == 1;
}

void connect_graph(string &amp;beginWord, vector&lt;string&gt; &amp;wordList,map&lt;string,vector&lt;string&gt;&gt; &amp;graph)
{
	wordList.push_back(beginWord);
	for (int i = 0; i &lt; wordList.size(); i++)
	{
		graph[wordList[i]] = vector&lt;string&gt;();
	}
	for (int i = 0; i &lt; wordList.size(); i++)
	{
		for (int j = i+1; j &lt; wordList.size(); j++)
		{
			if (connect(wordList[i],wordList[j]))
			{
				graph[wordList[i]].push_back(wordList[j]);
				graph[wordList[j]].push_back(wordList[i]);
			}
		}
	}
}

int BFS_graph(string &amp;beginWord, string &amp;endWord,map&lt;string,vector&lt;string&gt;&gt;&amp;graph)
{
	queue&lt;pair&lt;string, int&gt;&gt; Q;
	std::set&lt;string&gt; visit;
	Q.push(make_pair(beginWord, 1));
	visit.insert(beginWord);
	while (!Q.empty())
	{
		string node = Q.front().first;
		int step = Q.front().second;
		Q.pop();
		if (node == endWord)
		{
			return step;
		}
		const vector&lt;string&gt; &amp;neighbors = graph[node];
		for (int i = 0; i &lt; neighbors.size(); i++)
		{
			if (visit.find(neighbors[i]) == visit.end())
			{
				Q.push(make_pair(neighbors[i], ++step));
				visit.insert(neighbors[i]);
			}
		}
	}
	return 0;
}

class Solution {
public:
	int ladderLength(string beginWord, string endWord, vector&lt;string&gt;&amp; wordList) {
		map&lt;string, vector&lt;string&gt;&gt; graph;
		connect_graph(beginWord, wordList, graph);
		return BFS_graph(beginWord, endWord, graph);
	}
};
LeetCode 126 词语阶梯2(H)

问题描述:

已知两个单词(一个为起始单词与结束单词),一个单词词典,根据转换规则计算从起始单词到结束单词所有的最短路径:

转化规则如下:

1.在转化时,只能转换单词中的1个字符

2.转换得到的新词必须在单词词典中

主要思考问题:

  1. 在宽度优先搜索是,如何保存宽度搜索时的路径
  2. 如果起点与中点间有多条路径,如何将多条路径全部搜索出
  3. 在建立连接图时,若单词表中已包含 beginWord ,还按 LeetCode 200 中的方法,会出现什么问题?

需要使用新的结构来构建队列中的元素,到达某一个位置可能存在多条路劲,使用映射记录到达每个位置的最短需要的步数,新拓展到的位置只要未曾到达或到达步数与最短步数相同,即将该位置添加到队列中,从而存储了从不同前驱到达该位置的情况。

struct Qitem {
	string node;
	int parent_pos;
	int step;
	Qitem(string node,int parent_pos, int step):node(node),parent_pos(parent_pos),step(step){}
};

class Solution {
public:
    bool connect(const string &amp;word1, const string &amp;word2)
    {
    	int cnt = 0;
    	for (int i = 0; i &lt; word1.length(); i++)
    	{
    		if (word1[i] != word2[i]) {
    			cnt++;
    		}	
    	}
    	return cnt == 1;
    }
    
    void connect_graph(string &amp;beginWord, vector&lt;string&gt;&amp; wordList,map&lt;string,vector&lt;string&gt;&gt; &amp;graph)
    {
    	int has_begin_word = 0;
    	for (int i = 0; i &lt; wordList.size(); i++)
    	{
    		if (wordList[i] == beginWord) {
    			has_begin_word = 1;
    		}
    		graph[wordList[i]] = vector&lt;string&gt;();
    	}
    	for (int i = 0; i &lt; wordList.size(); i++)
    	{
    		for (int j = i+1; j &lt; wordList.size(); j++)
    		{
    			if (connect(wordList[i],wordList[j]))
    			{
    				graph[wordList[i]].push_back(wordList[j]);
    				graph[wordList[j]].push_back(wordList[i]);
    			}
    		}
    		if (has_begin_word == 0 &amp;&amp; connect(beginWord, wordList[i])) {
    			graph[beginWord].push_back(wordList[i]);
    		}
    	}
    }
    
    void BFS_graph(string &amp;beginWord, string &amp;endWord,map&lt;string,vector&lt;string&gt;&gt;&amp;graph,
    	vector&lt;Qitem&gt; &amp;Q,vector&lt;int&gt; &amp;end_word_pos)
    {
    	map&lt;string, int&gt; visit;
    	int min_step = 0;
    	Q.push_back(Qitem(beginWord.c_str(), -1, 1));
    	visit[beginWord] = 1;
    	int front = 0;
    	while (front !=Q.size())
    	{
    		const string &amp;node = Q[front].node;
    		int step = Q[front].step;
    		if (min_step != 0 &amp;&amp; min_step &lt; step)
    		{
    			break;
    		}
    		if (node == endWord)
    		{
    			min_step = step;
    			end_word_pos.push_back(front);
    		}
    		const vector&lt;string&gt; &amp;neighbors = graph[node];
    		for (int i = 0; i &lt; neighbors.size(); i++)
    		{
    			if (visit.find(neighbors[i]) == visit.end() ||
    				visit[neighbors[i]]== step+1) {
    				Q.push_back(Qitem(neighbors[i], front, step + 1));
    				visit[neighbors[i]] = step + 1;
    			}
    		}
			front++;
    	}
    }

	vector&lt;vector&lt;string&gt;&gt; findLadders(string beginWord, string endWord, vector&lt;string&gt;&amp; wordList) {
		map&lt;string, vector&lt;string&gt;&gt; graph;
		connect_graph(beginWord, wordList, graph);
		vector&lt;Qitem&gt; Q;
		vector&lt;int&gt; end_word_pos;
		BFS_graph(beginWord, endWord, graph, Q, end_word_pos);
		vector&lt;vector&lt;string&gt;&gt; result;
		for (int i = 0; i &lt; end_word_pos.size(); i++)
		{
			int pos = end_word_pos[i];
			vector&lt;string&gt; path;
			while (pos != -1)
			{
				path.push_back(Q[pos].node);
				pos = Q[pos].parent_pos;
			}
			result.push_back(vector&lt;string&gt;());
			for (int j = path.size()-1; j &gt;= 0; j--)
			{
				result[i].push_back(path[j]);
			}
		}
		return result;
	}
};
LeetCode 473 火柴棍摆正方形

问题描述:

已知一个数组,保存了n个(n<=15)火柴棍,问可否使用这n个火柴棍摆成1个正方形?

问题思考:

暴力搜索(回溯法)最多有 4^15种可能,肯定不可取,所以:

  1. 回溯算法如何设计
  2. 递归的回溯搜索(DFS)何时返回真,何时返回假
  3. 普通的回溯搜索(DFS)可否解决该问题,如何对深度优先搜索进行优化(剪枝)

正常算法如下:

将正方形的4条边视作4个捅,将每个火柴回溯的放置在每个桶中,在放完n个火柴杆后,检查4个桶中的火柴杆长度和是否相同,相同返回真,否则返回假,在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。

优化1: n个火柴杆的总和对4取余需要为 0 ,否则返回假

优化2:火柴杆按照从大到小的顺序排序,先尝试大的减少回溯可能

优化3:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆

class Solution {
public:
	bool generate(int i , vector&lt;int&gt; &amp; nums, int target, int bucket[])
	{
		if (i &gt;=nums.size())
		{
			return bucket[0] == target &amp;&amp;
				bucket[1] == target &amp;&amp;
				bucket[2] == target &amp;&amp;
				bucket[3] == target;
		}

		for (int j = 0; j &lt; 4; j++)
		{ // 4 个桶中分别尝试
			if (bucket[j] + nums[i] &gt; target) { 
				continue;
			}
			bucket[j] += nums[i];
			if (generate(i + 1, nums, target, bucket)) {
				return true;
			}
			bucket[j] -= nums[i];
		}
		return false;

	}

	bool makesquare(vector&lt;int&gt;&amp; nums) {
		if (nums.size()&lt;4)
		{
			return false;
		}
		int sum = 0;
		for (int i = 0; i &lt; nums.size(); i++)
		{
			sum += nums[i];
		}
		if (sum % 4 != 0)
		{
			return false;
		}

		sort(nums.rbegin(), nums.rend()); // 从大到小排序
		int bucket[4] = { 0 };
		return generate(0, nums, sum / 4, bucket);
	}
};
LeetCode 407 收集雨水2 (H)

问题描述:

已知一个 m*n 的二维数组,数组存储正整数,代表一个个单元的高度(立方体),将这些立方体想象成水槽,问如果下雨这些立方体中会有多少积水。

算法思路:

  1. 搜索队列使用优先级队列(堆),越低矮的点优先级越高(最小堆),越优先进行搜索。
  2. 以矩形四周的点作为起始点进行广度优先搜索
  3. 使用一个二维数组堆push进入队列的点进行标记,之后搜索到该点后,不再push到队列中
  4. 只要优先级队列不空,即取出优先级队列队头元素进行搜索,按照上下左右四个方向进行拓展,在这个过程中忽略超出边界与已入队列的点
  5. 当对某点(x,y,h)进行拓展时(h即为(x,y)位置的高度,heightMap[x][y]),得到新点(newx,newy),高度为height[newx][newy],如果h大于height[newx][newy]: 最终结果 + = h - height[newx][newy];将height[newx][newy]赋值为h。 将(newx,newy,heightMap[newx][newy])push进入优先级队列,并做标记。

n

struct Qitem
{
	int x;
	int y;
	int h;
	Qitem(int _x, int _y, int _h):x(_x),y(_y),h(_h){}
}
	struct cmp {
	bool operator()(const Qitem &amp;a, const Qitem &amp;b) {
		return a.h &gt; b.h;
	}
};
class Solution {
public:
	int trapRainWater(vector&lt;vector&lt;int&gt;&gt;&amp; heightMap) {
		priority_queue&lt;Qitem, vector&lt;Qitem&gt;, cmp&gt; Q;
		if (heightMap.size() &lt; 3 || heightMap[0].size() &lt;3)
		{
			return 0;
		}
		int row = heightMap.size();
		int column = heightMap[0].size();
		vector&lt;vector&lt;int&gt;&gt; mark;
		for (int i = 0; i &lt; row; i++)
		{
			mark.push_back(vector&lt;int&gt;());
			for (int j = 0; j &lt; column; j++)
			{
				mark[i].push_back(0);
			}
		} // 至此先初始化一个与 heightMap 等维度的全零数组
	for (int i = 0; i &lt; row; i++)
	{
		Q.push(Qitem(i, 0, heightMap[i][0]));0 //加入第一列的元素
		mark[i][0] = 1;
		Q.push(Qitem(i, column - 1, heightMap[i][column - 1])); //加入最后一列的元素
		mark[i][column - 1] = 1;
	}
	for (int i = 1; i &lt; column - 1; i++)
	{
		Q.push(Qitem(0, i, heightMap[0][i])); //加入第一行的元素
		mark[0][i] = 1;
		Q.push(Qitem(row - 1, i, heightMap[row - 1][i])); //加入最后一行的元素
		mark[row - 1][i] = 1;
	}
    // 设置方向数组进行遍历
	static const int dx[] = { -1,1,0,0 };
	static const int dy[] = { 0,0,-1,1 };
	int result = 0;
	while (!Q.empty())
	{
		int x = Q.top().x;
		int y = Q.top().y;
		int h = Q.top().h;
		Q.pop();
		for (int i = 0; i &lt; 4; i++)
		{
			int newx = x + dx[i];
			int newy = y + dy[i];
			if (newx&lt;0 || newx &gt;=row ||
				newy&lt;0 || newy &gt;=column || mark[newx][newy])
			{
				continue;
			}
			if (h&gt;heightMap[newx][newy])
			{
				result += h - heightMap[newx][newy];
				heightMap[newx][newy] = h;
			}
			Q.push(Qitem(newx, newy, heightMap[newx][newy]));
			mark[newx][newy] = 1;
		}
	}
	return result;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值