diff --git a/include/leetcode/problems/minimum-cost-to-convert-string-i.h b/include/leetcode/problems/minimum-cost-to-convert-string-i.h new file mode 100644 index 0000000..29cd612 --- /dev/null +++ b/include/leetcode/problems/minimum-cost-to-convert-string-i.h @@ -0,0 +1,20 @@ +#include "leetcode/core.h" + +namespace leetcode { +namespace problem_2976 { + +using Func = std::function&, vector&, vector&)>; + +class MinimumCostToConvertStringISolution : public SolutionBase { + public: + //! 2976. Minimum Cost to Convert String I + //! https://leetcode.com/problems/minimum-cost-to-convert-string-i/ + long long minimumCost(string source, string target, + vector& original, vector& changed, + vector& cost); + + MinimumCostToConvertStringISolution(); +}; + +} // namespace problem_2976 +} // namespace leetcode \ No newline at end of file diff --git a/src/leetcode/problems/minimum-cost-to-convert-string-i.cpp b/src/leetcode/problems/minimum-cost-to-convert-string-i.cpp new file mode 100644 index 0000000..d0c8b41 --- /dev/null +++ b/src/leetcode/problems/minimum-cost-to-convert-string-i.cpp @@ -0,0 +1,140 @@ +#include "leetcode/problems/minimum-cost-to-convert-string-i.h" + +namespace leetcode { +namespace problem_2976 { + +// 策略1:Floyd-Warshall 算法计算所有字符对之间的最小转换成本 +// 时间复杂度:O(26^3 + n + m),其中 n 是 source 长度,m 是转换规则数量 +// 空间复杂度:O(26^2) = O(1) +static long long solution1(string source, string target, + vector& original, vector& changed, + vector& cost) { + const long long INF = 1e18; + const int ALPHABET = 26; + vector> dist(ALPHABET, vector(ALPHABET, INF)); + + // 初始化对角线为 0(相同字符转换成本为 0) + for (int i = 0; i < ALPHABET; ++i) { + dist[i][i] = 0; + } + + // 根据给定的转换规则更新直接转换成本 + int m = original.size(); + for (int i = 0; i < m; ++i) { + int u = original[i] - 'a'; + int v = changed[i] - 'a'; + long long w = cost[i]; + if (w < dist[u][v]) { + dist[u][v] = w; + } + } + + // Floyd-Warshall 算法计算所有对最短路径 + for (int k = 0; k < ALPHABET; ++k) { + for (int i = 0; i < ALPHABET; ++i) { + if (dist[i][k] == INF) continue; + for (int j = 0; j < ALPHABET; ++j) { + if (dist[k][j] == INF) continue; + long long newDist = dist[i][k] + dist[k][j]; + if (newDist < dist[i][j]) { + dist[i][j] = newDist; + } + } + } + } + + // 计算将 source 转换为 target 的总成本 + long long totalCost = 0; + int n = source.size(); + for (int i = 0; i < n; ++i) { + if (source[i] == target[i]) { + continue; + } + int u = source[i] - 'a'; + int v = target[i] - 'a'; + if (dist[u][v] == INF) { + return -1; + } + totalCost += dist[u][v]; + } + return totalCost; +} + +// 策略2:对每个字符运行 Dijkstra 算法 +// 时间复杂度:O(26 * (26 log 26 + m)) + O(n) +// 空间复杂度:O(26^2 + m) +static long long solution2(string source, string target, + vector& original, vector& changed, + vector& cost) { + const long long INF = 1e18; + const int ALPHABET = 26; + + // 构建邻接表 + vector>> adj(ALPHABET); + int m = original.size(); + for (int i = 0; i < m; ++i) { + int u = original[i] - 'a'; + int v = changed[i] - 'a'; + long long w = cost[i]; + adj[u].emplace_back(v, w); + } + + // 预计算所有字符对的最短距离 + vector> dist(ALPHABET, vector(ALPHABET, INF)); + + // 对每个字符运行 Dijkstra 算法 + for (int start = 0; start < ALPHABET; ++start) { + priority_queue, vector>, greater>> pq; + dist[start][start] = 0; + pq.emplace(0, start); + + while (!pq.empty()) { + auto [d, u] = pq.top(); + pq.pop(); + + if (d != dist[start][u]) continue; + + for (auto& [v, w] : adj[u]) { + long long newDist = d + w; + if (newDist < dist[start][v]) { + dist[start][v] = newDist; + pq.emplace(newDist, v); + } + } + } + } + + // 计算总成本 + long long totalCost = 0; + int n = source.size(); + for (int i = 0; i < n; ++i) { + if (source[i] == target[i]) { + continue; + } + int u = source[i] - 'a'; + int v = target[i] - 'a'; + if (dist[u][v] == INF) { + return -1; + } + totalCost += dist[u][v]; + } + return totalCost; +} + +MinimumCostToConvertStringISolution::MinimumCostToConvertStringISolution() { + setMetaInfo({.id = 2976, + .title = "Minimum Cost to Convert String I", + .url = "https://leetcode.com/problems/minimum-cost-to-convert-string-i/"}); + registerStrategy("Floyd-Warshall", solution1); + registerStrategy("Dijkstra per character", solution2); +} + +long long MinimumCostToConvertStringISolution::minimumCost( + string source, string target, + vector& original, vector& changed, + vector& cost) { + return getSolution()(source, target, original, changed, cost); +} + +} // namespace problem_2976 +} // namespace leetcode \ No newline at end of file diff --git a/test/leetcode/problems/minimum-cost-to-convert-string-i.cpp b/test/leetcode/problems/minimum-cost-to-convert-string-i.cpp new file mode 100644 index 0000000..2822dca --- /dev/null +++ b/test/leetcode/problems/minimum-cost-to-convert-string-i.cpp @@ -0,0 +1,123 @@ +#include "leetcode/problems/minimum-cost-to-convert-string-i.h" + +#include "gtest/gtest.h" + +namespace leetcode { +namespace problem_2976 { + +class MinimumCostToConvertStringITest : public ::testing::TestWithParam { + protected: + void SetUp() override { solution.setStrategy(GetParam()); } + + MinimumCostToConvertStringISolution solution; +}; + +TEST_P(MinimumCostToConvertStringITest, Example1) { + string source = "abcd"; + string target = "acbe"; + vector original = {'a', 'b', 'c', 'c', 'e', 'd'}; + vector changed = {'b', 'c', 'b', 'e', 'b', 'e'}; + vector cost = {2, 5, 5, 1, 2, 20}; + long long expected = 28; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, Example2) { + string source = "aaaa"; + string target = "bbbb"; + vector original = {'a', 'c'}; + vector changed = {'c', 'b'}; + vector cost = {1, 2}; + long long expected = 12; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, Example3) { + string source = "abcd"; + string target = "abce"; + vector original = {'a'}; + vector changed = {'e'}; + vector cost = {10000}; + long long expected = -1; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, SameString) { + string source = "hello"; + string target = "hello"; + vector original = {'a', 'b'}; + vector changed = {'b', 'c'}; + vector cost = {1, 2}; + long long expected = 0; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, DirectConversion) { + string source = "abc"; + string target = "def"; + vector original = {'a', 'b', 'c'}; + vector changed = {'d', 'e', 'f'}; + vector cost = {10, 20, 30}; + long long expected = 10 + 20 + 30; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, IndirectConversion) { + string source = "aa"; + string target = "bb"; + vector original = {'a', 'c'}; + vector changed = {'c', 'b'}; + vector cost = {5, 7}; + // 转换路径:a -> c (5) + c -> b (7) = 12 per character + long long expected = 24; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, MultipleEdgesSamePair) { + string source = "a"; + string target = "b"; + vector original = {'a', 'a', 'a'}; + vector changed = {'b', 'b', 'b'}; + vector cost = {100, 10, 1}; + // 应该选择最小的成本 1 + long long expected = 1; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, Unreachable) { + string source = "a"; + string target = "b"; + vector original = {'a', 'c'}; + vector changed = {'c', 'd'}; + vector cost = {1, 2}; + // 没有从 a 到 b 的路径 + long long expected = -1; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +TEST_P(MinimumCostToConvertStringITest, SelfLoop) { + string source = "a"; + string target = "a"; + vector original = {'a'}; + vector changed = {'a'}; + vector cost = {100}; + // 相同字符不需要转换,成本为 0 + long long expected = 0; + long long result = solution.minimumCost(source, target, original, changed, cost); + EXPECT_EQ(expected, result); +} + +INSTANTIATE_TEST_SUITE_P( + LeetCode, MinimumCostToConvertStringITest, + ::testing::ValuesIn(MinimumCostToConvertStringISolution().getStrategyNames())); + +} // namespace problem_2976 +} // namespace leetcode \ No newline at end of file