Skip to content

Commit c666acc

Browse files
Merge pull request #1 from robertciotoiu/5-longest-palindromic-substring
Add solution for LeetCode problem: 5. Longest Palindromic Substring
2 parents 55dfb31 + 4bde379 commit c666acc

File tree

2 files changed

+220
-0
lines changed

2 files changed

+220
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import java.util.ArrayList;
2+
import java.util.List;
3+
import java.util.Optional;
4+
import java.util.stream.IntStream;
5+
6+
/**
7+
* 5. Longest Palindromic Substring
8+
* <p>
9+
* Medium
10+
* <p>
11+
* Given a string s, return the longest palindromic substring in s.
12+
* <p>
13+
* <p>
14+
* <p>
15+
* Example 1:
16+
* <p>
17+
* Input: s = "babad"
18+
* Output: "bab"
19+
* Explanation: "aba" is also a valid answer.
20+
* Example 2:
21+
* <p>
22+
* Input: s = "cbbd"
23+
* Output: "bb"
24+
* <p>
25+
* <p>
26+
* Constraints:
27+
* <p>
28+
* 1 <= s.length <= 1000
29+
* s consist of only digits and English letters.
30+
*/
31+
public class LongestPalindromicSubstring {
32+
33+
/**
34+
* Finds the longest substring which is a palindrome.
35+
* PROs:
36+
* - Very simple to algorithm to understand.
37+
* - Making use of Java Streams and parallelization.
38+
* - Even if theoretically the complexity is O(n^2), the algorithm will be pretty fast in practice because:
39+
* - each iteration of the algorithm is parallelized.
40+
* - at each isPalindrome check we iterate only string.length/2 but in many cases we return earlier if
41+
* invalid character is found
42+
* CONs:
43+
* - High complexity: O(1) + O(2) + ... + O(n) = (O(n) * O(n+1))/2 = O(n^2)
44+
* - Average in terms of performance(according to LeetCode).
45+
* Explanation:
46+
* As we want to find the longest substring, we will go from the longest substring to the smallest.
47+
* (0.) We create a stream of ints from 0 until input.length() to facilitate Stream parallelization.
48+
* 1. We generate all substrings of a string for a given length, where length ranges from @input.length -> 0
49+
* We map each previous int to List<String> - generated substrings.
50+
* 2. We want to filter each list to find the palindromes, and we use findFirst just to find the first substring
51+
* of a given length, instead of finding any which will give us a different result each time(BUT CORRECT, FOR THE
52+
* GIVEN CONSTRAINTS!). Using findAny will increase performance, but will not pass LeetCode tests...(in real word,
53+
* we would have smarter tests than the current ones from LeetCode and we would use findAny, for these current constraints!)
54+
* To filter we will use the isPalindrome method which will:
55+
* - compare if string[0] == string[n-1], string[1] == string[n-2] ...
56+
* return immediately if the condition is not satisfied.
57+
* by doing this we are halving the number of iterations for each string check!
58+
* and we are also lowering the number of iterations by returning immediately.
59+
* 3. We will have now a Stream<Optional<String>>, because from each List<String> we took only the first
60+
* valid palindrome. It is an Optional, because some lists may not have any palindrome. For this, we want
61+
* to filter only the present ones. Only then, we can perform findFirst to find the longest one.(Because of how we ranged from 0 to input.length, where to 0 we mapped actually
62+
* the longest string)
63+
* 4. We unwrap the optionals and return the longest palindromic substring if exists or an empty string.
64+
* @param input string
65+
* @return longest palindrome string
66+
*/
67+
public String find(String input) {
68+
var longestSubstring = IntStream.range(0, input.length())
69+
.parallel()
70+
.mapToObj(i -> generateSubstrings(input, input.length() - i))
71+
.map(i -> i.parallelStream().filter(this::isPalindrome).findFirst())
72+
.filter(Optional::isPresent)
73+
.findFirst();
74+
75+
return longestSubstring.orElse(Optional.empty()).orElse("");
76+
}
77+
78+
/**
79+
* Generates all combination of substrings of length @length for an input @input string.
80+
*
81+
* @param input string
82+
* @param length length of substring to generate
83+
* @return a List of all combination of length @length based on the input string
84+
*/
85+
private List<String> generateSubstrings(String input, int length) {
86+
ArrayList<String> substrings = new ArrayList<>();
87+
for (int i = 0; i <= input.length() - length; i++) {
88+
substrings.add(
89+
input.substring(i, length + i)
90+
);
91+
}
92+
return substrings;
93+
}
94+
95+
/**
96+
* Checks if input string is a palindrome
97+
*
98+
* @param input test to be checked
99+
* @return true if string is palindrome or false otherwise
100+
*/
101+
private boolean isPalindrome(String input) {
102+
for (int i = 0, j = input.length() - 1; i < input.length() / 2 || j > input.length() / 2; i++, j--) {
103+
if (input.charAt(i) != input.charAt(j)) {
104+
return false;
105+
}
106+
}
107+
return true;
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import org.junit.jupiter.api.Assertions;
2+
import org.junit.jupiter.api.Test;
3+
import org.junit.jupiter.api.Timeout;
4+
5+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
6+
7+
public class LongestPalindromicSubstringUnitTest {
8+
LongestPalindromicSubstring longestPalindromicSubstring = new LongestPalindromicSubstring();
9+
10+
@Test
11+
public void testEmptyString(){
12+
Assertions.assertEquals( "", longestPalindromicSubstring.find(""));
13+
}
14+
15+
@Test
16+
public void testOneCharPalindrome(){
17+
Assertions.assertEquals( "a", longestPalindromicSubstring.find("a"));
18+
}
19+
20+
@Test
21+
public void testEvenLengthPalindrome(){
22+
Assertions.assertEquals( "aa", longestPalindromicSubstring.find("aa"));
23+
}
24+
25+
@Test
26+
public void testOddLengthPalindrome(){
27+
Assertions.assertEquals( "aba", longestPalindromicSubstring.find("aba"));
28+
}
29+
30+
@Test
31+
public void testCaseSensitivity(){
32+
Assertions.assertEquals("a", longestPalindromicSubstring.find("abA"));
33+
}
34+
35+
@Test
36+
public void testEvenLengthSubstringPalindrome(){
37+
Assertions.assertEquals( "aaaa", longestPalindromicSubstring.find("ddaaaa"));
38+
}
39+
40+
@Test
41+
public void testOddLengthSubstringPalindrome(){
42+
Assertions.assertEquals( "aba", longestPalindromicSubstring.find("ddaaba"));
43+
}
44+
45+
@Test
46+
public void testEvenLengthSubstringAtBeginningPalindrome(){
47+
Assertions.assertEquals( "aaaa", longestPalindromicSubstring.find("aaaaddca"));
48+
}
49+
50+
@Test
51+
public void testOddLengthSubstringAtBeginningPalindrome(){
52+
Assertions.assertEquals( "aba", longestPalindromicSubstring.find("abaddca"));
53+
}
54+
55+
@Test
56+
public void testMultipleSubPalindromesLongestIsFound(){
57+
Assertions.assertEquals( "aszsdszsa", longestPalindromicSubstring.find("aaacaaaszsdszsacaa"));
58+
}
59+
60+
@Test
61+
@Timeout(value = 200, unit = MILLISECONDS)
62+
public void testTimeLimit(){
63+
64+
Assertions.assertEquals("ranynar",longestPalindromicSubstring.find("civilwartestingwhetherthatnaptionoranynartionsoconceivedandsodedicatedcanlongendureWeareqmetonagreatbattlefiemldoftzhatwarWehavecometodedicpateaportionofthatfieldasafinalrestingplaceforthosewhoheregavetheirlivesthatthatnationmightliveItisaltogetherfangandproperthatweshoulddothisButinalargersensewecannotdedicatewecannotconsecratewecannothallowthisgroundThebravelmenlivinganddeadwhostruggledherehaveconsecrateditfaraboveourpoorponwertoaddordetractTgheworldadswfilllittlenotlenorlongrememberwhatwesayherebutitcanneverforgetwhattheydidhereItisforusthelivingrathertobededicatedheretotheulnfinishedworkwhichtheywhofoughtherehavethusfarsonoblyadvancedItisratherforustobeherededicatedtothegreattdafskremainingbeforeusthatfromthesehonoreddeadwetakeincreaseddevotiontothatcauseforwhichtheygavethelastpfullmeasureofdevotionthatweherehighlyresolvethatthesedeadshallnothavediedinvainthatthisnationunsderGodshallhaveanewbirthoffreedomandthatgovernmentofthepeoplebythepeopleforthepeopleshallnotperishfromtheearth"));
65+
}
66+
67+
@Test
68+
@Timeout(value = 200, unit = MILLISECONDS)
69+
public void testTimeLimit2(){
70+
Assertions.assertEquals("grnrg",longestPalindromicSubstring.find("dtgrtoxuybwyfskikukcqlvprfipgaygawcqnfhpxoifwgpnzjfdnhpgmsoqzlpsaxmeswlhzdxoxobxysgmpkhcylvqlzenzhzhnakctrliyyngrquiuvhpcrnccapuuwrrdufwwungaevzkvwbkcietiqsxpvaaowrteqgkvovcoqumgrlsxvouaqzwaylehybqchsgpzbkfugujrostopwhtgrnrggocprnxwsecmvofawkkpjvcchtxixjtrnngrzqpiwywmnbdnjwqpmnifdiwzpmabosrmzhgdwgcgidmubywsnshcgcrawjvfiuxzyzxsbpfhzpfkjqcpfyynlpshzqectcnltfimkukopjzzmlfkwlgbzftsddnxrjootpdhjehaafudkkffmcnimnfzzjjlggzvqozcumjyazchjkspdlmifvsciqzgcbehqvrwjkusapzzxyiwxlcwpzvdsyqcfnguoidiiekwcjdvbxjvgwgcjcmjwbizhhcgirebhsplioytrgjqwrpwdciaeizxssedsylptffwhnedriqozvfcnsmxmdxdtflwjvrvmyausnzlrgcchmyrgvazjqmvttabnhffoe"));
71+
}
72+
73+
@Test
74+
@Timeout(value = 200, unit = MILLISECONDS)
75+
public void testTimeLimit3(){
76+
77+
Assertions.assertEquals("qjkjq",longestPalindromicSubstring.find("uwqrvqslistiezghcxaocjbhtktayupazvowjrgexqobeymperyxtfkchujjkeefmdngfabycqzlslocjqipkszmihaarekosdkwvsirzxpauzqgnftcuflzyqwftwdeizwjhloqwkhevfovqwyvwcrosexhflkcudycvuelvvqlbzxoajisqgwgzhioomucfmkmyaqufqggimzpvggdohgxheielsqucemxrkmmagozxhvxlwvtbbcegkvvdrgkqszgajebbobxnossfrafglxvryhvyfcibfkgpbsorqprfujfgbmbctsenvbzcvypcjubsnjrjvyznbswqawodghmigdwgijfytxbgpxreyevuprpztmjejkaqyhppchuuytkdsteroptkouuvmkvejfunmawyuezxvxlrjulzdikvhgxajohpzrshrnngesarimyopgqydcmsaciegqlpqnclpwcjqmhtmtwwtbkmtnntdllqbyyhfxsjyhugnjbebtxeljytoxvqvrxygmtogndrhlcmbmgiueliyfkkcuypvvzkomjrfhuhhnfbxeuvssvvllgukdolffukzwqaimxkngnjnmsbvwkajyxqntsqjkjqvwxnlxwjfiaofejtjcveqstqhdzgqistxwsgrovvwgorjaoosremqbzllgbgrwtqdggxnyvkivlcvnv"));
78+
}
79+
80+
@Test
81+
@Timeout(value = 200, unit = MILLISECONDS)
82+
public void testTimeLimit4(){
83+
84+
Assertions.assertEquals("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",longestPalindromicSubstring.find("\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""));
85+
}
86+
87+
@Test
88+
public void testSample1(){
89+
Assertions.assertEquals( "bab", longestPalindromicSubstring.find("babad"));
90+
}
91+
92+
@Test
93+
public void testSample2(){
94+
Assertions.assertEquals("bb", longestPalindromicSubstring.find("cbbd"));
95+
}
96+
97+
@Test
98+
public void testSample3(){
99+
Assertions.assertEquals("aba", longestPalindromicSubstring.find("caba"));
100+
}
101+
102+
@Test
103+
public void testSample4(){
104+
Assertions.assertEquals("aabaacaabaa", longestPalindromicSubstring.find("aabaacaabaa"));
105+
}
106+
107+
@Test
108+
public void testSample5(){
109+
Assertions.assertEquals("bcb", longestPalindromicSubstring.find("abcbc"));
110+
}
111+
}

0 commit comments

Comments
 (0)