Skip to content

Commit 6fa60c8

Browse files
Leetcode 1394 : Find Lucky Integer
1 parent 3463749 commit 6fa60c8

File tree

6 files changed

+333
-2
lines changed

6 files changed

+333
-2
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package array.lucky_integer;
2+
3+
/**
4+
* Solves the "Find Lucky Integer" problem using a highly optimized frequency array.
5+
*
6+
* <p>This approach leverages the problem's constraint that all numbers are within a
7+
* small, positive range (1 to 500). It uses a simple array as a direct-access
8+
* frequency map, which is significantly more performant and memory-efficient than
9+
* using a {@code HashMap} or {@code TreeMap} for this specific scenario.
10+
*/
11+
public class FrequencyArray {
12+
13+
/**
14+
* Finds the largest lucky integer using a frequency array.
15+
*
16+
* <p>The logic iterates backwards from the maximum possible value. The first
17+
* lucky number encountered is guaranteed to be the largest, allowing for an
18+
* early exit.
19+
*
20+
* <p><b>Complexity:</b>
21+
* <ul>
22+
* <li>Time: O(N + M), where N is the length of {@code arr} and M is the
23+
* maximum possible value (500). This is effectively O(N).
24+
* <li>Space: O(M), which is constant space O(1) as M is fixed.
25+
* </ul>
26+
*
27+
* @param arr The input array of integers.
28+
* @return The largest lucky integer in the array, or -1 if none exists.
29+
*/
30+
public int findLucky(int[] arr) {
31+
// Per the constraints, arr[i] is between 1 and 500.
32+
// We create an array of size 501 to use indices 1 through 500.
33+
int[] frequencies = new int[501];
34+
35+
// First pass: count the frequency of each number.
36+
// This is an O(N) operation.
37+
for (int num : arr) {
38+
// We can safely do this because of the constraint 1 <= num <= 500.
39+
frequencies[num]++;
40+
}
41+
42+
// Second pass: check for a lucky number, starting from the largest possible.
43+
// This is an O(M) operation.
44+
for (int i = 500; i >= 1; i--) {
45+
// If the number (i) is equal to its frequency (frequencies[i])...
46+
if (frequencies[i] == i) {
47+
// ...we've found the largest lucky number, so we can return immediately.
48+
return i;
49+
}
50+
}
51+
52+
// If the loop completes, no lucky number was found.
53+
return -1;
54+
}
55+
}

src/main/java/array/lucky_integer/FrequencyMap.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* <p>This approach first iterates through the array to build a frequency map of all
1010
* numbers, then iterates through the map to find the largest number whose value
1111
* matches its frequency.
12+
*
13+
* @see FrequencyArray Frequency Array for the optimal approach.
1214
*/
1315
public class FrequencyMap {
1416

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package array.lucky_integer;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
import java.util.TreeMap;
6+
7+
/**
8+
* Solves the "Find Lucky Integer" problem using a {@link TreeMap}.
9+
*
10+
* <p>This implementation leverages a {@code TreeMap} with a reverse order comparator
11+
* to store number frequencies. By sorting the numbers in descending order, the search
12+
* for the lucky integer can be optimized: the first lucky number found during iteration
13+
* is guaranteed to be the largest one, allowing for an early exit.
14+
*
15+
* <p>While this approach is a valid general-purpose solution, it is not the most
16+
* performant for this problem's specific constraints (numbers 1-500), where a
17+
* simple frequency array would be faster.
18+
*
19+
* @see FrequencyArray Frequency Array for the optimal approach.
20+
*/
21+
public class FrequencyTreeMap {
22+
23+
/**
24+
* Finds the largest lucky integer using a TreeMap for automatic sorting.
25+
*
26+
* <p>This approach uses a {@link TreeMap} with a reverse order comparator.
27+
* This keeps the keys sorted from largest to smallest. When iterating, the
28+
* first lucky number found is guaranteed to be the largest, simplifying the logic.
29+
*
30+
* <p><b>Complexity:</b>
31+
* <ul>
32+
* <li>Time: O(N * log U), where N is the number of elements and U is the
33+
* number of unique elements. Each {@code put} operation in a TreeMap
34+
* is O(log U).
35+
* <li>Space: O(U), to store the unique elements in the map.
36+
* </ul>
37+
*
38+
* @param arr The input array of integers.
39+
* @return The largest lucky integer in the array, or -1 if none exists.
40+
*/
41+
public int findLucky(int[] arr) {
42+
// Step 1: Create a frequency map that sorts keys in descending order.
43+
Map<Integer, Integer> freqMap = new TreeMap<>(Collections.reverseOrder());
44+
45+
// Build the frequency map. Each `put` takes O(log U) time.
46+
for (int num : arr) {
47+
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
48+
}
49+
50+
// Step 2: Find the first lucky integer.
51+
// Because the map is sorted from largest to smallest, the first match
52+
// we find is guaranteed to be the largest lucky number.
53+
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
54+
// If the number (key) equals its frequency (value)...
55+
if (entry.getKey().equals(entry.getValue())) {
56+
// ...we've found our answer.
57+
return entry.getKey();
58+
}
59+
}
60+
61+
// If the loop completes, no lucky number was found.
62+
return -1;
63+
}
64+
}

