动态规划帮我通关了《辐射4》 :: labuladong的算法小抄 #1025
Replies: 27 comments 7 replies
-
这孩子打的游戏可真多,果然优秀的人各方面都很优秀 |
Beta Was this translation helpful? Give feedback.
-
Java 版本 有需要可以参考一下 class Solution {
Map<Character, ArrayList<Integer>> charMap;
int[][] memo;
public int findRotateSteps(String ring, String key) {
int m = ring.length();
int n = key.length();
this.charMap = new HashMap<>();
for (int i = 0; i < ring.length(); i++) {
char ch = ring.charAt(i);
charMap.putIfAbsent(ch, new ArrayList<>());
charMap.get(ch).add(i);
}
this.memo = new int[m][n];
for (int[] array : memo) Arrays.fill(array, -1);
return dp(ring, 0, key, 0);
}
private int dp(String ring, int i, String key, int j) {
if (j == key.length()) return 0;
if (memo[i][j] != -1) return memo[i][j];
int n = ring.length();
int res = Integer.MAX_VALUE;
for (int k : charMap.get(key.charAt(j))) {
int diff = Math.abs(k - i);
diff = Math.min(diff, n - diff);
int subProblem = dp(ring, k, key, j + 1);
res = Math.min(res, 1 + diff + subProblem);
}
memo[i][j] = res;
return res;
}
} |
Beta Was this translation helpful? Give feedback.
-
简洁的Python: class Solution:
def findRotateSteps(self, ring: str, key: str) -> int:
char_dict = collections.defaultdict(list)
for index, c in enumerate(ring):
char_dict[c].append(index)
m = len(ring)
n = len(key)
# d[i][j]: 当指针指向ring[i]时,拼写key[j:]的最小步数
# base case: d[i][n] = 0
d = [[0] * (n + 1) for i in range(m)]
for j in range(n - 1, -1, -1):
for i in range(m):
for k in char_dict[key[j]]:
step = min(abs(k - i), abs(m - abs(k - i)))
d_i_j = d[k][j + 1] + step + 1
d[i][j] = min(d[i][j], d_i_j) if d[i][j] else d_i_j
return d[0][0] |
Beta Was this translation helpful? Give feedback.
-
class Solution:
def findRotateSteps(self, ring: str, key: str) -> int:
char_dict = collections.defaultdict(list)
for index, c in enumerate(ring):
char_dict[c].append(index)
m = len(ring)
n = len(key)
# d[i][j]: 当指针指向ring[i]时,拼写key[j:]的最小步数
# base case: d[i][n] = 0
d = [[0] * (n + 1) for i in range(m)]
for j in range(n - 1, -1, -1):
for i in range(m):
for k in char_dict[key[j]]:
step = min(abs(k - i), abs(m - abs(k - i)))
d_i_j = d[k][j + 1] + step + 1
d[i][j] = min(d[i][j], d_i_j) if d[i][j] else d_i_j
return d[0][0] |
Beta Was this translation helpful? Give feedback.
-
得到ring中所有字符的索引这样竟然是对的?我自己写的,现在觉得是错的,之后的重复字符算出来的value跟之前的不一样,put后会覆盖之前的值,那就错了呀~~~
|
Beta Was this translation helpful? Give feedback.
-
srds,这不是Java写的吗hhh |
Beta Was this translation helpful? Give feedback.
-
修改第二版代码 class Solution {
public int findRotateSteps(String ring, String key) {
//保存 ring中的所有字符对应的 索引
HashMap<Character,List<Integer>> map = new HashMap<>();
for(int i = 0; i<ring.length();i++){
if(!map.containsKey(ring.charAt(i))){
map.put(ring.charAt(i),new LinkedList<>());
}
map.get(ring.charAt(i)).add(i);
}
//dp[i][j] 表示 当前ring指针 指向 ring[i] 需要凑出 key[j..] 需要的最少步骤
int[][] dp = new int[ring.length()+1][key.length()+1];
for(int j = key.length()-1 ; j >= 0 ; j--){
for(int i = 0; i< ring.length(); i++){
char target = key.charAt(j);
List<Integer> list = map.get(target);
int result = Integer.MAX_VALUE;
for(int index : list){//所有key[j] 对应的 ring相同字符的索引 index , 计算 从index中
int step1 = Math.abs(index - i);//顺时针旋转
int step2 = ring.length()-step1;//逆时针旋转
int minStep = Math.min(step1,step2);
result = Math.min(result,minStep+1+dp[index][j+1]);//所有 ring中与 key中相同字符的 旋转次数最少的
}
dp[i][j] = result;
}
}
return dp[0][0];
}
} |
Beta Was this translation helpful? Give feedback.
-
感觉自底向上的dptable解法的难度要小于dp函数,因为dp函数里面用到了递归,感觉递归的那一部分总是不好理解,比较有难度,不知道有没有人和我一样? |
Beta Was this translation helpful? Give feedback.
-
请问dptable解法dp数据的定义是什么? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@zhongweiLeex 整体上就是这个意思,不过有一个小错误:动态规划的递归方法和迭代方法是自顶向下和自底向上的关系,而不是 DFS 和 BFS 的关系,具体可以看下 动态规划详解。 |
Beta Was this translation helpful? Give feedback.
-
class Solution {
// 字符,索引列表
HashMap<Character,ArrayList<Integer>>charToIndex;
// 备忘录
int [][]memo;
public int findRotateSteps(String ring, String key) {
int m=ring.length();
int n=key.length();
memo=new int[m][n];
// 备忘录全部初始化为
for(int []array:memo){
Arrays.fill(array,0);
}
// 记录圆环上字符到索引的映射
charToIndex=new HashMap();
for(int i=0;i<m;i++){
char ch = ring.charAt(i);
// putIfAbsent() 方法会先判断指定的键(key)是否存在,不存在则将键/值对插入到 HashMap 中。
charToIndex.putIfAbsent(ch, new ArrayList<>());
charToIndex.get(ch).add(i);
}
// 圆盘指针最初指向 12 点钟方向,
// 从第一个字符开始输入 key
return dp(ring,0,key,0);
}
// 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数
int dp(String ring,int i,String key,int j){
// 完成输入
if(j==key.length()){
return 0;
}
if(memo[i][j]!=0){
return memo[i][j];
}
int n=ring.length();
// 做选择
int res=Integer.MAX_VALUE;
// ring 上可能有多个字符 key[j]
for(int k:charToIndex.get(key.charAt(j))){
// 拨动指针的次数
int delta=Math.abs(k-i);
// 选择顺时针还是逆时针
delta=Math.min(delta,n-delta);
// 将指针拨到 ring[k],继续输入 key[j+1..]
int subProblem=dp(ring,k,key,j+1);
// 选择「整体」操作次数最少的
// 加一是因为按动按钮也是一次操作
res=Math.min(res,1+delta+subProblem);
}
// 将结果存入备忘录
memo[i][j] = res;
return res;
}
} |
Beta Was this translation helpful? Give feedback.
-
懂了,从base case 初始化那块就应该看出来,j 前面都没有初始化 根本是不确定的,对的 , 谢谢你! |
Beta Was this translation helpful? Give feedback.
-
dp数组解法 C++ class Solution {
public:
int findRotateSteps(string ring, string key) {
int m = ring.size();
int n = key.size();
vector<int> pos_r[26];
for(int i=0; i<m; i++){
pos_r[ring[i] - 'a'].push_back(i);
}
vector<vector<int>> dp(m+1,vector<int>(n+1,1000));
for(int i=0; i<m; i++){
dp[i][n] = 0;
}
for(int j=n-1; j>=0; j--){
for(int i=0; i<m; i++){
int res = INT_MAX;
for(int k : pos_r[key[j]-'a']){
int delta = abs(k-i);
delta = min(delta, m - delta);
res = min(res, dp[k][j+1] + delta + 1);
}
dp[i][j] = res;
}
}
return dp[0][0];
}
}; |
Beta Was this translation helpful? Give feedback.
-
这个递归解法的时间复杂度是ring.length的key.length次幂吗??有点震惊 |
Beta Was this translation helpful? Give feedback.
-
"当时我看了一眼就做出来了" (手动狗头doge) |
Beta Was this translation helpful? Give feedback.
-
感觉去掉memo就跟dfs差不多了 |
Beta Was this translation helpful? Give feedback.
-
py版 class Solution:
def findRotateSteps(self, ring: str, key: str) -> int:
@cache
def dp(i, j):
# 计算在ring[i], key[j..]的最少操作数
if j == len(key):
return 0
n = len(ring)
res = inf
for k in char_idx[key[j]]:
delta = abs(k - i)
# 选择顺时针还是逆时针
delta = min(delta, n - delta)
# 把指针播到ring[k], 继续输入key[j+1..]
sub = dp(k, j + 1)
res = min(res, 1 + delta + sub)
return res
char_idx = defaultdict(list)
for i, v in enumerate(ring):
char_idx[v].append(i)
return dp(0, 0) |
Beta Was this translation helpful? Give feedback.
-
java版,压缩空间 class Solution {
public int findRotateSteps(String ring, String key) {
int rLen = ring.length(), kLen = key.length();
HashMap<Character, List<Integer>> charToIndex = new HashMap<>();
for(int i = 0; i < rLen; i++) {
// 初始化charToIndex
char c = ring.charAt(i);
charToIndex.putIfAbsent(c, new LinkedList<>());
charToIndex.get(c).add(i);
}
// i为ring上索引,j为key上索引
// dp[i][j]为转盘指针指向i时,拼出[j...]后面字符的最小步骤数
int[][] dp = new int[rLen + 1][2];
// 每次更新step需要依赖j+1列上的所有结果
// 空间压缩,但还需要一列来存贮j+1的结果,因为每次访问的位置不确定
for(int j = kLen - 1; j >= 0; j--) {
// 每轮更新dp[i][j]时的base case
for(int i = rLen - 1; i >= 0; i--) {
dp[i][1] = dp[i][0];
dp[i][0] = Integer.MAX_VALUE;
}
for(int i = rLen - 1; i >= 0; i--) {
for(Integer index : charToIndex.get(key.charAt(j))) {
int step = Math.min(Math.abs(index - i), rLen - Math.abs(index - i)) + 1 + dp[index][1];
dp[i][0] = Math.min(dp[i][0], step);
}
}
}
return dp[0][0];
}
} 无论是否压缩空间,自下而上的运行速度比自顶向下慢一些是为什么呢? |
Beta Was this translation helpful? Give feedback.
-
太强了,尤其是圆环位置转hashmap,Math.abs 算圆环两点距离,Math.min(delta, n - delta) 求顺逆时针最小值这几步技巧! |
Beta Was this translation helpful? Give feedback.
-
class Solution:
# def find_best(self, i, j) -> int:
# if j >= self.n:
# return 0
# key = f"{i},{j}"
# if key in self.seen:
# return self.seen[key]
# if self.ring[i] == self.key[j]:
# res = 1 + self.find_best(i, j + 1)
# else:
# idx_list = self.ring_dict[self.key[j]]
# res = sys.maxsize
# for idx in idx_list:
# dis = min(abs(i - idx), self.m - abs(i - idx))
# res = min(res, dis + 1 + self.find_best(idx, j + 1))
# self.seen[key] = res
# return res
# def findRotateSteps(self, ring: str, key: str) -> int:
# self.ring_dict = defaultdict(list)
# for i, c in enumerate(ring):
# self.ring_dict[c].append(i)
# self.ring = ring
# self.key = key
# self.m = len(ring)
# self.n = len(key)
# self.seen = {}
# return self.find_best(0, 0)
def findRotateSteps(self, ring: str, key: str) -> int:
ring_dict = defaultdict(list)
for i, c in enumerate(ring):
ring_dict[c].append(i)
m = len(ring)
n = len(key)
dp = [[sys.maxsize] * m for _ in range(n)]
idx_list = ring_dict[key[n - 1]]
for idx in idx_list:
dp[n - 1][idx] = 1
for i in range(n - 2, -1, -1):
idx_list_1 = ring_dict[key[i]]
idx_list_2 = ring_dict[key[i + 1]]
if key[i] == key[i + 1]:
for j in idx_list_1:
dp[i][j] = dp[i + 1][j] + 1
continue
for j in idx_list_1:
for k in idx_list_2:
dp[i][j] = min(dp[i][j], 1 + min(abs(j - k), m - abs(j - k)) + dp[i + 1][k])
res = sys.maxsize
for idx in ring_dict[key[0]]:
res = min(res, dp[0][idx] + min(idx, m - idx))
return res |
Beta Was this translation helpful? Give feedback.
-
打卡,附上Java版本,注释的很清楚,感觉挺好理解的public class FreedomTrail {
//这个散列表用来记录ring中每个字符出现的索引位置
HashMap<Character, ArrayList<Integer>> charIndex=new HashMap<>();
//使用备忘录来消除重叠子问题
int[][] memo;
public int findRotateSteps(String ring, String key) {
int m=ring.length();
int n=key.length();
memo=new int[m][n];
for (int i = 0; i < m; i++) {
char c = ring.charAt(i);
//如果此时散列表中有这个字符的话,将这个索引位置添加到list中
if(charIndex.containsKey(c)){
charIndex.get(c).add(i);
}else{
//如果此时的散列表中没有这个字符的话,将这个字符以及它对应的索引加入到散列表中
ArrayList<Integer> list=new ArrayList<>();
list.add(i);
charIndex.put(c,list);
}
}
return dp(ring,0,key,0);
}
//声明一下dp函数的定义:从ring中的i位置开始,输入从key的j位置到最后位置所需要转动和按下去的最少次数
private int dp(String ring, int i, String key, int j) {
//base case:当j为key的最后一个的时候
if(j==key.length()){
return 0;
}
if(memo[i][j]!=0){
return memo[i][j];
}
int n=ring.length();
//开始做选择
int res=Integer.MAX_VALUE;
//得到key中我们此时需要输入的字符在ring中的所有的索引下标
for (Integer k : charIndex.get(key.charAt(j))) {
int distance=Math.abs(k-i);//求出此时的下标到ring中i位置的绝对距离
//顺时针或者逆时针选择
int choice=Math.min(distance,n-distance); //choice其实是这次的最小次数
//接着递归下面的
int subProblem=dp(ring,k,key,j+1);//subProblem其实是下面的问题的最小的次数
res=Math.min(res,1+choice+subProblem);//加1是因为还要按下去
}
memo[i][j]=res;
return res;
}
} |
Beta Was this translation helpful? Give feedback.
-
是我看错了吗,最后的代码显示java写的,AI翻译的c++。但是最后东哥描述的是C++版本的,我学C++的,如果要是C++是写的话,记得把dp函数里面加上引用,否则会拷贝很多份ring字符串和key字符串,虽然说这个复制也能通过,但是和加上引用就是两个效率。有的题在递归里面不加引用很大几率超时。 |
Beta Was this translation helpful? Give feedback.
-
这个对于刚入动态规划坑的新手来说,想破天也很难联系到一起呀! |
Beta Was this translation helpful? Give feedback.
-
想问一下,这道题为什么不能将dp[i][j]定义为,当前指向ring的i位置,输入字符串为[0:i]时最少的操作数,这样base case为j<0时返回0?我测试过,这道题和之前魔塔那道题一样,只能倒着dp,不能正着dp,想问一下什么时候应该倒着,什么时候应该正着来,感觉不是很好分辨。同时,我发现有人说过,对于没有状态的题目,正着倒着dp都可以,这道题和魔塔那个只能倒着,我是不是能理解为,全部倒着dp,肯定没错,谢谢大家回复 |
Beta Was this translation helpful? Give feedback.
-
备忘录递归、TreeSet查找key字符在ring中的左右(顺时针和逆时针)位置、取最小值 class Solution {
Map<Character, TreeSet<Integer>> map = new HashMap<>();
int[][] memo;
//两种选择顺时针和逆时针
public int findRotateSteps(String ring, String key) {
for (int i = 0; i < ring.length(); i++) {
char ch = ring.charAt(i);
map.putIfAbsent(ch, new TreeSet<>());
map.get(ch).add(i);
}
memo = new int[ring.length()][key.length()];
for (int[] m : memo) {
Arrays.fill(m, -1);
}
return dp(ring, key, 0, 0);
}
public int dp(String ring, String key, int i, int j) {
if (j == key.length()) {
return 0;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
char keyC = key.charAt(j);
char rC = ring.charAt(i);
int min;
//相等直接拼写操作数+1
if (keyC == rC) {
min = dp(ring, key, i, j + 1) + 1;
} else {
TreeSet<Integer> indexSet = map.get(keyC);
Integer left = indexSet.floor(i);
if (left == null) {
left = indexSet.last();
}
Integer right = indexSet.ceiling(i);
if (right == null) {
right = indexSet.first();
}
int leftStep = left > i ? i + ring.length() - left : i - left;
int rightStep = right < i ? ring.length() - i + right : right - i;
//顺时针逆时针去最小值
min = Math.min(dp(ring, key, left, j) + leftStep, dp(ring, key, right, j) + rightStep);
}
memo[i][j] = min;
return min;
}
} |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:动态规划帮我通关了《辐射4》
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions