1575. 统计所有可行路径
难度:困难。
标签:动态规划。
这个题我写了三个小时,然后超时了。哭了。
思路是这样的:
首先,肯定是动态规划,开始想到使用
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示从i到j花费燃料小于等于k的路径数,想了想试了试发现不行,若加上小于k的路径数,这样会重复计算很多路径,因此用
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示从i到j花费燃料恰好等于k的路径数。
主要思想就是分割,将i->j的路径分割成i->k和j->k,k是任何一个i在使用小于等于fuel可以到达的地方,其中有一种情况不包括,即i使用0燃料到达i,即k不等于i,可以等于j。
为了防止重复计算,每次规定i->k是直达的,即i->k花费燃料数为
f
1
=
a
b
s
(
l
o
c
a
t
i
o
n
s
[
i
]
−
l
o
c
a
t
i
o
n
s
[
k
]
)
f1 = abs(locations[i] - locations[k])
f1=abs(locations[i]−locations[k]),这种情况只有1种。k->j可以有多种方案,即
d
p
[
k
]
[
j
]
[
f
u
e
l
−
f
1
]
dp[k][j][fuel - f1]
dp[k][j][fuel−f1],其中可以k = j。
超时代码:
class Solution {
int max_value = pow(10, 9) + 7;
public:
int countRoutes(vector<int>& locations, int start, int finish, int fuel) {
int n = locations.size();
int startPos = locations[start];
int finishPos = locations[finish];
sort(locations.begin(), locations.end());
vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>(fuel + 1)));
for(int f = 1; f <= fuel; f++){
for(int step = 0; step < n; step++){
for(int i = 0; i < n - step; i++){
int j = i + step;
int min_fuel = abs(locations[i] - locations[j]);
if(min_fuel > f){
continue;
}
else if(min_fuel == f){
dp[i][j][f] = 1;
dp[j][i][f] = 1;
}
int begin = max(i - f, 0);
int end = min(i + f, n - 1);
for(int k = begin; k <= end; k++){
int use_f = abs(locations[k] - locations[i]);
if(use_f > f || use_f == 0)continue;
dp[i][j][f] += dp[k][j][f - use_f];
if(dp[i][j][f] > max_value)dp[i][j][f] %= max_value;
}
dp[j][i][f] = dp[i][j][f];
}
}
}
int a = -1, b = -1;
for(int i = 0; i < n; i++){
if(a != -1 && b != -1)break;
if(locations[i] == startPos)a = i;
if(locations[i] == finishPos)b = i;
}
long long result = 0;
for(int i = 1; i <= fuel; i++){
result += dp[a][b][i];
if(result > max_value)result %= max_value;
}
if(a == b)return result + 1;
return result;
}
};
优化一下,根据fuel,i,j求出k的范围。继续超时。
超时代码:
class Solution {
int max_value = pow(10, 9) + 7;
public:
int countRoutes(vector<int>& locations, int start, int finish, int fuel) {
int n = locations.size();
int startPos = locations[start];
int finishPos = locations[finish];
if(abs(startPos - finishPos) > fuel)return 0;
sort(locations.begin(), locations.end());
vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>(fuel + 1)));
for(int f = 1; f <= fuel; f++){
for(int step = 0; step < n; step++){
for(int i = 0; i < n - step; i++){
int j = i + step;
int min_fuel = abs(locations[i] - locations[j]);
if(min_fuel > f){
continue;
}
else if(min_fuel == f){
dp[i][j][f] = 1;
dp[j][i][f] = 1;
}
int begin = (locations[i] + locations[j] - f) / 2 - 1;
int end = (f + locations[i] + locations[j]) / 2 + 1;
int idx_begin = max(i - (locations[i] - begin) - 1, 0);
int idx_end = min(i + (end - locations[i]) + 1, n - 1);
for(int k = idx_begin; k <= idx_end; k++){
if(locations[k] < begin || locations[k] > end)continue;
int use_f = abs(locations[k] - locations[i]);
if(use_f > f || use_f == 0)continue;
dp[i][j][f] += dp[k][j][f - use_f];
if(dp[i][j][f] > max_value)dp[i][j][f] %= max_value;
}
dp[j][i][f] = dp[i][j][f];
}
}
}
vector<int>::iterator it;
it = find(locations.begin(), locations.end(), startPos);
int a = it - locations.begin();
int b = a;
if(startPos != finishPos){
it = find(locations.begin(), locations.end(), finishPos);
b = it - locations.begin();
}
long long result = 0;
for(int i = 1; i <= fuel; i++){
result += dp[a][b][i];
if(result > max_value)result %= max_value;
}
if(a == b)return result + 1;
return result;
}
};
好吧,我承认我失败了。看题解吧。这个思想不是最优的吧。
由于start是固定的,所以第一维是不需要的,使用
d
p
[
j
]
[
k
]
dp[j][k]
dp[j][k]表示兄start到j恰好使用燃料k的路径总数。
我吐了,就这么简单。
正确解法:
class Solution {
int max_value = pow(10, 9) + 7;
public:
int countRoutes(vector<int>& locations, int start, int finish, int fuel) {
int n = locations.size();
if(abs(locations[start] - locations[finish]) > fuel)return 0;
vector<vector<int>> dp(n, vector<int>(fuel + 1));
dp[start][0] = 1;
for(int f = 1; f <= fuel; f++){
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(i == j)continue;
int use_f = abs(locations[i] - locations[j]);
if(f - use_f < 0)continue;
dp[j][f] += dp[i][f - use_f];
if(dp[j][f] > max_value)dp[j][f] %= max_value;
}
}
}
long result = 0;
for(int i = 0; i <= fuel; i++){
result += dp[finish][i];
if(result > max_value)result %= max_value;
}
return result;
}
};
结果:
总结:思考动态规划问题,往往想到的可能都是复杂版本,比如上述那个三维的,在开始写之前,想一想给定的条件,比如从start开始出发,根据给定条件可以简化方法,理清思路。
PS:要改掉试出答案的坏习惯,这样太浪费时间了,要先好好思考各种情况,得出较完整的思路后再继续。
面试题 17.13. 恢复空格
开始看错了题,看成了返回未匹配的单词个数,而不是字符个数。所以导致整个理解都有偏差。
其实应该是一道比较简单的动态规划题,加上哈希表来匹配单词即可。但这样做效率很低。
class Solution {
public:
int respace(vector<string>& dictionary, string sentence) {
int word_num = dictionary.size();
int len = sentence.length();
if(word_num == 0)return len;
unordered_set<string> dict;
for(int i = 0; i < word_num; i++){
if(dict.find(dictionary[i]) == dict.end()){
dict.insert(dictionary[i]);
}
}
vector<int> dp(len + 1);
for(int i = 1; i <= len; i++){
dp[i] = dp[i - 1] + 1;
for(int k = 0; k < i; k++){
string s = sentence.substr(k, i - k);
if(dict.find(s) != dict.end())dp[i] = min(dp[i], dp[k]);
}
}
return dp[len];
}
};
结果:
可以使用字典树进行优化。学习一下字典树!根据动态规划原理,这个题适合后序生成字典树。
正确解法:
class TrieNode{
public:
bool isWord;
TrieNode* children[26] = {nullptr};
TrieNode(){
isWord = false;
}
};
class Trie{
public:
TrieNode* root;
Trie(){
root = new TrieNode();
}
void insert(string word){
TrieNode* cur = root;
for(int i = word.length() - 1; i >= 0; i--){
int c = word[i] - 'a';
if(cur->children[c] == NULL){
cur->children[c] = new TrieNode();
}
cur = cur->children[c];
}
cur->isWord = true;
}
vector<int> search(string sentence, int endPos){
vector<int> indices;
TrieNode* cur = root;
for(int i = endPos; i >= 0; i--){
int c = sentence[i] - 'a';
if(cur->children[c] == NULL)break;
cur = cur->children[c];
if(cur->isWord)indices.push_back(i);
}
return indices;
}
};
class Solution {
public:
int respace(vector<string>& dictionary, string sentence) {
int word_num = dictionary.size();
int len = sentence.length();
if(word_num == 0)return len;
Trie* tree = new Trie();
for(int i = 0; i < word_num; i++){
tree->insert(dictionary[i]);
}
vector<int> dp(len + 1);
for(int i = 1; i <= len; i++){
dp[i] = dp[i - 1] + 1;
vector<int> res = tree->search(sentence, i - 1);
for(int k = 0; k < res.size(); k++){
dp[i] = min(dp[i], dp[res[k]]);
}
}
return dp[len];
}
};
结果:
面试题 08.08. 有重复字符串的排列组合
正确解法:
class Solution {
vector<string> result;
void dfs(string s, string& a, vector<int>& visited){
if(a.length() == s.length()){
result.emplace_back(a);
return;
}
int last_index = -1;
for(int i = 0; i < s.length(); i++){
if(visited[i] == 0){
if(i > 0 && last_index != -1 && s[i] == s[last_index])continue;
a += s[i];
visited[i] = 1;
dfs(s, a, visited);
a.pop_back();
visited[i] = 0;
last_index = i;
}
}
}
public:
vector<string> permutation(string s) {
sort(s.begin(), s.end());
string a = "";
vector<int> visited(s.length());
dfs(s, a, visited);
return result;
}
};
结果:
1363. 形成三的最大倍数
难度:困难。
标签:数学,动态规划。
这个题很好想,但是确实有坑。不使用动态规划,使用贪心的思想。
结果是3的倍数,个个数字的和是3的倍数。
先对序列从大到小排序,使用一个矩阵把每个数字mod3的结果存储下来。
- 若a % 3 == 0,则直接加入最终结果中
- 将3个对3取余结果是1的数加入结果
- 将3个对3取余结果是2的数加入结果
- 将1个对3取余结果是1和1个对3取余结果是2的数加入结果
因此,只需要算出最终要加入结果的取余结果是1的数 m o d 1 N u m mod1Num mod1Num的个数和取余结果是2的数的个数 m o d 2 N u m mod2Num mod2Num即可。按照序列从大到小的顺序取数字。
其中对 m o d 1 N u m mod1Num mod1Num的计算和 m o d 2 N u m mod2Num mod2Num的计算需要稍微多思考一下,对于 m o d 1 N u m > m o d 2 N u m mod1Num > mod2Num mod1Num>mod2Num来说,有以下几种情况(反之同理):
- 若mod2Num==0,则mod1Num -= mod1Num % 3
- 若(mod1Num - mod2Num) % 3 == 2,则mod2Num -= 1
- 其余情况mod1_num -= (mod1_num - mod2_num) % 3
以下是易错的测试用例:
[1,1,1,2]
[5,8]
[1,4,0,0,0,0]
正确解法:
class Solution {
static bool cmp(int& a, int& b){
return a > b;
}
public:
string largestMultipleOfThree(vector<int>& digits) {
string result = "";
int n = digits.size();
sort(digits.begin(), digits.end(), cmp);
if(digits[0] == 0){
result = "0";
return result;
}
vector<int> mod_vec(n);
int mod2_num = 0, mod1_num = 0;
for(int i = 0; i < n; i++){
int x = digits[i] % 3;
mod_vec[i] = x;
if(x == 2)mod2_num++;
else if(x == 1)mod1_num++;
}
if(mod1_num < mod2_num){
if((mod2_num - mod1_num) % 3 == 2 && mod1_num > 0){
mod1_num -= 1;
}
else{
mod2_num -= (mod2_num - mod1_num) % 3;
}
}
else if(mod1_num > mod2_num){
if((mod1_num - mod2_num) % 3 == 2 && mod2_num > 0){
mod2_num -= 1;
}
else{
mod1_num -= (mod1_num - mod2_num) % 3;
}
}
for(int i = 0; i < n; i++){
if(mod_vec[i] == 0){
result += char('0' + digits[i]);
}
else if(mod_vec[i] == 1 && mod1_num){
result += char('0' + digits[i]);
mod1_num--;
}
else if(mod_vec[i] == 2 && mod2_num){
result += char('0' + digits[i]);
mod2_num--;
}
}
if(result[0] == '0')return "0";
return result;
}
};
结果: