题目
给你一个下标从 0 开始的二进制字符串 s ,表示一个列车车厢序列。s[i] = ‘0’ 表示第 i 节车厢 不 含违禁货物,而 s[i] = ‘1’ 表示第 i 节车厢含违禁货物。
作为列车长,你需要清理掉所有载有违禁货物的车厢。你可以不限次数执行下述三种操作中的任意一个:
从列车 左 端移除一节车厢(即移除 s[0]),用去 1 单位时间。
从列车 右 端移除一节车厢(即移除 s[s.length - 1]),用去 1 单位时间。
从列车车厢序列的 任意位置 移除一节车厢,用去 2 单位时间。
返回移除所有载有违禁货物车厢所需要的 最少 单位时间数。
注意,空的列车车厢序列视为没有车厢含违禁货物。
提示:
1 <= s.length <= 2 * 105
s[i] 为 '0' 或 '1'
示例 :
输入:s = "1100101"
输出:5
解释:
一种从序列中移除所有载有违禁货物的车厢的方法是:
- 从左端移除一节车厢 2 次。所用时间是 2 * 1 = 2 。
- 从右端移除一节车厢 1 次。所用时间是 1 。
- 移除序列中间位置载有违禁货物的车厢。所用时间是 2 。
总时间是 2 + 1 + 2 = 5 。
一种替代方法是:
- 从左端移除一节车厢 2 次。所用时间是 2 * 1 = 2 。
- 从右端移除一节车厢 3 次。所用时间是 3 * 1 = 3 。
总时间也是 2 + 3 = 5 。
5 是移除所有载有违禁货物的车厢所需要的最少单位时间数。
没有其他方法能够用更少的时间移除这些车厢。
思路
对于车厢i,可能从左边移除时间单位少,也可能从右边移除时间单位小。
因此如何判断出当前车厢的移除方式是解题的关键,也是最困扰我的地方。
这里应注意的是,前i个车厢可能用左移除,i+1到len个车厢可能右移除,并不是只能用一种方式移除。
因为左右都可以移除车厢,我们可以分别从左边和右边都进行车厢的移除。最后对于每一节车厢,判断通过哪种方式花费时间最少。
这里可以采用前缀和的方式记录到i节车厢要花费的时间,后面方便判断两种时间大小。
前缀和采用数组ans进行存储。
但是怎么进行记录呢,我们以从右边移除的方式为例:
- 当s[i]==‘0’,不需要移动该车厢,花费时间和上一个车厢移除时间相同,因此ans[i]=ans[i+1]
- 当s[i]==‘1’,需要移动该车厢,此时需判断移动它自己(花费两个单位时间)还是将它和它前面全部移走划算。即ans[i] = min(ans[i+1]+2, len-i)。(ans[i+1]+2为移走自己花费2个单位时间,len-i为移动它前面所有,因为是从右往左的移动,相当于移动它和它右边的车厢)
这里移动它前面所有车厢可能难以理解,在比赛时我也没想到。
其实很简单,比如"00001101",从右到左:
- 第一个为1,因为在最右端,花费1个单位时间,它右面没有车厢,相当于移动它前面所有车厢
- 第二个为2,不移动
- 第三个为1,此时它可以移除自己,总共花费(1+2)个单位时间;同时可以移动它和它右面所有车厢,花费3个单位时间
- 第四个为1,此时它可以移除自己,花费(1+2+2)个单位时间;可以移动它和它右面所有车厢,花费4个单位时间。此时应选择移动右边所有车厢。
因此实际上,对于每一节车厢,我们只需要考虑两种情况。
从右向左的前缀和数组存好后,此时ans[i]就是从i-len车厢从右向左移除的最小所需时间单位。
接着我们可以从左向右移除进行计算,采用从左向右前缀和:
对于当前车厢i,像前面一样判断0-i车厢移除违禁货物所需最小单位时间
假设left记录前i个车厢最小单位时间,那么left+ans[i+1]就代表前i个车厢从左移除,后i+1到len个车厢代表从右移除。
枚举一遍所有情况,就能找到最小值
代码
class Solution {
public:
int minimumTime(string s) {
int len = s.size(), minn = 0;
vector<int> ans(len+1);
for(int i=len-1;i>=0;i--)
ans[i] = s[i]=='0'? ans[i+1]:min(ans[i+1]+2, len-i);
minn = ans[0];
int left = 0;
for(int i=0;i<len;i++){
if(s[i]=='1')
left = min(left+2, i+1);
minn = min(minn, left+ans[i+1]);
}
return minn;
}
};