例1:岛屿数量(LeetCode 200-中等)
题目:
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
实例:
输入:
11000
11000
00100
00011
输出: 3
思路分析:
使用深度优先或者广度优先搜索对每个陆地连通得区域全部搜索并标记,最终进行深搜或宽搜得次数就是岛屿得数量
代码:
使用深度优先:
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int result=0;
vector<vector<int>> mark;
for(int i=0;i<grid.size();i++){
vector<int> temp(grid[i].size(),0);
mark.push_back(temp);
}
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){
result++;
DFS(mark,grid,i,j);
}
}
}
return result;
}
void DFS(std::vector<std::vector<int>> &mark,
std::vector<std::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 || newx >=mark.size() ||
newy<0 || newy >=mark[newx].size()){
continue;
}
if(mark[newx][newy]==0 && grid[newx][newy]=='1'){
DFS(mark,grid,newx,newy);
}
}
}
};
使用广度优先:
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int result=0;
vector<vector<int>> mark;
for(int i=0;i<grid.size();i++){
vector<int> temp(grid[i].size(),0);
mark.push_back(temp);
}
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){
result++;
BFS(mark,grid,i,j);
}
}
}
return result;
}
void BFS(std::vector<std::vector<int>> &mark,
std::vector<std::vector<char>>& grid,int x,int y ){
static const int dx[] ={-1,1,0,0};
static const int dy[] ={0,0,-1,1};
std::queue<std::pair<int ,int>> Q;
Q.push(std::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[0].size()){
continue;
}
if(mark[newx][newy]==0 && grid[newx][newy]=='1'){
mark[newx][newy]=1;
Q.push(make_pair(newx,newy));
}
}
}
}
};
例2:词语接龙(LeetCode 127-中等)
题目:
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
思路:
构建成图,把问题转化为图的最短路径搜索问题。使用宽度优先搜索即可找到最短的路径
代码:
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 construct_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]);
}
}
}
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
map<string,vector<string>> graph;
construct_graph(beginWord,wordList,graph);
return BFS_graph(beginWord,endWord,graph);
}
int BFS_graph(string &beginWord,string &endWord,map<string,vector<string>> &graph){
//搜索队列<顶点,步数>
queue<pair<string,int>> Q;
set<string> 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;
}
for(int i=0;i<graph[node].size();i++){
if(visit.find(graph[node][i])==visit.end()){
Q.push(make_pair(graph[node][i],step+1));
visit.insert(graph[node][i]);
}
}
}
return 0;
}
};
例3:单词接龙2(LeetCode-126 困难)
题目:
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换后得到的单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
思路:
可上一例题唯一的区别就是要输出所有的路径,所以要想办法记录遍历的路径。
思路就是用vector来实现队列的逻辑,同时通过index记录上一个遍历元素是哪一个
代码:
class Solution {
private:
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){}
};
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
map<string,vector<string>> graph;
construct_graph(beginWord,wordList,graph);
vector<Qitem> Q;//使用vector实现的队列
vector<vector<string>> result;
vector<int> end_word_pos;
BSF_graph(beginWord,endWord,graph,Q,end_word_pos);
//找出所有的路径
for(int i=0;i<end_word_pos.size();i++){
int pos=end_word_pos[i];
vector<string> path;
//倒序写入path
while(pos!=-1){
path.push_back(Q[pos].node);
pos=Q[pos].parent_pos;
}
result.push_back(vector<string>());
//result[i].push_back(beginWord);
//正序存入result
for(int j=path.size()-1;j>=0;j--){
result[i].push_back(path[j]);
}
}
return result;
}
private:
void construct_graph(string &beginWord,vector<string>& wordList,map<string,vector<string>>& 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<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]);
}
}
if(has_begin_word==0 && connect(beginWord,wordList[i])){
graph[beginWord].push_back(wordList[i]);
}
}
}
bool connect(string& str1,string& str2){
int count=0;
for(int i=0;i<str1.length();i++){
if(str1[i]!=str2[i]){
count++;
}
}
return count==1;
}
void BSF_graph(string &beginWord,string &endWord,map<string,vector<string>> &graph,
vector<Qitem> &Q,vector<int> &end_word_pos){
//<单词,步数>
map<string,int> visit;
int min_step=0;
Q.push_back(Qitem(beginWord,-1,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 && step>min_step){
break;
}
//搜索到了记录一下终点位置,并且设置最小步数
if(node==endWord){
min_step=step;
end_word_pos.push_back(front);
}
const vector<string> &neighbors=graph[node];
for(int i=0;i<neighbors.size();i++){
//没访问过或者访问过但step不比之前大
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++;
}
}
};
例3:火柴棍摆正方形(LeetCode473- 中等)
题目:
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例:
输入: [1,1,2,2,2] 输出: true 解释: 能拼成一个边长为2的正方形,每边两根火柴。
输入: [3,3,3,3,4] 输出: false 解释: 不能用所有火柴拼成一个正方形。
思路:
回溯法:
正方形4个边就是4个桶,每个火柴杆回溯放置每个桶内,放置完n个后检查是否符合即可;但这么做没有任何优化,时间复杂度很高!!!
优化策略:
- n个火柴杆的总喝对4取余需要为0
- 火柴杆按从大到小顺序排序,先尝试大的减少回溯可能
- 每次放置不可放置超过总数和长度1/4长度的火柴
位运算法:
代码:
回溯法:
class Solution {
public:
bool makesquare(vector<int>& 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);
}
private:
bool generate(int i,vector<int>& nums,int target,int bucket[]){
if(i>=nums.size()-1){
return bucket[0]==target && bucket[1]==target
&& bucket[2]==target && bucket[3]==target;
}
//四个桶分别尝试
for(int j=0;j<4;j++){
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;
}
};
位运算法:
class Solution {
public:
bool makesquare(vector<int>& 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;
}
int target=sum/4;
//满足条件的一个边代表的集合
vector<int> ok_subset;
//满足条件的两个边代表的集合
vector<int> ok_half;
//计算初始化all值
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)){
sum+=nums[j];
}
}
if(sum==target){
ok_subset.push_back(i);
}
}
for(int i=0;i<ok_subset.size();i++){
for(int j=i+1;j<ok_subset.size();j++){
if( (ok_subset[i] & ok_subset[j]) ==0){
ok_half.push_back(ok_subset[i] | ok_subset[j]);
}
}
}
for(int i=0;i<ok_half.size();i++){
for(int j=i+1;j<ok_half.size();j++){
if((ok_half[i]&ok_half[j]) == 0){
return true;
}
}
}
return false;
}
};
例5:收集雨水2(LeetCode 407-困难)
题目:
给你一个 m x n
的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
示例:
给出如下 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。
提示:
1 <= m, n <= 110
0 <= heightMap[i][j] <= 20000
思想:
代码:
class Solution {
public:
int trapRainWater(vector<vector<int>>& heightMap) {
priority_queue<Qitem,vector<Qitem>,cmp> Q;
//行或列小于3就无法存防水
if(heightMap.size()<3 || heightMap[0].size()<3){
return 0;
}
int row=heightMap.size();
int column=heightMap[0].size();
vector<vector<int>> mask;
//初始化mask
for(int i=0;i<row;i++){
mask.push_back(vector<int>());
for(int j=0;j<column;j++){
mask[i].push_back(0);
}
}
//将四周的点添加到队列和mask
for(int i=0;i<row;i++){
Q.push(Qitem(i,0,heightMap[i][0]));
mask[i][0]=1;
Q.push(Qitem(i,column-1,heightMap[i][column-1]));
mask[i][column-1]=1;
}
for(int i=1;i<column-1;i++){
Q.push(Qitem(0,i,heightMap[0][i]));
mask[0][i]=1;
Q.push(Qitem(row-1,i,heightMap[row-1][i]));
mask[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 || mask[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]));
mask[newx][newy]=1;
}
}
return result;
}
private:
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 &q1,const Qitem &q2){
return q1.h>q2.h;
}
};
};