1+
12package com .thealgorithms .others ;
23
34import java .util .ArrayList ;
5+ import java .util .List ;
6+ import java .util .Objects ;
47
58/**
6- * The {@code SkylineProblem} class is used to solve the skyline problem using a
7- * divide-and-conquer approach.
8- * It reads input for building data, processes it to find the skyline, and
9- * prints the skyline.
9+ * <h2>Skyline Problem</h2>
10+ * <p>
11+ * Solves the classic skyline problem using a divide-and-conquer approach. Given
12+ * a list of buildings (each defined by left, height, right),
13+ * computes the silhouette (skyline) formed by these buildings when viewed from
14+ * a distance.
15+ * </p>
16+ * <p>
17+ * Usage example:
18+ *
19+ * <pre>
20+ * SkylineProblem sp = new SkylineProblem();
21+ * sp.building = new SkylineProblem.Building[3];
22+ * sp.add(1, 10, 5);
23+ * sp.add(2, 15, 7);
24+ * sp.add(3, 12, 9);
25+ * List<SkylineProblem.Skyline> skyline = sp.findSkyline(0, 2);
26+ * </pre>
27+ * </p>
28+ * <p>
29+ * This class is not thread-safe.
30+ * </p>
1031 */
1132public class SkylineProblem {
1233
13- Building [] building ;
14- int count ;
34+ /**
35+ * Array of buildings to process. Must be initialized before use.
36+ */
37+ public Building [] building ;
38+
39+ /**
40+ * Number of buildings added so far.
41+ */
42+ public int count ;
1543
1644 /**
1745 * Adds a building with the given left, height, and right values to the
@@ -20,101 +48,121 @@ public class SkylineProblem {
2048 * @param left The left x-coordinate of the building.
2149 * @param height The height of the building.
2250 * @param right The right x-coordinate of the building.
51+ * @throws IllegalArgumentException if left >= right or height < 0
52+ * @throws IllegalStateException if building array is not initialized or is
53+ * full
2354 */
2455 public void add (int left , int height , int right ) {
56+ if (building == null ) {
57+ throw new IllegalStateException ("Building array not initialized" );
58+ }
59+ if (count >= building .length ) {
60+ throw new IllegalStateException ("Building array is full" );
61+ }
62+ if (left >= right ) {
63+ throw new IllegalArgumentException ("Left coordinate must be less than right coordinate" );
64+ }
65+ if (height < 0 ) {
66+ throw new IllegalArgumentException ("Height must be non-negative" );
67+ }
2568 building [count ++] = new Building (left , height , right );
2669 }
2770
2871 /**
2972 * Computes the skyline for a range of buildings using the divide-and-conquer
3073 * strategy.
3174 *
32- * @param start The starting index of the buildings to process.
33- * @param end The ending index of the buildings to process.
75+ * @param start The starting index of the buildings to process (inclusive) .
76+ * @param end The ending index of the buildings to process (inclusive) .
3477 * @return A list of {@link Skyline} objects representing the computed skyline.
78+ * @throws IllegalArgumentException if indices are out of bounds or building
79+ * array is null
3580 */
36- public ArrayList <Skyline > findSkyline (int start , int end ) {
81+ public List <Skyline > findSkyline (int start , int end ) {
82+ if (building == null ) {
83+ throw new IllegalArgumentException ("Building array is not initialized" );
84+ }
85+ if (start < 0 || end >= count || start > end ) {
86+ throw new IllegalArgumentException ("Invalid start or end index" );
87+ }
3788 // Base case: only one building, return its skyline.
3889 if (start == end ) {
39- ArrayList <Skyline > list = new ArrayList <>();
90+ List <Skyline > list = new ArrayList <>();
4091 list .add (new Skyline (building [start ].left , building [start ].height ));
41- list .add (new Skyline (building [end ].right , 0 )); // Add the end of the building
92+ list .add (new Skyline (building [start ].right , 0 )); // Add the end of the building
4293 return list ;
4394 }
4495
4596 int mid = (start + end ) / 2 ;
46-
47- ArrayList <Skyline > sky1 = this .findSkyline (start , mid ); // Find the skyline of the left half
48- ArrayList <Skyline > sky2 = this .findSkyline (mid + 1 , end ); // Find the skyline of the right half
97+ List <Skyline > sky1 = this .findSkyline (start , mid ); // Find the skyline of the left half
98+ List <Skyline > sky2 = this .findSkyline (mid + 1 , end ); // Find the skyline of the right half
4999 return this .mergeSkyline (sky1 , sky2 ); // Merge the two skylines
50100 }
51101
52102 /**
53103 * Merges two skylines (sky1 and sky2) into one combined skyline.
54104 *
55- * @param sky1 The first skyline list.
56- * @param sky2 The second skyline list.
105+ * @param sky1 The first skyline list. Not modified.
106+ * @param sky2 The second skyline list. Not modified.
57107 * @return A list of {@link Skyline} objects representing the merged skyline.
108+ * @throws NullPointerException if either argument is null
58109 */
59- public ArrayList <Skyline > mergeSkyline (ArrayList <Skyline > sky1 , ArrayList <Skyline > sky2 ) {
60- int currentH1 = 0 ;
61- int currentH2 = 0 ;
62- ArrayList <Skyline > skyline = new ArrayList <>();
63- int maxH = 0 ;
64-
65- // Merge the two skylines
66- while (!sky1 .isEmpty () && !sky2 .isEmpty ()) {
67- if (sky1 .get (0 ).coordinates < sky2 .get (0 ).coordinates ) {
68- int currentX = sky1 .get (0 ).coordinates ;
69- currentH1 = sky1 .get (0 ).height ;
70-
71- if (currentH1 < currentH2 ) {
72- sky1 .remove (0 );
73- if (maxH != currentH2 ) {
74- skyline .add (new Skyline (currentX , currentH2 ));
75- }
76- } else {
77- maxH = currentH1 ;
78- sky1 .remove (0 );
79- skyline .add (new Skyline (currentX , currentH1 ));
80- }
81- } else {
82- int currentX = sky2 .get (0 ).coordinates ;
83- currentH2 = sky2 .get (0 ).height ;
84-
85- if (currentH2 < currentH1 ) {
86- sky2 .remove (0 );
87- if (maxH != currentH1 ) {
88- skyline .add (new Skyline (currentX , currentH1 ));
89- }
90- } else {
91- maxH = currentH2 ;
92- sky2 .remove (0 );
93- skyline .add (new Skyline (currentX , currentH2 ));
94- }
110+ public List <Skyline > mergeSkyline (List <Skyline > sky1 , List <Skyline > sky2 ) {
111+ Objects .requireNonNull (sky1 , "sky1 must not be null" );
112+ Objects .requireNonNull (sky2 , "sky2 must not be null" );
113+ int i = 0 , j = 0 ;
114+ int h1 = 0 , h2 = 0 ;
115+ int prevHeight = 0 ;
116+ List <Skyline > result = new ArrayList <>();
117+ while (i < sky1 .size () && j < sky2 .size ()) {
118+ Skyline p1 = sky1 .get (i );
119+ Skyline p2 = sky2 .get (j );
120+ int x ;
121+ if (p1 .coordinates < p2 .coordinates ) {
122+ x = p1 .coordinates ;
123+ h1 = p1 .height ;
124+ i ++;
125+ } else if (p2 .coordinates < p1 .coordinates ) {
126+ x = p2 .coordinates ;
127+ h2 = p2 .height ;
128+ j ++;
129+ } else { // same x
130+ x = p1 .coordinates ;
131+ h1 = p1 .height ;
132+ h2 = p2 .height ;
133+ i ++;
134+ j ++;
135+ }
136+ int maxH = Math .max (h1 , h2 );
137+ if (result .isEmpty () || prevHeight != maxH ) {
138+ result .add (new Skyline (x , maxH ));
139+ prevHeight = maxH ;
95140 }
96141 }
97-
98- // Add any remaining points from sky1 or sky2
99- while (!sky1 .isEmpty ()) {
100- skyline .add (sky1 .get (0 ));
101- sky1 .remove (0 );
142+ // Append remaining points
143+ while (i < sky1 .size ()) {
144+ Skyline p = sky1 .get (i ++);
145+ if (result .isEmpty () || result .get (result .size () - 1 ).height != p .height || result .get (result .size () - 1 ).coordinates != p .coordinates ) {
146+ result .add (new Skyline (p .coordinates , p .height ));
147+ }
102148 }
103-
104- while (!sky2 .isEmpty ()) {
105- skyline .add (sky2 .get (0 ));
106- sky2 .remove (0 );
149+ while (j < sky2 .size ()) {
150+ Skyline p = sky2 .get (j ++);
151+ if (result .isEmpty () || result .get (result .size () - 1 ).height != p .height || result .get (result .size () - 1 ).coordinates != p .coordinates ) {
152+ result .add (new Skyline (p .coordinates , p .height ));
153+ }
107154 }
108-
109- return skyline ;
155+ return result ;
110156 }
111157
112158 /**
113- * A class representing a point in the skyline with its x-coordinate and height.
159+ * Represents a point in the skyline with its x-coordinate and height.
114160 */
115- public class Skyline {
116- public int coordinates ;
117- public int height ;
161+ public static class Skyline {
162+ /** The x-coordinate of the skyline point. */
163+ public final int coordinates ;
164+ /** The height of the skyline at the given coordinate. */
165+ public final int height ;
118166
119167 /**
120168 * Constructor for the {@code Skyline} class.
@@ -126,16 +174,36 @@ public Skyline(int coordinates, int height) {
126174 this .coordinates = coordinates ;
127175 this .height = height ;
128176 }
177+
178+ @ Override
179+ public boolean equals (Object o ) {
180+ if (this == o ) return true ;
181+ if (o == null || getClass () != o .getClass ()) return false ;
182+ Skyline skyline = (Skyline ) o ;
183+ return coordinates == skyline .coordinates && height == skyline .height ;
184+ }
185+
186+ @ Override
187+ public int hashCode () {
188+ return Objects .hash (coordinates , height );
189+ }
190+
191+ @ Override
192+ public String toString () {
193+ return "(" + coordinates + ", " + height + ")" ;
194+ }
129195 }
130196
131197 /**
132- * A class representing a building with its left, height, and right
133- * x-coordinates.
198+ * Represents a building with its left, height, and right x-coordinates.
134199 */
135- public class Building {
136- public int left ;
137- public int height ;
138- public int right ;
200+ public static class Building {
201+ /** The left x-coordinate of the building. */
202+ public final int left ;
203+ /** The height of the building. */
204+ public final int height ;
205+ /** The right x-coordinate of the building. */
206+ public final int right ;
139207
140208 /**
141209 * Constructor for the {@code Building} class.
@@ -149,5 +217,11 @@ public Building(int left, int height, int right) {
149217 this .height = height ;
150218 this .right = right ;
151219 }
220+
221+ @ Override
222+ public String toString () {
223+ return "Building{"
224+ + "left=" + left + ", height=" + height + ", right=" + right + '}' ;
225+ }
152226 }
153227}
0 commit comments