Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .claude/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,28 @@ Common short names (use consistently across the repo):
- Max line length: 100 characters (soft limit)
- Imports: group by package, alphabetize within groups, no wildcard imports

### Big-O Notation Convention

Always use explicit multiplication and parentheses in Big-O expressions for clarity:

```java
// ✓ GOOD — explicit and unambiguous
// Time: O(n*log(n))
// Time: O(n*log^2(n))
// Time: O(n^2*log(n))

// ✗ BAD — missing multiplication and parentheses
// Time: O(n log n)
// Time: O(n log^2 n)
// Time: O(n^2 log n)

// Simple expressions without multiplication are fine as-is
// Time: O(n)
// Time: O(n^2)
// Time: O(log(n))
// Space: O(n)
```

### Avoid Java Streams

Streams hurt readability for learners. Use plain loops instead:
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
# Geometry

- [Angle between 2D vectors](src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java) **- O(1)**
- [Angle between 3D vectors](src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java) **- O(1)**
- [Circle-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js) **- O(1)**
- [Circle-line intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)**
- [Circle-line segment intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)**
Expand All @@ -154,21 +153,28 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
- [Convex hull (Graham Scan algorithm)](src/main/java/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java) **- O(nlog(n))**
- [Convex hull (Monotone chain algorithm)](src/main/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java) **- O(nlog(n))**
- [Convex polygon area](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java) **- O(n)**
- [Convex polygon cut](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java) **- O(n)**
- [Convex polygon contains points](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java) **- O(log(n))**
- [Triangle area algorithms](src/main/java/com/williamfiset/algorithms/geometry/TriangleArea.java) **- O(1)**
- [Line segment-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)**
- [Line segment-line segment intersection](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java) **- O(1)**

<details>
<summary>More geometry algorithms</summary>

- [Angle between 3D vectors](src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java) **- O(1)**
- [Convex polygon cut](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java) **- O(n)**
- [Coplanar points test (are four 3D points on the same plane)](src/main/java/com/williamfiset/algorithms/geometry/CoplanarPoints.java) **- O(1)**
- [Line class (handy infinite line class)](src/main/java/com/williamfiset/algorithms/geometry/Line.java) **- O(1)**
- [Line-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)**
- [Line segment-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)**
- [Line segment to general form (ax + by = c)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java) **- O(1)**
- [Line segment-line segment intersection](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java) **- O(1)**
- [Longitude-Latitude geographic distance](src/main/java/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java) **- O(1)**
- [Point is inside triangle check](src/main/java/com/williamfiset/algorithms/geometry/PointInsideTriangle.java) **- O(1)**
- [Point rotation about point](src/main/java/com/williamfiset/algorithms/geometry/PointRotation.java) **- O(1)**
- [Triangle area algorithms](src/main/java/com/williamfiset/algorithms/geometry/TriangleArea.java) **- O(1)**
- [[UNTESTED] Circle-circle intersection area](src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java) **- O(1)**
- [[UNTESTED] Circular segment area](src/main/java/com/williamfiset/algorithms/geometry/CircularSegmentArea.java) **- O(1)**

</details>

# Graph theory

### Tree algorithms
Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,123 @@
package com.williamfiset.algorithms.datastructures.segmenttree;

import java.util.Arrays;

/**
* A compact array based segment tree implementation. This segment tree supports point updates and
* range queries.
* Compact Array-Based Segment Tree
*
* A space-efficient segment tree stored in a flat array of size 2*n (no
* recursion, no pointers). Supports point updates and range queries using
* any associative combine function (sum, min, max, product, GCD, etc.).
*
* The tree is stored bottom-up: leaves occupy indices [n, 2n) and internal
* nodes occupy [1, n). Index 0 is unused. Each internal node i is the
* combination of its children at 2i and 2i+1.
*
* Use cases:
* - Range sum / min / max queries with point updates
* - Competitive programming (very short, cache-friendly implementation)
*
* Time: O(n) construction, O(log(n)) per query and update
* Space: O(n)
*
* @author Al.Cash & William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.datastructures.segmenttree;

public class CompactSegmentTree {

private int N;

// Let UNIQUE be a value which does NOT
// and will not appear in the segment tree
private long UNIQUE = 8123572096793136074L;

// Segment tree values
private long[] tree;
// Flat array storing the segment tree. Leaves are at indices [N, 2N),
// internal nodes at [1, N). Index 0 is unused. Uninitialized slots
// are null, which acts as the identity element for the combine function.
private Long[] tree;

/**
* Creates an empty segment tree of the given size, with all slots
* initialized to null.
*
* @param size the number of elements (leaves) in the segment tree
*/
public CompactSegmentTree(int size) {
tree = new long[2 * (N = size)];
java.util.Arrays.fill(tree, UNIQUE);
tree = new Long[2 * (N = size)];
}

