From 3c170ed968e3cf280eee2a81cc27d752f4f40eae Mon Sep 17 00:00:00 2001 From: Divijh Mangtani Date: Sun, 16 Nov 2025 20:26:09 +0530 Subject: [PATCH 1/2] draft commit --- algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ | 0 algorithms/leetcode/P5_longest-palindrome-substring.typ | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ create mode 100644 algorithms/leetcode/P5_longest-palindrome-substring.typ diff --git a/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ b/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ new file mode 100644 index 0000000..e69de29 diff --git a/algorithms/leetcode/P5_longest-palindrome-substring.typ b/algorithms/leetcode/P5_longest-palindrome-substring.typ new file mode 100644 index 0000000..e69de29 From 1ef31059aa54c6d9210e7c437b1817d3fe7b0291 Mon Sep 17 00:00:00 2001 From: Divijh Mangtani Date: Thu, 20 Nov 2025 05:43:26 +0530 Subject: [PATCH 2/2] actual algorithm commit --- .../P121_best-time-to-buy-and-sell-stock.typ | 316 +++++++++++++++++ .../P5_longest-palindrome-substring.typ | 320 ++++++++++++++++++ 2 files changed, 636 insertions(+) diff --git a/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ b/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ index e69de29..0c941a4 100644 --- a/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ +++ b/algorithms/leetcode/P121_best-time-to-buy-and-sell-stock.typ @@ -0,0 +1,316 @@ +#import "../../lib/style.typ": * +#import "../../lib/mapcode.typ": * + +#show link: underline + +== Best Time to Buy and Sell Stock #link("https://leetcode.com/problems/best-time-to-buy-and-sell-stock")[LeetCode P.121] + +You are given an array `prices` where `prices[i]` is the price of a given stock on the `i`-th day. + +You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. + +Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return `0`. + +_Example 1_: +/ Input: $"prices" = [7,1,5,3,6,4]$ +/ Output: $5$ +/ Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. + +_Example 2_: +/ Input: $"prices" = [7,6,4,3,1]$ +/ Output: $0$ +/ Explanation: In this case, no transactions are done and the max profit = 0. + +Constraints: +- $1 <= |"prices"| <= 10^5$ +- $0 <= "prices"[i] <= 10^4$ + +*Mapcode Formalization:* + +_primitives_: `max`(max), `subtract`($-$) + +The recursion represents choices at each day: skip the day, or buy/sell depending on state. + +#set text(size: 9.5pt) +$ +n = |"prices"| quad "prices" in NN^n\ +I : "prices" in NN^n\ +X_("prices") & : [0..n] times {"bought", "not_bought"} -> NN_bot\ +A & : NN "maximum profit"\ +\ +rho("prices") & = { (i, b) -> bot | i in {0 dots n}, b in {"bought", "not_bought"}}\ +$ +#set text(size: 8.5pt) +$ +F_("prices") (x_(i,b)) & = cases( + 0 & "if " i = n, + max(x_(i+1,"bought"), "prices"_i) & "if " b = "bought" and x_(i+1,"bought") != bot, + max(x_(i+1,"not_bought"), x_(i+1,"bought") - "prices"_i) & "if " b = "not_bought" "and" x_(i+1,"bought") != bot "and" x_(i+1,"not_bought") != bot, + bot & "otherwise" + )\ +$ +#set text(size: 9.5pt) +$ +pi_("prices") (x) & = x_(0,"not_bought") +$ + +The recursion fills the table backwards from day $n$ to day $0$: +- At the end (day $n$), profit is 0 +- If already bought: either skip this day or sell at current price +- If not bought: either skip this day or buy at current price (subtracts from future profit) + +#let inst_prices = (7, 1, 5, 3, 6, 4); +#let inst_n = inst_prices.len(); + +#figure( + caption: [Best Time to Buy and Sell Stock using \ mapcode for $"prices" = #inst_prices$; state table showing \ maximum profit achievable from each day in each state.], + gap: 3em, +$#{ + // Input visualization: display the input array + let I_h = (prices) => { + text(repr(prices)) + } + + // Output visualization: display the final profit + let A_h = (profit) => { + text(str(profit)) + } + + let rho = (prices) => { + let n = prices.len() + let x = () + // States: (day, bought_status) + // We need n+1 days (0 to n) + // The 'bought' state represents the maximum possible selling price from this day forward. + for i in range(0, n + 1) { + x.push((none, none)) // (bought, not_bought) + } + x + } + + let F_i = (prices) => (x) => ((i,)) => { + let n = prices.len() + let (bought, not_bought) = x.at(i) + + if i == n { + // Base case: at end, profit is 0 + (0, 0) + } else if i < n { + let (next_bought, next_not_bought) = x.at(i + 1) + + let new_bought = none + let new_not_bought = none + + // If already bought: can sell at current price or skip + if next_bought != none { + let skip = next_bought + let sell = prices.at(i) // Selling gives us the price directly + new_bought = calc.max(skip, sell) + } + + // If not bought: can buy at current price (subtracting cost) or skip + if next_bought != none and next_not_bought != none { + let skip = next_not_bought + let buy = next_bought - prices.at(i) // Buying: future profit minus cost + new_not_bought = calc.max(skip, buy) + } + + (new_bought, new_not_bought) + } else { + (bought, not_bought) + } + } + + let F = (prices) => map_tensor(F_i(prices), dim: 1) + + let pi = (prices) => (x) => { + // Start from day 0, not bought state + let (_, not_bought) = x.at(0) + not_bought + } + + // Visualization helper + let x_h(x, diff_mask: none) = { + set text(weight: "bold", size: 8pt) + let n = x.len() + let rows = () + + // Header + let header = () + header.push(rect(fill: purple.transparentize(70%), inset: 5pt, stroke: gray)[Day]) + header.push(rect(fill: orange.transparentize(70%), inset: 5pt, stroke: gray)[Price]) + header.push(rect(fill: teal.transparentize(70%), inset: 5pt, stroke: gray)[Bought]) + header.push(rect(fill: green.transparentize(70%), inset: 5pt, stroke: gray)[Not Bought]) + rows.push(grid(columns: (35pt, 35pt, 45pt, 55pt), rows: 20pt, align: center + horizon, ..header)) + + for i in range(0, n) { + let row = () + let (bought, not_bought) = x.at(i) + + // Day number + row.push(rect(fill: purple.transparentize(80%), inset: 5pt, stroke: gray)[$#i$]) + + // Price (if not at end) + if i < inst_prices.len() { + row.push(rect(inset: 5pt, stroke: gray)[$#inst_prices.at(i)$]) + } else { + row.push(rect(inset: 5pt, stroke: gray)[$-$]) + } + + // Bought state + let bought_val = if bought != none {[$#bought$]} else {[$bot$]} + if diff_mask != none and diff_mask.at(i).at(0) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 5pt)[$#bought_val$]) + } else { + row.push(rect(stroke: gray, inset: 5pt)[$#bought_val$]) + } + + // Not bought state + let not_bought_val = if not_bought != none {[$#not_bought$]} else {[$bot$]} + if diff_mask != none and diff_mask.at(i).at(1) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 5pt)[$#not_bought_val$]) + } else { + row.push(rect(stroke: gray, inset: 5pt)[$#not_bought_val$]) + } + + rows.push(grid(columns: (35pt, 35pt, 45pt, 55pt), rows: 20pt, align: center + horizon, ..row)) + } + + grid(align: center, ..rows) + } + + mapcode-viz( + rho, F(inst_prices), pi(inst_prices), + I_h: I_h, + X_h: x_h, + A_h: A_h, + pi_name: [$mpi$], + group-size: 2, + cell-size: 90mm, + scale-fig: 60% + )(inst_prices) +}$) + +#pagebreak() + +=== Additional Test Cases + +#let test_cases = ((7, 6, 4, 3, 1), (2, 4, 1), (3, 3, 5, 0, 0, 3, 1, 4)) + +#for test_prices in test_cases [ + #let test_n = test_prices.len() + #figure( + caption: [Best Time to Buy/Sell Stock for \ $"prices" = #test_prices$], + gap: 4.5em, + $#{ + let I_h = (prices) => { + text(repr(prices)) + } + + let A_h = (profit) => { + text(str(profit)) + } + + let rho = (prices) => { + let n = prices.len() + let x = () + // The 'bought' state represents the maximum possible selling price from this day forward. + for i in range(0, n + 1) { + x.push((none, none)) + } + x + } + + let F_i = (prices) => (x) => ((i,)) => { + let n = prices.len() + let (bought, not_bought) = x.at(i) + + if i == n { + (0, 0) + } else if i < n { + let (next_bought, next_not_bought) = x.at(i + 1) + + let new_bought = none + let new_not_bought = none + + if next_bought != none { + let skip = next_bought + let sell = prices.at(i) + new_bought = calc.max(skip, sell) + } + + if next_bought != none and next_not_bought != none { + let skip = next_not_bought + let buy = next_bought - prices.at(i) + new_not_bought = calc.max(skip, buy) + } + + (new_bought, new_not_bought) + } else { + (bought, not_bought) + } + } + + let F = (prices) => map_tensor(F_i(prices), dim: 1) + + let pi = (prices) => (x) => { + let (_, not_bought) = x.at(0) + not_bought + } + + let x_h(x, diff_mask: none) = { + set text(weight: "bold", size: 8pt) + let n = x.len() + let rows = () + + let header = () + header.push(rect(fill: purple.transparentize(70%), inset: 5pt, stroke: gray)[Day]) + header.push(rect(fill: orange.transparentize(70%), inset: 5pt, stroke: gray)[Price]) + header.push(rect(fill: teal.transparentize(70%), inset: 5pt, stroke: gray)[Bought]) + header.push(rect(fill: green.transparentize(70%), inset: 5pt, stroke: gray)[Not Bought]) + rows.push(grid(columns: (35pt, 35pt, 45pt, 55pt), rows: 20pt, align: center + horizon, ..header)) + + for i in range(0, n) { + let row = () + let (bought, not_bought) = x.at(i) + + row.push(rect(fill: purple.transparentize(80%), inset: 5pt, stroke: gray)[$#i$]) + + if i < test_prices.len() { + row.push(rect(inset: 5pt, stroke: gray)[$#test_prices.at(i)$]) + } else { + row.push(rect(inset: 5pt, stroke: gray)[$-$]) + } + + let bought_val = if bought != none {[$#bought$]} else {[$bot$]} + if diff_mask != none and diff_mask.at(i).at(0) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 5pt)[$#bought_val$]) + } else { + row.push(rect(stroke: gray, inset: 5pt)[$#bought_val$]) + } + + let not_bought_val = if not_bought != none {[$#not_bought$]} else {[$bot$]} + if diff_mask != none and diff_mask.at(i).at(1) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 5pt)[$#not_bought_val$]) + } else { + row.push(rect(stroke: gray, inset: 5pt)[$#not_bought_val$]) + } + + rows.push(grid(columns: (35pt, 35pt, 45pt, 55pt), rows: 20pt, align: center + horizon, ..row)) + } + + grid(align: center, ..rows) + } + + mapcode-viz( + rho, F(test_prices), pi(test_prices), + I_h: I_h, + X_h: x_h, + A_h: A_h, + pi_name: [$mpi$], + group-size: 2, + cell-size: 80mm, + scale-fig: 55% + )(test_prices) + }$) +] \ No newline at end of file diff --git a/algorithms/leetcode/P5_longest-palindrome-substring.typ b/algorithms/leetcode/P5_longest-palindrome-substring.typ index e69de29..e154fec 100644 --- a/algorithms/leetcode/P5_longest-palindrome-substring.typ +++ b/algorithms/leetcode/P5_longest-palindrome-substring.typ @@ -0,0 +1,320 @@ +#import "../../lib/style.typ": * +#import "../../lib/mapcode.typ": * + +#show link: underline + +== Longest Palindromic Substring #link("https://leetcode.com/problems/longest-palindromic-substring")[LeetCode P.5] + +Given a string `s`, return the longest palindromic substring in `s`. + +A string is palindromic if it reads the same forward and backward. + +_Example 1_: +/ Input: $s = "babad"$ +/ Output: $"bab"$ +/ Explanation: "aba" is also a valid answer. + +_Example 2_: +/ Input: $s = "cbbd"$ +/ Output: $"bb"$ + +_Example 3_: +/ Input: $s = "racecar"$ +/ Output: $"racecar"$ + +Constraints: +- $1 <= |s| <= 1000$ +- $s$ consists of only digits and English letters. + +*Mapcode Formalization:* + +_primitives_: `and`($and$), `equals`($=$), `max`(max) + +A palindrome check can be expressed recursively: +- A single character is always a palindrome +- Two characters are palindromic if they're equal +- A substring $s[i..j]$ is palindromic if $s[i] = s[j]$ and $s[i+1..j-1]$ is palindromic + +$ +n = |s| quad s in Sigma^* quad Sigma = {"letters and digits"}\ +I : s in Sigma^*\ +X_s & : [0..n) times [0..n) -> {bot, "True", "False"}\ +A & : (i:"start index", l:"length") in NN times NN\ +\ +rho(s) & = { (i,j) -> bot | i in {0 dots n-1}, j in {0 dots n-1}}\ +\ +F_s (x_(i,j)) & = cases( + "True" & "if " i = j, + s_i = s_j & "if " j = i + 1, + s_i = s_j and x_(i+1,j-1) & "if " j > i + 1 and x_(i+1,j-1) != bot, + bot & "otherwise" + )\ +\ +pi_s (x) & = (i^*, l^*) "where"\ +& i^*, l^* = arg max_((i,j) : x_(i,j) = "True") (j - i + 1)\ +& "result substring" = s[i^* : i^* + l^*] +$ + +#let inst_s = "babad"; +#let inst_n = inst_s.len(); + +#figure( + caption: [Longest Palindromic Substring computation using mapcode for $s = "#inst_s"$; dynamic-programming table where each cell $(i,j)$ indicates if substring $s[i..j]$ is palindromic. Legend: #box(fill: orange.transparentize(70%), inset: 3pt, stroke: gray)[column headers], #box(fill: green.transparentize(70%), inset: 3pt, stroke: gray)[row headers], #box(fill: blue.transparentize(70%), inset: 3pt, stroke: gray)[palindrome (T)], #box(fill: gray.transparentize(90%), inset: 3pt, stroke: gray)[invalid cells], #box(fill: yellow.transparentize(50%), inset: 3pt, stroke: gray)[updated cells]], +$#{ + // Convert string to array for indexing + let s_arr = inst_s.codepoints() + + let rho = (s) => { + let n = s.len() + let x = () + for i in range(0, n) { + let row = () + for j in range(0, n) { + row.push(none) + } + x.push(row) + } + x + } + + let F_i = (s) => (x) => ((i, j)) => { + let n = s.len() + let s_arr = s.codepoints() + + if i == j { + // Single character is always palindrome + true + } else if j == i + 1 { + // Two characters: check if equal + s_arr.at(i) == s_arr.at(j) + } else if j > i + 1 { + // Longer substring: check ends and inner + if i + 1 < n and j - 1 < n and x.at(i + 1).at(j - 1) != none { + (s_arr.at(i) == s_arr.at(j)) and x.at(i + 1).at(j - 1) + } else { + none + } + } else { + none + } + } + + let F = (s) => map_tensor(F_i(s), dim: 2) + + let pi = (s) => (x) => { + let n = s.len() + let max_len = 1 + let start = 0 + + for i in range(0, n) { + for j in range(i, n) { + if x.at(i).at(j) == true and (j - i + 1) > max_len { + max_len = j - i + 1 + start = i + } + } + } + + // Return the substring + let s_arr = s.codepoints() + s_arr.slice(start, start + max_len).join("") + } + + // Visualization helper for input + let I_h = (s) => { + text(weight: "bold", size: 11pt)["#s"] + } + + // Visualization helper for output + let A_h = (res) => { + text(weight: "bold", size: 11pt, fill: blue)["#res"] + } + + // Visualization helper for DP table + let x_h(x, diff_mask: none) = { + set text(weight: "bold", size: 9pt) + let n = x.len() + let rows = () + + // Header row with string characters + let header_cells = () + header_cells.push(rect(stroke: none, inset: 3pt)[$emptyset$]) + for j in range(0, n) { + header_cells.push(rect(fill: orange.transparentize(70%), inset: 3pt, stroke: gray)[#s_arr.at(j)]) + } + rows.push(grid(columns: header_cells.len() * (12pt,), rows: 12pt, align: center + horizon, ..header_cells)) + + for i in range(0, n) { + let row = () + // Left label with character + row.push(rect(fill: green.transparentize(70%), inset: 3pt, stroke: gray)[#s_arr.at(i)]) + + for j in range(0, n) { + let val = if x.at(i).at(j) == none { + [$bot$] + } else if x.at(i).at(j) == true { + [T] + } else { + [F] + } + + let cell_color = if j < i { + gray.transparentize(90%) + } else if x.at(i).at(j) == true { + blue.transparentize(70%) + } else { + white + } + + if diff_mask != none and diff_mask.at(i).at(j) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 3pt)[$#val$]) + } else { + row.push(rect(stroke: gray, fill: cell_color, inset: 3pt)[$#val$]) + } + } + rows.push(grid(columns: row.len() * (12pt,), rows: 12pt, align: center + horizon, ..row)) + } + grid(align: center, ..rows) + } + + mapcode-viz( + rho, F(inst_s), pi(inst_s), + I_h: I_h, + X_h: x_h, + A_h: A_h, + pi_name: [$mpi$], + group-size: 2, + cell-size: 45mm, + scale-fig: 70% + )(inst_s) +}$) + +#pagebreak() + +=== Additional Test Cases + +#let test_cases = ("cbbd", "a", "ac") + +#for test_s in test_cases [ + #let test_n = test_s.len() + #figure( + caption: [LPS for $s = "#test_s"$. Legend: #box(fill: orange.transparentize(70%), inset: 3pt, stroke: gray)[column headers], #box(fill: green.transparentize(70%), inset: 3pt, stroke: gray)[row headers], #box(fill: blue.transparentize(70%), inset: 3pt, stroke: gray)[palindrome (T)], #box(fill: gray.transparentize(90%), inset: 3pt, stroke: gray)[invalid cells], #box(fill: yellow.transparentize(50%), inset: 3pt, stroke: gray)[updated cells]], + $#{ + let s_arr = test_s.codepoints() + + let rho = (s) => { + let n = s.len() + let x = () + for i in range(0, n) { + let row = () + for j in range(0, n) { + row.push(none) + } + x.push(row) + } + x + } + + let F_i = (s) => (x) => ((i, j)) => { + let n = s.len() + let s_arr = s.codepoints() + + if i == j { + true + } else if j == i + 1 { + s_arr.at(i) == s_arr.at(j) + } else if j > i + 1 { + if i + 1 < n and j - 1 < n and x.at(i + 1).at(j - 1) != none { + (s_arr.at(i) == s_arr.at(j)) and x.at(i + 1).at(j - 1) + } else { + none + } + } else { + none + } + } + + let F = (s) => map_tensor(F_i(s), dim: 2) + + let pi = (s) => (x) => { + let n = s.len() + let max_len = 1 + let start = 0 + + for i in range(0, n) { + for j in range(i, n) { + if x.at(i).at(j) == true and (j - i + 1) > max_len { + max_len = j - i + 1 + start = i + } + } + } + + let s_arr = s.codepoints() + s_arr.slice(start, start + max_len).join("") + } + + let I_h = (s) => { + text(weight: "bold", size: 11pt)["#s"] + } + + let A_h = (res) => { + text(weight: "bold", size: 11pt, fill: blue)["#res"] + } + + let x_h(x, diff_mask: none) = { + set text(weight: "bold", size: 9pt) + let n = x.len() + let rows = () + + let header_cells = () + header_cells.push(rect(stroke: none, inset: 3pt)[$emptyset$]) + for j in range(0, n) { + header_cells.push(rect(fill: orange.transparentize(70%), inset: 3pt, stroke: gray)[#s_arr.at(j)]) + } + rows.push(grid(columns: header_cells.len() * (12pt,), rows: 12pt, align: center + horizon, ..header_cells)) + + for i in range(0, n) { + let row = () + row.push(rect(fill: green.transparentize(70%), inset: 3pt, stroke: gray)[#s_arr.at(i)]) + + for j in range(0, n) { + let val = if x.at(i).at(j) == none { + [$bot$] + } else if x.at(i).at(j) == true { + [T] + } else { + [F] + } + + let cell_color = if j < i { + gray.transparentize(90%) + } else if x.at(i).at(j) == true { + blue.transparentize(70%) + } else { + white + } + + if diff_mask != none and diff_mask.at(i).at(j) { + row.push(rect(stroke: gray, fill: yellow.transparentize(50%), inset: 3pt)[$#val$]) + } else { + row.push(rect(stroke: gray, fill: cell_color, inset: 3pt)[$#val$]) + } + } + rows.push(grid(columns: row.len() * (12pt,), rows: 12pt, align: center + horizon, ..row)) + } + grid(align: center, ..rows) + } + + mapcode-viz( + rho, F(test_s), pi(test_s), + I_h: I_h, + X_h: x_h, + A_h: A_h, + pi_name: [$mpi$], + group-size: 2, + cell-size: 30mm, + scale-fig: 100% + )(test_s) + }$) +] \ No newline at end of file