记录算法基础题思路:
step1:
岛屿数量:https://leetcode-cn.com/problems/number-of-islands/
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
public:
int numIslands(vector<vector<char>>& grid) {
/* 核心算法在于设计出算法从图中任何一个点出发,
探索出地图该岛屿的全部土地,并标记(宽度或深度搜索))。
遍历一次地图,如果为陆地,且未被mark就开始探索*/
int land_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(mark, grid, i, j); //方法二:(广)宽度搜索
land_num++;
}
}
}
return land_num;
}
void DFS(vector<vector<int>>& mark, vector<vector<char>>& grid,
int x, int y) {
/* 方法一 深度搜索(DFS):标记当前位置-》往4个方向拓展-》
若新位置在地图范围内/是土地/未被探索标记-》
以此新地方为位置继续DFS */
mark[x][y] = 1;//标记当前探索位置
const int dx[] = {-1, 1, 0, 0};//方向数组
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 (grid[newx][newy] == '1' && mark[newx][newy] == 0) {
DFS(mark, grid, newx, newy);
}
}
}
void BFS(vector<vector<int>>& mark, vector<vector<char>>& grid,
int x, int y) {
/* 方法二 广(宽)度搜索(BFS):标记当前位置送que-》
只要队列不空 取出头元素,按4个方向拓展新位置-》
若新位置在地图范围内/是土地/未被探索标记 继续入que*/
mark[x][y] = 1;//标记当前探索位置
const int dx[] = {-1, 1, 0, 0};//方向数组
const int dy[] = {0, 0, -1, 1};
queue <pair<int, int>> pos_que;
mark[x][y] = 1;
pos_que.push(make_pair(x, y));
while (!pos_que.empty()) {
/* 取点 */
x = pos_que.front().first;
y = pos_que.front().second;
pos_que.pop();
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 (grid[newx][newy] == '1' && mark[newx][newy] == 0) {
mark[newx][newy] = 1;
pos_que.push(make_pair(newx, newy));
}
}
}
}
词语阶梯:https://leetcode-cn.com/problems/word-ladder/
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
/* 先宽度搜索找到最小路径,
遍历的过程记录下begin到每个word到的step, 以及每个word前一个word(可能多个),
然后再深度查找 从 endword 所有可能的路径,
从endword往前找,直到找到begin,就把过程记录放到res中 */
vector<vector<string>> res;
bool can_search = false;
for (auto a : wordList) {
if (a == endWord) {
can_search = true;
}
}
if (can_search == false) return res;
unordered_map <string, int> dist_to_begin; // 存储起始点到每个点距离
unordered_map<string, unordered_set<string>> from; // 记录前一个单词(距离相同)
queue <pair<string, int>> node_que; // pair为节点和到起点距离
unsigned int min_step = 0;
node_que.push({ beginWord, 0 });
dist_to_begin[beginWord] = 0;
from[beginWord] = unordered_set<string>{ "" };
while (!node_que.empty()) {
for (int i = node_que.size(); i > 0; i--) {
string node = node_que.front().first;
int step = node_que.front().second;
node_que.pop();
for (auto &word : wordList) {
if (conect(node, word)) {
if (dist_to_begin.count(word) == 0) { //如果没有达到过
node_que.push({ word, step + 1 });
dist_to_begin[word] = step + 1;
from[word].insert(node);
} else if (dist_to_begin[word] == step + 1) {
from[word].insert(node);
}
if (word == endWord) {
min_step = step + 1;
}
}
}
}
if (min_step != 0) {
break;
}
}
vector<string> path;
dfs_for_path(from, res, endWord, path);
for (auto &i : res) {
reverse(i.begin(), i.end());
}
return res;
}
private:
bool conect(string &a, string &b) {
int diff_cnt = 0;
for (int i = 0; i < a.length(); i++) {
if (a[i] != b[i]) {
diff_cnt++;
if (diff_cnt > 1) { //优化时间
return false;
}
}
}
return diff_cnt == 1;
}
void dfs_for_path(unordered_map<string, unordered_set<string>>& from,
vector<vector<string>>& res, string word, vector<string> path) {
/* 若当前节点的上一级节点为空,表明到达了起始节点,
那么将这个节点推入path中,然后将完整的path推入答案中 */
if (from[word] == unordered_set<string>{ "" }) {
path.push_back(word);
res.push_back(path);
return;
}
//每遍历到一个节点那么就将它放入路径中
path.push_back(word);
//然后去深度优先遍历他的每一个上层节点
for (auto i : from[word])
dfs_for_path(from, res, i, path);
}
};
火柴摆正方形:https://leetcode-cn.com/problems/matchsticks-to-square/submissions/
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
public:
bool makesquare(vector<int>& nums) {
/* 递归回溯 + 剪枝
回溯:每个火柴都在4边尝试放置,如果放完后四边相同即true,否则为false
剪枝:1、入口条件 和不为4整数倍 2、某一边超过target 3、先放大的试错(排序后) */
int sum = 0;
for (auto i: nums) {
sum += i;
}
if (nums.size() < 4 || sum % 4 != 0) {
return false;
}
sum /= 4;
sort(nums.rbegin(), nums.rend()); //剪枝
int side_sum[4] = { 0 };
return backtrace_node(0, nums, side_sum, sum);
}
private:
bool backtrace_node(int index, vector<int>& nums, int side_sum[], int target) {
if (index >= nums.size()) {
return side_sum[0] == target && side_sum[1] == target &&
side_sum[2] == target && side_sum[3] == target;
}
for (int i = 0; i < 4; i++) { // 把nums尝试放到每一边
if (side_sum[i] + nums[index] > target) {
continue; // 剪枝
}
side_sum[i] += nums[index];
if (backtrace_node(index + 1, nums, side_sum, target) == true) {
return true;
}
side_sum[i] -= nums[index];
}
return false;
}
收集雨水2:https://leetcode-cn.com/problems/trapping-rain-water-ii/submissions/
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
示例:
给出如下 3x6 的高度图:
[
[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;
}
};
public:
int trapRainWater(vector<vector<int>>& heightMap) {
/* 使用最小堆搜索, 越底越优先,从四周的点开始 宽度优先遍历,
使用一个二维数组堆点进行标记,遍历过改点后不再访问呢,
只要堆中不空,就取出顶点进行访问,按上下左右方向进行拓展,
某点(x,y,h)如果h > heightMap[newx][newy],
最终结果+= h-heightMap[newx][newy],
heightMap[newx][newy]赋值为h(升高水面),
然后将(newx newy heightMap[newx][newy])入堆,并标记 */
if (heightMap.size() < 3 || heightMap[0].size() < 3) {
return 0; // 行或者列小于3无法存水
}
int row = heightMap.size();
int column = heightMap[0].size();
vector <vector <int>> mark;//标记哪些点已经访问
priority_queue <Qitem, vector<Qitem>, cmp> min_heap;
/* 构建mark */
for (int i = 0; i < row; i++) {
mark.push_back(vector<int>());
for (int j = 0; j < column; j++) {
mark[i].push_back(0);
}
}
/* 把四周的点都先入堆 */
for (int i = 0; i < row; i++) {
min_heap.push(Qitem(i, 0, heightMap[i][0]));
mark[i][0] = 1;
min_heap.push(Qitem(i, column - 1, heightMap[i][column - 1]));
mark[i][column - 1] = 1;
}
for (int i = 1; i < column - 1; i++) {
min_heap.push(Qitem(0, i, heightMap[0][i]));
mark[0][i] = 1;
min_heap.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 res = 0;
while (!min_heap.empty()) {
int x = min_heap.top().x;
int y = min_heap.top().y;
int h = min_heap.top().h;
min_heap.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]) { //如果新的遍历点 比当前点底,可以存h - newh
res += h - heightMap[newx][newy];
heightMap[newx][newy] = h; // 更新新的这个点h(水位已经到了h)
}
min_heap.push(Qitem(newx, newy, heightMap[newx][newy]));
mark[newx][newy] = 1;
}
}
return res;
}