本文内容是基于小象学院——林沐 《面试算法 LeetCode 刷题班》,后期仍将对相关内容进行不定期更新!
问题描述:
用一个二维数组代表一张地图,全由“0”和“1”组成,其中“0”代表水域,“1”代表小岛,小岛“1”被水域“0”所包围,当小岛土地“1”在水平和垂直方向相连接时,认为是同一块土地。求这张地图中小岛的数量。
算法思路1: DFS
1.标记当前搜索位置已被搜索
2.按照方向数组的4个方向,拓展4个新位置 newx,newy
3.若新位置不在地图范围内,则忽略。
4.如果新位置未曾到达过、且是陆地,继续DFS该位置。
class Solution {
public:
void DFS(vector> &mark, vector> &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>& grid) {
int island_num = 0;
vector> mark;
for (int i = 0; i < grid.size(); i++)
{
mark.push_back(vector());
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> &mark, vector> &grid, int x, int y)
{
static const int dx[] = { -1,1,0,0 };
static const int dy[] = { 0,0,-1,1 };
queue> 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>& grid) {
int island_num = 0;
vector> mark;
for (int i = 0; i < grid.size(); i++)
{
mark.push_back(vector());
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;
}
};
问题描述:
已知两个单词(一个为起始单词与结束单词),一个单词词典,根据转换规则计算从起始单词到结束单词最短转换步数:
转化规则如下:
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 &wordList,map> &graph)
{
wordList.push_back(beginWord);
for (int i = 0; i < wordList.size(); i++)
{
graph[wordList[i]] = vector();
}
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,搜索过程中记录到达步数:
实现图的宽度优先搜索:
int BFS_graph(string &beginWord, string &endWord,map>&graph)
{
queue> Q;
std::set 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 &neighbors = graph[node];
for (int i = 0; i < 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 &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 &wordList,map> &graph)
{
wordList.push_back(beginWord);
for (int i = 0; i < wordList.size(); i++)
{
graph[wordList[i]] = vector();
}
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]);
}
}
}
}
int BFS_graph(string &beginWord, string &endWord,map>&graph)
{
queue> Q;
std::set 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 &neighbors = graph[node];
for (int i = 0; i < 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& wordList) {
map> graph;
connect_graph(beginWord, wordList, graph);
return BFS_graph(beginWord, endWord, graph);
}
};
问题描述:
已知两个单词(一个为起始单词与结束单词),一个单词词典,根据转换规则计算从起始单词到结束单词所有的最短路径:
转化规则如下:
1.在转化时,只能转换单词中的1个字符
2.转换得到的新词必须在单词词典中
主要思考问题:
需要使用新的结构来构建队列中的元素,到达某一个位置可能存在多条路劲,使用映射记录到达每个位置的最短需要的步数,新拓展到的位置只要未曾到达或到达步数与最短步数相同,即将该位置添加到队列中,从而存储了从不同前驱到达该位置的情况。
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 &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& wordList,map> &graph)
{
int has_begin_word = 0;
for (int i = 0; i < wordList.size(); i++)
{
if (wordList[i] == beginWord) {
has_begin_word = 1;
}
graph[wordList[i]] = vector();
}
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]);
}
}
if (has_begin_word == 0 && connect(beginWord, wordList[i])) {
graph[beginWord].push_back(wordList[i]);
}
}
}
void BFS_graph(string &beginWord, string &endWord,map>&graph,
vector &Q,vector &end_word_pos)
{
map 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 &node = Q[front].node;
int step = Q[front].step;
if (min_step != 0 && min_step < step)
{
break;
}
if (node == endWord)
{
min_step = step;
end_word_pos.push_back(front);
}
const vector &neighbors = graph[node];
for (int i = 0; i < 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> findLadders(string beginWord, string endWord, vector& wordList) {
map> graph;
connect_graph(beginWord, wordList, graph);
vector Q;
vector end_word_pos;
BFS_graph(beginWord, endWord, graph, Q, end_word_pos);
vector> result;
for (int i = 0; i < end_word_pos.size(); i++)
{
int pos = end_word_pos[i];
vector path;
while (pos != -1)
{
path.push_back(Q[pos].node);
pos = Q[pos].parent_pos;
}
result.push_back(vector());
for (int j = path.size()-1; j >= 0; j--)
{
result[i].push_back(path[j]);
}
}
return result;
}
};
问题描述:
已知一个数组,保存了n个(n<=15)火柴棍,问可否使用这n个火柴棍摆成1个正方形?
问题思考:
暴力搜索(回溯法)最多有 4^15种可能,肯定不可取,所以:
正常算法如下:
将正方形的4条边视作4个捅,将每个火柴回溯的放置在每个桶中,在放完n个火柴杆后,检查4个桶中的火柴杆长度和是否相同,相同返回真,否则返回假,在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。
优化1: n个火柴杆的总和对4取余需要为 0 ,否则返回假
优化2:火柴杆按照从大到小的顺序排序,先尝试大的减少回溯可能
优化3:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆
class Solution {
public:
bool generate(int i , vector & nums, int target, int bucket[])
{
if (i >=nums.size())
{
return bucket[0] == target &&
bucket[1] == target &&
bucket[2] == target &&
bucket[3] == target;
}
for (int j = 0; j < 4; j++)
{ // 4 个桶中分别尝试
if (bucket[j] + nums[i] > target) {
continue;
}
bucket[j] += nums[i];
if (generate(i + 1, nums, target, bucket)) {
return true;
}
bucket[j] -= nums[i];
}
return false;
}
bool makesquare(vector& nums) {
if (nums.size()<4)
{
return false;
}
int sum = 0;
for (int i = 0; i < 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);
}
};
问题描述:
已知一个 m*n 的二维数组,数组存储正整数,代表一个个单元的高度(立方体),将这些立方体想象成水槽,问如果下雨这些立方体中会有多少积水。
算法思路:
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 &a, const Qitem &b) {
return a.h > b.h;
}
};
class Solution {
public:
int trapRainWater(vector>& heightMap) {
priority_queue, cmp> Q;
if (heightMap.size() < 3 || heightMap[0].size() <3)
{
return 0;
}
int row = heightMap.size();
int column = heightMap[0].size();
vector> mark;
for (int i = 0; i < row; i++)
{
mark.push_back(vector());
for (int j = 0; j < column; j++)
{
mark[i].push_back(0);
}
} // 至此先初始化一个与 heightMap 等维度的全零数组
for (int i = 0; i < 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 < 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 < 4; i++)
{
int newx = x + dx[i];
int newy = y + dy[i];
if (newx<0 || newx >=row ||
newy<0 || newy >=column || mark[newx][newy])
{
continue;
}
if (h>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;
}
};