package class03; import java.util.ArrayList; import java.util.HashMap; import java.util.TreeSet; // 本题测试链接 : https://leetcode.com/problems/freedom-trail/ public class Code07_FreedomTrail { // 提交时把findRotateSteps1方法名字改成findRotateSteps可以直接通过 public static int findRotateSteps1(String r, String k) { char[] ring = r.toCharArray(); int N = ring.length; HashMap> map = new HashMap<>(); for (int i = 0; i < N; i++) { if (!map.containsKey(ring[i])) { map.put(ring[i], new ArrayList<>()); } map.get(ring[i]).add(i); } char[] str = k.toCharArray(); int M = str.length; int[][] dp = new int[N][M + 1]; // hashmap // dp[][] == -1 : 表示没算过! for (int i = 0; i < N; i++) { for (int j = 0; j <= M; j++) { dp[i][j] = -1; } } return process(0, 0, str, map, N, dp); } // 电话里:指针指着的上一个按键preButton // 目标里:此时要搞定哪个字符?keyIndex // map : key 一种字符 value: 哪些位置拥有这个字符 // N: 电话大小 // f(0, 0, aim, map, N) public static int process(int preButton, int index, char[] str, HashMap> map, int N, int[][] dp) { if (dp[preButton][index] != -1) { return dp[preButton][index]; } int ans = Integer.MAX_VALUE; if (index == str.length) { ans = 0; } else { // 还有字符需要搞定呢! char cur = str[index]; ArrayList nextPositions = map.get(cur); for (int next : nextPositions) { int cost = dial(preButton, next, N) + 1 + process(next, index + 1, str, map, N, dp); ans = Math.min(ans, cost); } } dp[preButton][index] = ans; return ans; } public static int dial(int i1, int i2, int size) { return Math.min(Math.abs(i1 - i2), Math.min(i1, i2) + size - Math.max(i1, i2)); } // 以下方法来自陆振星同学 // 可以省掉枚举行为 // 提交时把findRotateSteps2方法名字改成findRotateSteps可以直接通过 // 来龙去脉 : https://www.mashibing.com/question/detail/67299 // 例 : // ring = aaca // key = ca // 首先来到 2 位置的 c, 下一步有 0 1 3 三个位置的 a 可以选择 // 只需要在 1 3 里面选择最优的即可, 因为 0 位置的 a 在任何情况下都不会比 1 位置的 a 更优 // 这是对的 // 应该是可以的,课上讲述的方法根据数据量能过,就没有继续优化了。 // 如下的贪心方式不对: // 当前来到的位置,然后要去下一个字符,左边离下一个字符近就选左边,左边离下一个字符近就选右边。 // 两条路只选1条,彻底的每一步都选当前步最优。 // 但陆振星同学的贪心方式是: // 1)当前来到的位置,然后要去下一个字符,左边离下一个字符最近的位置,去往左边,然后可能性展开,选最好的解; // 2)当前来到的位置,然后要去下一个字符,右边离下一个字符最近的位置,去往右边,然后可能性展开,选最好的解; // 1)和2)中最好的解,选出来,返回。 // 这是对的,因为如果去往离左右两边都更远的位置,那么为什么不在走的过程中,顺带就满足了下一个字符呢? // 这样还能省掉枚举行为 // 如下所有代码都提交,再次注意:提交时把findRotateSteps2方法名字改成findRotateSteps可以直接通过 private int ringLength; private char[] key; private ArrayList> ringSet; private final HashMap dp = new HashMap<>(); public int findRotateSteps2(String ring, String key) { char[] chars = ring.toCharArray(); this.key = key.toCharArray(); ringLength = chars.length; ringSet = new ArrayList<>(); for (int i = 0; i < 26; i++) { ringSet.add(new TreeSet<>()); } for (int i = 0; i < chars.length; i++) { ringSet.get(chars[i] - 'a').add(i); } return findRotateSteps(0, 0); } // kIndex : 当前要搞定的字符 // cur : 当前所在的位置 private int findRotateSteps(int kIndex, int cur) { if (kIndex == key.length) { return 0; } // kIndex 和 cur 的最大值为 100, 所以用 十进制的低两位 表示 cur, 用高两位表示 kIndex int k = kIndex * 100 + cur - 1; Integer v = dp.get(k); if (v != null) { return v; } else { v = Integer.MAX_VALUE; } // key[kIndex] 的所有位置 TreeSet treeSet = ringSet.get(key[kIndex] - 'a'); // 从 cur 向左走, 最近的符合 key[kIndex] 的位置 Integer floor = treeSet.floor(cur); if (floor == null) { floor = treeSet.last(); } int len = Math.abs(cur - floor); len = Math.min(len, ringLength - len); v = Math.min(v, len + 1 + findRotateSteps(kIndex + 1, floor)); // 从 cur 向右走, 最近的符合 key[kIndex] 的位置 Integer ceiling = treeSet.ceiling(cur); if (ceiling == null) { ceiling = treeSet.first(); } len = Math.abs(cur - ceiling); len = Math.min(len, ringLength - len); v = Math.min(v, len + 1 + findRotateSteps(kIndex + 1, ceiling)); dp.put(k, v); return v; } }