0
点赞
收藏
分享

微信扫一扫

C++ 路径问题汇总 涉及DP, DFS, BFS和回溯


文章目录

  • ​​1. 广度优先搜索和优先队列配合​​
  • ​​1.1 不同道路不同消费和障碍物, 从起点到终点最低消费​​
  • ​​1.2 地图只有障碍物, 能否从起点到达终点​​
  • ​​2. 回溯加visited​​
  • ​​2.1 需要删除多少个障碍物才能到达目的地​​
  • ​​3. DP问题​​
  • ​​62.不同路径​​
  • ​​[63. 不同路径II](https://leetcode-cn.com/problems/unique-paths-ii/)​​
  • ​​[64. 最短路径和](https://leetcode-cn.com/problems/minimum-path-sum/)​​
  • ​​未完, 待续...​​


最近这几天忙着参加各大互联网公司的笔试, 发现有一个规律, 算法题都喜欢出路径问题, 二维数组作为地图, 从(0,0)点到(m-1,n-1)点最短花费, 至少删除多少个障碍才能够到达等等, 总之就是二维数组找路径, 其中有不同的障碍物, 不同的分值, 总之八仙过海各显神通, 可能是因为涉及到很多的知识, 因此大家都乐意出这样的题吧;


今天小弟在这里献上收集来的各种题型和解题思路

1. 广度优先搜索和优先队列配合

1.1 不同道路不同消费和障碍物, 从起点到终点最低消费

不同道路不同消费并且含有障碍物, 问从(0,0)到(m-1, n-1)的最低消费, 注意: 只能上下左右走
遇到水​​​nums[i][j]=0​​​, 消费为2
遇到路​​​nums[i][j]=1​​​, 消费为1
遇到障碍物​​​nums[i][j]=2​​, 此路不通

输入nums = {{1,1,1,1,0},{0,1,0,1,0},{1,1,2,1,1},{0,2,0,0,1}};
输出: 7
解释: (0,0)->(0,1)->(0,2)->(0,3)->(1,3)->(2,3)->(2,4)->(3,4)

思考:

  1. 如果用dp解题, 只能向下或者向右, 才能根据左和上来判断最小消费, 但是题目要求是上下左右, 因此有点难搞
  2. 如果用深度优先加一个​​visited标记​​, 好像不对, 因为按照顺序标记不知道哪个节点是最小的消费
  3. 如果用广度优先加一个​​visited标记​​, 从左上到右下,斜着扫还有点意思, 但是也不对, 因为如果按照顺序扫, 就不知道哪个是消费最低的节点
  4. 如果广度优先, 还能按照小的值先遍历就好了, 小任说, 那这个好办, 使用优先队列做广度优先不就好了, 核心就在与构建结构体保存每个点的结果嘛

#include<iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <queue>
using namespace std;

vector<int> direction = {-1, 0 , 1, 0 , -1};
struct Node{
Node(int xx,int yy,int s){
x = xx;
y = yy;
spend = s;
}
int x,y;
int spend;
// 这里需要注意 重载运算符<的使用
friend bool operator<(const Node& x, const Node& y) {
return x.spend>y.spend;
}
};

/*
核心就是利用了优先队列来进行的广度优先搜索, 由于使用优先队列, 小根堆,
这样每次都会把spend小的拿出来先遍历, 这样就保证了
*/
int minSailCost(vector<vector<int> >& nums){
if(nums.empty() && nums[0].empty()) return 0;

priority_queue<Node>temp;
vector<vector<bool>> visited(105, vector<bool>(105, false));
int m = nums.size(), n = nums[0].size();
visited[0][0] = true;
temp.push(Node(0,0,0)); // 左上角是起点, 消费是0
while(!temp.empty())
{
Node now = temp.top();
temp.pop();

//到右下角了
if(now.x==m-1 && now.y==n-1)
return now.spend;

// 上下左右遍历
for(int i=0; i<4; i++)
{
int x = now.x + direction[i], y = now.y + direction[i+1];
// 判断遍历的点是否越界, 是否被遍历过, 是否是障碍物
if(x<0 || y<0 || x>=m || y>=n || visited[x][y] || nums[x][y]==2)
continue;
visited[x][y] = true;
int spend = now.spend;
if(nums[x][y] == 1)
spend += 1; // 陆地+1
else if(nums[x][y] == 0)
spend += 2; // 河水+2
printf("x=%d, y=%d, spend=%d\n", x,y,spend);
temp.push(Node(x,y,spend));
}
}
return -1; // 如果到最后都没到达右下角, 那就是凉了
}

/*
x=0, y=1, spend=1
x=1, y=0, spend=2
x=0, y=2, spend=2
x=1, y=1, spend=2
x=2, y=0, spend=3
x=0, y=3, spend=3
x=1, y=2, spend=4
x=2, y=1, spend=3
x=0, y=4, spend=5
x=1, y=3, spend=4
x=3, y=0, spend=5
x=1, y=4, spend=6
x=2, y=3, spend=5
x=2, y=4, spend=6
x=3, y=3, spend=7
x=3, y=4, spend=7
x=3, y=2, spend=9
7
*/
int main(){
vector<vector<int>> nums = {{1,1,1,1,0},{0,1,0,1,0},{1,1,2,1,1},{0,2,0,0,1}};
cout<<minSailCost(nums)<<endl;
return 0;
}

1.2 地图只有障碍物, 能否从起点到达终点

class Solution {
public:
vector<int> direction = {-1, 0, 1, 0, -1};
struct Node{
Node(int xx, int yy, int s){
x = xx;
y = yy;
spend = s;
}
int x,y;
int spend;

friend bool operator<(const Node & x, const Node &y){
return x.spend>y.spend;
}
};

bool CanGo(vector<vector<int>> &nums,
int x1, int y1, // 起点
int x2, int y2){ // 终点
if(nums.empty() && nums[0].empty()) return false;
priority_queue<Node> temp;
int m=nums.size(), n=nums[0].size();
vector<vector<bool>> visited(m+1, vector<bool>(n+1, false));
visited[x1][y1] = true; // 起点
temp.push(Node(x1, y1, 0));
while(!temp.empty()){
Node now= temp.top();
temp.pop();
if(now.x == x2 && now.y == y2) return true; // 到达目的地

// 上下左右遍历
for(int i=0; i<4; i++){
int x = now.x+direction[i], y=now.y+direction[i+1];
if(x<0 || y<0 || x>=m || y>=n || visited[x][y]){
continue;
}
visited[x][y] = true;
if(nums[x][y] == 1){
temp.push(Node(x, y, 0));
}
}
}
return false;
}

vector<bool> pathAvailable(vector<vector<int> >& matrix,
vector<vector<int> >& starts,
vector<vector<int> >& ends) {
vector<bool> ans;
for(int i=0; i<starts.size(); i++){
bool t = CanGo(matrix, starts[i][0], starts[i][1], ends[i][0], ends[i][1]);
ans.push_back(t);
}
return ans;
}
};

2. 回溯加visited

2.1 需要删除多少个障碍物才能到达目的地

不同道路含有障碍物, 问从(0,0)到(m-1, n-1)需要最少删除多少障碍物才能到

{ ".####",
"###..",
"..###"};
显然需要删除4个障碍物才能实现到达目的地

思路:

  1. 本题其实是深度搜索, 把所有的障碍物都遍历一遍, 最后找到最少的障碍物
  2. 这里使用visited保证每个障碍物的遍历情况

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

vector<int> direction = {-1, 0, 1, 0, -1}; // 上下左右的向导
void backTracking(vector<string> &nums, // 二维数组保存整个地图
int i, int j, // 遍历当前节点
vector<vector<bool>>&visited, // 是否遍历过当前这个点
vector<int> &ans, // 保存每个到达终点路径需要消除的障碍数量
int obstacle){ // 保存当前遇到障碍物的数量
// 超出边界 或遍历过
if(i<0 || i>=nums.size() || j<0 || j>=nums[0].size() || visited[i][j]) {
cout<<"越界了/遍历过"<<endl;
return;
}
// 当前这个点是障碍物就加入进去
if(nums[i][j] == '#') obstacle++;
// 遍历到终点了
if(i==nums.size()-1 && j==nums[0].size()-1){
cout<<"到终点了========"<<obstacle<<endl;
ans.push_back(obstacle);
return;
}

// visited 回溯
visited[i][j] = true;
for(int k=0; k<4; k++){
int x=i+direction[k], y=j+direction[k+1];
backTracking(nums, x, y, visited, ans, obstacle);
}
visited[i][j] = false;
}

int main(int argc, char const *argv[])
{
// vector<string> nums = { ".##",
// "##.",
// "..."};
vector<string> nums = { ".####",
"###..",
"..###"};
int n=nums.size(), m=nums[0].size();

vector<vector<bool>> visited(n, vector<bool>(m, false));
vector<int> ans;
backTracking(nums, 0,0, visited, ans, 0);

// 打印每一条路径下删除的障碍物数量
for(const int & res : ans){
cout<<res << " ";
}cout<<endl;

cout<<"因此, 想要到达终点, 删除障碍物最少"<<*min_element(ans.begin(),ans.end())<<endl;
return 0;
}

3. DP问题

62.不同路径

1 1 1 1 1 1 1 
1 2 3 4 5 6 7
1 3 6 10 15 21 28

int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n));
for(int i=0; i<m; i++){
dp[i][0] = 1;
}
for(int j=0; j<n; j++){
dp[0][j] = 1;
}

