Skip to content

975. Odd Even Jump

You are given an integer array arr. From some starting index, you can make a series of jumps. The (1st, 3rd, 5th, ...) jumps in the series are called odd-numbered jumps, and the (2nd, 4th, 6th, ...) jumps in the series are called even-numbered jumps. Note that the jumps are numbered, not the indices.

You may jump forward from index i to index j (with i < j) in the following way:

  • During odd-numbered jumps (i.e., jumps 1, 3, 5, ...), you jump to the index j such that arr[i] <= arr[j] and arr[j] is the smallest possible value. If there are multiple such indices j, you can only jump to the smallest such index j.
  • During even-numbered jumps (i.e., jumps 2, 4, 6, ...), you jump to the index j such that arr[i] >= arr[j] and arr[j] is the largest possible value. If there are multiple such indices j, you can only jump to the smallest such index j.
  • It may be the case that for some index i, there are no legal jumps.

A starting index is good if, starting from that index, you can reach the end of the array (index arr.length - 1) by jumping some number of times (possibly 0 or more than once).

Return the number of good starting indices.

Example 1:

Input: arr = [10,13,12,14,15]
Output: 2
Explanation: 
From starting index i = 0, we can make our 1st jump to i = 2 (since arr[2] is the smallest among arr[1], arr[2], arr[3], arr[4] that is greater or equal to arr[0]), then we cannot jump any more.
From starting index i = 1 and i = 2, we can make our 1st jump to i = 3, then we cannot jump any more.
From starting index i = 3, we can make our 1st jump to i = 4, so we have reached the end.
From starting index i = 4, we have reached the end already.
In total, there are 2 different starting indices i = 3 and i = 4, where we can reach the end with some number of
jumps.

Example 2:

Input: arr = [2,3,1,1,4]
Output: 3
Explanation: 
From starting index i = 0, we make jumps to i = 1, i = 2, i = 3:
During our 1st jump (odd-numbered), we first jump to i = 1 because arr[1] is the smallest value in [arr[1], arr[2], arr[3], arr[4]] that is greater than or equal to arr[0].
During our 2nd jump (even-numbered), we jump from i = 1 to i = 2 because arr[2] is the largest value in [arr[2], arr[3], arr[4]] that is less than or equal to arr[1]. arr[3] is also the largest value, but 2 is a smaller index, so we can only jump to i = 2 and not i = 3
During our 3rd jump (odd-numbered), we jump from i = 2 to i = 3 because arr[3] is the smallest value in [arr[3], arr[4]] that is greater than or equal to arr[2].
We can't jump from i = 3 to i = 4, so the starting index i = 0 is not good.
In a similar manner, we can deduce that:
From starting index i = 1, we jump to i = 4, so we reach the end.
From starting index i = 2, we jump to i = 3, and then we can't jump anymore.
From starting index i = 3, we jump to i = 4, so we reach the end.
From starting index i = 4, we are already at the end.
In total, there are 3 different starting indices i = 1, i = 3, and i = 4, where we can reach the end with some
number of jumps.

Example 3:

Input: arr = [5,1,3,4,2]
Output: 3
Explanation: We can reach the end from starting indices 1, 2, and 4.

Constraints:

  • 1 <= arr.length <= 2 * 104
  • 0 <= arr[i] < 105

Solution:

开始都是第一次跳跃 -> odd-numbered jumps -> 比如从10开始跳, 13,12, 14,15 里找比10大的最小值 -> 12

开始第二次跳跃 -> even-numbered jumps -> 从12开始跳, 14, 15 里找比12小的最大值 -> x. not good

Screenshot 2024-10-26 at 13.59.41

class Solution {
    public int oddEvenJumps(int[] arr) {
        int n = arr.length;
        int result = 0;

        // 遍历每个起始位置 i
        for (int i = 0; i < n; i++) {
            int pos = i;
            boolean isOddJump = true;

            while (pos < n) {
                int nextPos;
                if (isOddJump) {
                    nextPos = findGreater(arr, pos);  // 奇数跳跃,找下一个更大的位置
                } else {
                    nextPos = findSmaller(arr, pos);  // 偶数跳跃,找下一个更小的位置
                }

                // 如果没有符合条件的位置,则跳跃结束
                if (nextPos == -1) break;

                // 更新位置和跳跃类型
                pos = nextPos;
                isOddJump = !isOddJump;  // 切换跳跃类型
            }

            // 如果到达数组末尾,计数加1
            if (pos == n - 1) result++;
        }

        return result;
    }

    // findGreater 函数:寻找大于等于当前值且索引最小的下一个位置
    private int findGreater(int[] arr, int pos) {
        int n = arr.length;
        int minValue = Integer.MAX_VALUE;
        int nextPos = -1;

        for (int j = pos + 1; j < n; j++) {
            if (arr[j] >= arr[pos] && arr[j] < minValue) {
                minValue = arr[j];
                nextPos = j;
            }
        }

        return nextPos;
    }

    // findSmaller 函数:寻找小于等于当前值且索引最小的下一个位置
    private int findSmaller(int[] arr, int pos) {
        int n = arr.length;
        int maxValue = Integer.MIN_VALUE;
        int nextPos = -1;

        for (int j = pos + 1; j < n; j++) {
            if (arr[j] <= arr[pos] && arr[j] > maxValue) {
                maxValue = arr[j];
                nextPos = j;
            }
        }

        return nextPos;
    }
}
// 这样写逻辑更通顺

