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 thatarr[i] <= arr[j]
andarr[j]
is the smallest possible value. If there are multiple such indicesj
, you can only jump to the smallest such indexj
. - During even-numbered jumps (i.e., jumps 2, 4, 6, ...), you jump to the index
j
such thatarr[i] >= arr[j]
andarr[j]
is the largest possible value. If there are multiple such indicesj
, you can only jump to the smallest such indexj
. - 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
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
可以按照键的自然顺序(默认)或自定义顺序来存储和排序数据。
- 主要特点
- 自动排序:
TreeMap
中的元素是按键的顺序自动排序的。默认情况下,排序是按自然顺序(即键的compareTo
方法)进行的。- 时间复杂度:由于它是基于红黑树实现的,大部分操作(例如插入、删除、查找)都能在 O(logn)的时间内完成。
- 不允许
null
键:TreeMap
不允许键为null
,但可以接受null
值。
- 常用方法
TreeMap
提供了一些特别适合需要排序或范围查询的场景的方法,常用的有:
- put(key, value):插入一个键值对。
- get(key):获取指定键对应的值。
- remove(key):删除指定键的键值对。
- firstKey() 和 lastKey():获取最小键和最大键。
- ceilingKey(key):返回大于或等于指定
key
的最小键(即 "不小于" 的最小键),如果没有则返回null
。- floorKey(key):返回小于或等于指定
key
的最大键(即 "不大于" 的最大键),如果没有则返回null
。- higherKey(key):返回严格大于指定
key
的最小键。- lowerKey(key):返回严格小于指定
key
的最大键。
- 示例代码
以下是
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 } }
- 应用场景
TreeMap
常用于需要排序和范围查询的场景。比如:
- 排行榜:自动按分数(键)排序,可以快速查找比某个分数高或低的用户。
- 区间查询:在给定范围内查找数据,如获取某个值附近的最小或最大值。