for(int i=1; i<m; i++){
for(int j=1; j<n; j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}

63. 不同路径II

跟上一题的区别在于本题的路上有障碍物了
因此

[0,0,0],
[0,1,0],
[0,0,0]
输出为2

由于数组默认为​​0​​​, 因此遇到障碍物的时候, 就可以直接​​continue​​了

int uniquePathsWithObstacles(vector<vector<int>>& nums) {
if(nums.empty() || nums[0].empty()) return 0;
int m = nums.size(), n = nums[0].size();
vector<vector<int>> dp(m, vector<int>(n));
for(int i=0; i<m; i++){
if(nums[i][0] == 1) break;;
dp[i][0] = 1;
}
for(int j=0; j<n; j++){
if(nums[0][j] == 1) break;;
dp[0][j] = 1;
}

for(int i=1; i<m; i++){
for(int j=1; j<n; j++){
if(nums[i][j] == 1) continue;
dp[i][j] = dp[i-1][j] + dp[i][j-1];

}
}
return dp[m-1][n-1];
}

64. 最短路径和

给定一个包含非负整数的​​m x n​​​网格​​grid​​​ ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
输入:grid = [[1,2,3],[4,5,6]]
输出:12

C++ 路径问题汇总 涉及DP, DFS, BFS和回溯_动态规划_02

思考:

  1. 最短路径和有点像贪婪算法的意思
  2. 从左上角开始, 找右/下最小值走, 但是显然不对, 就如上图所示
  3. 但是可以想一下, 当前值只跟左/上两个有关系, 所以最终​​dp[i][j]​​​就可表示为从​​(0,0)->(i,j)​​的对小花费

dp[i][j] = min(dp(i-1, j) , dp(i, j-1)) + nums[i][j];
边界条件就是dp的第一列和第一行是nums的累加

class Solution {
public:
int minPathSum(vector<vector<int>>& nums) {
if(nums.empty() || nums[0].empty()) return 0;
int m = nums.size(), n = nums[0].size();

vector<vector<int>> dp(m, vector<int>(n));

for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(i==0 && j==0) dp[0][0] = nums[0][0];
else if(i==0) dp[i][j] = dp[i][j-1]+nums[i][j];
else if(j==0) dp[i][j] = dp[i-1][j]+nums[i][j];
else dp[i][j] = min(dp[i-1][j], dp[i][j-1])+nums[i][j];
}
}
return dp[m-1][n-1];
}
};

未完, 待续…


举报

相关推荐

0 条评论