Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.thealgorithms.dynamicprogramming;

/**
* Smith–Waterman algorithm for local sequence alignment.
* Finds the highest scoring local alignment between substrings of two sequences.
*
* Time Complexity: O(n * m)
* Space Complexity: O(n * m)
*/
public final class SmithWaterman {

private SmithWaterman() {
// Utility Class
}

/**
* Computes the Smith–Waterman local alignment score between two strings.
*
* @param s1 first string
* @param s2 second string
* @param matchScore score for a match
* @param mismatchPenalty penalty for mismatch (negative)
* @param gapPenalty penalty for insertion/deletion (negative)
* @return the maximum local alignment score
*/
public static int align(String s1, String s2, int matchScore, int mismatchPenalty, int gapPenalty) {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("Input strings must not be null.");
}

int n = s1.length();
int m = s2.length();
int maxScore = 0;

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

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int matchOrMismatch = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? matchScore : mismatchPenalty;

dp[i][j] = Math.max(0,
Math.max(Math.max(dp[i - 1][j - 1] + matchOrMismatch, // match/mismatch
dp[i - 1][j] + gapPenalty // deletion
),
dp[i][j - 1] + gapPenalty // insertion
));

if (dp[i][j] > maxScore) {
maxScore = dp[i][j];
}
}
}

return maxScore;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.thealgorithms.dynamicprogramming;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

/**
* Unit tests for the {@code SmithWaterman} class.
*/
class SmithWatermanTest {

@Test
void testIdenticalStrings() {
int score = SmithWaterman.align("GATTACA", "GATTACA", 2, -1, -2);
assertEquals(14, score); // full match, 7*2
}

@Test
void testPartialMatch() {
int score = SmithWaterman.align("GATTACA", "TTAC", 2, -1, -2);
assertEquals(8, score); // best local alignment "TTAC"
}

@Test
void testNoMatch() {
int score = SmithWaterman.align("AAAA", "TTTT", 1, -1, -2);
assertEquals(0, score); // no alignment worth keeping
}

@Test
void testInsertionDeletion() {
int score = SmithWaterman.align("ACGT", "ACGGT", 1, -1, -2);
assertEquals(3, score); // local alignment "ACG"
}

@Test
void testEmptyStrings() {
assertEquals(0, SmithWaterman.align("", "", 1, -1, -2));
}

@ParameterizedTest
@CsvSource({"null,ABC", "ABC,null", "null,null"})
void testNullInputs(String s1, String s2) {
String first = "null".equals(s1) ? null : s1;
String second = "null".equals(s2) ? null : s2;

IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> SmithWaterman.align(first, second, 1, -1, -2));
assertEquals("Input strings must not be null.", ex.getMessage());
}
}