Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# 1621. Number of Sets of K Non-Overlapping Line Segments

[LeetCode Link](https://leetcode.com/problems/number-of-sets-of-k-non-overlapping-line-segments/)

Difficulty: Medium
Topics: Math, Dynamic Programming, Combinatorics, Prefix Sum
Acceptance Rate: 58.2%

## Hints

### Hint 1

Nothing about the points matters except their order — point `i` sits at `x = i`, so a
segment is fully described by the pair of indices `(a, b)` with `a < b`. A set of `k`
non-overlapping segments is therefore just a sequence of index pairs

```
a1 < b1 <= a2 < b2 <= a3 < b3 <= ... <= ak < bk
```

(sorted left to right; `b_i <= a_{i+1}` because segments may touch at an endpoint but
may not overlap). So the question is: *how many such sequences of `2k` indices are
there?* That reframing alone is most of the work. Think "count monotone sequences,"
either with dynamic programming or with a counting argument.

### Hint 2

The straightforward route is DP. Scan the points left to right and track two things:
how many segments you have completed, and whether the segment you are currently
building is still "open." That gives states like `dp[i][j][0/1]` = ways to process the
first `i` points having finished `j` segments, with the last one closed (`0`) or still
open (`1`). Every transition is O(1), so the whole thing is O(n·k) — fast enough for
`n <= 1000`. Write that down and check it against `n = 4, k = 2 -> 5`.

But look at the chain of inequalities again. It is *almost* strictly increasing: the
only non-strict steps are the `b_i <= a_{i+1}` ones, and there are exactly `k - 1` of
them. Can you make those steps strict?

### Hint 3

Yes — shift each segment right by how many segments precede it. Map the `i`-th
segment `(a_i, b_i)` (1-indexed) to `(a_i + i - 1, b_i + i - 1)`. Each `<=` in the
chain becomes `<`, so the `2k` shifted values are **strictly increasing**. They live in
`[0, (n - 1) + (k - 1)] = [0, n + k - 2]`, a range with `n + k - 1` distinct values.

The map is reversible: given any strictly increasing `2k` values in that range,
subtract `i - 1` from the `i`-th pair to recover a valid configuration. It is a
bijection, so the answer is a single binomial coefficient:

```
answer = C(n + k - 1, 2k) (mod 1e9 + 7)
```

## Approach

The solution used here is the closed form. The reasoning, in full:

**Step 1 — normalize the configuration.** A set of `k` non-overlapping segments has a
canonical left-to-right ordering. Writing the `i`-th segment as `(a_i, b_i)`, the
constraints "each segment covers two or more points" and "segments may share endpoints
but not overlap" become exactly

```
0 <= a1 < b1 <= a2 < b2 <= ... <= ak < bk <= n - 1
```

Sets of segments correspond one-to-one with such chains, so counting chains counts
sets.

**Step 2 — make the chain strictly increasing.** The chain alternates between strict
`<` (inside a segment) and non-strict `<=` (between segments). There are `k - 1`
non-strict steps. Define

```
a_i' = a_i + (i - 1) b_i' = b_i + (i - 1)
```

Adding a larger offset to each successive segment breaks every tie: if `b_i = a_{i+1}`
then `b_i' = b_i + i - 1 < a_{i+1} + i = a_{i+1}'`. Now

```
0 <= a1' < b1' < a2' < b2' < ... < ak' < bk' <= (n - 1) + (k - 1) = n + k - 2
```

**Step 3 — count.** The primed values are `2k` *distinct* numbers chosen from the
`n + k - 1` values `{0, 1, ..., n + k - 2}`, and since they are sorted, the choice of
the set determines the sequence. Conversely, any `2k`-element subset, sorted and
un-shifted, yields a valid configuration (subtracting the offsets preserves the
required inequalities). The correspondence is a bijection, hence

```
answer = C(n + k - 1, 2k)
```

**Step 4 — compute it mod 1e9 + 7.** Build the binomial multiplicatively:

```
C(N, r) = product over i in [0, r) of (N - i) / (i + 1)
```

Accumulate the numerator and denominator separately modulo the prime `p = 1e9 + 7`,
then divide once at the end using Fermat's little theorem: `den^(p-2) ≡ den^(-1)
(mod p)`. Dividing only once keeps the code short and avoids any need for a full
factorial table. Note that `n + k - 1 <= 1998 < p`, so no factor in the denominator is
ever a multiple of `p` and the inverse always exists.

**Worked example (`n = 4, k = 2`).** The five configurations are `{(0,2),(2,3)}`,
`{(0,1),(1,3)}`, `{(0,1),(2,3)}`, `{(1,2),(2,3)}`, `{(0,1),(1,2)}`. Shifting the second
segment right by one turns them into the strictly increasing quadruples
`0<2<3<4`, `0<1<2<4`, `0<1<3<4`, `1<2<3<4`, `0<1<2<3` — exactly the
`C(5, 4) = 5` four-element subsets of `{0,1,2,3,4}`. Likewise `n = 3, k = 1` gives
`C(3, 2) = 3`, and `n = 30, k = 7` gives `C(36, 14) = 3796297200 ≡ 796297179`.

If the bijection does not feel convincing yet, the O(n·k) DP from Hint 2 is a perfectly
good accepted solution and is worth writing first — then compare its table against the
binomials and watch Pascal's rule fall out. Be honest with yourself: the shift trick is
the kind of insight that is obvious *afterwards*, and most people reach it by first
staring at a DP table.

## Complexity Analysis

Time Complexity: O(k + log MOD) — one pass of `2k <= 2(n-1)` multiplications to build
the binomial, plus a single modular exponentiation for the inverse. This is
O(n) overall, versus O(n·k) for the DP formulation.

Space Complexity: O(1) — only a handful of accumulators; no DP table is materialized.

## Edge Cases

- **`k = 1`**: reduces to `C(n, 2)`, the number of index pairs. A good sanity check
that the formula is not off by one.
- **`k = n - 1` (the maximum allowed)**: `C(n + k - 1, 2k) = C(2n - 2, 2n - 2) = 1` —
the only option is to chain every unit segment `(0,1), (1,2), ..., (n-2,n-1)`. Any
off-by-one in the shift would break this case loudly.
- **Smallest input `n = 2, k = 1`**: `C(2, 2) = 1`. Guards against loops that assume
at least two segments or at least three points.
- **`2k > n + k - 1`**: mathematically impossible under the stated constraints
(`k <= n - 1`), but the binomial helper still returns `0` for out-of-range `r` rather
than producing garbage, so the code is safe if the constraints are ever relaxed.
- **Overflow**: every intermediate product is of two values below `1e9 + 7`, which fits
comfortably in a 64-bit Go `int`. Reducing after each multiplication is what keeps it
that way — dropping a single `% MOD` silently corrupts large inputs like
`n = 1000, k = 500`.
- **Modulus handling**: the answer must be taken mod `1e9 + 7` even when the true count
is small; returning the raw product is wrong only for large inputs, which is exactly
the kind of bug that passes the sample tests and fails on submission.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
number: "1621"
frontend_id: "1621"
title: "Number of Sets of K Non-Overlapping Line Segments"
slug: "number-of-sets-of-k-non-overlapping-line-segments"
difficulty: "Medium"
topics:
- "Math"
- "Dynamic Programming"
- "Combinatorics"
- "Prefix Sum"
acceptance_rate: 5820.5
is_premium: false
created_at: "2026-09-16T05:02:27.030395+00:00"
fetched_at: "2026-09-16T05:02:27.030395+00:00"
link: "https://leetcode.com/problems/number-of-sets-of-k-non-overlapping-line-segments/"
date: "2026-09-16"
---

# 1621. Number of Sets of K Non-Overlapping Line Segments

Given `n` points on a 1-D plane, where the `ith` point (from `0` to `n-1`) is at `x = i`, find the number of ways we can draw **exactly** `k` **non-overlapping** line segments such that each segment covers two or more points. The endpoints of each segment must have **integral coordinates**. The `k` line segments **do not** have to cover all `n` points, and they are **allowed** to share endpoints.

Return _the number of ways we can draw_`k` _non-overlapping line segments_ _._ Since this number can be huge, return it **modulo** `109 + 7`.



**Example 1:**

![](https://assets.leetcode.com/uploads/2020/09/07/ex1.png)


**Input:** n = 4, k = 2
**Output:** 5
**Explanation:** The two line segments are shown in red and blue.
The image above shows the 5 different ways {(0,2),(2,3)}, {(0,1),(1,3)}, {(0,1),(2,3)}, {(1,2),(2,3)}, {(0,1),(1,2)}.


**Example 2:**


**Input:** n = 3, k = 1
**Output:** 3
**Explanation:** The 3 ways are {(0,1)}, {(0,2)}, {(1,2)}.


**Example 3:**


**Input:** n = 30, k = 7
**Output:** 796297179
**Explanation:** The total number of possible ways to draw 7 line segments is 3796297200. Taking this number modulo 109 + 7 gives us 796297179.




**Constraints:**

* `2 <= n <= 1000`
* `1 <= k <= n-1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

// 1621. Number of Sets of K Non-Overlapping Line Segments
//
// A set of k non-overlapping segments is a chain of indices
//
// 0 <= a1 < b1 <= a2 < b2 <= ... <= ak < bk <= n-1
//
// Shifting the i-th segment (1-indexed) right by i-1 turns every "<=" into "<",
// so the 2k shifted values are strictly increasing and live in [0, n+k-2], a
// range of n+k-1 values. The shift is reversible, so configurations correspond
// one-to-one with 2k-element subsets of that range:
//
// answer = C(n+k-1, 2k) mod 1e9+7
//
// The binomial is built multiplicatively, with a single modular inverse (via
// Fermat's little theorem) applied at the end. O(k + log MOD) time, O(1) space.

const mod1621 = 1_000_000_007

func numberOfSets(n int, k int) int {
return binomMod1621(n+k-1, 2*k)
}

// binomMod1621 returns C(n, r) modulo mod1621, and 0 when r is out of range.
func binomMod1621(n, r int) int {
if r < 0 || r > n {
return 0
}
if r > n-r {
r = n - r
}

num, den := 1, 1
for i := 0; i < r; i++ {
num = num * ((n - i) % mod1621) % mod1621
den = den * (i + 1) % mod1621
}
return num * powMod1621(den, mod1621-2) % mod1621
}

// powMod1621 computes base^exp modulo mod1621 by fast exponentiation.
func powMod1621(base, exp int) int {
base %= mod1621
result := 1
for exp > 0 {
if exp&1 == 1 {
result = result * base % mod1621
}
base = base * base % mod1621
exp >>= 1
}
return result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import "testing"

func TestNumberOfSets(t *testing.T) {
tests := []struct {
name string
n int
k int
expected int
}{
{"example 1: n=4, k=2 gives 5 arrangements", 4, 2, 5},
{"example 2: n=3, k=1 counts every index pair", 3, 1, 3},
{"example 3: n=30, k=7 needs the modulus", 30, 7, 796297179},
{"edge case: smallest input n=2, k=1", 2, 1, 1},
{"edge case: k=n-1 forces the unit-segment chain", 6, 5, 1},
{"edge case: k=n-1 at the upper bound", 1000, 999, 1},
{"edge case: k=1 reduces to C(n,2)", 1000, 1, 499500},
{"edge case: n=5, k=2", 5, 2, 15},
{"edge case: large n and k exercise modular reduction", 1000, 500, 70047606},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if result := numberOfSets(tt.n, tt.k); result != tt.expected {
t.Errorf("numberOfSets(%d, %d) = %v, want %v", tt.n, tt.k, result, tt.expected)
}
})
}
}

// TestNumberOfSetsAgainstBruteForce cross-checks the closed form against an
// explicit enumeration of every valid chain of segments for small inputs.
func TestNumberOfSetsAgainstBruteForce(t *testing.T) {
for n := 2; n <= 9; n++ {
for k := 1; k <= n-1; k++ {
want := bruteForceSets1621(n, k)
if got := numberOfSets(n, k); got != want {
t.Errorf("numberOfSets(%d, %d) = %v, want %v (brute force)", n, k, got, want)
}
}
}
}

// bruteForceSets1621 enumerates chains a1 < b1 <= a2 < b2 <= ... < bk <= n-1,
// counting each set of segments exactly once via its left-to-right ordering.
func bruteForceSets1621(n, k int) int {
var count func(start, remaining int) int
count = func(start, remaining int) int {
if remaining == 0 {
return 1
}
total := 0
for a := start; a < n; a++ {
for b := a + 1; b < n; b++ {
total += count(b, remaining-1)
}
}
return total
}
return count(0, k)
}