// TC: O(n^2)
// SC: O(1)
class Solution {
    public int oddEvenJumps(int[] arr) {
        int n = arr.length;
        int result = 0;

        for (int i = 0; i < n; i++){
            int pos = i;
            boolean isOddJump = true;

            while(pos < n - 1){
                int nextPos = -1;

                if (isOddJump == true){ // > min
                    int minValue = Integer.MAX_VALUE;
                    for (int j = pos + 1; j < n; j++){
                        if (arr[j] >= arr[pos] && arr[j] < minValue){
                            minValue = arr[j];
                            nextPos = j;
                        }
                    }
                }else{
                    // Even 
                    int maxValue = Integer.MIN_VALUE;
                    for (int j = pos + 1; j < n; j++){
                        if (arr[j] <= arr[pos] && arr[j] > maxValue){
                            maxValue = arr[j];
                            nextPos = j;
                        }
                    }
                }

                // If not find, then break
                if (nextPos == -1){
                    break;
                }

                // update pos and type
                pos = nextPos;
                isOddJump = !isOddJump;
            }

            // last one
            if (pos == n - 1){
                result++;
            }
        }

        return result;
    }
}
// TC: O(n^2)
// SC: O(1)
class Solution {
    public int oddEvenJumps(int[] arr) {
        int n = arr.length;

        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(arr[n - 1], n - 1);

        int result = 1;

        for (int i = n - 2; i >= 0; i--){
            Integer higherKey = map.ceilingKey(arr[i]);
            if (higherKey != null){
                int oddJumpIndex = map.get(higherKey);
                odd[i] = even[oddJumpIndex];
            }

            Integer lowerKey = map.floorKey(arr[i]);
            if (lowerKey != null){
                int evenJumpIndex = map.get(lowerKey);
                even[i] = odd[evenJumpIndex];
            }

            if (odd[i] == true){
                result++;
            }


            map.put(arr[i], i);
        }

        return result;


    }
}

// TC: O(nlogn)
// SC: O(n)

在这个问题中,我们只计算奇数跳跃(odd[i])是否能到达数组末尾,因为每个“良好起始位置”必须从奇数跳跃开始。如果从一个起始索引可以通过奇数跳跃到达末尾,那么这个索引就是一个有效的“良好起始位置”。我们不需要检查偶数跳跃(even[i])是否能到达末尾,因为它不会是跳跃的起点。

Better Solution:

Start from index n - 1 to 0:

use calculated information. We need to find value ont its right. -> save elements from right to left

TreeMap to look up whether we could some value fullfill the requirement.

oddJump map.ceilingEntry(A[i]) : greater or euqal (logn)

evenJump map.floorEntry(A[i]) : smaller or equal (logn)

whether we can reach i, is transitivly

and by odd jum and even jump alternatively

O(nlogn)

题看不懂系列???

https://www.bilibili.com/video/BV1nE411b7Lt/?spm_id_from=333.337.search-card.all.click&vd_source=73e7d2c4251a7c9000b22d21b70f5635

TreeMap 是 Java 中的一种集合类,它实现了 NavigableMap 接口,并基于红黑树(Red-Black Tree)来存储键值对。由于它是基于树的实现,TreeMap 可以按照键的自然顺序(默认)或自定义顺序来存储和排序数据。

  1. 主要特点
  • 自动排序TreeMap 中的元素是按键的顺序自动排序的。默认情况下,排序是按自然顺序(即键的 compareTo 方法)进行的。
  • 时间复杂度:由于它是基于红黑树实现的,大部分操作(例如插入、删除、查找)都能在 O(log⁡n)的时间内完成。
  • 不允许 nullTreeMap 不允许键为 null,但可以接受 null 值。
  1. 常用方法

TreeMap 提供了一些特别适合需要排序或范围查询的场景的方法,常用的有:

  • put(key, value):插入一个键值对。
  • get(key):获取指定键对应的值。
  • remove(key):删除指定键的键值对。
  • firstKey()lastKey():获取最小键和最大键。
  • ceilingKey(key):返回大于或等于指定 key 的最小键(即 "不小于" 的最小键),如果没有则返回 null
  • floorKey(key):返回小于或等于指定 key 的最大键(即 "不大于" 的最大键),如果没有则返回 null
  • higherKey(key):返回严格大于指定 key 的最小键。
  • lowerKey(key):返回严格小于指定 key 的最大键。
  1. 示例代码

以下是 TreeMap 的一些基本用法示例:

import java.util.TreeMap;

public class TreeMapExample {
    public static void main(String[] args) {
        TreeMap<Integer, String> treeMap = new TreeMap<>();

        // 插入键值对
        treeMap.put(10, "Apple");
        treeMap.put(20, "Banana");
        treeMap.put(15, "Cherry");
        treeMap.put(25, "Date");

        // 自动按键排序输出
        System.out.println("TreeMap: " + treeMap);

        // 获取某个键的值
        System.out.println("Value of key 15: " + treeMap.get(15));

        // 获取大于等于 18 的最小键
        System.out.println("Ceiling key for 18: " + treeMap.ceilingKey(18)); // 输出 20

        // 获取小于等于 18 的最大键
        System.out.println("Floor key for 18: " + treeMap.floorKey(18)); // 输出 15

        // 获取 TreeMap 的最小和最大键
        System.out.println("First key: " + treeMap.firstKey()); // 输出 10
        System.out.println("Last key: " + treeMap.lastKey());   // 输出 25
    }
}
  1. 应用场景

TreeMap 常用于需要排序和范围查询的场景。比如:

  • 排行榜:自动按分数(键)排序,可以快速查找比某个分数高或低的用户。
  • 区间查询:在给定范围内查找数据,如获取某个值附近的最小或最大值。