Skip to content

Commit e8f3ca4

Browse files
authored
Merge branch 'master' into update-2
2 parents 120f3bc + b3e31b5 commit e8f3ca4

6 files changed

Lines changed: 406 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.awt.geom.Point2D;
4+
import java.util.Optional;
5+
6+
/**
7+
* Utility methods for checking and computing 2D line segment intersections.
8+
*/
9+
public final class LineIntersection {
10+
private LineIntersection() {
11+
}
12+
13+
/**
14+
* Checks whether two line segments intersect.
15+
*
16+
* @param p1 first endpoint of segment 1
17+
* @param p2 second endpoint of segment 1
18+
* @param q1 first endpoint of segment 2
19+
* @param q2 second endpoint of segment 2
20+
* @return true when the segments intersect (including touching endpoints)
21+
*/
22+
public static boolean intersects(Point p1, Point p2, Point q1, Point q2) {
23+
int o1 = orientation(p1, p2, q1);
24+
int o2 = orientation(p1, p2, q2);
25+
int o3 = orientation(q1, q2, p1);
26+
int o4 = orientation(q1, q2, p2);
27+
28+
if (o1 != o2 && o3 != o4) {
29+
return true;
30+
}
31+
32+
if (o1 == 0 && onSegment(p1, q1, p2)) {
33+
return true;
34+
}
35+
if (o2 == 0 && onSegment(p1, q2, p2)) {
36+
return true;
37+
}
38+
if (o3 == 0 && onSegment(q1, p1, q2)) {
39+
return true;
40+
}
41+
if (o4 == 0 && onSegment(q1, p2, q2)) {
42+
return true;
43+
}
44+
45+
return false;
46+
}
47+
48+
/**
49+
* Computes the single geometric intersection point between two non-parallel
50+
* segments when it exists.
51+
*
52+
* <p>For parallel/collinear overlap, this method returns {@code Optional.empty()}.
53+
*
54+
* @param p1 first endpoint of segment 1
55+
* @param p2 second endpoint of segment 1
56+
* @param q1 first endpoint of segment 2
57+
* @param q2 second endpoint of segment 2
58+
* @return the intersection point when uniquely defined and on both segments
59+
*/
60+
public static Optional<Point2D.Double> intersectionPoint(Point p1, Point p2, Point q1, Point q2) {
61+
if (!intersects(p1, p2, q1, q2)) {
62+
return Optional.empty();
63+
}
64+
65+
long x1 = p1.x();
66+
long y1 = p1.y();
67+
long x2 = p2.x();
68+
long y2 = p2.y();
69+
long x3 = q1.x();
70+
long y3 = q1.y();
71+
long x4 = q2.x();
72+
long y4 = q2.y();
73+
74+
long denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
75+
if (denominator == 0L) {
76+
return sharedEndpoint(p1, p2, q1, q2);
77+
}
78+
79+
long determinant1 = x1 * y2 - y1 * x2;
80+
long determinant2 = x3 * y4 - y3 * x4;
81+
long numeratorX = determinant1 * (x3 - x4) - (x1 - x2) * determinant2;
82+
long numeratorY = determinant1 * (y3 - y4) - (y1 - y2) * determinant2;
83+
84+
return Optional.of(new Point2D.Double(numeratorX / (double) denominator, numeratorY / (double) denominator));
85+
}
86+
87+
private static int orientation(Point a, Point b, Point c) {
88+
long cross = ((long) b.x() - a.x()) * ((long) c.y() - a.y()) - ((long) b.y() - a.y()) * ((long) c.x() - a.x());
89+
return Long.compare(cross, 0L);
90+
}
91+
92+
private static Optional<Point2D.Double> sharedEndpoint(Point p1, Point p2, Point q1, Point q2) {
93+
if (p1.equals(q1) || p1.equals(q2)) {
94+
return Optional.of(new Point2D.Double(p1.x(), p1.y()));
95+
}
96+
if (p2.equals(q1) || p2.equals(q2)) {
97+
return Optional.of(new Point2D.Double(p2.x(), p2.y()));
98+
}
99+
return Optional.empty();
100+
}
101+
102+
private static boolean onSegment(Point a, Point b, Point c) {
103+
return b.x() >= Math.min(a.x(), c.x()) && b.x() <= Math.max(a.x(), c.x()) && b.y() >= Math.min(a.y(), c.y()) && b.y() <= Math.max(a.y(), c.y());
104+
}
105+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.HashMap;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
/**
11+
* Merges account records using Disjoint Set Union (Union-Find) on shared emails.
12+
*
13+
* <p>Input format: each account is a list where the first element is the user name and the
14+
* remaining elements are emails.
15+
*/
16+
public final class AccountMerge {
17+
private AccountMerge() {
18+
}
19+
20+
public static List<List<String>> mergeAccounts(List<List<String>> accounts) {
21+
if (accounts == null || accounts.isEmpty()) {
22+
return List.of();
23+
}
24+
25+
UnionFind dsu = new UnionFind(accounts.size());
26+
Map<String, Integer> emailToAccount = new HashMap<>();
27+
28+
for (int i = 0; i < accounts.size(); i++) {
29+
List<String> account = accounts.get(i);
30+
for (int j = 1; j < account.size(); j++) {
31+
String email = account.get(j);
32+
Integer previous = emailToAccount.putIfAbsent(email, i);
33+
if (previous != null) {
34+
dsu.union(i, previous);
35+
}
36+
}
37+
}
38+
39+
Map<Integer, List<String>> rootToEmails = new LinkedHashMap<>();
40+
for (Map.Entry<String, Integer> entry : emailToAccount.entrySet()) {
41+
int root = dsu.find(entry.getValue());
42+
rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey());
43+
}
44+
for (int i = 0; i < accounts.size(); i++) {
45+
if (accounts.get(i).size() <= 1) {
46+
int root = dsu.find(i);
47+
rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>());
48+
}
49+
}
50+
51+
List<List<String>> merged = new ArrayList<>();
52+
for (Map.Entry<Integer, List<String>> entry : rootToEmails.entrySet()) {
53+
int root = entry.getKey();
54+
List<String> emails = entry.getValue();
55+
Collections.sort(emails);
56+
57+
List<String> mergedAccount = new ArrayList<>();
58+
mergedAccount.add(accounts.get(root).getFirst());
59+
mergedAccount.addAll(emails);
60+
merged.add(mergedAccount);
61+
}
62+
63+
merged.sort((a, b) -> {
64+
int cmp = a.getFirst().compareTo(b.getFirst());
65+
if (cmp != 0) {
66+
return cmp;
67+
}
68+
if (a.size() == 1 || b.size() == 1) {
69+
return Integer.compare(a.size(), b.size());
70+
}
71+
return a.get(1).compareTo(b.get(1));
72+
});
73+
return merged;
74+
}
75+
76+
private static final class UnionFind {
77+
private final int[] parent;
78+
private final int[] rank;
79+
80+
private UnionFind(int size) {
81+
this.parent = new int[size];
82+
this.rank = new int[size];
83+
for (int i = 0; i < size; i++) {
84+
parent[i] = i;
85+
}
86+
}
87+
88+
private int find(int x) {
89+
if (parent[x] != x) {
90+
parent[x] = find(parent[x]);
91+
}
92+
return parent[x];
93+
}
94+
95+
private void union(int x, int y) {
96+
int rootX = find(x);
97+
int rootY = find(y);
98+
if (rootX == rootY) {
99+
return;
100+
}
101+
102+
if (rank[rootX] < rank[rootY]) {
103+
parent[rootX] = rootY;
104+
} else if (rank[rootX] > rank[rootY]) {
105+
parent[rootY] = rootX;
106+
} else {
107+
parent[rootY] = rootX;
108+
rank[rootX]++;
109+
}
110+
}
111+
}
112+
}