/**
* Creates a segment tree from an array of values.
*
* @param values the initial leaf values
*/
public CompactSegmentTree(long[] values) {
this(values.length);
// TODO(william): Implement smarter construction.
for (int i = 0; i < N; i++) modify(i, values[i]);
}

// This is the segment tree function we are using for queries.
// The function must be an associative function, meaning
// the following property must hold: f(f(a,b),c) = f(a,f(b,c)).
// Common associative functions used with segment trees
// include: min, max, sum, product, GCD, and etc...
private long function(long a, long b) {
if (a == UNIQUE) return b;
else if (b == UNIQUE) return a;

return a + b; // sum over a range
// return (a > b) ? a : b; // maximum value over a range
// return (a < b) ? a : b; // minimum value over a range
// return a * b; // product over a range (watch out for overflow!)
/**
* The associative combine function used for queries. This function must
* satisfy f(f(a,b), c) = f(a, f(b,c)) for correct segment tree behavior.
* Null acts as the identity element: f(null, x) = f(x, null) = x.
*
* Change this to customize the query type:
* return a + b; // sum over a range
* return (a > b) ? a : b; // maximum over a range
* return (a < b) ? a : b; // minimum over a range
* return a * b; // product over a range (watch for overflow!)
*/
private Long function(Long a, Long b) {
if (a == null) return b;
if (b == null) return a;
return a + b;
}

// Adjust point i by a value, O(log(n))
/**
* Updates the value at index i by combining it with the given value
* using the combine function, then propagates changes up to the root.
*
* @param i the leaf index to update (0-based)
* @param value the value to combine at position i
*
* Time: O(log(n))
*/
public void modify(int i, long value) {
// Update the leaf node
tree[i + N] = function(tree[i + N], value);
// Propagate up: recompute each ancestor from its two children
for (i += N; i > 1; i >>= 1) {
tree[i >> 1] = function(tree[i], tree[i ^ 1]);
}
}

// Query interval [l, r), O(log(n))
/**
* Queries the aggregate value over the half-open interval [l, r).
*
* Works by starting at the leaves and moving up. At each level, if the
* left boundary is a right child, include it and move right. If the right
* boundary is a right child, move left and include it.
*
* @param l left endpoint (inclusive, 0-based)
* @param r right endpoint (exclusive, 0-based)
* @return the combined result over [l, r)
* @throws IllegalStateException if the query range is empty
*
* Time: O(log(n))
*/
public long query(int l, int r) {
long res = UNIQUE;
Long res = null;
for (l += N, r += N; l < r; l >>= 1, r >>= 1) {
// If l is a right child, include it and move to next subtree
if ((l & 1) != 0) res = function(res, tree[l++]);
// If r is a right child, include its left sibling
if ((r & 1) != 0) res = function(res, tree[--r]);
}
if (res == UNIQUE) {
throw new IllegalStateException("UNIQUE should not be the return value.");
if (res == null) {
throw new IllegalStateException("Empty query range.");
}
return res;
}

public static void main(String[] args) {
// exmaple1();
example2();
}

private static void example1() {
long[] values = new long[] {3, 0, 8, 9, 8, 2, 5, 3, 7, 1};
CompactSegmentTree st = new CompactSegmentTree(values);
System.out.println(java.util.Arrays.toString(st.tree));
}

private static void example2() {
long[] values = new long[] {1, 1, 1, 1, 1, 1};
CompactSegmentTree st = new CompactSegmentTree(values);
System.out.println(java.util.Arrays.toString(st.tree));

System.out.println(Arrays.toString(st.tree));
System.out.println(st.query(0, 6)); // 6
System.out.println(st.query(1, 5)); // 4
System.out.println(st.query(0, 2)); // 2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
package com.williamfiset.algorithms.datastructures.suffixarray;

import java.util.Arrays;

/**
* Suffix array construction implementation.
* Fast Suffix Array Construction (Prefix Doubling with Radix Sort)
*
* Builds a suffix array using prefix doubling with counting sort (radix sort)
* instead of comparison-based sorting. Each doubling round uses two passes of
* counting sort to sort suffix pairs by their rank, achieving O(n) per round
* instead of O(n*log(n)) with comparison sort.
*
* <p>Time Complexity: O(nlog(n))
* Compare with SuffixArraySlow (O(n^2*log(n))) for a naive approach, and
* SuffixArrayMed (O(n*log^2(n))) for prefix doubling with comparison sort.
*
* Time: O(n*log(n)) -- O(log(n)) doubling rounds, each O(n) with radix sort
* Space: O(n + alphabetSize)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.datastructures.suffixarray;

public class SuffixArrayFast extends SuffixArray {

private static final int DEFAULT_ALPHABET_SIZE = 256;

int alphabetSize;
int[] sa2, rank, tmp, c;
private int alphabetSize;
private int[] sa2, rank, tmp, c;

public SuffixArrayFast(String text) {
this(toIntArray(text), DEFAULT_ALPHABET_SIZE);
Expand All @@ -22,12 +33,22 @@ public SuffixArrayFast(int[] text) {
this(text, DEFAULT_ALPHABET_SIZE);
}

// Designated constructor
/**
* Creates a suffix array with a custom alphabet size.
*
* @param text the input text as an integer array
* @param alphabetSize the number of distinct symbols (e.g., 256 for ASCII)
*/
public SuffixArrayFast(int[] text, int alphabetSize) {
super(text);
this.alphabetSize = alphabetSize;
}

/**
* Constructs the suffix array using prefix doubling with radix sort.
* Each round doubles the comparison window and re-ranks suffixes using
* counting sort for O(n) per round, giving O(n*log(n)) total.
*/
@Override
protected void construct() {
sa = new int[N];
Expand All @@ -36,26 +57,48 @@ protected void construct() {
c = new int[Math.max(alphabetSize, N)];

int i, p, r;

// --- Initial sort: rank suffixes by their first character using counting sort ---

// Count occurrences of each character
for (i = 0; i < N; ++i) c[rank[i] = T[i]]++;
// Convert counts to cumulative positions
for (i = 1; i < alphabetSize; ++i) c[i] += c[i - 1];
// Place suffixes into sa in sorted order (stable, right-to-left)
for (i = N - 1; i >= 0; --i) sa[--c[T[i]]] = i;

// --- Prefix doubling: sort by first 2^k characters each round ---
for (p = 1; p < N; p <<= 1) {

// Build sa2: suffixes sorted by their *second half* (positions i+p).
// Suffixes near the end (i >= N-p) have no second half, so they sort first.
for (r = 0, i = N - p; i < N; ++i) sa2[r++] = i;
// Remaining suffixes inherit order from sa (already sorted by first half)
for (i = 0; i < N; ++i) if (sa[i] >= p) sa2[r++] = sa[i] - p;
java.util.Arrays.fill(c, 0, alphabetSize, 0);

// Counting sort sa2 by first-half rank to get the final sorted order.
// This is a radix sort: sa2 provides second-key order, we sort by first key.
Arrays.fill(c, 0, alphabetSize, 0);
for (i = 0; i < N; ++i) c[rank[i]]++;
for (i = 1; i < alphabetSize; ++i) c[i] += c[i - 1];
for (i = N - 1; i >= 0; --i) sa[--c[rank[sa2[i]]]] = sa2[i];

// Compute new ranks from the sorted order. Two suffixes get the same
// rank only if both their first-half and second-half ranks match.
for (sa2[sa[0]] = r = 0, i = 1; i < N; ++i) {
if (!(rank[sa[i - 1]] == rank[sa[i]]
&& sa[i - 1] + p < N
&& sa[i] + p < N
&& rank[sa[i - 1] + p] == rank[sa[i] + p])) r++;
sa2[sa[i]] = r;
}

// Swap rank and sa2 arrays to avoid allocation
tmp = rank;
rank = sa2;
sa2 = tmp;

// All ranks unique means sorting is complete
if (r == N - 1) break;
alphabetSize = r + 1;
}
Expand Down
Loading
Loading