Range XOR Queries with Subarray Reversals
Problem
You are given an integer array nums of length n and a 2D integer array queries of length q, where each query is one of the following three types:
-
Update:
queries[i] = [1, index, value]Setnums[index] = value. -
Range XOR Query:
queries[i] = [2, left, right]Compute the bitwise XOR of all elements in the subarraynums[left...right], and record this result. -
Reverse Subarray:
queries[i] = [3, left, right]Reverse the subarraynums[left...right]in place.
Return an array of the results of all range XOR queries in the order they were encountered.
Examples
Example 1
Input: nums = [1,2,3,4,5], queries = [[2,1,3],[1,2,10],[3,0,4],[2,0,4]]
Output: [5,8]
Explanation:
Query 1: [2, 1, 3] – Compute XOR of subarray [2, 3, 4] resulting in 5.
Query 2: [1, 2, 10] – Update nums[2] to 10, updating the array to [1, 2, 10, 4, 5].
Query 3: [3, 0, 4] – Reverse the entire array to get [5, 4, 10, 2, 1].
Query 4: [2, 0, 4] – Compute XOR of subarray [5, 4, 10, 2, 1] resulting in 8.
Example 2
Input: nums = [7,8,9], queries = [[1,0,3],[2,0,2],[3,1,2]]
Output: [2]
Explanation:
Query 1: [1, 0, 3] – Update nums[0] to 3, updating the array to [3, 8, 9].
Query 2: [2, 0, 2] – Compute XOR of subarray [3, 8, 9] resulting in 2.
Query 3: [3, 1, 2] – Reverse the subarray [8, 9] to get [9, 8].
Constraints
1 <= nums.length <= 1050 <= nums[i] <= 1091 <= queries.length <= 105queries[i].length == 3queries[i][0] ∈ {1, 2, 3}- If
queries[i][0] == 1:0 <= index < nums.length0 <= value <= 109
- If
queries[i][0] == 2orqueries[i][0] == 3:0 <= left <= right < nums.length
Solution
Method 1 – Segment Tree with Lazy Propagation
Intuition
The problem requires efficiently handling range XOR queries and subarray reversals. A segment tree with lazy propagation can efficiently support both range XOR and range reverse operations by maintaining additional information for each segment.
Approach
- Build a segment tree where each node stores the XOR of its segment and a flag for pending reversals.
- For a range reversal, propagate the reversal flag down the tree and swap children as needed.
- For a range XOR query, use the segment tree to get the XOR in O(log n) time, considering any pending reversals.
- Use lazy propagation to ensure all operations are efficient.
Code
C++
// This is a template for the solution. Actual implementation may vary based on the problem's API.
class Solution {
public:
// Assume arr is the array, queries is a list of queries
// Each query: [type, l, r] where type=1 for reverse, type=2 for xor query
// Returns vector<int> for xor queries
struct Node {
int l, r, x;
bool rev;
Node *left, *right;
Node(int l, int r): l(l), r(r), x(0), rev(false), left(nullptr), right(nullptr) {}
};
Node* build(vector<int>& arr, int l, int r) {
Node* node = new Node(l, r);
if (l == r) { node->x = arr[l]; return node; }
int m = (l + r) / 2;
node->left = build(arr, l, m);
node->right = build(arr, m+1, r);
node->x = node->left->x ^ node->right->x;
return node;
}
void push(Node* node) {
if (node->rev) {
swap(node->left, node->right);
if (node->left) node->left->rev ^= 1;
if (node->right) node->right->rev ^= 1;
node->rev = false;
}
}
void reverse(Node* node, int l, int r) {
if (node->r < l || node->l > r) return;
if (l <= node->l && node->r <= r) { node->rev ^= 1; return; }
push(node);
reverse(node->left, l, r);
reverse(node->right, l, r);
node->x = node->left->x ^ node->right->x;
}
int query(Node* node, int l, int r) {
if (node->r < l || node->l > r) return 0;
if (l <= node->l && node->r <= r) return node->x;
push(node);
return query(node->left, l, r) ^ query(node->right, l, r);
}
};
Go
// This is a template for the solution. Actual implementation may vary based on the problem's API.
type Node struct {
l, r int
x int
rev bool
left, right *Node
}
func build(arr []int, l, r int) *Node {
node := &Node{l: l, r: r}
if l == r {
node.x = arr[l]
return node
}
m := (l + r) / 2
node.left = build(arr, l, m)
node.right = build(arr, m+1, r)
node.x = node.left.x ^ node.right.x
return node
}
func push(node *Node) {
if node.rev {
node.left, node.right = node.right, node.left
if node.left != nil {
node.left.rev = !node.left.rev
}
if node.right != nil {
node.right.rev = !node.right.rev
}
node.rev = false
}
}
func reverse(node *Node, l, r int) {
if node.r < l || node.l > r {
return
}
if l <= node.l && node.r <= r {
node.rev = !node.rev
return
}
push(node)
reverse(node.left, l, r)
reverse(node.right, l, r)
node.x = node.left.x ^ node.right.x
}
func query(node *Node, l, r int) int {
if node.r < l || node.l > r {
return 0
}
if l <= node.l && node.r <= r {
return node.x
}
push(node)
return query(node.left, l, r) ^ query(node.right, l, r)
}
Java
// This is a template for the solution. Actual implementation may vary based on the problem's API.
class Solution {
class Node {
int l, r, x;
boolean rev;
Node left, right;
Node(int l, int r) { this.l = l; this.r = r; }
}
Node build(int[] arr, int l, int r) {
Node node = new Node(l, r);
if (l == r) { node.x = arr[l]; return node; }
int m = (l + r) / 2;
node.left = build(arr, l, m);
node.right = build(arr, m+1, r);
node.x = node.left.x ^ node.right.x;
return node;
}
void push(Node node) {
if (node.rev) {
Node tmp = node.left; node.left = node.right; node.right = tmp;
if (node.left != null) node.left.rev ^= true;
if (node.right != null) node.right.rev ^= true;
node.rev = false;
}
}
void reverse(Node node, int l, int r) {
if (node.r < l || node.l > r) return;
if (l <= node.l && node.r <= r) { node.rev ^= true; return; }
push(node);
reverse(node.left, l, r);
reverse(node.right, l, r);
node.x = node.left.x ^ node.right.x;
}
int query(Node node, int l, int r) {
if (node.r < l || node.l > r) return 0;
if (l <= node.l && node.r <= r) return node.x;
push(node);
return query(node.left, l, r) ^ query(node.right, l, r);
}
}
Kotlin
// This is a template for the solution. Actual implementation may vary based on the problem's API.
class Solution {
inner class Node(val l: Int, val r: Int) {
var x = 0
var rev = false
var left: Node? = null
var right: Node? = null
}
fun build(arr: IntArray, l: Int, r: Int): Node {
val node = Node(l, r)
if (l == r) { node.x = arr[l]; return node }
val m = (l + r) / 2
node.left = build(arr, l, m)
node.right = build(arr, m+1, r)
node.x = node.left!!.x xor node.right!!.x
return node
}
fun push(node: Node) {
if (node.rev) {
val tmp = node.left; node.left = node.right; node.right = tmp
node.left?.rev = node.left?.rev != false
node.right?.rev = node.right?.rev != false
node.rev = false
}
}
fun reverse(node: Node, l: Int, r: Int) {
if (node.r < l || node.l > r) return
if (l <= node.l && node.r <= r) { node.rev = !node.rev; return }
push(node)
reverse(node.left!!, l, r)
reverse(node.right!!, l, r)
node.x = node.left!!.x xor node.right!!.x
}
fun query(node: Node, l: Int, r: Int): Int {
if (node.r < l || node.l > r) return 0
if (l <= node.l && node.r <= r) return node.x
push(node)
return query(node.left!!, l, r) xor query(node.right!!, l, r)
}
}
Python
# This is a template for the solution. Actual implementation may vary based on the problem's API.
from typing import List
class Node:
def __init__(self, l: int, r: int):
self.l = l
self.r = r
self.x = 0
self.rev = False
self.left = None
self.right = None
def build(arr: List[int], l: int, r: int) -> Node:
node = Node(l, r)
if l == r:
node.x = arr[l]
return node
m = (l + r) // 2
node.left = build(arr, l, m)
node.right = build(arr, m+1, r)
node.x = node.left.x ^ node.right.x
return node
def push(node: Node):
if node.rev:
node.left, node.right = node.right, node.left
if node.left: node.left.rev ^= True
if node.right: node.right.rev ^= True
node.rev = False
def reverse(node: Node, l: int, r: int):
if node.r < l or node.l > r:
return
if l <= node.l and node.r <= r:
node.rev ^= True
return
push(node)
reverse(node.left, l, r)
reverse(node.right, l, r)
node.x = node.left.x ^ node.right.x
def query(node: Node, l: int, r: int) -> int:
if node.r < l or node.l > r:
return 0
if l <= node.l and node.r <= r:
return node.x
push(node)
return query(node.left, l, r) ^ query(node.right, l, r)
Rust
// This is a template for the solution. Actual implementation may vary based on the problem's API.
struct Node {
l: usize,
r: usize,
x: i32,
rev: bool,
left: Option<Box<Node>>,
right: Option<Box<Node>>,
}
impl Node {
fn build(arr: &[i32], l: usize, r: usize) -> Box<Node> {
let mut node = Box::new(Node { l, r, x: 0, rev: false, left: None, right: None });
if l == r {
node.x = arr[l];
return node;
}
let m = (l + r) / 2;
node.left = Some(Node::build(arr, l, m));
node.right = Some(Node::build(arr, m+1, r));
node.x = node.left.as_ref().unwrap().x ^ node.right.as_ref().unwrap().x;
node
}
fn push(node: &mut Box<Node>) {
if node.rev {
std::mem::swap(&mut node.left, &mut node.right);
if let Some(ref mut l) = node.left { l.rev ^= true; }
if let Some(ref mut r) = node.right { r.rev ^= true; }
node.rev = false;
}
}
fn reverse(node: &mut Box<Node>, l: usize, r: usize) {
if node.r < l || node.l > r { return; }
if l <= node.l && node.r <= r { node.rev ^= true; return; }
Node::push(node);
Node::reverse(node.left.as_mut().unwrap(), l, r);
Node::reverse(node.right.as_mut().unwrap(), l, r);
node.x = node.left.as_ref().unwrap().x ^ node.right.as_ref().unwrap().x;
}
fn query(node: &mut Box<Node>, l: usize, r: usize) -> i32 {
if node.r < l || node.l > r { return 0; }
if l <= node.l && node.r <= r { return node.x; }
Node::push(node);
Node::query(node.left.as_mut().unwrap(), l, r) ^ Node::query(node.right.as_mut().unwrap(), l, r)
}
}
TypeScript
// This is a template for the solution. Actual implementation may vary based on the problem's API.
class Node {
l: number; r: number; x: number; rev: boolean;
left: Node | null; right: Node | null;
constructor(l: number, r: number) {
this.l = l; this.r = r; this.x = 0; this.rev = false;
this.left = null; this.right = null;
}
}
function build(arr: number[], l: number, r: number): Node {
const node = new Node(l, r);
if (l === r) { node.x = arr[l]; return node; }
const m = Math.floor((l + r) / 2);
node.left = build(arr, l, m);
node.right = build(arr, m+1, r);
node.x = node.left.x ^ node.right.x;
return node;
}
function push(node: Node) {
if (node.rev) {
[node.left, node.right] = [node.right, node.left];
if (node.left) node.left.rev = !node.left.rev;
if (node.right) node.right.rev = !node.right.rev;
node.rev = false;
}
}
function reverse(node: Node, l: number, r: number) {
if (node.r < l || node.l > r) return;
if (l <= node.l && node.r <= r) { node.rev = !node.rev; return; }
push(node);
reverse(node.left!, l, r);
reverse(node.right!, l, r);
node.x = node.left!.x ^ node.right!.x;
}
function query(node: Node, l: number, r: number): number {
if (node.r < l || node.l > r) return 0;
if (l <= node.l && node.r <= r) return node.x;
push(node);
return query(node.left!, l, r) ^ query(node.right!, l, r);
}
Complexity
- ⏰ Time complexity:
O(log n)per query or update, since each operation traverses the height of the segment tree. - 🧺 Space complexity:
O(n), for the segment tree nodes.