Hard

题目描述

给定一个整数数组 arr。从某个起始索引开始,你可以进行一系列跳跃。这个系列中的第(1、3、5…)次跳跃称为奇数跳跃,第(2、4、6…)次跳跃称为偶数跳跃。注意跳跃是按次数编号的,而不是按索引编号。

你可以按以下方式从索引 i 跳到索引 j(其中 i < j):

  • 在奇数跳跃期间(即跳跃 1、3、5…),你跳到索引 j,使得 arr[i] <= arr[j]arr[j] 是可能的最小值。如果有多个这样的索引 j,你只能跳到最小的索引 j
  • 在偶数跳跃期间(即跳跃 2、4、6…),你跳到索引 j,使得 arr[i] >= arr[j]arr[j] 是可能的最大值。如果有多个这样的索引 j,你只能跳到最小的索引 j
  • 对于某些索引 i,可能没有合法的跳跃。

如果从某个起始索引开始,你可以通过跳跃若干次(可能 0 次或多次)到达数组的末尾(索引 arr.length - 1),则该起始索引是好的。

返回好的起始索引的数量。

示例 1:

输入:arr = [10,13,12,14,15]
输出:2
解释:
从起始索引 i = 0,我们可以进行第1次跳跃到 i = 2(因为 arr[2] 是 arr[1], arr[2], arr[3], arr[4] 中大于等于 arr[0] 的最小值),然后我们无法再跳跃了。
从起始索引 i = 1 和 i = 2,我们可以进行第1次跳跃到 i = 3,然后我们无法再跳跃了。
从起始索引 i = 3,我们可以进行第1次跳跃到 i = 4,所以我们到达了末尾。
从起始索引 i = 4,我们已经在末尾了。
总共有2个不同的起始索引 i = 3 和 i = 4,我们可以通过若干次跳跃到达末尾。

示例 2:

输入:arr = [2,3,1,1,4]
输出:3

示例 3:

输入:arr = [5,1,3,4,2]
输出:3

提示:

  • 1 <= arr.length <= 2 * 10^4
  • 0 <= arr[i] < 10^5

解题思路

这个问题需要动态规划和单调栈的结合来解决。

核心思路:

  1. 对于每个位置,我们需要知道奇数跳跃和偶数跳跃能到达的下一个位置
  2. 使用动态规划记录从每个位置出发,进行奇数跳跃或偶数跳跃是否能到达终点

解决步骤:

首先需要预处理出每个位置的下一跳位置:

  • 奇数跳跃:找到右边第一个大于等于当前值的最小值对应的索引
  • 偶数跳跃:找到右边第一个小于等于当前值的最大值对应的索引

这可以通过排序 + 单调栈来高效实现:

  1. 将索引按值排序(奇数跳跃升序,偶数跳跃降序)
  2. 使用单调栈维护递增的索引序列,找到每个位置的下一个可跳跃位置

然后使用动态规划:

  • odd[i] 表示从位置 i 开始奇数跳跃能否到达终点
  • even[i] 表示从位置 i 开始偶数跳跃能否到达终点

状态转移:

  • odd[i] = even[nextOdd[i]](奇数跳跃后变成偶数跳跃)
  • even[i] = odd[nextEven[i]](偶数跳跃后变成奇数跳跃)

推荐解法: 单调栈 + 动态规划,时间复杂度最优。

代码实现