src/main/java/array/lucky_integer/package-info.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,24 @@
1818
* <li>{@code 1 <= arr[i] <= 500}</li>
1919
* </ul>
2020
*
21-
* @version 1.0
21+
* <h3>Implementation Approaches:</h3>
22+
*
23+
* <p>This package provides multiple solutions, each demonstrating different trade-offs:
24+
* <ul>
25+
* <li><b>{@link array.lucky_integer.FrequencyArray FrequencyArray}</b>: The most optimal
26+
* solution given the problem's constraints. It uses a simple {@code int[]} as a
27+
* direct-access frequency map, offering O(N) time and O(1) space complexity.</li>
28+
* <li><b>{@link array.lucky_integer.FrequencyMap FrequencyMap} (using HashMap)</b>: A flexible,
29+
* general-purpose solution that works for any range of input numbers. It provides
30+
* O(N) time complexity but has higher memory overhead than the array approach.</li>
31+
* <li><b>{@link array.lucky_integer.FrequencyTreeMap FrequencyTreeMap}</b>: An approach that uses a
32+
* sorted map ({@code TreeMap}) to simplify finding the largest lucky number. The
33+
* first lucky number found is guaranteed to be the answer, at the cost of a
34+
* slightly higher O(N * log U) time complexity.</li>
35+
* </ul>
36+
*
2237
* @see <a href="https://leetcode.com/problems/find-lucky-integer-in-an-array/">LeetCode 1394: Find Lucky Integer in an Array</a>
23-
* @since 1.0
38+
* @since 2.0
39+
* @version 1.0
2440
*/
2541
package array.lucky_integer;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package array.lucky_integer;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
/**
10+
* Test suite for the {@link FrequencyArray} class.
11+
*
12+
* <p>This suite validates the correctness of the frequency array approach for finding
13+
* the largest lucky integer, covering various scenarios and edge cases.
14+
*/
15+
class FrequencyArrayTest {
16+
17+
private FrequencyArray frequencyArray;
18+
19+
@BeforeEach
20+
void setUp() {
21+
frequencyArray = new FrequencyArray();
22+
}
23+
24+
@Test
25+
@DisplayName("Should return the largest lucky integer when multiple exist")
26+
void findLucky_whenMultipleLuckyIntegersExist_shouldReturnLargest() {
27+
int[] arr = {1, 2, 2, 3, 3, 3};
28+
assertEquals(3, frequencyArray.findLucky(arr), "The largest lucky integer is 3, as 1 and 3 are both lucky.");
29+
}
30+
31+
@Test
32+
@DisplayName("Should return a single lucky integer when one exists")
33+
void findLucky_whenOneLuckyIntegerExists_shouldReturnIt() {
34+
int[] arr = {2, 2, 3, 4};
35+
assertEquals(2, frequencyArray.findLucky(arr), "The only lucky integer is 2.");
36+
}
37+
38+
@Test
39+
@DisplayName("Should return -1 when no lucky integer exists")
40+
void findLucky_whenNoLuckyIntegerExists_shouldReturnMinusOne() {
41+
int[] arr = {1, 2, 2, 3, 3, 2, 1};
42+
assertEquals(-1, frequencyArray.findLucky(arr), "No number has a frequency equal to its value.");
43+
}
44+
45+
@Test
46+
@DisplayName("Should return -1 for an empty array")
47+
void findLucky_whenArrayIsEmpty_shouldReturnMinusOne() {
48+
int[] arr = {};
49+
assertEquals(-1, frequencyArray.findLucky(arr), "An empty array has no lucky integers.");
50+
}
51+
52+
@Test
53+
@DisplayName("Should handle an array where all elements are the same and form a lucky number")
54+
void findLucky_whenAllElementsAreSameAndLucky_shouldReturnTheNumber() {
55+
int[] arr = {4, 4, 4, 4};
56+
assertEquals(4, frequencyArray.findLucky(arr), "4 appears 4 times, so it is lucky.");
57+
}
58+
59+
@Test
60+
@DisplayName("Should handle an array where all elements are the same but not lucky")
61+
void findLucky_whenAllElementsAreSameAndNotLucky_shouldReturnMinusOne() {
62+
int[] arr = {5, 5, 5};
63+
assertEquals(-1, frequencyArray.findLucky(arr), "5 appears 3 times, so it is not lucky.");
64+
}
65+
66+
@Test
67+
@DisplayName("Should handle a single element array that is lucky")
68+
void findLucky_whenSingleElementIsLucky_shouldReturnTheNumber() {
69+
int[] arr = {1};
70+
assertEquals(1, frequencyArray.findLucky(arr), "1 appears 1 time, so it is lucky.");
71+
}
72+
73+
@Test
74+
@DisplayName("Should handle a single element array that is not lucky")
75+
void findLucky_whenSingleElementIsNotLucky_shouldReturnMinusOne() {
76+
int[] arr = {2};
77+
assertEquals(-1, frequencyArray.findLucky(arr), "2 appears 1 time, so it is not lucky.");
78+
}
79+
80+
@Test
81+
@DisplayName("Should handle a complex case with various numbers")
82+
void findLucky_withComplexArray_shouldReturnCorrectLargestLucky() {
83+
int[] arr = {7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 2, 2, 8, 9};
84+
assertEquals(7, frequencyArray.findLucky(arr), "Both 5 and 7 are lucky, but 7 is the largest.");
85+
}
86+
87+
@Test
88+
@DisplayName("Should handle the maximum constraint value being lucky")
89+
void findLucky_withMaximumValueAsLuckyNumber_shouldReturnIt() {
90+
// Create an array with 500 instances of the number 500.
91+
int[] arr = new int[500];
92+
for (int i = 0; i < 500; i++) {
93+
arr[i] = 500;
94+
}
95+
assertEquals(500, frequencyArray.findLucky(arr), "500 appears 500 times and should be a valid lucky number.");
96+
}
97+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package array.lucky_integer;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
/**
10+
* Test suite for the {@link FrequencyArray} class.
11+
*
12+
* <p>This suite validates the correctness of the frequency array approach for finding
13+
* the largest lucky integer, covering various scenarios and edge cases.
14+
*/
15+
class FrequencyTreeMapTest {
16+
17+
private FrequencyTreeMap frequencyTreeMap;
18+
19+
@BeforeEach
20+
void setUp() {
21+
frequencyTreeMap = new FrequencyTreeMap();
22+
}
23+
24+
@Test
25+
@DisplayName("Should return the largest lucky integer when multiple exist")
26+
void findLucky_whenMultipleLuckyIntegersExist_shouldReturnLargest() {
27+
int[] arr = {1, 2, 2, 3, 3, 3};
28+
assertEquals(3, frequencyTreeMap.findLucky(arr), "The largest lucky integer is 3, as 1 and 3 are both lucky.");
29+
}
30+
31+
@Test
32+
@DisplayName("Should return a single lucky integer when one exists")
33+
void findLucky_whenOneLuckyIntegerExists_shouldReturnIt() {
34+
int[] arr = {2, 2, 3, 4};
35+
assertEquals(2, frequencyTreeMap.findLucky(arr), "The only lucky integer is 2.");
36+
}
37+
38+
@Test
39+
@DisplayName("Should return -1 when no lucky integer exists")
40+
void findLucky_whenNoLuckyIntegerExists_shouldReturnMinusOne() {
41+
int[] arr = {1, 2, 2, 3, 3, 2, 1};
42+
assertEquals(-1, frequencyTreeMap.findLucky(arr), "No number has a frequency equal to its value.");
43+
}
44+
45+
@Test
46+
@DisplayName("Should return -1 for an empty array")
47+
void findLucky_whenArrayIsEmpty_shouldReturnMinusOne() {
48+
int[] arr = {};
49+
assertEquals(-1, frequencyTreeMap.findLucky(arr), "An empty array has no lucky integers.");
50+
}
51+
52+
@Test
53+
@DisplayName("Should handle an array where all elements are the same and form a lucky number")
54+
void findLucky_whenAllElementsAreSameAndLucky_shouldReturnTheNumber() {
55+
int[] arr = {4, 4, 4, 4};
56+
assertEquals(4, frequencyTreeMap.findLucky(arr), "4 appears 4 times, so it is lucky.");
57+
}
58+
59+
@Test
60+
@DisplayName("Should handle an array where all elements are the same but not lucky")
61+
void findLucky_whenAllElementsAreSameAndNotLucky_shouldReturnMinusOne() {
62+
int[] arr = {5, 5, 5};
63+
assertEquals(-1, frequencyTreeMap.findLucky(arr), "5 appears 3 times, so it is not lucky.");
64+
}
65+
66+
@Test
67+
@DisplayName("Should handle a single element array that is lucky")
68+
void findLucky_whenSingleElementIsLucky_shouldReturnTheNumber() {
69+
int[] arr = {1};
70+
assertEquals(1, frequencyTreeMap.findLucky(arr), "1 appears 1 time, so it is lucky.");
71+
}
72+
73+
@Test
74+
@DisplayName("Should handle a single element array that is not lucky")
75+
void findLucky_whenSingleElementIsNotLucky_shouldReturnMinusOne() {
76+
int[] arr = {2};
77+
assertEquals(-1, frequencyTreeMap.findLucky(arr), "2 appears 1 time, so it is not lucky.");
78+
}
79+
80+
@Test
81+
@DisplayName("Should handle a complex case with various numbers")
82+
void findLucky_withComplexArray_shouldReturnCorrectLargestLucky() {
83+
int[] arr = {7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 2, 2, 8, 9};
84+
assertEquals(7, frequencyTreeMap.findLucky(arr), "Both 5 and 7 are lucky, but 7 is the largest.");
85+
}
86+
87+
@Test
88+
@DisplayName("Should handle the maximum constraint value being lucky")
89+
void findLucky_withMaximumValueAsLuckyNumber_shouldReturnIt() {
90+
// Create an array with 500 instances of the number 500.
91+
int[] arr = new int[500];
92+
for (int i = 0; i < 500; i++) {
93+
arr[i] = 500;
94+
}
95+
assertEquals(500, frequencyTreeMap.findLucky(arr), "500 appears 500 times and should be a valid lucky number.");
96+
}
97+
}

0 commit comments

Comments
 (0)