leetcode
leetcode 851 ~ 900
奇偶跳

奇偶跳

难度:

标签:

题目描述

代码结果

运行时间: 90 ms, 内存: 19.3 MB


/*
思路:
1. 使用TreeMap和IntStream来实现奇数跳和偶数跳。
2. 维护两个数组odd和even来记录每个索引的奇数跳和偶数跳状态。
3. 通过IntStream倒序遍历数组,从后往前更新奇数跳和偶数跳的状态。
4. 使用TreeMap来找到满足条件的索引。
*/
import java.util.*;
import java.util.stream.IntStream;

public class OddEvenJumpsStream {
    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] = true;
        even[n - 1] = true;
        TreeMap<Integer, Integer> map = new TreeMap<>();
        map.put(A[n - 1], n - 1);
        int count = 1;
        IntStream.iterate(n - 2, i -> i >= 0, i -> i - 1).forEach(i -> {
            Integer oddJump = map.ceilingKey(A[i]);
            Integer evenJump = map.floorKey(A[i]);
            if (oddJump != null) {
                odd[i] = even[map.get(oddJump)];
            }
            if (evenJump != null) {
                even[i] = odd[map.get(evenJump)];
            }
            if (odd[i]) count++;
            map.put(A[i], i);
        });
        return count;
    }
}

解释

方法:

本题解采用动态规划与单调栈的结合来解决奇偶跳的问题。首先,为了找到每个位置的奇数跳和偶数跳的目的地,我们使用单调栈两次:一次处理奇数跳,一次处理偶数跳。奇数跳需要找到右边第一个不小于当前值的最小值,而偶数跳需要找到右边第一个不大于当前值的最大值。这可以通过对数组的索引排序,并以此顺序遍历数组,使用单调栈完成。之后,使用动态规划自后向前计算,对于每个位置,记录其奇数跳和偶数跳是否能到达数组的末尾。最后,统计可以通过奇数跳到达末尾的起始位置数量,即为答案。

时间复杂度:

O(N log N)

空间复杂度:

O(N)

代码细节讲解

🦆
在单调栈的使用中,为什么要选择在处理奇数跳时按照数组值的升序排序,而在处理偶数跳时按照降序排序?
在处理奇数跳时,我们需要找到右边第一个不小于当前元素的最小元素,这可以通过将数组索引按照元素值的升序排序来实现。这样,当我们遍历排序后的索引时,能保证每一个新遍历到的元素都不小于之前的元素。使用单调栈来维护一个从栈底到栈顶递减的元素顺序,可以保证每次弹出栈顶元素时,找到的是右边第一个不小于它的元素。对于偶数跳,需要找到右边第一个不大于当前元素的最大元素,因此按照元素值的降序排序索引,使用同样的单调栈策略,可以有效找到满足条件的元素。
🦆
在构造奇数跳和偶数跳目的地的函数`make`中,为什么在遇到idx大于栈顶元素时,要进行弹栈操作?
在函数`make`中使用单调栈来维护一个索引的序列,这些索引代表的元素从栈底到栈顶是按照一定的顺序(奇数跳为递减,偶数跳为递增)排列的。当当前索引`idx`大于栈顶元素时(对于奇数跳来说,意味着我们找到了一个右边的、不小于栈顶元素并且是最接近的一个),我们需要进行弹栈操作,将栈顶元素对应的下一个可跳至的索引设置为当前索引`idx`。这确保了每个元素都能找到其合适的跳跃目的地。
🦆
代码中的`Odp`和`Edp`数组分别表示什么意义,并且这两个数组是如何交互以确定最终的路径可行性的?
`Odp`数组用于存储从每个位置出发,通过奇数次跳跃是否能够到达数组末尾的布尔值。`Edp`数组则存储通过偶数次跳跃是否能到达数组末尾的布尔值。在动态规划的过程中,我们从数组的末尾向前计算,对于每个位置,如果其奇数跳的目的地存在,则`Odp`的值取决于该目的地通过偶数跳能否到达末尾(即`Edp[nexto[idx]]`)。同理,`Edp`的值则取决于其偶数跳的目的地通过奇数跳能否到达末尾(即`Odp[nexte[idx]]`)。这种交互确保了从任一位置出发的跳跃路径的可行性可以被正确计算。
🦆
动态规划在这个问题中是如何从后向前计算的,具体是基于什么样的逻辑来更新`Odp`和`Edp`数组的?
在这个问题中,动态规划是从数组的末尾向前计算的。这是因为每个位置是否能够通过奇偶跳到达末尾取决于其后面的位置的状态。具体地,对于每个位置`idx`,如果存在奇数跳的目的地`nexto[idx]`,则`Odp[idx]`的值将取决于在`nexto[idx]`通过偶数跳是否能到达末尾的状态(即`Edp[nexto[idx]]`)。同样,如果存在偶数跳的目的地`nexte[idx]`,则`Edp[idx]`的值将取决于在`nexte[idx]`通过奇数跳是否能到达末尾的状态(即`Odp[nexte[idx]]`)。这种从后向前的更新保证了每一个位置的状态都是基于其后续位置的正确状态计算得出的,从而确保整体计算的正确性。

相关问题