当前位置: 首页 > news >正文

做的一些题目的答案和自己的一些思考

LeetCode

class Solution {
public:// 题目要求的接口函数int climbStairs(int n) {return helper(n); // 调用自定义函数}private:// 自定义辅助函数(带记忆化)int helper(int n) {if(n <= 2) return n;return helper(n-1) + helper(n-2);}
};

动态规划

最大子序和
1线性 DP最大子序和LC-53状态转移与决策分析2
class Solution {
public:int maxSubArray(vector<int>& nums) {int sz = nums.size();int f[100005];f[0] = nums[0];int max_val = f[0];for(int i = 1;i<sz;i++){f[i] = max(nums[i],f[i-1]+nums[i]);max_val = max(max_val,f[i]);}return max_val;}
};
最小路径和
最小路径和LC-64二维DP递推
class Solution {
public:int minPathSum(vector<vector<int>>& grid) {int m = grid.size();int n = grid[0].size();vector<vector<int>>dp(m,vector<int>(n,0));dp[0][0] = grid[0][0];for(int i = 1;i<m;i++) dp[i][0] = dp[i-1][0] + grid[i][0];for(int j = 1;j<n;j++) dp[0][j] = dp[0][j-1] + grid[0][j];for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){int x = dp[i-1][j];int y = dp[i][j-1];dp[i][j] = min(x,y)+grid[i][j];}}return dp[m-1][n-1];}
};
乘积最大子数组
乘积最大子数组LC-152状态转移变种(维护最大最小值)
class Solution {
public:int maxProduct(vector<int>& nums) {int sz = nums.size();int max_product = nums[0];int min_product = nums[0];int result = nums[0];for(int i = 1;i<sz;i++){//用临时变量存储 这样才能保证临时最小值求的是按照前i-1个数求的//因为这样max_product没在求临时最大值时改变int temp_max_product = max({nums[i],min_product*nums[i],max_product*nums[i]});int temp_min_product = min({nums[i],min_product*nums[i],max_product*nums[i]});max_product = temp_max_product;min_product = temp_min_product;result = max(max_product,result);}return result;}
};

由于 max_product 是当前的 max_product*nums[i] / min_product*nums[i] / nums[i] 中选的
所以即使 max_product 可能比 max_product*nums[i] / min_product*nums[i] / nums[i] 三者大
更新后的 max_product 也是 max_product*nums[i] / min_product*nums[i] / nums[i] 中的最大值
因此要有个全局变量 result来维护最大值

编辑距离
编辑距离LC-72经典双序列DP
class Solution {
public:int minDistance(string word1, string word2) {int sz1 = word1.size();int sz2 = word2.size();vector<vector<int>> dp(sz1 + 1, vector<int>(sz2 + 1, 0));//初始化for(int i = 0;i<=sz1;i++) dp[i][0] = i;for(int j = 0;j<=sz2;j++) dp[0][j] = j;//状态转移  dp[i][j]代表word1的前i个字母要修改到和word2的前j个字母 所需的次数//分三个操作//插入 相当于再word1的第i+1位置插入和word2的第j个字母对应的字母 接下来要处理到word1的1~i和word2的1~(j-1)一样即可//删除 dp[i-1][j] word1的第i个删除,要考虑word1的前i-1个变化到和word2的前j个相等//替换 dp[i-1][j-1]for(int i = 1;i<=sz1;i++){for(int j = 1;j<=sz2;j++){if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1];else{dp[i][j] = min({dp[i-1][j],dp[i][j-1],dp[i-1][j-1]})+1;}}}return dp[sz1][sz2];}
};
导弹拦截
导弹拦截洛谷-P1020最长不升子序列+贪心优化+二分 (卡时间)
关于upper_bound和lower_bound
#include <bits/stdc++.h>
using namespace std;const int N = 1e5 + 5;
int nums[N];int main() {int n = 0;while (cin >> nums[n]) n++; // 读取输入数据// 第一问:最长不升子序列vector<int> lis;for (int i = 0; i < n; ++i) {if (lis.empty() || nums[i] <= lis.back()) {lis.push_back(nums[i]);} else {auto it = upper_bound(lis.begin(), lis.end(), nums[i], greater<int>());*it = nums[i];}}cout << lis.size() << endl;// 第二问:最长上升子序列(Dilworth定理)vector<int> seq;for (int i = 0; i < n; ++i) {if (seq.empty() || nums[i] > seq.back()) {seq.push_back(nums[i]);} else {auto it = lower_bound(seq.begin(), seq.end(), nums[i]);*it = nums[i];}}cout << seq.size();return 0;
}

求最长不上升子序列时
利用了贪心+二分
其中贪心策略是,

  1. 维护一个递减数组 lis
  2. 遍历每个元素 x
    • 若 x ≤ lis.back(),直接加入 lis
    • 否则,找到 lis 中第一个比 x 小的位置,用 x 替换该位置的值。
  3. 最终 lis 的长度即为 LNIS 的长度。
    因为当 x ≤ lis.back() 时,LNIS 的长度不会有变化发生
    只有 x > lis.back() 时,可以将 x 添加到 lis 的末尾,此时 LNIS 的长度才会增加
    ⭐⭐⭐
  • 核心思想:让 lis 的每个位置保存该长度下最大的末尾元素
    ⭐⭐⭐
俄罗斯套娃信封问题
俄罗斯套娃信封问题LC-354二维LIS优化
class Solution {
public:int maxEnvelopes(vector<vector<int>>& envelopes) {// 正确排序:宽度升序,相同宽度则高度降序sort(envelopes.begin(), envelopes.end(), [](const vector<int>& a, const vector<int>& b) {if (a[0] == b[0]) return a[1] > b[1];return a[0] < b[0];});// 提取高度数组并寻找LISvector<int> lis;for (const auto& env : envelopes) {int h = env[1];//高度auto it = lower_bound(lis.begin(), lis.end(), h);if (it == lis.end()) {lis.push_back(h);} else {*it = h;}}return lis.size();}
};
/*
将排序改为先按宽度升序,宽度相同按高度降序。
在构建LIS时,仅比较高度,因为宽度已经通过排序处理好了。
使用标准的LIS算法处理高度数组。
排序后的数组是按照宽度升序,高度降序,那么相同宽度的信封不会相互干扰,
因为它们的高度是降序的,这样后面遇到的高度只会更小,避免重复排序的问题
也就是说这样排序后
为  a3 a2 a1 b2 b1 c2 c1(a<b<c)
则单独看高度时为
[3 2 1] [2 1] [2 1]
找高度的LIS,每组之内至多只有一个数字在LIS中
不会有多个,因为本身每一组中高度是降序的,不会在升序序列中出现*/
通配符匹配
通配符匹配LC-44复杂模式匹配DP
class Solution {
public:bool isMatch(string s, string p) {int m = s.size();int n = p.size();vector<vector<int >> dp(m + 1, vector<int>(n + 1));dp[0][0] = true;//空匹配空for (int i = 1; i <= m; i++) dp[i][0] = false;for (int i = 1; i <= n; i++) {if (p[i - 1] == '*') {dp[0][i] = true;} else {break;}}for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (s[i - 1] == p[j - 1]  || p[j - 1] == '?') dp[i][j] = dp[i - 1][j - 1];else if (p[j - 1] == '*') {dp[i][j] = dp[i - 1][j] || dp[i][j - 1];//略过'*'去匹配P的下一位||保留'*'的活性 匹配s的下一位}}}return dp[m][n];}
};

重点在于理解 dp[i][j] = dp[i-1][j]||dp[i][j-1]
这句的意思是,当 p[j-1]‘*’ 的时候,因为 ‘*’ 可以匹配 0 个或者多个字符

  • 所以当 ‘*’ 视为空时(匹配 0 个),相当于直接掠过 p 的这一位,从原先的由 s[i-1]p[j-1] 是否匹配的状态转移成了现在的看 s[i-1]p[j] 是否匹配的转移
  • ‘*’ 视为匹配多个 s 的字符时,相当于 p[j-1] 的这个 ‘*’ 一直保持活性,固定 p 的视角在 p[j-1] == ‘*’ 这里,移动 's' 进行比较
背包问题
数字组合 (0-1背包)
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[105];
int dp[105][10005];
/*
dp[i][j]前i个数中选 恰好等于j的方案数
dp[i][j] = max(dp[i-1][j], dp[i-1][j-a[i]]+1)
前i-1个数中恰好等于j-a[i]的方案数
*/
int main(){cin>>n>>m;for(int i = 1;i<=n;i++){cin>>a[i];}dp[0][0] = 1;//前n个数 dp初始化for(int i = 1;i<=n;i++){for(int j = 0;j<=m;j++){dp[i][j] = dp[i-1][j];if(j>=a[i]){dp[i][j] += dp[i-1][j-a[i]];}}}cout<<dp[n][m];return 0;
}

定义 dp[i][j] 表示 i 个数中选出若干个数,和为 j 的方案数

  • 维度解释
    • i:前 i 个数(决策阶段)。
    • j:当前目标和(状态变量)。

1. 决策分析
对于第 i 个数 a[i],有两种选择:

  • 不选:方案数等于前 i-1 个数中和为 j 的方案数,即 dp[i-1][j]
  • (仅当 j >= a[i]):方案数等于前 i-1 个数中和为 j - a[i] 的方案数,即 dp[i-1][j - a[i]]

2. 方程形式化
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − a [ i ] ] if  j ≥ a [ i ] d p [ i − 1 ] [ j ] otherwise dp[i][j] = \begin{cases} dp[i-1][j] + dp[i-1][j - a[i]] & \text{if } j \geq a[i] \\dp[i-1][j] & \text{otherwise}\end{cases} dp[i][j]={dp[i1][j]+dp[i1][ja[i]]dp[i1][j]if ja[i]otherwise

搜索

DFS && BFS
岛屿数量 (连通块问题)

相当于是图的 DFS 和 BFS

1-2搜索岛屿数量LC-200中等DFS/BFS基础(连通块问题)方向数组Yes, 经典的模板题
//DFS做法
class Solution {
private:void dfs(vector<vector<char>>& grid, int x,int y){int m = grid.size();int n = grid[0].size();grid[x][y] = 0;//代表访问过;if(x-1>=0&&grid[x-1][y] == '1') dfs(grid,x-1,y);if(x+1<m&&grid[x+1][y] == '1') dfs(grid,x+1,y);if(y-1>=0&&grid[x][y-1] == '1') dfs(grid,x,y-1);if(y+1<n&&grid[x][y+1] == '1') dfs(grid,x,y+1);}
public:int numIslands(vector<vector<char>>& grid) {int m = grid.size();int n = grid[0].size();int res = 0;for(int i = 0;i<m;i++){for(int j = 0;j<n;j++){{res++;dfs(grid,i,j);}}}return res;}
};//BFS做法
class Solution {
public:int numIslands(vector<vector<char>>& grid) {int m = grid.size();if(!m) return 0;int n = grid[0].size();//存放坐标queue <pair<int,int>> adj;int res = 0;for(int i = 0;i<m;i++){for(int j = 0;j<n;j++){if(grid[i][j] == '1'){res ++ ;adj.push({i,j});while(!adj.empty()){//获取队列中首元素的坐标后输出auto rc = adj.front();adj.pop();int r = rc.first;int c = rc.second;grid[r][c] == '0';if(r-1>=0 && grid[r-1][c] == '1'){adj.push({r-1,c});grid[r-1][c] = '0';}if(c-1>=0 && grid[r][c-1] == '1'){adj.push({r,c-1});grid[r][c-1] = '0';}if(r+1<m && grid[r+1][c] == '1'){adj.push({r+1,c});grid[r+1][c] = 0;}if(c+1<n && grid[r][c+1] == '1'){adj.push({r,c+1});grid[r][c+1] = '0';}}}}}return res;}
};
二叉树的层序遍历(树的BFS遍历)

相当于是树的 BFS

二叉树的层序遍历LC-102中等BFS模板(队列应用)
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> res;if(!root) return res;queue<TreeNode*> q;q.push(root);while(!q.empty()) {int levelSize = q.size();vector<int> levelVal;for(int i = 0; i < levelSize; ++i) {TreeNode* tmp = q.front();q.pop();levelVal.push_back(tmp->val);// 修正点:判断子节点存在才入队if(tmp->left) q.push(tmp->left);if(tmp->right) q.push(tmp->right);}res.push_back(levelVal);}return res;}
};

初始化队列,如果根节点存在则入队。
然后,当队列不为空时:

  • 获取当前层的节点数量size = q.size()
  • 创建一个临时vector level
  • 循环size次:
    • 取出队列前端节点
    • 将节点值加入level
    • 如果有左子节点,入队
    • 如果有右子节点,入队
  • 将level添加到res中
    这样就能正确分层处理节点,并且避免越界问题。
离开中山路(BFS求图中最短路径)
离开中山路洛谷
P1706
简单BFS最短路径基础
#include <bits/stdc++.h>
using namespace std;int n, st_x, st_y, ed_x, ed_y;
char grid[1005][1005];
int dist[1005][1005]; // 距离数组兼做访问标记int main() {cin >> n;for (int i = 1; i <= n; ++i)for (int j = 1; j <= n; ++j)cin >> grid[i][j];cin >> st_x >> st_y >> ed_x >> ed_y;// 检查起点合法性if (grid[st_x][st_y] != '0') {cout << -1;return 0;}queue<pair<int, int >> q;memset(dist, -1, sizeof(dist));//距离一开始都设置为不可达q.push({st_x, st_y});//起始点dist[st_x][st_y] = 0;//起点到起点的距离为0// 四方向数组int dirs[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};while(!q.empty()) {auto[x, y] = q.front();q.pop();if(x == ed_x && y == ed_y) {cout << dist[x][y];return 0;}for(auto [dx, dy] : dirs) {int nx = x+dx;int ny = y+dy;if(nx>=1&&nx<=n&&ny>=1&&ny<=n&&//在范围内grid[nx][ny] == '0'&&//是路dist[nx][ny]==-1//未访问过){dist[nx][ny] = dist[x][y]+1;q.push({nx,ny});}}}cout << -1; //不可达return 0;
}
记忆化搜索:斐波那契数
记忆化搜索示例:斐波那契数LC-509简单记忆化搜索入门
class Solution {
private:int dfs(int n ,vector<int>& memo){if(memo[n] != -1) return memo[n];if(n<=1){memo[n] = n;return n;}memo[n] = dfs(n-1,memo)+dfs(n-2,memo);return memo[n];}
public:int fib(int n) {vector<int>memo(n+2,-1);return dfs(n,memo);}
};
迷宫问题 (求路径条数回溯算法)
迷宫问题洛谷 P1605普及-DFS回溯(路径搜索)
#include<bits/stdc++.h>
using namespace std;
int n,m,t,st_x,st_y,ed_x,ed_y;
int grid[6][6];//迷宫
int res;
//0是路 1是障碍
void dfs(int x,int y){if(x == ed_x&&y==ed_y){res++;return ;}//到达终点if(grid[x][y]!=0){return ;//撞 障碍物/走过的部分 了}if(x<1||y<1||x>n||y>m){return ;//撞墙}grid[x][y] = 1;//标记走过了dfs(x+1,y);dfs(x-1,y);dfs(x,y+1);dfs(x,y-1);grid[x][y] = 0;
}
int main(){//长n宽m,t个障碍cin>>n>>m>>t;//起点 终点坐标cin>>st_x>>st_y>>ed_x>>ed_y;for(int i = 0;i<t;i++){int x,y;cin>>x>>y;grid[x][y] = 2;//代表障碍}//DFSif(grid[ed_x][ed_y]!=0){cout<<0;return 0;}dfs(st_x,st_y);cout<<res;return 0;return 0;
}
单词搜索 (DFS判断有无正确路线)
单词搜索LC-79中等DFS回溯(二维矩阵搜索)
class Solution {
private:bool found = false;void dfs(int x,int y,int& res,vector<vector<char>>& board,string word,vector<vector<int>>&vis){if(found) return ;//找到了 returnint m = board.size();int n = board[0].size();if(res == word.size()-1){found = true;return ;}vis[x][y] = 1;res ++;if(x-1>=0&&board[x-1][y] == word[res]&&vis[x-1][y]!=1){dfs(x-1,y,res,board,word,vis);}if(y-1>=0&&board[x][y-1] == word[res]&&vis[x][y-1]!=1){dfs(x,y-1,res,board,word,vis);}if(x+1<m&&board[x+1][y] == word[res]&&vis[x+1][y]!=1){dfs(x+1,y,res,board,word,vis);}if(y+1<n&&board[x][y+1] == word[res]&&vis[x][y+1]!=1){dfs(x,y+1,res,board,word,vis);}res -- ;vis[x][y] = 0;}
public:bool exist(vector<vector<char>>& board, string word) {int m = board.size();int n = board[0].size();//选出和word第一个字符一样的点的位置//存放在rc中vector<pair<int,int>>rc;for(int i = 0;i<m;i++){for(int j = 0;j<n;j++){if(board[i][j] == word[0]){rc.push_back({i,j});}}}//遍历这几个和word[0]一样的点,以他们为起点 进行回溯for(auto p : rc){int res = 0;int x = p.first;int y = p.second;vector<vector<int>>vis(m,vector<int>(n,0));dfs(x,y,res,board,word,vis);if(found){return true;}}return false;}
};

在用 dfs 时,因为要用到递归,所以莫忘记写递归结束的条件
该题是 res == word.size() - 1
不要把条件写在 dfs 函数外面(比如写在主函数中)
因为这样的话,dfs 就会一直循环,进行多次重复计算
并且该题目是只要有一种结果就是 true,所以可以用 found 变量,这样可以防止某一次已经找到了最终结果但仍重复计算的事情发生

滑雪(DFS记忆化搜索寻找最长路径)
滑雪洛谷 P1434普及+/提高记忆化搜索(DFS+DP)
#include <bits/stdc++.h>
using namespace std;
int m , n;
int grid[105][105];
int dp[105][105];
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
int dfs(int x,int y){if(dp[x][y] != 0) return dp[x][y];dp[x][y] = 1;//每次到达这个点都要初始化for(int i = 0;i<4;i++){int xx = x+dx[i];int yy = y+dy[i];if(xx>=1&&xx<=m&&yy>=1&&yy<=n&&grid[xx][yy]<grid[x][y]){dfs(xx,yy);dp[x][y] = max(dp[x][y],dp[xx][yy]+1);}}return dp[x][y];
}
int main(){//m行n列cin>>m>>n;for(int i = 1;i<=m;i++){for(int j = 1;j<=n;j++){cin>>grid[i][j];}}//int ans = 0;for(int i = 1;i<=m;i++){for(int j =1;j<=n;j++){ans = max(ans,dfs(i,j));}}cout<<ans;return 0;
}

dp[x][y] 表示 从坐标 (x, y) 出发,能滑行的最长路径的长度

状态转移方程:
d p [ x ] [ y ] = max ⁡ ( x x , y y ) ∈ neighbors grid [ x x ] [ y y ] < grid [ x ] [ y ] ( d p [ x x ] [ y y ] + 1 ) dp[x][y] = \max_{\substack{(xx, yy) \in \text{neighbors} \\ \text{grid}[xx][yy] < \text{grid}[x][y]}} \left ( dp[xx][yy] + 1 \right) dp[x][y]=max(xx,yy)neighborsgrid[xx][yy]<grid[x][y](dp[xx][yy]+1)

滑动谜题(BFS+状态压缩)
滑动谜题LC-773困难BFS+回溯+记忆化搜索+DP用了 BFS +状态压缩2
class Solution {
public:int slidingPuzzle(vector<vector<int>>& board) {string target= "123450";string start;//board拼接为start字符串for(int i = 0;i<2;i++){for(int j = 0;j<3;j++){start +=(board[i][j] + '0');}}//BFS初始化queue<pair<string,int>>q;vector<vector<int>>dirs = {{0,1},{0,-1},{1,0},{-1,0}};unordered_set<string>vis;//若某个状态存在 则vis.count(state) != 0//状态转移逻辑q.push({start,0});//start  0步vis.insert(start);//start  已访问while(!q.empty()){//取出来第一个元素auto [state,steps] = q.front();q.pop();//出队 勿忘//破壳if(state == target) return steps;//找0的位置int pos = state.find('0');//0所在位置的坐标int x = pos/3;int y = pos%3;for(int i = 0;i<4;i++){int nx = x+dirs[i][0];int ny = y+dirs[i][1];if(nx>=0&&ny>=0&&nx<2&&ny<3){string newState = state;int newPos = nx*3+ny;swap(newState[newPos],newState[pos]);//如果没被访问过if(!vis.count(newState)){vis.insert(newState);q.push({newState,steps+1});}}}}return -1;}
};

1. 状态压缩
将二维数组转换为字符串,例如:

初始状态 [[1,2,3],[4,0,5]] → 字符串 "123405"
目标状态 [[1,2,3],[4,5,0]] → 字符串 "123450"

2. BFS初始化

  • 队列:存储待处理的状态和当前步数。
  • 哈希集合:记录已访问状态,避免重复。
  • 方向数组:定义 0 的移动方向(上下左右)。
    3. 状态转移逻辑
  1. 找到 0 的位置:通过字符串的 find 方法。
  2. 转换为二维坐标:计算行 x = pos / 3,列 y = pos % 3
  3. 尝试四个方向移动
    • 计算新坐标 (nx, ny)
    • 检查是否越界(行范围 0~1,列范围 0~2)。
  4. 生成新状态:交换 0 和新位置的字符。
    4. 终止条件
    当前状态等于目标状态时,返回步数。

并查集

765. 情侣牵手

我们不关心具体交换过程,只关心“最终配对结果”。
把每对情侣看作一个编号(情侣对编号 = person_id / 2),那么:

  • 每两个相邻的人(row[i], row[i+1])分别属于两个情侣编号。

  • 如果这两个编号不是同一个,就表示“当前配错了位置”,我们就将这两个情侣编号合并成一个集合。
    最终我们会得到若干个连通块(集合),每个块内的人只能通过内部交换来变正确

  • 每个集合内部,需要的最少交换次数是:集合大小 - 1

  • 所以所有集合最少交换次数总和是:总对数 - 集合数量

class Solution {
private:int fa[32];int sets;int find(int x){if(fa[x] == x) return x;int root = find(fa[x]);//递归找最深层次的根节点,找每次递归点的父节点的父节点的…fa[x] = root;//扁平化处理return root;}void unite(int x,int y){if(find(x) != find(y)){int root_x = find(x);int root_y = find(y);fa[root_x] = root_y;//x挂在y上sets -- ;}}
public:int minSwapsCouples(vector<int>& row) {int sz = row.size();int m = sz/2;sets = sz/2;//集合个数for(int i = 0;i<m;i++) fa[i] = i;//该题是对情侣编号进行操作的for(int i = 0;i<sz;i+=2){unite(row[i]/2,row[i+1]/2);//合并的是两者的情侣编号}return m - sets;}
};
839. 相似字符串组

我们定义“相似”具有传递性:
如果 A ~ B 且 B ~ C,则 A ~ C

  • 每个字符串视为图中的一个点。
  • 如果两个字符串“相似”,就在它们之间连一条边。
  • 那么整个问题就变成了:图中有多少个连通块(也就是「相似字符串组」)。
class Solution {
private:int fa[302];int sets ;int find(int x){if(x == fa[x]) return x;int root = find(fa[x]);fa[x] = root;//扁平化return root;}void unite(int x,int y){int root_x = find(x);int root_y = find(y);if(root_x != root_y){fa[root_x] = root_y;sets -- ;}}bool isSimilarStr(string s1,string s2){int m = s1.size();int diff = 0;for(int i = 0;i<m && diff<3;i++){if(s1[i] != s2[i]){diff ++;}}if(diff == 0 || diff == 2) return true;else return false;}
public:int numSimilarGroups(vector<string>& strs) {int n = strs.size();//n个单词sets = n;//sets个集合for(int i = 0;i<n;i++) fa[i] = i;//每个单词指向自己for(int i = 0;i<n;i++){for(int j = i+1;j<n;j++){if(isSimilarStr(strs[i],strs[j])){unite(i,j);}}}return sets;}
};
947. 移除最多的同行或同列石头

如果两个石头在同一行或同一列,那么我们可以通过某种顺序把它们全部“连通”起来(因为移除一个不会影响另一个的“可达性”)。

所以我们可以将这些石头抽象为图上的点,只要两个石头满足“同行或同列”,就认为它们之间有一条边,构成一个 连通块

一个连通块里,总是可以移除 块中石头数 - 1 个石头(留一个不动就行)。

class Solution {
private:int fa[1005];int sets;int find(int x){if(x == fa[x]) return x;int root = find(fa[x]);fa[x] = root;//扁平化处理return root;}void unite(int x,int y){int root_x = find(x);int root_y = find(y);if(find(x) != find(y)){fa[root_x] = root_y;sets -- ;}}
public:int removeStones(vector<vector<int>>& stones) {map<int, int> firstRow; // 列,石头序号map<int, int> firstCol; // 行,石头序号int n = stones.size();iota(fa,fa+n+1,0);sets = n;// 数据前置处理for (int i = 0; i < n; i++) {int col = stones[i][0];int row = stones[i][1];if (firstCol.find(col) == firstCol.end()) {firstCol.insert({col, i});} else {unite(i, firstCol[col]); // 合并石头}if (firstRow.find(row) == firstRow.end()) {firstRow.insert({row, i});} else {unite(i, firstRow[row]); // 合并石头}}return n - sets;}
};
2092. 找出知晓秘密的所有专家

这题的做法是:
首先专家 0 和 firstPerson 知道秘密,将他们俩合并
然后后续的会议按照时间戳排序
对于相同的时间内的会议做处理(筛选相同时间的操作看代码,很妙)
在这个时间内,一个个的按照并查集的方式分组,并且更新是否知晓秘密的信息
每一段相同的时间完成后,再重新遍历一遍会议,把无意义的会议造成的集合的合并撤销掉
最后按照 secret[find (x)]来得到答案

class Solution {
private:int fa[100005];bool secret[100005];int find(int x){if(x == fa[x]) return x;int root = find(fa[x]);fa[x] = root;//扁平化return root;}void unite(int x,int y){int fx = find(x);int fy = find(y);if(fx != fy){fa[fx] = fy;//x挂在y上secret[fy] |= secret[fx];//更新fy的信息}}
public:vector<int> findAllPeople(int n, vector<vector<int>>& meetings, int firstPerson) {iota(fa,fa+n,0);//每个人的父节点for(int i = 0;i<n;i++) secret[i] = false;secret[0] = true;secret[firstPerson] = true;//知道秘密//时间排序sort(meetings.begin(),meetings.end(),[](const vector<int>& a,const vector<int>& b){return a[2] < b[2];});//按照时刻做int m = meetings.size();for(int l = 0;l<m ;){int r = l;while(r+1<m && meetings[r+1][2] == meetings[l][2]){r ++;//找到右边界}for(int i = l;i<=r;i++){unite(meetings[i][0],meetings[i][1]);//合并此时的会议专家}//撤销选择for(int i = l;i<=r;i++){int a = meetings[i][0];int b = meetings[i][1];if(!secret[find(a)]){fa[a] = a;}if(!secret[find(b)]){fa[b] = b;}}l = r + 1;}//计算答案vector<int>ans;for(int i = 0;i<n;i++){if(secret[find(i)]){ans.push_back(i);}}return ans;}
};
2421. 好路径的数目

该题目讲解
这题的做法是,除了开 fa 数组存放父节点外,还开一个 maxcnt 标记数组
然后按照边的两端的节点中的值更大的那个值升序(从小到大)排序
先处理边最小(也就是所有边中,边的顶点值的最大值最小的)的,后续依次遍历
把每次遍历到的边的两个点的所在的两个集合进行合并 (在同一个集合无需合并)
对于合并:要选择集合的代表顶点值更大的那个点做两个集合所合并成的集团的代表节点,然后若合并前的两个集合的代表节点的最大值相同,且数量为 m 和 n (由 cntmax[fa[x]]和 cntmax[fa[y]] 得到) ,则说明这两个集合可以构成的好路径的个数为 (m×n),因为 m 和 n 都是集合内节点的最大节点值,到另一个集合的最大节点的路径上,肯定没有比首尾节点(也就是这里的最大节点)还大的值了,所以每两个节点形成一条好路径。两两组合就是 m×n
最终的答案再加上每个节点到自身的路径(也是好路径)

class Solution {
private://const int MAXN = 30010;int fa[30010];//father数组int maxcnt[30010];//集团中的最大值出现次数void build(int n){//节点个数for(int i = 0;i<n;i++){fa[i] = i;//指向自身maxcnt[i] = 1;//初始都为1}}int find(int x){if(x == fa[x]) return x;int root = find(fa[x]);//递归找rootfa[x] = root;//扁平化return root;}int unite(int x,int y,vector<int>& vals){//返回好路径的条数int path = 0;int fx = find(x);int fy = find(y);//既是集团的代表节点 也是集团中出现的最大的节点值的下标if(vals[fx] > vals[fy]){//值小的合并在值大的集团上fa[fy] = fx;}else if(vals[fx] < vals[fy]){fa[fx] = fy;}else{//怎么合并无所谓 但是maxcnt要根据合并方法计数path = maxcnt[fx]*maxcnt[fy];fa[fx] = fy;//x合并在y上maxcnt[fy] += maxcnt[fx];}return path;}public:int numberOfGoodPaths(vector<int>& vals, vector<vector<int>>& edges) {int n = vals.size();build(n);sort(edges.begin(),edges.end(),[&](const vector<int>& a,const vector<int>&b){//按照边的最大节点的大小排序int val_a = max(vals[a[0]],vals[a[1]]);int val_b = max(vals[b[0]],vals[b[1]]);return val_a < val_b;//从小到大排序});//并查集处理int ans = n;for(auto edge : edges){ans += unite(edge[0],edge[1],vals);}return ans;}
};

小题

罗马数字转整数 (哈希表映射)
★小题罗马数字转整数LC-13哈希表映射
class Solution {
public:int romanToInt(string s) {unordered_map<char, int> roman = {{'I',1}, {'V',5}, {'X',10}, {'L',50},{'C',100}, {'D',500}, {'M',1000}};int total = 0;for(int i=0; i<s.size(); ++i){// 当前字符值 < 后一个字符值 → 减去当前值if(i+1 < s.size() && roman[s[i]] < roman[s[i+1]]){total -= roman[s[i]];} else {total += roman[s[i]];}}return total;}
};
婚配问题 (GS 算法)
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
#include <map>
#include <string>
using namespace std;
int main() {int x;cin >> x;bool first = true;while (x--) {if (!first) cout << endl;first = false;int n;cin >> n;vector<char> male_names(n), female_names(n);for (int i = 0; i < n; ++i) cin >> male_names[i];//输入男性名字for (int i = 0; i < n; ++i) cin >> female_names[i];//输入女性名字//男性名字 女性名字 映射序号map<char, int>male_map, female_map;for (int i = 0; i < n; i++) {male_map[male_names[i]] = i;//'a' -> 0female_map[female_names[i]] = i;//'A' -> 0}//=====男性偏好索引列表=====vector<vector<int> > male_prefs(n); //存储偏好索引for(int i = 0; i<n; i++) {string line;cin >> line;//此行对应的男性名称char c = line[0];//喜欢的那几个提取出来string line_male_prefs = line.substr(2);int male_index = male_map[c];//当前行的男性序号for (int j = 0; j < line_male_prefs.size(); ++j) {  // 修改为传统循环char x = line_male_prefs[j];int female_index = female_map[x];//当前男性喜欢的每个女性的序号male_prefs[male_index].push_back(female_index);}}//=====女性偏好索引列表====vector<vector<int> > female_prefs(n); //同上vector<vector<int> > female_rank(n, vector<int>(n)); //存储女性对男性的排名for (int i = 0; i < n; i++) {string line ;cin >> line;//此行对应的女性名字char c = line[0];//提取喜欢的人的序号string line_female_prefs = line.substr(2);int female_index = female_map[c];//当前行的女性序号for (int j = 0; j < line_female_prefs.size(); ++j) {  // 修改为传统循环char x = line_female_prefs[j];int male_index = male_map[x];//当前行女性喜欢的男性的序号的映射female_prefs[female_index].push_back(male_index);//喜欢的每个女性push进去}//反向数组 存储女性对男性的喜欢程度排名,用于下面的配偶调换for (int j = 0; j < n; j++) {int m = female_prefs[female_index][j];female_rank[female_index][m] = j;}}//=====输入的数据处理完毕=====/*a:BACb:BACc:ACBA:acbB:bacC:cab则male_prefs为   [[1 0 2],[1 0 2],[0 2 1]]female_prefs为 [[0 2 1],[1 0 2],[2 0 1]]frmale_prefs[0][2] = 10号女第(2+1)喜欢的男性是1号男==>female_rank[0][1] = 2;0号女对1号男的喜欢顺序是第(2+1)*///=====开始GS算法========vector<int> wife(n, -1); // wife[m] = 男性m当前匹配的女性索引vector<int> husband(n, -1); // husband[f] = 女性f当前匹配的男性索引stack<int> q; // 存储当前单身的男性for (int i = 0; i < n; i++) q.push(i); // 初始所有男性入栈while (!q.empty()) {int cur_index = q.top();//当前未婚配男性q.pop();//如果当前男性没有配偶,对于喜欢列表的女性依次匹配for (int k = 0; k < male_prefs[cur_index].size(); ++k) {  // 修改为传统循环int p = male_prefs[cur_index][k];if (wife[cur_index] != -1) break; // 已匹配则跳过if (husband[p] == -1) {husband[p] = cur_index;//男性找到配偶wife[cur_index] = p;//女性找到配偶break;//退出循环} else if (husband[p] != -1) { //当前女性有配偶int this_woman_husband = husband[p];//p对cur_index对应的男的喜欢程度比原配更大,则换//这是排名 越小越喜欢if (female_rank[p][cur_index] < female_rank[p][this_woman_husband]) {//当前匹配成功husband[p] = cur_index;wife[cur_index] = p;//原配偶变为单身wife[this_woman_husband] = -1;//入单身栈q.push(this_woman_husband);break;} else {//p对cur_index喜欢程度更小continue;}}}}//========== 按字典序输出结果 ==========vector<char> sorted_males = male_names;sort(sorted_males.begin(), sorted_males.end()); // 按字母顺序排序for (int i = 0; i < sorted_males.size(); ++i) {  // 修改为传统循环char c = sorted_males[i];int m = male_map[c];         // 获取男性索引int f = wife[m];             // 获取匹配的女性索引cout << c << " " << female_names[f] << endl;}}return 0;
}

相关文章:

  • 【WLAN】华为无线AC双机热备负载分担—双链路热备份
  • 驱动汽车供应链数字化转型的标杆解决方案:全星研发项目管理APQP软件系统:
  • Oracle 租户、用户、模式之间的关系
  • zephyr架构下Bluetooth advertising接口
  • Ubuntu20.04部署Ragflow(Docker方式)
  • Android studio学习之路(八)---Fragment碎片化页面的使用
  • MCP 协议解读:STDIO 高效通信与 JSON-RPC 实战
  • Dify + Mermaid 实现自然语言转图表
  • 第十六届蓝桥杯大赛软件赛省赛第二场 C/C++ 大学 A 组
  • Python 实现从 MP4 视频文件中平均提取指定数量的帧
  • 大端存储与小端存储:数据存储的镜像世界探秘
  • 【昇腾】PaddleOCR转om推理
  • 二分类任务中,统计置信区间
  • Linux下终端命令行安装常见字体示例
  • Unity任务系统笔记
  • springboot入门-业务逻辑核心service层
  • Python-MCPServerStdio开发
  • Unity之基于MVC的UI框架-含案例
  • Linux——动静态库
  • 解决conda虚拟环境安装包却依旧安装到base环境下
  • 贵州通报9起群众身边不正之风和腐败问题典型案例
  • 蚂蚁集团将向全体股东分红
  • 科克托是说真话的骗子,而毕加索是一言不发、让大家去猜的人
  • 大家聊中国式现代化|郑崇选:提升文化软实力,打造文化自信自强的上海样本
  • 国际观察|伦敦会谈“降级”凸显美国乌克兰政策窘境
  • 解放军报社论:谱写新时代双拥工作崭新篇章