第一题:岛屿数量
用一个二维数组代表一张地图,这张地图由字符“0”与字符“1”组成,其中“0”代表水域,“1”代表小岛土地,小岛“1”被水"0"所包围,当小岛土地“1”在水平和垂直方向相连接时,认为是同一块土地。求这张地图中小岛的数量。Leetcode 200
思考:给定该二维地图grid,与一个二维标记数组mark(初始化为0),mark数组的每个位置都与grid对应,设计一个搜索算法(BFS和DFS),从该地图中的某个岛的某个位置触发,探索该岛的全部土地,将探索到的位置在mark数组中标记为1.
DFS:
1.标记当前搜索位置已被搜索(标记当前位置的mark数组为1)
2.按照方向数组得到的4个方向,拓展4个新位置newx、newy。
3.若新位置不在地图范围内,则忽略
4.如果新位置未曾到达过(mark[newx][newy]为0)、且是陆地(grid[newx][newy]为“1”),继续DFS该位置。
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,1};
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,x, y); //继续深搜
}
}
BFS:
1.设置搜索队列Q,标记mark[x][y]=1,并将待搜索的位置(x,y)push进入队列Q;
2.只要队列不空,即取队头元素,按照方向数组的4个方向,拓展4个新位置newx,newy。
3.若新位置不在地图范围内,则忽略。
4.如果新位置未曾到达(mark[newx][newy]为0)、且是陆地(grid[newx][newy]为“1”);将该新位置push进入队列,并标记mark[newx][newy]=1。
void BFS(vector<vector<int>> &mark,vector<vector<char>> &grid , int x, int y){
queue<pair<x,y>> Q;
static const int dx[]={-1,1,0,1};
static const int dy[]={0,0,-1,1}; //方向数组
Q.push(make_pair(x,y));
mark[x][y]=1;
while(!Q.empty()){
int x = Q.front().first;
int 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));
mark[newx][newy]=1;
}
}
}
}
整体算法思路:
1.设置岛屿数量island_num=0;
2.设置mark数组,并初始化.
3.遍历地图grid上所有的点,如果当前点是陆地,且未被访问过,调用搜索接口search(mark,grid,i,j);search可以是DFS或BFS;完成搜索之后islan_num++;
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(mark[i][j]==0 && grid[i][j]=='1'){
DFS(mark,grid,i,j); //或BFS
island_num++;
}
}
}
}
第二题:
已知两个单词(分别是起始单词与结束单词),一个单词词典,根据转换规则计算从起始单词到结束单词的最短转换步数。
转换规则如下:Leetcode 127
1.在转换时,只能转换单词中的一个字符
2.转换得到的新单词,必须在单词词典中。
思路:单词与单词之间的转换。可以理解为一张图,图的顶点为单词,若两单词之间可以互相转换,则这两个单词所代表的顶点间有一条边,求图中节点hit(beginword)到节点cog(endWord)的所有路径中,最少包括多少个节点。即图的宽度优先搜索。
使用map构造邻接表表示的图,map定义为string为key(代表图的顶点),vector为value(代表图的各个顶点邻接二代顶点)。如下图所示:
将bgeinword push进入wordlist。遍历wordList,对任意两个单词wordList[i]与wordList[j],若wordList[i]与wordList[j]只差一个字符,则将其相连。
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]);
}
}
}
}
图得到宽度遍历:
给定图的起点beginword,终点endWord,图graph,从beginword开始宽短优先搜索图graph,搜索过程中记录到达的步数;
1.设置搜索队列Q,队列节点为pair<顶点,步数>;设置集合visit,记录搜索过的顶点;将<beginword,1>添加至队列;
2.只要队列不空,取出队列头部元素:
(1).若取出的队列头部元素为wndword,返回到达当前节点的步数;
(2).否则拓展该节点,将于该节点相邻的且未添加到visit中的节点与步数同时添加至队列Q,并将拓展节点假如visit;
3.最终都无法搜索到endword,返回0
int BFS(string &beginword,string &endword,map<string,vector<string>>& graph){
vector<string> visit;
queue<pair<string,int>> Q; // 顶点,步数
Q.push(make_pair(begin,1));
visit.insert(beginword);
while(!Q.empty()){
string node = Q.front().first;
int step = Q.front().second;
Q.pop();
if(node==endword)
return step;
vector<string> neighbors = graph[node];
for(int i=0;i<neighbors.size();i++){
if(visit.find(heighbors[i])==visit.end()){
Q.push(make_pair(heighbors[i],step+1));
visit.insert(heighbors[i]);
}
}
}
return 0;
}
int ladderLength(string beginword,string endword, vector<string>& wordList){
map<string,vector<string>> graph;
construct_graph(beginword, wordList,graph);
return BFS(beginword,endword, graph);
}
第三题:火柴棍摆正方形
已知一个数组,保存了n个(n<=15)火柴棍,问可否使用这n个火柴棍摆成1个正方形?Leetcode 473
n个火柴杆(n<=15),每个火柴杆可以属于正方形的4个边中的其中1个。故,暴力搜索有4^15种可能
算法如下:
想象正方形的四条边即为四个桶,将每个火柴杆回溯的放置在每个桶中,在放完n个火柴杆后,检查四个桶中的火柴杆长度和是否相同,相同返回真,否则返回假;在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。
但是这个肯定会超时,所以我们需要对算法进行优化。
优化1:n个火柴杆的总和对4取余需要为0,否则返回假。
优化2:火柴杆按照从大到小的顺序排序,先尝试大的减少回溯可能。
优化3:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆。
bool generate(int i,int target,vector<int>& nums,int buckets[]){
if(i>=nums.size())
return buckets[0]==target && buckets[1]==target && buckets[2]==target && buckets[3]==target;
for(int j=0;j<4;j++){
if(bucket[j]+nums[i]>target)
continue;
buckte[j]+=nums[i];
if(generate(i+1,target, nums,buckets));
return true;
bucket[j]-=nums[i]; //回溯
}
return false;
}
bool makesquare(vector<int>& nums){
if(nums.szie()<4) return false;
int sum=0;
for(int i=0;i<nums.size();i++)
sum+=nums[i];
if(sum%4) return false;
sort(nums.rbegin(),nums.rend());
int buckets[]={0};
return generate(0,sum/4,nums,buckets);
}