- 思路:
使用dfs回溯,主要解决两件事:
- 路径。(找到路径,并防止有环)
- 该路径上每次选择的节点都是字典序最小的(答案不唯一时)。
- 解决问题1:如果要找一条路径,可以用组合的dfs模版,当path中存放可达地点字符串数量够机票张,则找到了一种情况。但是如何防止走到环中出不去。使用二维map,第二维记录当前地点可达下一位置的机票数量,在同层dfs处,如果发现机票数量为0,则跳过。这样就不会走入环。
解决问题2:如代码中使用二维map记录起始到目的地的机票数,第二维也使用了map,这样一来, 从src->dst的多个选择中,由于map是红黑树会自动排序,所以字典序小的字符串自动放在了靠前位置。递归中,每次从map中找可达位置,会优先选择字典序较小的目的地。
(关于上述两个点,代码随想录中写的很清楚,这里我只记录自己的一些思考点)。
- 疑惑:
-
dfs什么时候带返回值,这个题不带会怎么样?
不带返回值,最后path会因为遍历其它路径而更改掉,或者被pop_back(),path最后存的就不是正确答案。所以当答案唯一的dfs题,最好dfs带返回值,就不必执行多余的递归了。 -
如果我不带返回值,使用更高维度的res来存,且走完全部案例,返回res[0],会怎么样?
会超时!试过了!
写法1:
#include<iostream>
#include<vector>
#include<string>
#include<map>
#include<unordered_map>
#include<algorithm>
using namespace std;
// tar第二维一定优先字典序小的,第一个找到的就是
// 如果回溯的值,path就是所求,可以给dfs加判断条件,或者另外存变量赋值即可。
// 如果借用和path同维的变量,这个值还是会被后面的值改变,用一个更高维度的值,然后返回高维度值的第一个有元素即可
// 这个题会超过内存限制:所以还是得剪枝
class Solution {
public:
// str:s1:可达+数量、s2:、s3:
unordered_map<string, map<string, int>> tar;
vector<vector<string>> res;
vector<string> path;
int nums; // 票数
bool dfs() {
if (path.size() == nums+1) {
res.push_back(path);
return true;
}
// 当前
string src = path.back();
for (auto& kv: tar[src])
{
if (kv.second == 0)
continue;
kv.second--;
path.push_back(kv.first);
bool tmp = dfs();
// 如果发现已经成了,就返回
if (tmp)
return true;
kv.second++;
path.pop_back();
}
// 末尾应该返回 false,因为如果中间部分不能返回true,则说明这条路径下来没找到,
// 所以必须返回false,因为不可能dfs第一次选了正确的目标,不断给正确的走
// 如果这里返回true,需要一路正确着下来,
// 主要是这里的true,会影响到一些前面的错误情况
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
//
nums = tickets.size();
for (int i = 0; i < tickets.size(); i++)
{
string src = tickets[i][0];
string dst = tickets[i][1];
if (tar.find(src) != tar.end()) {
tar[src][dst]++;
}
else{
tar[src][dst] = 1;
}
}
path.push_back("JFK");
dfs();
return res.front();
}
};
int main()
{
Solution* s = new Solution();
//vector<vector<string>> tickets = { {"JFK","SFO"}, {"JFK", "ATL"}, {"SFO","ATL"}, {"ATL","JFK"}, {"ATL","SFO"} };
vector<vector<string>> tickets = { {"JFK","SFO"}, {"JFK", "ATL"}, {"SFO","ATL"}, {"ATL","JFK"}, {"ATL","SFO"} };
s->findItinerary(tickets);
return 0;
}
写法2:会超时,只有最后1组用例不通过。(其它思路)
将对票数的计数,转为全局的vis标记。vis[i]表示第i张票是否使用,且回溯过程中,对vis变量做回溯。
#include<iostream>
#include<vector>
#include<string>
#include<map>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<string> path;
vector<vector<string>> mp;
// 模仿Swift中的写法:第二维用字符串数组
int n;
vector<bool> vis;
bool dfs() {
if (path.size() == n + 1) {
return true;
}
for (int i = 0; i < n; i++)
{
string cur = path.back();
/*cout << "当前是:" << cur << endl;
cout << "查看的票是:" << mp[i][0] << endl;*/
// 该票起始是现在,且票没用过才能出发,否则跳去下一张
if (mp[i][0] != cur || vis[i]) {
continue;
}
vis[i] = true;
path.push_back(mp[i][1]);
bool res = dfs();
if (res) {
return true;
}
path.pop_back();
vis[i] = false;
}
return false;
}
static bool cmp(const vector<string>& s, const vector<string>& t)
{
if (s[0] != t[0])
return s[0] < t[0];
else
return s[1] < t[1];
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
n = tickets.size();
vis.resize(n, false); // 记录每张票是否使用
// 直接对tickets的每一个票的终点的第二维排序,先获取每个点可达其点
mp = tickets;
sort(mp.begin(), mp.end(), cmp);
path.push_back("JFK");
dfs();
return path;
}
};
int main()
{
Solution* s = new Solution();
vector<vector<string>>tickets = { {"JFK","SFO"}, {"JFK","ATL"},{"SFO","ATL"}, {"ATL","JFK"}, {"ATL","SFO"}};
s->findItinerary(tickets);
return 0;
};