前言
https://leetcode-cn.com/problems/freedom-trail/
题目见链接,这里不再赘述
一、解法的关键点
示例:这道题目是一道典型的动态规划题目,这个问题里dp函数是一个二维的,以key的第i个位置的值作为索引,去推导dp[i][j]。以ring=“godding” key="gd"为例。以d为例,在ring中,有两个d在不同的位置出现,因此,在dp[i-1][jg]确定的情况下,我们可以求得从不同的g出发,转到两个d的位置,即从index :0->2,0->3,6->2,6->3这四个选项,取其中最小的值即为最近的路径,在加上每次按下button的一步,即为最后的结果。
为了快速的确定key中第i个字符在ring中的备选位置,因此要维护一个post[]数组存储备选位置。转移方程为,其中i为key中第i个字符,j为ring中的选项,k为key中第i-1个字符所对应的在ring中的位置index
dp[i][j]=Math.min(dp[i-1][k]+Math.min(Math.abs(j-k),n-Math.abs(j-k))+1,dp[i][j])
二、使用步骤
1核心代码
for (int i = 1; i < m; ++i) {//key中i
for (int j : pos[key.charAt(i) - 'a']) {//ring中 j
for (int k : pos[key.charAt(i - 1) - 'a']) {//dp中j-1
dp[i][j] = Math.min(dp[i][j], dp[i - 1][k] + Math.min(Math.abs(j - k), n - Math.abs(j - k)) + 1);
}
}
}
2.完整代码
int m=key.length();
int n=ring.length();
List<Integer>[] pos = new List[26];
for (int i = 0; i < 26; ++i) {
pos[i] = new ArrayList<Integer>();
}
for (int i = 0; i < n; ++i) {
pos[ring.charAt(i) - 'a'].add(i);
}
int[][] dp=new int[m][n];
for (int i = 0; i < m; ++i) {
Arrays.fill(dp[i], 0x3f3f3f);
}
for (int x: pos[key.charAt(0)-'a']) {
dp[0][x]=Math.min(x,n-x)+1;
}
for (int i=1;i<m;++i){
for (int k:pos[key.charAt(i)-'a']){
for (int ki:pos[key.charAt(i-1)-'a']){
dp[i][k]=Math.min(dp[i-1][ki]+Math.min(Math.abs(k-ki),n-Math.abs(k-ki))+1,dp[i][k]);
}
}
}
return Arrays.stream(dp[m - 1]).min().getAsInt();
总结
这道题目的难度是hard,但是是一到相当典型的动态规划题目,思路比较清晰,但是代码的实现存在一些难点。1.如何清楚明确的维护一个位置表post,最开始的编程中,我使用了Map<Charactar,Integer> 的方式,但是在初始化的过程中存在很大的问题。而且调用很麻烦。现在的这种方式是leetcode官方解答的方式,比较简单。
2.三层循环的代码写的时候要谨慎,避免出现错误。
3.最后的结果为min(dp[m-1][]),即从最后的被选中选出最小值。
动态规划问题,核心终究是枚举的问题,如何更好地枚举需要我们不断学习。这个问题里,dp[i][j]只与dp[i-1][j]有关,因此dp可以简化为一维,但是相应的实现难度也会相应提高。