src/main/java/com/thealgorithms/searches/JumpSearch.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
* <b>Space Complexity:</b> O(1) - only uses a constant amount of extra space
3737
*
3838
* <p>
39+
* <b>Edge Cases:</b>
40+
* <ul>
41+
* <li>Empty array → returns -1</li>
42+
* <li>Element not present → returns -1</li>
43+
* <li>Single element array</li>
44+
* </ul>
45+
* <p>
3946
* <b>Note:</b> Jump Search requires a sorted array. For unsorted arrays, use Linear Search.
4047
* Compared to Linear Search (O(n)), Jump Search is faster for large arrays.
4148
* Compared to Binary Search (O(log n)), Jump Search is less efficient but may be
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.thealgorithms.geometry;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.awt.geom.Point2D;
8+
import java.util.Optional;
9+
import org.junit.jupiter.api.Test;
10+
11+
class LineIntersectionTest {
12+
13+
@Test
14+
void testCrossingSegments() {
15+
Point p1 = new Point(0, 0);
16+
Point p2 = new Point(4, 4);
17+
Point q1 = new Point(0, 4);
18+
Point q2 = new Point(4, 0);
19+
20+
assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
21+
Optional<Point2D.Double> intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
22+
assertTrue(intersection.isPresent());
23+
assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
24+
assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
25+
}
26+
27+
@Test
28+
void testParallelSegments() {
29+
Point p1 = new Point(0, 0);
30+
Point p2 = new Point(3, 3);
31+
Point q1 = new Point(0, 1);
32+
Point q2 = new Point(3, 4);
33+
34+
assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
35+
assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
36+
}
37+
38+
@Test
39+
void testTouchingAtEndpoint() {
40+
Point p1 = new Point(0, 0);
41+
Point p2 = new Point(2, 2);
42+
Point q1 = new Point(2, 2);
43+
Point q2 = new Point(4, 0);
44+
45+
assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
46+
Optional<Point2D.Double> intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
47+
assertTrue(intersection.isPresent());
48+
assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
49+
assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
50+
}
51+
52+
@Test
53+
void testCollinearOverlapHasNoUniquePoint() {
54+
Point p1 = new Point(0, 0);
55+
Point p2 = new Point(4, 4);
56+
Point q1 = new Point(2, 2);
57+
Point q2 = new Point(6, 6);
58+
59+
assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
60+
assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
61+
}
62+
63+
@Test
64+
void testCollinearDisjointSegments() {
65+
Point p1 = new Point(0, 0);
66+
Point p2 = new Point(2, 2);
67+
Point q1 = new Point(3, 3);
68+
Point q2 = new Point(5, 5);
69+
70+
assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
71+
assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
72+
}
73+
74+
@Test
75+
void testCollinearSegmentsTouchingAtEndpointHaveUniquePoint() {
76+
Point p1 = new Point(0, 0);
77+
Point p2 = new Point(2, 2);
78+
Point q1 = new Point(2, 2);
79+
Point q2 = new Point(4, 4);
80+
81+
assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
82+
Optional<Point2D.Double> intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
83+
assertTrue(intersection.isPresent());
84+
assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
85+
assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
86+
}
87+
88+
@Test
89+
void testVerticalAndHorizontalCrossingSegments() {
90+
Point p1 = new Point(2, 0);
91+
Point p2 = new Point(2, 5);
92+
Point q1 = new Point(0, 3);
93+
Point q2 = new Point(4, 3);
94+
95+
assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
96+
Optional<Point2D.Double> intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
97+
assertTrue(intersection.isPresent());
98+
assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
99+
assertEquals(3.0, intersection.orElseThrow().getY(), 1e-9);
100+
}
101+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.List;
6+
import org.junit.jupiter.api.Test;
7+
8+
class AccountMergeTest {
9+
10+
@Test
11+
void testMergeAccountsWithSharedEmails() {
12+
List<List<String>> accounts = List.of(List.of("abc", "abc@mail.com", "abx@mail.com"), List.of("abc", "abc@mail.com", "aby@mail.com"), List.of("Mary", "mary@mail.com"), List.of("John", "johnnybravo@mail.com"));
13+
14+
List<List<String>> merged = AccountMerge.mergeAccounts(accounts);
15+
16+
List<List<String>> expected = List.of(List.of("John", "johnnybravo@mail.com"), List.of("Mary", "mary@mail.com"), List.of("abc", "abc@mail.com", "abx@mail.com", "aby@mail.com"));
17+
18+
assertEquals(expected, merged);
19+
}
20+
21+
@Test
22+
void testAccountsWithSameNameButNoSharedEmailStaySeparate() {
23+
List<List<String>> accounts = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
24+
25+
List<List<String>> merged = AccountMerge.mergeAccounts(accounts);
26+
List<List<String>> expected = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
27+
28+
assertEquals(expected, merged);
29+
}
30+
31+
@Test
32+
void testEmptyInput() {
33+
assertEquals(List.of(), AccountMerge.mergeAccounts(List.of()));
34+
}
35+
36+
@Test
37+
void testNullInput() {
38+
assertEquals(List.of(), AccountMerge.mergeAccounts(null));
39+
}
40+
41+
@Test
42+
void testTransitiveMergeAndDuplicateEmails() {
43+
List<List<String>> accounts = List.of(List.of("A", "a1@mail.com", "a2@mail.com"), List.of("A", "a2@mail.com", "a3@mail.com"), List.of("A", "a3@mail.com", "a4@mail.com", "a4@mail.com"));
44+
45+
List<List<String>> merged = AccountMerge.mergeAccounts(accounts);
46+
47+
List<List<String>> expected = List.of(List.of("A", "a1@mail.com", "a2@mail.com", "a3@mail.com", "a4@mail.com"));
48+
49+
assertEquals(expected, merged);
50+
}
51+
52+
@Test
53+
void testAccountsWithNoEmailsArePreserved() {
54+
List<List<String>> accounts = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
55+
56+
List<List<String>> merged = AccountMerge.mergeAccounts(accounts);
57+
List<List<String>> expected = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
58+
59+
assertEquals(expected, merged);
60+
}
61+
}

0 commit comments

Comments
 (0)