Skip to content

Commit 339f951

Browse files
committed
Merge master into refactor-knapsack and resolve conflict in BUILD file
2 parents 7d85bbf + cc2a749 commit 339f951

8 files changed

Lines changed: 321 additions & 149 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
140140
### Tiling problems
141141

142142
- [:movie_camera:](https://youtu.be/yn2jnmlepY8) [Tiling Dominoes](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/TilingDominoes.java)
143-
- [:movie_camera:](https://www.youtube.com/watch?v=CecjOo4Zo-g) [Tiling Dominoes and Trominoes](src/main/java/com/williamfiset/algorithms/dp/examples/domino-and-tromino-tiling)
143+
- [:movie_camera:](https://www.youtube.com/watch?v=CecjOo4Zo-g) [Tiling Dominoes and Trominoes](src/main/java/com/williamfiset/algorithms/dp/examples/dominoandtrominotiling)
144144
- [:movie_camera:](https://youtu.be/pPgBZqY_Xh0) [Mountain Scenes](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/Scenes.java)
145145

146146
# Geometry

src/main/java/com/williamfiset/algorithms/dp/BUILD

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,6 @@ java_binary(
6969
runtime_deps = [":dp"],
7070
)
7171

72-
# bazel run //src/main/java/com/williamfiset/algorithms/dp:LongestCommonSubstring
73-
java_binary(
74-
name = "LongestCommonSubstring",
75-
main_class = "com.williamfiset.algorithms.dp.LongestCommonSubstring",
76-
runtime_deps = [":dp"],
77-
)
78-
7972
# bazel run //src/main/java/com/williamfiset/algorithms/dp:LongestIncreasingSubsequence
8073
java_binary(
8174
name = "LongestIncreasingSubsequence",
Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,101 @@
1+
package com.williamfiset.algorithms.dp;
2+
13
/**
2-
* This file contains an implementation of finding the Longest Common Subsequence (LCS) between two
3-
* strings using dynamic programming.
4+
* Longest Common Subsequence (LCS)
5+
*
6+
* Given two strings A and B, find the longest subsequence present in both.
7+
* A subsequence is a sequence that appears in the same relative order but
8+
* not necessarily contiguously (unlike a substring).
9+
*
10+
* Builds an (n+1) x (m+1) DP table where dp[i][j] = length of the LCS of
11+
* A[0..i-1] and B[0..j-1], then backtracks to recover one LCS string.
12+
*
13+
* Tested against: https://leetcode.com/problems/longest-common-subsequence
414
*
5-
* <p>Time Complexity: O(nm)
15+
* Time: O(n*m)
16+
* Space: O(n*m)
617
*
718
* @author William Fiset, william.alexandre.fiset@gmail.com
819
*/
9-
package com.williamfiset.algorithms.dp;
10-
1120
public class LongestCommonSubsequence {
1221

13-
// Returns a non unique Longest Common Subsequence
14-
// between the strings str1 and str2 in O(nm)
15-
public static String lcs(char[] A, char[] B) {
22+
/**
23+
* Finds one Longest Common Subsequence between A and B.
24+
*
25+
* @param A - first string
26+
* @param B - second string
27+
* @return one LCS string, or null if either input is null
28+
*/
29+
public static String lcs(String A, String B) {
30+
if (A == null || B == null) return null;
31+
return lcs(A.toCharArray(), B.toCharArray());
32+
}
1633

34+
/**
35+
* Finds one Longest Common Subsequence between A and B using bottom-up DP.
36+
*
37+
* Builds a table dp[i][j] = length of LCS of A[0..i-1] and B[0..j-1],
38+
* then backtracks through the table to reconstruct the actual subsequence.
39+
*
40+
* @param A - first character array
41+
* @param B - second character array
42+
* @return one LCS string, or null if either input is null
43+
*
44+
* Time: O(n*m)
45+
* Space: O(n*m)
46+
*/
47+
public static String lcs(char[] A, char[] B) {
1748
if (A == null || B == null) return null;
1849

1950
final int n = A.length;
2051
final int m = B.length;
2152

22-
if (n == 0 || m == 0) return null;
53+
if (n == 0 || m == 0) return "";
2354

2455
int[][] dp = new int[n + 1][m + 1];
2556

26-
// Suppose A = a1a2..an-1an and B = b1b2..bn-1bn
57+
// Fill the DP table
2758
for (int i = 1; i <= n; i++) {
2859
for (int j = 1; j <= m; j++) {
29-
30-
// If ends match the LCS(a1a2..an-1an, b1b2..bn-1bn) = LCS(a1a2..an-1, b1b2..bn-1) + 1
31-
if (A[i - 1] == B[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
32-
33-
// If the ends do not match the LCS of a1a2..an-1an and b1b2..bn-1bn is
34-
// max( LCS(a1a2..an-1, b1b2..bn-1bn), LCS(a1a2..an-1an, b1b2..bn-1) )
35-
else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
60+
// If characters match, extend the LCS from the diagonal
61+
if (A[i - 1] == B[j - 1])
62+
dp[i][j] = dp[i - 1][j - 1] + 1;
63+
// Otherwise take the best LCS excluding one character from either string
64+
else
65+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
3666
}
3767
}
3868

39-
int lcsLen = dp[n][m];
40-
char[] lcs = new char[lcsLen];
41-
int index = 0;
42-
43-
// Backtrack to find a LCS. We search for the cells
44-
// where we included an element which are those with
45-
// dp[i][j] != dp[i-1][j] and dp[i][j] != dp[i][j-1])
69+
// Backtrack from dp[n][m] to reconstruct the LCS string.
70+
// At each cell, if the characters match, that character is part of
71+
// the LCS — take it and move diagonally. Otherwise, move toward
72+
// the neighbor with the larger value (up or left) to stay on the
73+
// path that produced the optimal length.
74+
StringBuilder sb = new StringBuilder();
4675
int i = n, j = m;
47-
while (i >= 1 && j >= 1) {
48-
49-
int v = dp[i][j];
5076

51-
// The order of these may output different LCSs
52-
while (i > 1 && dp[i - 1][j] == v) i--;
53-
while (j > 1 && dp[i][j - 1] == v) j--;
54-
55-
// Make sure there is a match before adding
56-
if (v > 0) lcs[lcsLen - index++ - 1] = A[i - 1]; // or B[j-1];
57-
58-
i--;
59-
j--;
77+
while (i > 0 && j > 0) {
78+
if (A[i - 1] == B[j - 1]) {
79+
sb.append(A[i - 1]);
80+
i--;
81+
j--;
82+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
83+
i--;
84+
} else {
85+
j--;
86+
}
6087
}
6188

62-
return new String(lcs, 0, lcsLen);
89+
return sb.reverse().toString();
6390
}
6491

65-
public static void main(String[] args) {
92+
// ==================== Main ====================
6693

67-
char[] A = {'A', 'X', 'B', 'C', 'Y'};
68-
char[] B = {'Z', 'A', 'Y', 'W', 'B', 'C'};
69-
System.out.println(lcs(A, B)); // ABC
94+
public static void main(String[] args) {
95+
// LCS: ABC
96+
System.out.println("LCS: " + lcs("AXBCY", "ZAYWBC"));
7097

71-
A = new char[] {'3', '9', '8', '3', '9', '7', '9', '7', '0'};
72-
B = new char[] {'3', '3', '9', '9', '9', '1', '7', '2', '0', '6'};
73-
System.out.println(lcs(A, B)); // 339970
98+
// LCS: 339970
99+
System.out.println("LCS: " + lcs("398397970", "3399917206"));
74100
}
75101
}

src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubstring.java

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,64 @@
1+
package com.williamfiset.algorithms.dp;
2+
13
/**
2-
* This file shows you how to find the maximal subarray in an integer array Time complexity: O(n)
4+
* Maximum Subarray Problem (Kadane's Algorithm)
5+
*
6+
* Given an integer array, find the contiguous subarray with the largest sum.
7+
* Kadane's algorithm solves this in a single pass by maintaining the best
8+
* sum ending at the current position:
9+
*
10+
* sum[i] = max(arr[i], sum[i-1] + arr[i])
11+
*
12+
* At each index, we either extend the current subarray or start a new one
13+
* from the current element — whichever gives the larger sum. The global
14+
* maximum across all positions is the answer.
15+
*
16+
* Tested against: https://leetcode.com/problems/maximum-subarray
17+
*
18+
* Time: O(n)
19+
* Space: O(1)
320
*
421
* @author William Fiset, william.alexandre.fiset@gmail.com
522
*/
6-
package com.williamfiset.algorithms.dp;
7-
823
public class MaximumSubarray {
924

10-
public static void main(String[] args) {
11-
System.out.println(maximumSubarrayValue(new int[] {-5}));
12-
System.out.println(maximumSubarrayValue(new int[] {-5, -4, -10, -3, -1, -12, -6}));
13-
System.out.println(maximumSubarrayValue(new int[] {1, 2, 1, -7, 2, -1, 40, -89}));
14-
}
25+
/**
26+
* Returns the sum of the maximum contiguous subarray.
27+
*
28+
* @param arr the input array (must be non-null and non-empty)
29+
* @return the maximum subarray sum
30+
*
31+
* Time: O(n)
32+
* Space: O(1)
33+
*/
34+
public static long maximumSubarrayValue(int[] arr) {
35+
if (arr == null || arr.length == 0)
36+
throw new IllegalArgumentException("Array must not be null or empty");
1537

16-
// Return the value of the maximum subarray in 'ar'
17-
public static long maximumSubarrayValue(int[] ar) {
38+
long maxValue = arr[0];
39+
long sum = arr[0];
1840

19-
if (ar == null || ar.length == 0) return 0L;
20-
int n = ar.length, maxValue, sum;
41+
for (int i = 1; i < arr.length; i++) {
42+
// Either start a new subarray at arr[i], or extend the current one
43+
sum = Math.max(arr[i], sum + arr[i]);
2144

22-
maxValue = sum = ar[0];
45+
if (sum > maxValue)
46+
maxValue = sum;
47+
}
2348

24-
for (int i = 1; i < n; i++) {
49+
return maxValue;
50+
}
2551

26-
// At each step consider continuing the current subarray
27-
// or starting a new one because adding the next element
28-
// doesn't acutally make the subarray sum any better.
29-
if (ar[i] > sum + ar[i]) sum = ar[i];
30-
else sum = sum + ar[i];
52+
public static void main(String[] args) {
53+
// Single negative element
54+
System.out.println(maximumSubarrayValue(new int[] {-5})); // -5
3155

32-
if (sum > maxValue) maxValue = sum;
33-
}
56+
// All negative: best subarray is the largest single element (-1)
57+
System.out.println(
58+
maximumSubarrayValue(new int[] {-5, -4, -10, -3, -1, -12, -6})); // -1
3459

35-
return maxValue;
60+
// Mixed: subarray [2, -1, 40] has sum 41
61+
System.out.println(
62+
maximumSubarrayValue(new int[] {1, 2, 1, -7, 2, -1, 40, -89})); // 41
3663
}
3764
}

src/test/java/com/williamfiset/algorithms/dp/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ java_test(
6161
deps = TEST_DEPS,
6262
)
6363

64+
# bazel test //src/test/java/com/williamfiset/algorithms/dp:MaximumSubarrayTest
65+
java_test(
66+
name = "MaximumSubarrayTest",
67+
srcs = ["MaximumSubarrayTest.java"],
68+
main_class = "org.junit.platform.console.ConsoleLauncher",
69+
use_testrunner = False,
70+
args = ["--select-class=com.williamfiset.algorithms.dp.MaximumSubarrayTest"],
71+
runtime_deps = JUNIT5_RUNTIME_DEPS,
72+
deps = TEST_DEPS,
73+
)
74+
75+
# bazel test //src/test/java/com/williamfiset/algorithms/dp:LongestCommonSubsequenceTest
76+
java_test(
77+
name = "LongestCommonSubsequenceTest",
78+
srcs = ["LongestCommonSubsequenceTest.java"],
79+
main_class = "org.junit.platform.console.ConsoleLauncher",
80+
use_testrunner = False,
81+
args = ["--select-class=com.williamfiset.algorithms.dp.LongestCommonSubsequenceTest"],
82+
runtime_deps = JUNIT5_RUNTIME_DEPS,
83+
deps = TEST_DEPS,
84+
)
85+
6486
# bazel test //src/test/java/com/williamfiset/algorithms/dp:KnapsackTest
6587
java_test(
6688
name = "KnapsackTest",

0 commit comments

Comments
 (0)