Leetcode 1320 (一维DP)

题意

给定一个英文单词,用两只手指去码字。每只手指移动所需要的代价为键盘上两字母之间的距离,求码完整个单词所需要的最小代价。

解法

最直观的想法是,运用 d p [ i d x ] [ p o s 1 ] [ p o s 2 ] dp[idx][pos1][pos2] dp[idx][pos1][pos2]来表示输入到第 i d x idx idx个字母,并且第一只手指在 p o s 1 pos1 pos1,第二只手指在 p o s 2 pos2 pos2时的最小代价。这时状态转移方程为:

  • 移动左手: d p [ i d x + 1 ] [ c u r ] [ o t h e r ] = m i n ( d p [ i d x + 1 ] [ c u r ] [ o t h e r ] , d p [ i d x ] [ p r e v ] [ o t h e r ] + c o s t ) ; dp[idx+1][cur][other] = min(dp[idx+1][cur][other], dp[idx][prev][other] + cost); dp[idx+1][cur][other]=min(dp[idx+1][cur][other],dp[idx][prev][other]+cost);
  • 移动右手: d p [ i d x + 1 ] [ o t h e r ] [ c u r ] = m i n ( d p [ i d x + 1 ] [ o t h e r ] [ c u r ] , d p [ i d x ] [ o t h e r ] [ p r e v ] + c o s t ) ; dp[idx+1][other][cur] = min(dp[idx+1][other][cur], dp[idx][other][prev] + cost); dp[idx+1][other][cur]=min(dp[idx+1][other][cur],dp[idx][other][prev]+cost);

由于每个状态都是从上个状态转移过来的,通过优化,我们可以把第一维度优化掉,变成 d p [ 2 ] [ p o s 1 ] [ p o s 2 ] dp[2][pos1][pos2] dp[2][pos1][pos2]这个形式。这种方法的时间复杂度没变,为 O ( w o r d L e n g t h ∗ 26 ∗ 26 ) O(wordLength*26*26) O(wordLength2626)

仔细观察后,我们发现区分左右手的其实是没有必要的,比如 d p [ 2 ] [ p o s 1 ] [ p o s 2 ] dp[2][pos1][pos2] dp[2][pos1][pos2] d p [ 2 ] [ p o s 2 ] [ p o s 1 ] dp[2][pos2][pos1] dp[2][pos2][pos1]是一样的,因为我们可以想象随时左右手交换,结果也不会改变。同时,一个更重要的发现是,由于我们是按顺序码字,因此每次总有一只手停留在上一个刚码字母的那个字母处。

有了这个信息,我们可以只记录后手所在的位置(因为先手一定落在刚刚敲击完字母的那个位置),来进行动态规划。我们用 d p [ i d x ] [ x ] dp[idx][x] dp[idx][x]表示先手在刚刚敲击完 w o r d [ i d x ] word[idx] word[idx],后手停留在字母 x x x处的最小代价。于是状态转移方程变为:

  • 移动先手: d p [ i d x ] [ t ] = m i n ( d p [ i d x ] [ t ] , d p [ i d x − 1 ] [ t ] + c o s t [ a 1 ] [ a 2 ] ) ; dp[idx][t] = min(dp[idx][t], dp[idx-1][t] + cost[a1][a2]); dp[idx][t]=min(dp[idx][t],dp[idx1][t]+cost[a1][a2]);
  • 移动后手: d p [ i d x ] [ a 1 ] = m i n ( d p [ i d x ] [ a 1 ] , d p [ i d x − 1 ] [ t ] + c o s t [ t ] [ a 2 ] ) ; dp[idx][a1] = min(dp[idx][a1], dp[idx-1][t] + cost[t][a2]); dp[idx][a1]=min(dp[idx][a1],dp[idx1][t]+cost[t][a2]);

这样一来,时间复杂度便可优化到 O ( w o r d L e n g t h ∗ 26 ) O(wordLength*26) O(wordLength26)。同理,我们可以优化掉第一维空间,使得最终的空间复杂度为常数级别。

思考

