一、岛屿的个数 LeetCode 200
题目:
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
思路:
int numIslands(vector<vector<char>>& grid) {
int island_num = 0;//初始化岛屿数量为零
vector<vector<int>> mark;//声明mark数组
for(int i = 0; i < grid.size();i++){
//初始化mark数组,使mark数组与grid同维并且初始值为0
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++){//遍历grid所有=坐标
for(int j=0; j < grid[i].size();j++){
if(mark[i][j] == 0 && grid[i][j]=='1'){//如果没有访问过改点,并且是陆地则进行岛屿搜索
DFS(mark,grid,i,j);//搜索完成岛屿数加1 或者BFS(mark,grid,newx,newy);
island_num++;
}
}
}
return island_num;
}
1、深搜搜索岛屿
//深搜实现对各个岛屿的搜索
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 = x + dx[i];//遍历到的新的坐标点
int newy = y + dy[i];
if(newx < 0||newy < 0||newx>=mark.size()||newy>=mark[newx].size())
{//如果超出边界,则遍历新的坐标点
continue;
}
if(mark[newx][newy] == 0 && grid[newx][newy]=='1'){
//如果新的坐标点没有访问过并且是陆地,则继续从该节点进行搜索
DFS(mark,grid,newx,newy);
}
}
}
2、宽搜搜索岛屿
//宽搜实现对各个岛屿的搜索
void BFS(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};
queue<pair<int, int>> Q;//宽度优先搜索的队列
Q.push(make_pair(x,y));//将当前坐标push进搜索队列中
while(!Q.empty()){
x = Q.front().first;//取出坐标
y = Q.front().second;
Q.pop();//将当前节点弹出
for(int i = 0;i<4;i++){
int newx = x + dx[i];
int newy = y + dy[i];
if(newx < 0||newy < 0||newx>=mark.size()||newy>=mark[newx].size())
{//如果超出边界,则遍历新的坐标点
continue;
}
if(mark[newx][newy] == 0 && grid[newx][newy]=='1'){
//如果新的坐标点没有访问过并且是陆地,则将该节点放进队列准备搜索,并标记该店
Q.push(make_pair(newx,newy));
mark[newx][newy] = 1;
}
}
}
}
二、单词接龙 LeetCode 127
题目:
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出: 5
解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: 0
解释: endWord “cog” 不在字典中,所以无法进行转换。
思路:
1、图的构造与表示
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 construct_graph(string &begin_word,
vector<string>& word_list,
map<string, vector<string>> &graph){
word_list.push_back(begin_word);//将初始词汇push进词汇表中
for(int i=0; i<word_list.size();i++){
graph[word_list[i]] = vector<string>();//初始化以word_list为key的图
}
for(int i=0; i< word_list.size();i++){//遍历图中所有词汇将能与之转换的单词存进字符串数组中
for(int j=i+1;j<word_list.size();j++){
if(connect(word_list[i],word_list[j])){
graph[word_list[i]].push_back(word_list[j]);//因为是无向图因此能够相互转换
graph[word_list[j]].push_back(word_list[i]);
}
}
}
}
2、图的宽搜遍历
int BFS_graph(string &begin_word, string &end_word,map<string, vector<string>> &graph){
queue<pair<string,int>> Q;//搜索队列<顶点,步数>
set<string> visited;//记录访问过的节点
Q.push(make_pair(begin_word,1));//将第一个单词放进搜索队列,并记录
visited.insert(begin_word);
while(!Q.empty()){
string node = Q.front().first;//取出第一个节点的值以及步数后,将该节点弹出
int step = Q.front().second;
Q.pop();
if(node == end_word) //如果已经搜索到最后一个单词返回步数
return step;
const vector<string> &neighbors = graph[node];//将当前节点的所有能够转换的单词存进neighbors中
for(int i = 0; i<neighbors.size();i++){//遍历所有可以转换的单词
if(visited.find(neighbors[i])==visited.end()){//如果没访问过该单词则将其push进搜索数组中且步数加1
Q.push(make_pair(neighbors[i],step+1));
visited.insert(neighbors[i]);//同时记录该顶点
}
}
}
}
3、总思路
class Solution{
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
map<string, vector<string>> graph;
set<string> test;//将wordList的单词放入test中检测最后一个单词是否在wordList中
for(int i=0;i<wordList.size();i++){
test.insert(wordList[i]);
}
if(test.find(endWord)==test.end())
return -1;
else{
construct_graph(beginWord,wordList,graph);//建立图
return BFS_graph(beginWord,endWord,graph);
}
}
};
三、单词接龙2 LeetCode126
题目:
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出:
[
[“hit”,“hot”,“dot”,“dog”,“cog”],
[“hit”,“hot”,“lot”,“log”,“cog”]
]
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: []
思路:
与上一个题相比,这个题稍微复杂,在搜索出最短路径的同时还希望能够输出所以从开始单词到结尾单词的路径。因此,这就出现了一个问题,那就是如何在搜索路径的同时保存路径,同时,如果有多条最短路径又应该如何同时保存下这些路径。其次,在建立图的过程中,如果初始单词在单词表中,那么在搜索路径时势必会对结果路径带来影响,那么这个问题又应该如何解决呢?
对于搜索过程中存在的问题:
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){}
};
void BFS_graph(string &begin_word, string &end_word, map<string, vector<string>> &graph,
vector<Qitem> &Q, //使用vector实现队列,可保留所有信息
vector<int> &end_word_pos){//重点word所在队列位置的下标
map<string,int> visited;//访问过的顶点<顶点,步数>
int min_step = 0;//到达end_word的最小步数
Q.push_back(Qitem(begin_word.c_str(),-1,1));//将第一个单词放进搜索队列,起始单词前驱为-1
visited[begin_word] = 1;//起始单词步数为1
int front = 0;//代表队列头部
while(front != Q.size()){
string &node = Q[front].node;//取出第一个节点的值以及步数后,将该节点弹出
int step = Q[front].step;
if(min_step !=0 && step > min_step)//如果搜索完成则跳出循环
break;
if(node == end_word){ //如果已经搜索到最后一个单词令min_step等于step,此时front为结尾单词所在位置
min_step = step;
end_word_pos.push_back(front);
}
const vector<string> &neighbors = graph[node];//将当前节点的所有能够转换的单词存进neighbors中
for(int i = 0; i<neighbors.size();i++){//遍历所有可以转换的单词
if(visited.find(neighbors[i])==visited.end()||visited[neighbors[i]] == step+1){//如果没访问过该单词或者没有另一条更短路径
Q.push_back(Qitem(neighbors[i],front,step+1));//记录该顶点
visited[neighbors[i]] = step+1;//并且访问步数+1
}
}
front++;
}
}
对于建图过程中出现的问题:
对此可以设置一个变量对初始单词进行特殊处理,如果改单词不在单词表中才进行插入操作
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 construct_graph(string &begin_word,
vector<string>& word_list,
map<string, vector<string>> &graph){
int has_begin_word = 0;//用来排除可能有初始单词的情况
for(int i=0; i<word_list.size();i++){
if(begin_word == word_list[i])
has_begin_word = 1;
graph[word_list[i]] = vector<string>();//初始化以word_list为key的图
}
for(int i=0; i< word_list.size();i++){//遍历图中所有词汇将能与之转换的单词存进字符串数组中
for(int j = i+1;j<word_list.size();j++){
if(connect(word_list[i],word_list[j])){
graph[word_list[i]].push_back(word_list[j]);//因为是无向图因此能够相互转换
graph[word_list[j]].push_back(word_list[i]);
}
}
if(has_begin_word == 0 && connect(begin_word,word_list[i]))//如果单词列表中没有初始词汇,并且可以与第i个单词相互转换
graph[begin_word].push_back(word_list[i]);
}
}
四、火柴拼正方形 LeetCode 473
题目:
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
注意:
给定的火柴长度和在 0 到 10^9之间。
火柴数组的长度不超过15。
思路:
1、采用无优化的深度优先搜索
优化与剪枝:
class Solution {
public:
bool makesquare(vector<int>& nums) {
int sum=0;
if(nums.size()<4)//边的数量小于4直接返回false
return false;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
if(sum%4)//边数之和除有余数也不能拼成正方形
return false;
sort(nums.rbegin(),nums.rend());//nums从大到小排列
int bucket[4] ={0};//用四维数组代表四个边长
return generate(0, nums,sum/4,bucket);
}
private:
bool generate(int i, vector<int>& nums, int target, int bucket[]){
if( i >= nums.size()){//如果i扫描完所有边数则返回是否可以拼成正方形
return bucket[0]==target&&bucket[1]==target//必须要四个边都等于边数总和的1/4才能拼成
&&bucket[2]==target&&bucket[3]==target;
}
for(int j=0;j<4;j++){//对四个边依次摆放长为nums[i]的火柴
if(bucket[j]+nums[i] > target)//如果边的现有长度加上nums[i]大于边数总和的1/4则不放该火柴
continue;
bucket[j] += nums[i];//否则放该火柴
if(generate(i+1,nums,target,bucket))//递归放完所有火柴并且返回为真则说明能够摆放成正方形
return true;
bucket[j] -= nums[i];//回溯
}
return false;
}
};
2、位运算
class Solution {
public:
bool makesquare(vector<int>& nums) {
int sum=0;
if(nums.size()<4)//边的数量小于4直接返回false
return false;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
if(sum%4)//边数之和除有余数也不能拼成正方形
return false;
//位运算
vector<int> _subset;//和为sum/4的边的集合
vector<int> _halfset;//_subset的两两组合
int target = sum/4;
int all = 1<<nums.size();//所有可能的组合
for(int i=0;i<all;i++){
int sum = 0;//验证每种可能时记得初始化
for(int j =0 ;j<nums.size();j++){
if(i & (1<<j)){//如果i代表的组合包含了第j个位置,则sum加上第j个位置数
sum += nums[j];
}
if(sum == target){//和为target则说明该种组合方式满足要求,放进待选集合中
_subset.push_back(i);
}
}
}
for(int i=0; i<_subset.size();i++){
for(int j = i+1;j<_subset.size();j++){
//将待选集合中的数两两组合,如果组成他们的数不重合则将它们或事物结果放进_halfset中
if((_subset[i]&_subset[j])==0)
_halfset.push_back(_subset[i]|_subset[j]);
}
}
for(int i=0; i<_halfset.size();i++){
for(int j = i+1;j<_halfset.size();j++){
//将halfset集合中的数两两组合,如果组成他们的数不重合则说明可以拼成正方形
if((_halfset[i]&_halfset[j])==0)
return true;
}
}
return false;
}
};
(leetcode上容易超时)
五、收集雨水 2 LeetCode407
题目:
给定一个 m x n 的矩阵,其中的值均为正整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
说明:
m 和 n 都是小于110的整数。每一个单位的高度都大于0 且小于 20000
示例:
给出如下 3x6 的高度图:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4。
如上图所示,这是下雨前的高度图[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] 的状态。
下雨后,雨水将会被存储在这些方块中。总的接雨水量是4。
思路:
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<vector<int>>& heightMap) {
priority_queue<Qitem, vector<Qitem>, cmp> Q;
if(heightMap.size()<3||heightMap[0].size()<3)//维数小于3是不可能积水的
return 0;
int row = heightMap.size();//记录heightMap的行数
int column = heightMap[0].size();//记录heightMap的列数
vector<vector<int>> mark;//记录有没有进展的数组,与hightMap同维
for(int i = 0; i < row;i++){//初始化
mark.push_back(vector<int>());
for(int j = 0;j < column; j++)
mark[i].push_back(0);
}
//先将heightMap四周的元素push进Q中,并在mark记录
for(int i=0; i < row; i++){
Q.push(Qitem(i,0,heightMap[i][0]));
mark[i][0] = 1;
Q.push(Qitem(i,column-1,heightMap[i][column-1]));
mark[i][column-1] = 1;
}
for(int j=1; j < column-1; j++){
Q.push(Qitem(0,j,heightMap[0][j]));
mark[0][j] = 1;
Q.push(Qitem(row-1,j,heightMap[row-1][j]));
mark[row-1][j] = 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||newy<0||newx >= row ||newy >= column||mark[newx][newy])
continue;//如果超过边界或者已入队列则搜索下一个方向
if(h > heightMap[newx][newy]){
//新搜索到的节点的高度小于现在的节点,并且没有在搜索过则会积水
result += h-heightMap[newx][newy];
heightMap[newx][newy] = h; //并且新坐标的高度要更新!!!
//mark[newx][newy] = 1;
}
Q.push(Qitem(newx,newy,heightMap[newx][newy]));//将该点放进队列
mark[newx][newy] = 1;//在mark上标记
}
}
return result;
}
};