题目 975. 奇偶跳
难度困难
示例1:
输入:[10,13,12,14,15]
输出:2
解释:
从起始索引 i = 0 出发,我们可以跳到 i = 2,(因为 A[2] 是 A[1],A[2],A[3],A[4] 中大于或等于 A[0] 的最小值),然后我们就无法继续跳下去了。
从起始索引 i = 1 和 i = 2 出发,我们可以跳到 i = 3,然后我们就无法继续跳下去了。
从起始索引 i = 3 出发,我们可以跳到 i = 4,到达数组末尾。
从起始索引 i = 4 出发,我们已经到达数组末尾。
总之,我们可以从 2 个不同的起始索引(i = 3, i = 4)出发,通过一定数量的跳跃到达数组末尾。
示例2:
输入:[2,3,1,1,4]
输出:3
解释:
从起始索引 i=0 出发,我们依次可以跳到 i = 1,i = 2,i = 3:
在我们的第一次跳跃(奇数)中,我们先跳到 i = 1,因为 A[1] 是(A[1],A[2],A[3],A[4])中大于或等于 A[0] 的最小值。
在我们的第二次跳跃(偶数)中,我们从 i = 1 跳到 i = 2,因为 A[2] 是(A[2],A[3],A[4])中小于或等于 A[1] 的最大值。A[3] 也是最大的值,但 2 是一个较小的索引,所以我们只能跳到 i = 2,而不能跳到 i = 3。
在我们的第三次跳跃(奇数)中,我们从 i = 2 跳到 i = 3,因为 A[3] 是(A[3],A[4])中大于或等于 A[2] 的最小值。
我们不能从 i = 3 跳到 i = 4,所以起始索引 i = 0 不是好的起始索引。
类似地,我们可以推断:
从起始索引 i = 1 出发, 我们跳到 i = 4,这样我们就到达数组末尾。
从起始索引 i = 2 出发, 我们跳到 i = 3,然后我们就不能再跳了。
从起始索引 i = 3 出发, 我们跳到 i = 4,这样我们就到达数组末尾。
从起始索引 i = 4 出发,我们已经到达数组末尾。
总之,我们可以从 3 个不同的起始索引(i = 1, i = 3, i = 4)出发,通过一定数量的跳跃到达数组末尾。
示例3:
输入:[5,1,3,4,2]
输出:3
解释:
我们可以从起始索引 1,2,4 出发到达数组末尾。
解法1 - 暴力解法(超时)
我拿到题目的解法还是暴力解法,循环遍历每一个数,判断是否可以执行奇数或偶数跳。最终结果超时了,我傻了,hhh虽然早就觉得会超时。
class Solution {
public:
int oddEvenJumps(vector<int>& A) {
int i,j,time = 0,jump,flag = 0,count=0,temp=0;
for(i = 0; i < A.size(); i++){
jump = i;
while(1)
{
if(time % 2 == 1){
//奇数跳
flag = 0;
for(j=jump+1; j<A.size();j++)
{
if(A[j] >= A[jump])
{
flag = 1;
if(temp < A[j]){
temp = A[j];
jump = j;
}else if(temp == A[j] && jump < j){
jump = jump;
}
}
}
if(jump == A.size() && flag == 1){
count ++;
break;
}else if(flag == 0){
break;
}
}else
{
if(time % 2 == 1)
{
//偶数跳
flag = 0;
for(j=jump+1; j<A.size();j++)
{
if(A[j] <= A[jump])
{
flag = 1;
if(temp > A[j]){
temp = A[j];
jump = j;
}else if(temp == A[j] && jump < j){
jump = jump;
}
}
}
if(jump == A.size() && flag == 1){
count ++;
break;
}else if(flag == 0){
break;
}
}
}
}
}
return count;
}
};
解法2 - 树映射(Tree Map)
这是一个很符合题目类型的解法,数据结构选的是 TreeMap 以及一个单调栈,运用了动态规划
思路
首先,我们可以发现下一步应该跳到哪里只与我们当前的位置与跳跃次数的奇偶性有关系。
对于每一种状态,接下来可以跳到的状态一定只有一种(或者接下来不能跳跃了)。如果我们使用某种方法知道了不同状态之间的转移关系,我们就可以通过一次简单的遍历解决这个问题了。
于是,问题就简化为了:从索引 i 进行奇数次跳跃时,下一步应该跳到哪里去(如果有的话)。偶数次跳跃也是类似的。
算法
使用 TreeMap,一个维护有序数据的绝佳数据结构。我们将索引 i 映射到 v = A[i] 上。
从 i = N-2 到 i = 0 的遍历过程中,对于 v = A[i], 我们想知道比它略大一点和略小一点的元素是谁。 TreeMap.lowerKey 与 TreeMap.higherKey 函数就是用来做这样一件事情的。
了解这一点之后,解法接下来的内容就非常直接了:
我们使用动态规划来维护 odd[i] 和 even[i]:从索引 i 出发奇数次跳跃与偶数次跳跃是否能到达数组末尾。
class Solution {
public int oddEvenJumps(int[] A) {
int N = A.length;
if (N <= 1) return N;
boolean[] odd = new boolean[N];
boolean[] even = new boolean[N];
odd[N-1] = even[N-1] = true;
TreeMap<Integer, Integer> vals = new TreeMap();
vals.put(A[N-1], N-1);
for (int i = N-2; i >= 0; --i) {
int v = A[i];
if (vals.containsKey(v)) {
odd[i] = even[vals.get(v)];
even[i] = odd[vals.get(v)];
} else {
Integer lower = vals.lowerKey(v);
Integer higher = vals.higherKey(v);
if (lower != null)
even[i] = odd[vals.get(lower)];
if (higher != null) {
odd[i] = even[vals.get(higher)];
}
}
vals.put(v, i);
}
int ans = 0;
for (boolean b: odd)
if (b) ans++;
return ans;
}
}