对于此类题型,我们如果发现以下两个特征:

  • 多维DP数组填不满或者有冗余(左右顺序交换不太重要,比如 d p [ p o s 1 ] [ p o s 2 ] dp[pos1][pos2] dp[pos1][pos2]等同于 d p [ p o s 2 ] [ p o s 1 ] dp[pos2][pos1] dp[pos2][pos1]
  • 多维DP数组里不同维度的值加起来等于一个恒定值(比如 p o s 1 + p o s 2 = i d x pos1 + pos2 = idx pos1+pos2=idx

这种情况,可以去想着通过优化空间得到更优解。

代码

const int INF = 0x3f3f3f3f;
class Solution {
public:
    int dp[2][27];
    vector<vector<int>> cost;
    
    pair<int, int> getPos(int x) {
        return make_pair(x/6, x%6);
    }
    
    int calculateDis(pair<int, int> pos1, pair<int, int> pos2) {
        return abs(pos1.first-pos2.first) + abs(pos1.second-pos2.second);
    }
    
    int minimumDistance(string word) {
        cost = vector<vector<int>>(30, vector<int>(30, 0));
        for (int i=0; i<26; i++)
            for (int j=0; j<=i; j++) {
                pair<int, int> pos1 = getPos(i);
                pair<int, int> pos2 = getPos(j);
                cost[i][j] = cost[j][i] = calculateDis(pos1, pos2);
            }
        memset(dp, 0x3f, sizeof(dp));
        for (int i=0; i<26; i++)
            dp[0][i] = 0;
        int len = word.length();
        for (int idx=1; idx<len; idx++) {
            int cur = idx&1, prev = !cur;
            int a1 = word[idx-1] - 'A', a2 = word[idx] - 'A';
            for (int t=0; t<26; t++) if (dp[prev][t] != INF) {
                dp[cur][t] = min(dp[cur][t], dp[prev][t] + cost[a1][a2]);
                dp[cur][a1] = min(dp[cur][a1], dp[prev][t] + cost[t][a2]);
            }
            for (int i=0; i<26; i++)
                dp[prev][i] = INF;
        }
        int ans = INF;
        for (int i=0; i<26; i++)
            ans = min(ans, dp[(len-1)&1][i]);
        return ans;
    }
};

相关题型

题目:
给定一个类似双端队列的数组 a r r arr arr,每次取元素只能从两头取,但是读此数组中的元素可以读任意位置上的。再给定一个数 K K K,以及长度为K的 m u l mul mul数组,要求从 a r r arr arr里取 K K K个元素,与这个 m u l mul mul的元素从左到右依次相乘,求乘积和最大为多少?

思路:
这个题还是可以开一个 d p [ s t e p ] [ p o s 1 ] [ p o s 2 ] dp[step][pos1][pos2] dp[step][pos1][pos2]的数组,代表总共取了step个数字,其中数组左边取了 p o s 1 pos1 pos1个元素,数组右边取了 p o s 2 pos2 pos2个元素时的最优乘积。

然而,注意到我们可以利用类似上个题空间优化的思想,用dp[step][x]表示 目前已经取了 s t e p step step个数字,并且这 s t e p step step个数字里包含左端 x x x个数字的最优乘积。得到以下状态转移方程:

d p [ s t e p ] [ x ] = m a x ( d p [ s t e p − 1 ] [ x − 1 ] + a r r R i g h t [ x ] ∗ m u l [ s t e p ] , d p [ s t e p − 1 ] [ x ] + a r r L e f t [ s t e p − x ] ∗ m u l [ s t e p ] ) ) dp[step][x] = max(dp[step-1][x-1] + arrRight[x]*mul[step], dp[step-1][x] + arrLeft[step-x]*mul[step])) dp[step][x]=max(dp[step1][x1]+arrRight[x]mul[step],dp[step1][x]+arrLeft[stepx]mul[step]))

接着同理,优化掉第一维度数组,自底向上DP求解即可。这里时间复杂度没变,为 O ( K 2 ) O(K^2) O(K2),但空间复杂度优化到了 O ( K ) O(K) O(K)

参考文献

https://leetcode.com/problems/minimum-distance-to-type-a-word-using-two-fingers/discuss/477652/JavaC%2B%2BPython-1D-DP-O(1)-Space

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值