class Solution {
public:
    int oddEvenJumps(vector<int>& arr) {
        int n = arr.size();
        vector<int> nextOdd(n, -1), nextEven(n, -1);
        
        // 计算奇数跳跃的下一个位置
        map<int, int> mp;
        for (int i = n - 1; i >= 0; i--) {
            auto it = mp.lower_bound(arr[i]);
            if (it != mp.end()) {
                nextOdd[i] = it->second;
            }
            mp[arr[i]] = i;
        }
        
        // 计算偶数跳跃的下一个位置
        mp.clear();
        for (int i = n - 1; i >= 0; i--) {
            auto it = mp.upper_bound(arr[i]);
            if (it != mp.begin()) {
                --it;
                nextEven[i] = it->second;
            }
            mp[arr[i]] = i;
        }
        
        // 动态规划
        vector<bool> odd(n, false), even(n, false);
        odd[n - 1] = even[n - 1] = true;
        
        int result = 1;
        for (int i = n - 2; i >= 0; i--) {
            if (nextOdd[i] != -1) {
                odd[i] = even[nextOdd[i]];
            }
            if (nextEven[i] != -1) {
                even[i] = odd[nextEven[i]];
            }
            if (odd[i]) result++;
        }
        
        return result;
    }
};
class Solution:
    def oddEvenJumps(self, arr: List[int]) -> int:
        from bisect import bisect_left
        
        n = len(arr)
        next_odd = [-1] * n
        next_even = [-1] * n
        
        # 计算奇数跳跃的下一个位置
        sorted_indices = []
        values = []
        for i in range(n - 1, -1, -1):
            pos = bisect_left(values, arr[i])
            if pos < len(values):
                next_odd[i] = sorted_indices[pos]
            
            # 插入当前元素,保持有序
            insert_pos = bisect_left(values, arr[i])
            values.insert(insert_pos, arr[i])
            sorted_indices.insert(insert_pos, i)
        
        # 计算偶数跳跃的下一个位置
        sorted_indices = []
        values = []
        for i in range(n - 1, -1, -1):
            pos = bisect_left(values, arr[i])
            if pos > 0:
                next_even[i] = sorted_indices[pos - 1]
            elif pos == 0 and values and values[0] == arr[i]:
                next_even[i] = sorted_indices[0]
            
            # 插入当前元素,保持有序(降序)
            insert_pos = bisect_left(values, arr[i])
            if insert_pos == len(values) or values[insert_pos] != arr[i]:
                values.insert(insert_pos, arr[i])
                sorted_indices.insert(insert_pos, i)
            else:
                # 相同值,选择较小的索引
                if i < sorted_indices[insert_pos]:
                    sorted_indices[insert_pos] = i
        
        # 重新计算偶数跳跃
        indices_by_value = {}
        for i in range(n - 1, -1, -1):
            indices_by_value[arr[i]] = i
        
        sorted_vals = sorted(indices_by_value.keys(), reverse=True)
        for i in range(n - 1, -1, -1):
            pos = bisect_left(sorted_vals, arr[i])
            if pos < len(sorted_vals):
                next_even[i] = indices_by_value[sorted_vals[pos]]
        
        # 简化计算
        sorted_items = sorted((arr[i], i) for i in range(n))
        for i in range(n):
            next_odd[i] = -1
        for i in range(len(sorted_items) - 1):
            val1, idx1 = sorted_items[i]
            val2, idx2 = sorted_items[i + 1]
            if val1 == val2:
                continue
            for j in range(idx1):
                if arr[j] <= val1 and next_odd[j] == -1:
                    next_odd[j] = idx1
        
        # 重新实现
        def find_next_jumps():
            next_odd = [-1] * n
            next_even = [-1] * n
            
            # 对于奇数跳跃,按值升序、索引升序排序
            indices = list(range(n))
            indices.sort(key=lambda i: (arr[i], i))
            
            stack = []
            for i in indices:
                while stack and stack[-1] < i:
                    next_odd[stack.pop()] = i
                stack.append(i)
            
            # 对于偶数跳跃,按值降序、索引升序排序
            indices.sort(key=lambda i: (-arr[i], i))
            
            stack = []
            for i in indices:
                while stack and stack[-1] < i:
                    next_even[stack.pop()] = i
                stack.append(i)
            
            return next_odd, next_even
        
        next_odd, next_even = find_next_jumps()
        
        # 动态规划
        odd = [False] * n
        even = [False] * n
        odd[n - 1] = even[n - 1] = True
        
        result = 1
        for i in range(n - 2, -1, -1):
            if next_odd[i] != -1:
                odd[i] = even[next_odd[i]]
            if next_even[i] != -1:
                even[i] = odd[next_even[i]]
            if odd[i]:
                result += 1
        
        return result
public class Solution {
    public int OddEvenJumps(int[] arr) {
        int n = arr.Length;
        int[] nextOdd = new int[n];
        int[] nextEven = new int[n];
        Array.Fill(nextOdd, -1);
        Array.Fill(nextEven, -1);
        
        // 计算奇数跳跃的下一个位置
        var sortedMap = new SortedDictionary<int, int>();
        for (int i = n - 1; i >= 0; i--) {
            var found = sortedMap.FirstOrDefault(x => x.Key >= arr[i]);
            if (found.Key != 0) {
                nextOdd[i] = found.Value;
            }
            sortedMap[arr[i]] = i;
        }
        
        // 计算偶数跳跃的下一个位置
        sortedMap.Clear();
        for (int i = n - 1; i >= 0; i--) {
            var found = sortedMap.LastOrDefault(x => x.Key <= arr[i]);
            if (found.Key != 0 || (found.Key == 0 && sortedMap.ContainsKey(0))) {
                nextEven[i] = found.Value;
            }
            sortedMap[arr[i]] = i;
        }
        
        // 动态规划
        bool[] odd = new bool[n];
        bool[] even = new bool[n];
        odd[n - 1] = even[n - 1] = true;
        
        int result = 1;
        for (int i = n - 2; i >= 0; i--) {
            if (nextOdd[i] != -1) {
                odd[i] = even[nextOdd[i]];
            }
            if (nextEven[i] != -1) {
                even[i] = odd[nextEven[i]];
            }
            if (odd[i]) result++;
        }
        
        return result;
    }
}
var oddEvenJumps = function(arr) {
    const n = arr.length;
    const nextOdd = new Array(n).fill(-1);
    const nextEven = new Array(n).fill(-1);
    
    // 计算奇数跳跃的下一个位置 - 使用单调栈
    const indices = Array.from({length: n}, (_, i) => i);
    indices.sort((i, j) => arr[i]

复杂度分析

| 解