Skip to content

Commit e4db606

Browse files
committed
LeetCodeTest using BinarySearch to solve LC binary search problems
1 parent fbb202a commit e4db606

File tree

1 file changed

+362
-0
lines changed

1 file changed

+362
-0
lines changed
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
package com.google.mu.collect;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import java.util.Arrays;
6+
7+
import org.junit.Test;
8+
import org.junit.experimental.runners.Enclosed;
9+
import org.junit.runner.RunWith;
10+
11+
import com.google.common.collect.Range;
12+
13+
/** Shows how some LeetCode binary search algorithms are solved with this library. */
14+
@RunWith(Enclosed.class)
15+
public class LeetCodeTest {
16+
17+
static abstract class Koko {
18+
@Test public void verify() {
19+
assertThat(minEatingSpeed(new int[] {3,6,7,11}, 8)).isEqualTo(4);
20+
assertThat(minEatingSpeed(new int[] {30,11,23,4,20}, 5)).isEqualTo(30);;
21+
}
22+
23+
abstract int minEatingSpeed(int[] piles, int h);
24+
}
25+
26+
public static class ManualKoKo extends Koko {
27+
// Generated by GPT
28+
@Override int minEatingSpeed(int[] piles, int h) {
29+
int low = 1, high = Arrays.stream(piles).max().getAsInt();
30+
while (low < high) {
31+
int mid = low + (high - low) / 2;
32+
int hours = 0;
33+
for (int pile : piles) {
34+
hours += (pile + mid - 1) / mid;
35+
}
36+
if (hours > h) {
37+
low = mid + 1;
38+
} else {
39+
high = mid;
40+
}
41+
}
42+
return low;
43+
}
44+
}
45+
46+
public static class MugKoko extends Koko {
47+
@Override int minEatingSpeed(int[] piles, final int h) {
48+
return BinarySearch.forInts(Range.closed(1, Arrays.stream(piles).max().getAsInt()))
49+
.insertionPointFor(
50+
(lo, speed, hi) ->
51+
Integer.compare(
52+
Arrays.stream(piles).map(pile -> (pile + speed - 1) / speed).sum(),
53+
h))
54+
.floor();
55+
}
56+
}
57+
58+
static abstract class Sqrt {
59+
@Test public void verify() {
60+
assertThat(mySqrt(0)).isEqualTo(0);
61+
assertThat(mySqrt(1)).isEqualTo(1);
62+
assertThat(mySqrt(2)).isEqualTo(1);
63+
assertThat(mySqrt(3)).isEqualTo(1);
64+
assertThat(mySqrt(4)).isEqualTo(2);
65+
assertThat(mySqrt(10)).isEqualTo(3);
66+
assertThat(mySqrt(2147395599)).isEqualTo(46339);
67+
}
68+
69+
abstract int mySqrt(int x);
70+
}
71+
72+
public static class ManualSqrt extends Sqrt {
73+
@Override int mySqrt(int x) {
74+
int low = 0, high = x;
75+
while (low <= high) {
76+
int mid = low + (high - low) / 2;
77+
if ((long) mid * mid <= x) {
78+
low = mid + 1;
79+
} else {
80+
high = mid - 1;
81+
}
82+
}
83+
return high;
84+
}
85+
}
86+
87+
public static class MugSqrt extends Sqrt {
88+
@Override int mySqrt(int x) {
89+
return BinarySearch.forInts(Range.closed(0, x))
90+
.insertionPointFor((lo, mid, hi) -> Long.compare(x, (long) mid * mid))
91+
.floor();
92+
}
93+
}
94+
95+
static abstract class FindDuplicate {
96+
@Test public void verifyCorrectness() {
97+
assertThat(findDuplicate(new int[]{1,1})).isEqualTo(1);
98+
assertThat(findDuplicate(new int[]{1,1,2})).isEqualTo(1);
99+
assertThat(findDuplicate(new int[]{2,2,2,2,2})).isEqualTo(2);
100+
assertThat(findDuplicate(new int[]{1,3,4,2,2})).isEqualTo(2);
101+
assertThat(findDuplicate(new int[]{3,1,3,4,2})).isEqualTo(3);
102+
}
103+
104+
abstract int findDuplicate(int[] nums);
105+
}
106+
107+
public static class ManualFindDuplicate extends FindDuplicate {
108+
@Override int findDuplicate(int[] nums) {
109+
int low = 1, high = nums.length - 1;
110+
while (low < high) {
111+
int mid = (low + high) / 2;
112+
int count = 0;
113+
for (int num : nums) {
114+
if (num <= mid) count++;
115+
}
116+
if (count > mid) {
117+
high = mid;
118+
} else {
119+
low = mid + 1;
120+
}
121+
}
122+
return low;
123+
}
124+
}
125+
126+
public static class MugFindDuplicate extends FindDuplicate {
127+
@Override int findDuplicate(int[] nums) {
128+
return BinarySearch.forInts(Range.closed(1, nums.length - 1))
129+
.insertionPointFor(
130+
(lo, mid, hi) ->
131+
Arrays.stream(nums).filter(n -> n <= mid).count() > mid ? -1 : 1)
132+
.ceiling();
133+
}
134+
}
135+
136+
static abstract class WoodCutting {
137+
@Test public void verifyCorrectness() {
138+
assertThat(woodCut(new int[]{232, 124, 456}, 7)).isEqualTo(114);
139+
}
140+
141+
abstract int woodCut(int[] lengths, int k);
142+
}
143+
144+
public static class ManualWoodCutting extends WoodCutting {
145+
@Override int woodCut(int[] lengths, int k) {
146+
int low = 1, high = Arrays.stream(lengths).max().getAsInt();
147+
while (low <= high) {
148+
int mid = low + (high - low) / 2;
149+
int count = 0;
150+
for (int len : lengths) {
151+
count += len / mid;
152+
}
153+
if (count >= k) {
154+
low = mid + 1;
155+
} else {
156+
high = mid - 1;
157+
}
158+
}
159+
return high;
160+
}
161+
}
162+
163+
public static class MugWoodCutting extends WoodCutting {
164+
@Override int woodCut(int[] lengths, int k) {
165+
return BinarySearch.forInts(Range.closed(1, Arrays.stream(lengths).max().getAsInt()))
166+
.insertionPointFor(
167+
(lo, mid, hi) ->
168+
Integer.compare(Arrays.stream(lengths).map(len -> len / mid).sum(), k))
169+
.floor();
170+
}
171+
}
172+
173+
static abstract class SplitArray {
174+
@Test public void verifyCorrectness() {
175+
assertThat(splitArray(new int[]{1}, 1)).isEqualTo(1);
176+
assertThat(splitArray(new int[]{1, 2, 3, 4, 5}, 5)).isEqualTo(5);
177+
assertThat(splitArray(new int[]{7,2,5,10,8}, 2)).isEqualTo(18);
178+
}
179+
180+
abstract int splitArray(int[] nums, int k);
181+
182+
static boolean canSplit(int[] nums, int k, int maxSum) {
183+
int count = 1, sum = 0;
184+
for (int num : nums) {
185+
if (sum + num > maxSum) {
186+
count++;
187+
sum = 0;
188+
}
189+
sum += num;
190+
}
191+
return count <= k;
192+
}
193+
}
194+
195+
public static class ManualSplitArray extends SplitArray {
196+
@Override int splitArray(int[] nums, int k) {
197+
int low = Arrays.stream(nums).max().getAsInt();
198+
int high = Arrays.stream(nums).sum();
199+
while (low < high) {
200+
int mid = (low + high) / 2;
201+
if (canSplit(nums, k, mid)) {
202+
high = mid;
203+
} else {
204+
low = mid + 1;
205+
}
206+
}
207+
return low;
208+
}
209+
}
210+
211+
public static class MugSplitArray extends SplitArray {
212+
@Override int splitArray(int[] nums, int k) {
213+
int low = Arrays.stream(nums).max().getAsInt();
214+
int high = Arrays.stream(nums).sum();
215+
return BinarySearch.forInts(Range.closed(low, high))
216+
.insertionPointFor((lo, mid, hi) -> canSplit(nums, k, mid) ? -1 : 1)
217+
.ceiling();
218+
}
219+
}
220+
221+
static abstract class ShipWithinNDays {
222+
@Test public void verifyCorrectness() {
223+
assertThat(shipWithinDays(new int[]{1}, 1)).isEqualTo(1);
224+
assertThat(shipWithinDays(new int[]{1,2,3,1,1}, 4)).isEqualTo(3);
225+
assertThat(shipWithinDays(new int[]{10,50,50,10}, 2)).isEqualTo(60);
226+
assertThat(shipWithinDays(new int[]{3,2,2,4,1,4}, 3)).isEqualTo(6);
227+
}
228+
229+
abstract int shipWithinDays(int[] weights, int D);
230+
231+
static boolean canShip(int[] weights, int D, int capacity) {
232+
int days = 1, sum = 0;
233+
for (int w : weights) {
234+
if (sum + w > capacity) {
235+
days++;
236+
sum = 0;
237+
}
238+
sum += w;
239+
}
240+
return days <= D;
241+
}
242+
}
243+
244+
public static class ManualShipWithinNDays extends ShipWithinNDays {
245+
@Override int shipWithinDays(int[] weights, int D) {
246+
int low = Arrays.stream(weights).max().getAsInt();
247+
int high = Arrays.stream(weights).sum();
248+
while (low < high) {
249+
int mid = (low + high) / 2;
250+
if (canShip(weights, D, mid)) {
251+
high = mid;
252+
} else {
253+
low = mid + 1;
254+
}
255+
}
256+
return low;
257+
}
258+
}
259+
260+
public static class MugShipWithinNDays extends ShipWithinNDays {
261+
@Override int shipWithinDays(int[] weights, int D) {
262+
int low = Arrays.stream(weights).max().getAsInt();
263+
int high = Arrays.stream(weights).sum();
264+
return BinarySearch.forInts(Range.closed(low, high))
265+
.insertionPointFor((lo, mid, hi) -> canShip(weights, D, mid) ? -1 : 1)
266+
.ceiling();
267+
}
268+
}
269+
270+
static abstract class KthSmallest {
271+
@Test public void verifyCorrectness() {
272+
assertThat(kthSmallest(new int[][]{{1}}, 1)).isEqualTo(1);
273+
assertThat(kthSmallest(new int[][]{{1,2},{1,3}}, 2)).isEqualTo(1);
274+
assertThat(kthSmallest(new int[][]{{1,5,9},{10,11,13},{12,13,15}}, 8)).isEqualTo(13);
275+
}
276+
277+
abstract int kthSmallest(int[][] matrix, int k);
278+
279+
static int countLessEqual(int[][] matrix, int target) {
280+
int count = 0, n = matrix.length, j = n - 1;
281+
for (int i = 0; i < n; i++) {
282+
while (j >= 0 && matrix[i][j] > target) j--;
283+
count += (j + 1);
284+
}
285+
return count;
286+
}
287+
}
288+
289+
public static class ManualKthSmallest extends KthSmallest {
290+
@Override int kthSmallest(int[][] matrix, int k) {
291+
int n = matrix.length;
292+
int low = matrix[0][0], high = matrix[n-1][n-1];
293+
while (low < high) {
294+
int mid = low + (high - low) / 2;
295+
int count = countLessEqual(matrix, mid);
296+
if (count < k) {
297+
low = mid + 1;
298+
} else {
299+
high = mid;
300+
}
301+
}
302+
return low;
303+
}
304+
}
305+
306+
public static class MugKthSmallest extends KthSmallest {
307+
@Override int kthSmallest(int[][] matrix, int k) {
308+
int n = matrix.length;
309+
return BinarySearch.forInts(Range.closed(matrix[0][0], matrix[n-1][n-1]))
310+
.insertionPointFor((lo, mid, hi) -> countLessEqual(matrix, mid) < k ? 1 : -1)
311+
.ceiling();
312+
}
313+
}
314+
315+
static abstract class FindMaxAverage {
316+
@Test public void verifyCorrectness() {
317+
assertThat(findMaxAverage(new int[]{1,12,-5,-6,50,3}, 4))
318+
.isWithin(0.01)
319+
.of(12.75);;
320+
}
321+
322+
abstract double findMaxAverage(int[] nums, int k);
323+
324+
static boolean check(int[] nums, int k, double avg) {
325+
double sum = 0, prev = 0, minPrev = 0;
326+
for (int i = 0; i < k; ++i) {
327+
sum += nums[i] - avg;
328+
}
329+
if (sum >= 0) return true;
330+
for (int i = k; i < nums.length; ++i) {
331+
sum += nums[i] - avg;
332+
prev += nums[i - k] - avg;
333+
minPrev = Math.min(minPrev, prev);
334+
if (sum >= minPrev) return true;
335+
}
336+
return false;
337+
}
338+
}
339+
340+
public static class ManualFindMaxAverage extends FindMaxAverage {
341+
@Override double findMaxAverage(int[] nums, int k) {
342+
double low = -10000, high = 10000;
343+
while (high - low > 1e-5) {
344+
double mid = (low + high) / 2;
345+
if (check(nums, k, mid)) {
346+
low = mid;
347+
} else {
348+
high = mid;
349+
}
350+
}
351+
return low;
352+
}
353+
}
354+
355+
public static class MugFindMaxAverage extends FindMaxAverage {
356+
@Override double findMaxAverage(int[] nums, int k) {
357+
return BinarySearch.forDoubles(Range.closed(-10000.0, 10000.0))
358+
.insertionPointFor((lo, mid, hi) -> check(nums, k, mid) ? 1 : -1)
359+
.floor();
360+
}
361+
}
362+
}

0 commit comments

Comments
 (0)