Skip to content
Open
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
18 changes: 8 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>dev.stegmaier</groupId>
Expand All @@ -28,8 +28,10 @@
</scm>
<properties>
<java.version>23</java.version>
<mvn-fmt-plugin.version>2.25</mvn-fmt-plugin.version>
<lombok.version>1.18.34</lombok.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<mvn-fmt-plugin.version>2.27</mvn-fmt-plugin.version>
<lombok.version>1.18.38</lombok.version>
<asciidoctor-plugin.version>3.2.0</asciidoctor-plugin.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -70,13 +72,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>dev.stegmaier.leetcode.LeetcodeApplication</mainClass> <!-- Adjust if needed -->
<mainClass>dev.stegmaier.leetcode.LeetcodeApplication</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.1</version>
<version>${asciidoctor-plugin.version}</version>
<executions>
<execution>
<id>generate-docs</id>
Expand All @@ -98,10 +100,6 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
Expand All @@ -117,7 +115,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version> <!-- or the latest version -->
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package dev.stegmaier.leetcode.controllers.questions;

import static dev.stegmaier.leetcode.utils.CorrelationIDUtil.getCorrelationId;
import static org.slf4j.LoggerFactory.getLogger;

import dev.stegmaier.leetcode.services.LeetCodeQuestions.Q3085MinimumDeletionService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/questions") // Base URI for all methods in this controller
public class Q3085MinimumDeletionstoMakeStringKSpecialController {

private static final Logger log =
getLogger(Q3085MinimumDeletionstoMakeStringKSpecialController.class);
private final Q3085MinimumDeletionService q3085MinimumDeletionService;

public Q3085MinimumDeletionstoMakeStringKSpecialController(
Q3085MinimumDeletionService q3085MinimumDeletionService) {
this.q3085MinimumDeletionService = q3085MinimumDeletionService;
}

// Display the question page for Leetcode Question #1106
@GetMapping("/3085-Minimum-Deletions-To-Make-String-K-Special")
public String question3085Page(HttpServletRequest request, HttpServletResponse response) {
String clientIP = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String correlationID = getCorrelationId(request);

log.info(
"question3085Page was requested by: {} Host: {} Correlation ID:{}",
clientIP,
userAgent,
correlationID);

response.setHeader("X-Correlation-ID", correlationID);

return "questions/3085-Minimum-Deletions-To-Make-String-K-Special"; // Refers to addition.html
// template
}

// Display the question page for Leetcode Question #1106
@PostMapping("/3085-Minimum-Deletions-To-Make-String-K-Special")
public String handleQuestion3085Input(
@RequestParam("expression") String expression,
@RequestParam("kValue") int kValue,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
String clientIP = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String correlationID = getCorrelationId(request);

log.info(
"{} with a value of k={} was submitted to question3085Page by: {} Host: {} Correlation ID:{}",
expression,
kValue,
clientIP,
userAgent,
correlationID);

try {
int result =
q3085MinimumDeletionService.getMinimumDeletions(
expression, kValue); // Using the injected MathService
model.addAttribute("result", "minimum number of deletions: " + result);
log.info(
"{} returned from the Deletion Service. Correlation ID: {}", expression, correlationID);
} catch (IllegalArgumentException e) {
model.addAttribute("result", "Invalid input. Please enter a valid input.");
log.error(
"{} with a value of k= {} caused an error to occur in the Deletion Service. Correlation ID: {}",
expression,
kValue,
correlationID);
}

response.setHeader("X-Correlation-ID", correlationID);

return "questions/3085-Minimum-Deletions-To-Make-String-K-Special"; // Refers to addition.html
// template
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package dev.stegmaier.leetcode.services.LeetCodeQuestions;

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import org.springframework.stereotype.Component;

@Component
public class Q3085MinimumDeletionService {

private static final Logger log = getLogger(Q3085MinimumDeletionService.class);

public int getMinimumDeletions(String expression, int kValue) {
validateInputMeetsRequirements(expression, kValue);

return 0;
}

private void validateInputMeetsRequirements(String expression, int kValue) {
checkExpressionMeetsRequirements(expression);
checkKValueMeetsRequirements(kValue);
}

private void checkKValueMeetsRequirements(int kValue) {
validateKValueIsGreaterThanZero(kValue);
validateKValueIsSmallerThanOneHundredThousand(kValue);
}

private void validateKValueIsSmallerThanOneHundredThousand(int kValue) {
if (kValue > 100000) {
throw new IllegalArgumentException("K value provided is greater than 10⁵");
}
}

private void validateKValueIsGreaterThanZero(int kValue) {
if (kValue < 0) {
throw new IllegalArgumentException("K value provided is less than 0");
}
}

private void checkExpressionMeetsRequirements(String expression) {
validateExpressionIsNotNull(expression);
validateExpressionIsLargerThanOneCharacterLong(expression);
validateExpressionIsSmallerThanOneHundredThousandCharacters(expression);
validateExpressionOnlyContainsLowercaseLetters(expression);
}

private void validateExpressionOnlyContainsLowercaseLetters(String expression) {
if (!expression.matches("^[a-z]+$")) {
throw new IllegalArgumentException("Expression does not contain only lowercase letters");
}
}

private void validateExpressionIsNotNull(String expression) {
if (null == expression) {
throw new NullPointerException("expression is somehow null");
}
}

private void validateExpressionIsLargerThanOneCharacterLong(String expression) {
if (expression.isEmpty()) {
throw new IllegalArgumentException("expression length less than 1");
}
}

private void validateExpressionIsSmallerThanOneHundredThousandCharacters(String expression) {
if (expression.length() > 100000) {
throw new IllegalArgumentException("expression length exceeds 10⁵");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public List<Question> getAllQuestions() {
new Question(1, "Addition Question", "/questions/addition"),
new Question(2, "Subtraction Question", "/questions/subtraction"),
new Question(
3, "Parsing A Boolean Expression", "/questions/1106-Parsing-A-Boolean-Expression"));
3, "Parsing A Boolean Expression", "/questions/1106-Parsing-A-Boolean-Expression"),
new Question(
4,
"Minimum Deletions To Make String K-Special",
"/questions/3085-Minimum-Deletions-To-Make-String-K-Special"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minimum Deletions To Make String K-Special</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
<link rel="stylesheet" th:href="@{/css/navbar.css}">
</head>
<body>

<!-- Navbar -->
<div class="navbar">
<div>
<a href="/">Home</a>
<a href="/questions">Questions</a>
</div>
<button id="toggle-btn" class="toggle-btn">Toggle Dark Mode</button>
</div>

<div class="container">
<h1>Minimum Deletions To Make String K-Special</h1>
<p>You are given a string <code>word</code> and an integer<code>k</code>.</p>
<p>We consider <code>word</code> to be <strong>k-special</strong> if <code>|freq(word[i]) - freq(word[j]) | <=k</code> for all indices <code>i</code> and <code>j</code> in the string.</p>
<p>Here, <code>freq(x)</code> denotes the frequency of the character <code>x</code> in <code>word</code>, and <code>|y|</code> denotes the absolute value of <code>y</code>.</p>
<p>Return <em>the <strong>minimum</strong> number of characters you need to delete to make <code>word</code> <strong>k-special</strong></em>.</p>
<div class="expression-examples">
<h3>Example 1:</h3>
<p><strong>Input:</strong> <code>word = "aabcaba", k = 0</code></p>
<p><strong>Output:</strong> <code>3</code></p>
<p><strong>Explanation:</strong> We can make <code>word</code> <code>0</code>-special by deleting <code>2</code> occurrences of <code>"a"</code> and <code>1</code> occurrence of <code>"c"</code>. Therefore, <code>word</code> becomes equal to <code>"baba"</code> where <code>freq('a') == freq('b') == 2</code>.</p>
</div>
<div class="expression-examples">
<h3>Example 2:</h3>
<p><strong>Input:</strong> <code>word = "dabdcbdcdcd", k = 2</code></p>
<p><strong>Output:</strong> <code>2</code></p>
<p><strong>Explanation:</strong> We can make <code>word</code> <code>2</code>-special by deleting <code>1</code> occurrences of <code>"a"</code> and <code>1</code> occurrence of <code>"d"</code>. Therefore, <code>word</code> becomes equal to <code>"bdcbdcdcd"</code> where <code>freq('b') == 2</code>, <code>freq('c') == 3</code>, and <code>freq('d') == 4</code>.</p>
</div>
<div class="expression-examples">
<h3>Example 3:</h3>
<p><strong>Input:</strong> <code>word = "aaabaaa", k = 2</code></p>
<p><strong>Output:</strong> <code>1</code></p>
<p><strong>Explanation:</strong> We can make <code>word</code> <code>2</code>-special by deleting <code>1</code> occurrence of <code>"b"</code>. Therefore, <code>word</code> becomes equal to <code>"aaaaaa"</code> where each letter's frequency is now uniformly <code>6</code>.</p>
</div>

<h2>Submit Your Expression:</h2>
<form action="/questions/3085-Minimum-Deletions-To-Make-String-K-Special" method="post">
<label for="expression">Enter a string of lower case letters and a value for k:</label>
<input type="text" id="expression" name="expression" required>
<label for="kValue"></label>
<input type="number" id="kValue" name="kValue" required>
<button type="submit">Evaluate K</button>
</form>

<p th:if="${result != null}">
<strong>Result:</strong> <span th:text="${result}"></span>
</p>

<h2>Constraints:</h2>
<ul>
<li><code>1 <= word.length <= 10⁵</code></li>
<li><code>0 <= k <= 10⁵</code></li>
<li><code>word</code> consists only of lowercase English letters.</li>
</ul>
</div>

<script th:src="@{/js/dark-mode.js}"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions src/test/java/dev/stegmaier/leetcode/constants/Q3085Constants.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(Q1106ParsingABooleanExpressionController.class)
class Q1106ParsingABooleanExpressionControllerTest {

@Autowired private MockMvc mockMvc;

@MockBean private Q1106BooleanParsingService booleanParsingService;
@MockitoBean private Q1106BooleanParsingService booleanParsingService;

@Test
void testQuestion1106PageLoads() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev.stegmaier.leetcode.controllers.questions;

import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import dev.stegmaier.leetcode.services.LeetCodeQuestions.Q3085MinimumDeletionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(Q3085MinimumDeletionstoMakeStringKSpecialController.class)
class Q3085MinimumDeletionstoMakeStringKSpecialControllerTest {
@Autowired private MockMvc mockMvc;

@MockitoBean private Q3085MinimumDeletionService minimumDeletionService;

@Test
void testQuestion3085PageLoads() throws Exception {
mockMvc
.perform(get("/questions/3085-Minimum-Deletions-To-Make-String-K-Special"))
.andExpect(status().isOk())
.andExpect(view().name("questions/3085-Minimum-Deletions-To-Make-String-K-Special"))
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Minimum Deletions To Make String K-Special")));
}

@Test
void testHandleQuestion3085Input() throws Exception {
// Mocking service result
when(minimumDeletionService.getMinimumDeletions("aaaabbbccc", 12)).thenReturn(0);

mockMvc
.perform(
post("/questions/3085-Minimum-Deletions-To-Make-String-K-Special")
.param("expression", "aaaabbbccc")
.param("kValue", "12"))
.andExpect(status().isOk())
.andExpect(model().attribute("result", "minimum number of deletions: 0"))
.andExpect(view().name("questions/3085-Minimum-Deletions-To-Make-String-K-Special"));
}

@Test
void testHandleQuestion3085Input_InvalidExpression() throws Exception {
// Mocking service to throw an IllegalArgumentException
when(minimumDeletionService.getMinimumDeletions("invalid", 12))
.thenThrow(new IllegalArgumentException());

mockMvc
.perform(
post("/questions/3085-Minimum-Deletions-To-Make-String-K-Special")
.param("expression", "invalid")
.param("kValue", "12"))
.andExpect(status().isOk())
.andExpect(model().attribute("result", "Invalid input. Please enter a valid input."))
.andExpect(view().name("questions/3085-Minimum-Deletions-To-Make-String-K-Special"));
}
}
Loading
Loading