Search Blogs

Showing results for "Two Pointer Technique"

Found 19 results

LeetCode 1855 Maximum Distance Between Pair of Values | Two Pointer Java Solution

LeetCode 1855 Maximum Distance Between Pair of Values | Two Pointer Java Solution

IntroductionLeetCode 1855 โ€“ Maximum Distance Between a Pair of Values is a classic problem that beautifully demonstrates the power of the Two Pointer technique on sorted (non-increasing) arrays.At first glance, it may feel like a brute-force problemโ€”but using the right observation, it can be solved efficiently in O(n) time.In this article, we will cover:Problem intuitionWhy brute force failsOptimized two-pointer approach (your solution)Alternative approachesTime complexity analysis๐Ÿ”— Problem LinkLeetCode: Maximum Distance Between a Pair of ValuesProblem StatementYou are given two non-increasing arrays:nums1nums2A pair (i, j) is valid if:i <= jnums1[i] <= nums2[j]๐Ÿ‘‰ Distance = j - iReturn the maximum distance among all valid pairs.ExamplesExample 1Input:nums1 = [55,30,5,4,2]nums2 = [100,20,10,10,5]Output:2Key InsightThe most important observation:Both arrays are NON-INCREASING๐Ÿ‘‰ This allows us to use two pointers efficiently instead of brute force.โŒ Naive Approach (Brute Force)IdeaTry all (i, j) pairsCheck conditionsTrack maximumComplexityTime: O(n ร— m) โŒ๐Ÿ‘‰ This will cause TLE for large inputs (up to 10โต)โœ… Optimized Approach: Two PointersIntuitionUse two pointers:i โ†’ nums1j โ†’ nums2We try to:Expand j as far as possibleMove i only when necessaryKey LogicIf nums1[i] <= nums2[j] โ†’ valid โ†’ increase jElse โ†’ move i forwardJava Codeclass Solution { public int maxDistance(int[] nums1, int[] nums2) { int i = 0; // pointer for nums1 int j = 0; // pointer for nums2 int max = Integer.MIN_VALUE; // Traverse both arrays while (i < nums1.length && j < nums2.length) { // Valid pair condition if (nums1[i] <= nums2[j] && i <= j) { // Update maximum distance max = Math.max(max, j - i); // Try to expand distance by moving j j++; } else if (nums1[i] >= nums2[j]) { // nums1[i] is too large โ†’ move i forward i++; } else { // Just move j forward j++; } } // If no valid pair found, return 0 return max == Integer.MIN_VALUE ? 0 : max; }}Step-by-Step Dry RunInput:nums1 = [55,30,5,4,2]nums2 = [100,20,10,10,5]Execution:(0,0) โ†’ valid โ†’ distance = 0Move j โ†’ (0,1) โ†’ invalid โ†’ move iContinue...Final best pair:(i = 2, j = 4) โ†’ distance = 2Why This WorksArrays are sorted (non-increasing)Moving j increases distanceMoving i helps find valid pairs๐Ÿ‘‰ No need to re-check previous elementsComplexity AnalysisTime ComplexityO(n + m)Each pointer moves at most once through the array.Space ComplexityO(1) (no extra space used)Alternative Approach: Binary SearchIdeaFor each i in nums1:Use binary search in nums2Find farthest j such that:nums2[j] >= nums1[i]ComplexityO(n log m)Code (Binary Search Approach)class Solution { public int maxDistance(int[] nums1, int[] nums2) { int max = 0; for (int i = 0; i < nums1.length; i++) { int left = i, right = nums2.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums2[mid] >= nums1[i]) { max = Math.max(max, mid - i); left = mid + 1; } else { right = mid - 1; } } } return max; }}Two Pointer vs Binary SearchApproachTimeSpaceTwo PointerO(n + m) โœ…O(1)Binary SearchO(n log m)O(1)๐Ÿ‘‰ Two pointer is optimal hereKey TakeawaysUse two pointers when arrays are sortedAlways look for monotonic propertiesAvoid brute force when constraints are largeGreedy pointer movement can optimize drasticallyCommon Interview PatternsThis problem is related to:Two pointer problemsSliding windowBinary search on arraysGreedy expansionConclusionThe Maximum Distance Between a Pair of Values problem is a great example of how recognizing array properties can drastically simplify the solution.By using the two-pointer technique, we reduce complexity from O(nยฒ) to O(n)โ€”a massive improvement.Frequently Asked Questions (FAQs)1. Why do we use two pointers?Because arrays are sorted, allowing linear traversal.2. Why not brute force?It is too slow for large inputs (10โต).3. What is the best approach?๐Ÿ‘‰ Two-pointer approach

MediumLeetCodeJavaArrayBinary SearchTwo Pointer
LeetCode 143 Reorder List - Java Solution Explained

LeetCode 143 Reorder List - Java Solution Explained

IntroductionLeetCode 143 Reorder List is one of those problems that looks simple when you read it but immediately makes you wonder โ€” where do I even start? There is no single trick that solves it. Instead it combines three separate linked list techniques into one clean solution. Mastering this problem means you have genuinely understood linked lists at an intermediate level.You can find the problem here โ€” LeetCode 143 Reorder List.This article walks through everything โ€” what the problem wants, the intuition behind each step, all three techniques used, a detailed dry run, complexity analysis, and common mistakes beginners make.What Is the Problem Really Asking?You have a linked list: L0 โ†’ L1 โ†’ L2 โ†’ ... โ†’ LnYou need to reorder it to: L0 โ†’ Ln โ†’ L1 โ†’ Ln-1 โ†’ L2 โ†’ Ln-2 โ†’ ...In plain English โ€” take one node from the front, then one from the back, then one from the front, then one from the back, and keep alternating until all nodes are used.Example:Input: 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5Output: 1 โ†’ 5 โ†’ 2 โ†’ 4 โ†’ 3Node 1 from front, Node 5 from back, Node 2 from front, Node 4 from back, Node 3 stays in middle.Real Life Analogy โ€” Dealing Cards From Both EndsImagine you have a deck of cards laid out in a line face up: 1, 2, 3, 4, 5. Now you deal them by alternately picking from the left end and the right end of the line:Pick 1 from left โ†’ placePick 5 from right โ†’ place after 1Pick 2 from left โ†’ place after 5Pick 4 from right โ†’ place after 2Pick 3 (only one left) โ†’ place after 4Result: 1, 5, 2, 4, 3That is exactly what the problem wants. The challenge is doing this efficiently on a singly linked list where you cannot just index from the back.Why This Problem Is Hard for BeginnersIn an array you can just use two pointers โ€” one at the start and one at the end โ€” and swap/interleave easily. But a singly linked list only goes forward. You cannot go backwards. You cannot easily access the last element.This is why the problem requires a three-step approach that cleverly works around the limitations of a singly linked list.The Three Step ApproachEvery experienced developer solves this problem in exactly three steps:Step 1 โ€” Find the middle of the linked list using the Fast & Slow Pointer techniqueStep 2 โ€” Reverse the second half of the linked listStep 3 โ€” Merge the two halves by alternating nodes from eachLet us understand each step deeply before looking at code.Step 1: Finding the Middle โ€” Fast & Slow PointerThe Fast & Slow Pointer technique (also called Floyd's algorithm) uses two pointers moving at different speeds through the list:slow moves one step at a timefast moves two steps at a timeWhen fast reaches the end, slow is exactly at the middle. This works because fast covers twice the distance of slow in the same number of steps.ListNode fast = head;ListNode slow = head;while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next;}// slow is now at the middleFor 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5:Start: slow=1, fast=1Step 1: slow=2, fast=3Step 2: slow=3, fast=5 (fast.next is null, stop)Middle is node 3For 1 โ†’ 2 โ†’ 3 โ†’ 4:Start: slow=1, fast=1Step 1: slow=2, fast=3Step 2: fast.next.next is null, stopslow=2, middle is node 2After finding the middle, we cut the list in two by setting slow.next = null. This disconnects the first half from the second half.Step 2: Reversing the Second HalfOnce we have the second half starting from slow.next, we reverse it. After reversal, what was the last node becomes the first โ€” giving us easy access to the back elements of the original list.public ListNode reverse(ListNode head) { ListNode curr = head; ListNode prev = null; while (curr != null) { ListNode next = curr.next; // save next curr.next = prev; // reverse the link prev = curr; // move prev forward curr = next; // move curr forward } return prev; // prev is the new head}For second half 3 โ†’ 4 โ†’ 5 (from the first example):Reverse โ†’ 5 โ†’ 4 โ†’ 3Now we have:First half: 1 โ†’ 2 โ†’ 3 (but 3 is the end since we cut at slow)Wait โ€” actually after cutting at slow=3: first half is 1 โ†’ 2 โ†’ 3, second half reversed is 5 โ†’ 4Let us be precise. For 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5, slow stops at 3. slow.next = null cuts to give:First half: 1 โ†’ 2 โ†’ 3 โ†’ nullSecond half before reverse: 4 โ†’ 5Second half after reverse: 5 โ†’ 4Step 3: Merging Two HalvesNow we have two lists and we merge them by alternately taking one node from each:Take from first half, take from second half, take from first half, take from second half...ListNode orig = head; // pointer for first halfListNode newhead = second; // pointer for reversed second halfwhile (newhead != null) { ListNode temp1 = orig.next; // save next of first half ListNode temp2 = newhead.next; // save next of second half orig.next = newhead; // first โ†’ second newhead.next = temp1; // second โ†’ next of first orig = temp1; // advance first half pointer newhead = temp2; // advance second half pointer}Why do we loop on newhead != null and not orig != null? Because the second half is always equal to or shorter than the first half (we cut at middle). Once the second half is exhausted, the remaining first half nodes are already in the correct position.Complete Solutionclass Solution { public ListNode reverse(ListNode head) { ListNode curr = head; ListNode prev = null; while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } return prev; } public void reorderList(ListNode head) { // Step 1: Find middle using fast & slow pointer ListNode fast = head; ListNode slow = head; while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; } // Step 2: Reverse second half ListNode newhead = reverse(slow.next); slow.next = null; // cut the list into two halves // Step 3: Merge two halves alternately ListNode orig = head; while (newhead != null) { ListNode temp1 = orig.next; ListNode temp2 = newhead.next; orig.next = newhead; newhead.next = temp1; orig = temp1; newhead = temp2; } }}Complete Dry Run โ€” head = [1, 2, 3, 4, 5]Step 1: Find MiddleList: 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5Initial: slow=1, fast=1Iteration 1: slow=2, fast=3Iteration 2: fast.next=4, fast.next.next=5 โ†’ slow=3, fast=5fast.next is null โ†’ stopslow is at node 3Step 2: Cut and ReverseCut: slow.next = nullFirst half: 1 โ†’ 2 โ†’ 3 โ†’ nullSecond half: 4 โ†’ 5Reverse second half 4 โ†’ 5:prev=null, curr=4 โ†’ next=5, 4.next=null, prev=4, curr=5prev=4, curr=5 โ†’ next=null, 5.next=4, prev=5, curr=nullReturn prev=5Reversed second half: 5 โ†’ 4 โ†’ nullStep 3: Mergeorig=1, newhead=5Iteration 1:temp1 = orig.next = 2temp2 = newhead.next = 4orig.next = newhead โ†’ 1.next = 5newhead.next = temp1 โ†’ 5.next = 2orig = temp1 = 2newhead = temp2 = 4List so far: 1 โ†’ 5 โ†’ 2 โ†’ 3Iteration 2:temp1 = orig.next = 3temp2 = newhead.next = nullorig.next = newhead โ†’ 2.next = 4newhead.next = temp1 โ†’ 4.next = 3orig = temp1 = 3newhead = temp2 = nullList so far: 1 โ†’ 5 โ†’ 2 โ†’ 4 โ†’ 3newhead is null โ†’ loop endsFinal result: 1 โ†’ 5 โ†’ 2 โ†’ 4 โ†’ 3 โœ…Dry Run โ€” head = [1, 2, 3, 4]Step 1: Find MiddleInitial: slow=1, fast=1Iteration 1: slow=2, fast=3fast.next=4, fast.next.next=null โ†’ stopslow is at node 2Step 2: Cut and ReverseFirst half: 1 โ†’ 2 โ†’ nullSecond half: 3 โ†’ 4Reversed: 4 โ†’ 3 โ†’ nullStep 3: Mergeorig=1, newhead=4Iteration 1:temp1=2, temp2=31.next=4, 4.next=2orig=2, newhead=3List: 1 โ†’ 4 โ†’ 2 โ†’ 3Iteration 2:temp1=null (2.next was originally 3 but we cut at slow=2, so 2.next = null... wait)Actually after cutting at slow=2, first half is 1 โ†’ 2 โ†’ null, so orig when it becomes 2, orig.next = null.temp1 = orig.next = nulltemp2 = newhead.next = null2.next = 3, 3.next = nullorig = null, newhead = nullnewhead is null โ†’ stopFinal result: 1 โ†’ 4 โ†’ 2 โ†’ 3 โœ…Why slow.next = null Must Come After Saving newheadThis is a subtle but critical ordering detail in the code. Look at this sequence:ListNode newhead = reverse(slow.next); // save reversed second half FIRSTslow.next = null; // THEN cut the listIf you cut first (slow.next = null) and then try to reverse, you lose the reference to the second half entirely because slow.next is already null. Always save the second half reference before cutting.Time and Space ComplexityTime Complexity: O(n) โ€” each of the three steps (find middle, reverse, merge) makes a single pass through the list. Total is 3 passes = O(3n) = O(n).Space Complexity: O(1) โ€” everything is done by rearranging pointers in place. No extra arrays, no recursion stack, no additional data structures. Just a handful of pointer variables.This is the optimal solution โ€” linear time and constant space.Alternative Approach โ€” Using ArrayList (Simpler but O(n) Space)If you find the three-step approach hard to implement under interview pressure, here is a simpler approach using extra space:public void reorderList(ListNode head) { // store all nodes in ArrayList for random access List<ListNode> nodes = new ArrayList<>(); ListNode curr = head; while (curr != null) { nodes.add(curr); curr = curr.next; } int left = 0; int right = nodes.size() - 1; while (left < right) { nodes.get(left).next = nodes.get(right); left++; if (left == right) break; // odd number of nodes nodes.get(right).next = nodes.get(left); right--; } nodes.get(left).next = null; // terminate the list}This is much easier to understand and code. Store all nodes in an ArrayList, use two pointers from both ends, and wire up the next pointers.Time Complexity: O(n) Space Complexity: O(n) โ€” ArrayList stores all nodesThis is acceptable in most interviews. Mention the O(1) space approach as the optimal solution if asked.Common Mistakes to AvoidNot cutting the list before merging If you do not set slow.next = null after finding the middle, the first half still points into the second half. During merging, this creates cycles and infinite loops. Always cut before merging.Wrong loop condition for finding the middle The condition fast.next != null && fast.next.next != null ensures fast does not go out of bounds when jumping two steps. Using just fast != null && fast.next != null moves slow one step too far for even-length lists.Looping on orig instead of newhead The merge loop should run while newhead != null, not while orig != null. The second half is always shorter or equal to the first half. Once the second half is done, the remaining first half is already correctly placed.Forgetting to save both temp pointers before rewiring In the merge step, you must save both orig.next and newhead.next before changing any pointers. Changing orig.next first and then trying to access orig.next to save it gives you the wrong node.How This Problem Combines Multiple PatternsThis problem is special because it does not rely on a single technique. It is a combination of three fundamental linked list operations:Fast & Slow Pointer โ€” you saw this concept in problems like finding the middle of a list and detecting cycles (LeetCode 141, 142).Reverse a Linked List โ€” the most fundamental linked list operation, appears in LeetCode 206 and as a subtask in dozens of problems.Merge Two Lists โ€” similar to merging two sorted lists (LeetCode 21) but here order is not sorted, it is alternating.Solving this problem proves you are comfortable with all three patterns individually and can combine them when needed.FAQs โ€” People Also AskQ1. What is the most efficient approach for LeetCode 143 Reorder List? The three-step approach โ€” find middle with fast/slow pointer, reverse second half, merge alternately โ€” runs in O(n) time and O(1) space. It is the optimal solution. The ArrayList approach is O(n) time and O(n) space but simpler to code.Q2. Why use fast and slow pointer to find the middle? Because a singly linked list has no way to access elements by index. You cannot just do list[length/2]. The fast and slow pointer technique finds the middle in a single pass without knowing the length beforehand.Q3. Why reverse the second half instead of the first half? The problem wants front-to-back alternation. If you reverse the second half, its first node is the original last node โ€” exactly what you need to interleave with the front of the first half. Reversing the first half would give the wrong order.Q4. What is the time complexity of LeetCode 143? O(n) time for three linear passes (find middle, reverse, merge). O(1) space since all operations are in-place pointer manipulations with no extra data structures.Q5. Is LeetCode 143 asked in coding interviews? Yes, frequently at companies like Amazon, Google, Facebook, and Microsoft. It is considered a benchmark problem for linked list mastery because it requires combining three separate techniques cleanly under pressure.Similar LeetCode Problems to Practice Next206. Reverse Linked List โ€” Easy โ€” foundation for step 2 of this problem876. Middle of the Linked List โ€” Easy โ€” fast & slow pointer isolated21. Merge Two Sorted Lists โ€” Easy โ€” merging technique foundation234. Palindrome Linked List โ€” Easy โ€” also uses find middle + reverse second half148. Sort List โ€” Medium โ€” merge sort on linked list, uses same split techniqueConclusionLeetCode 143 Reorder List is one of the best Medium linked list problems because it forces you to think in multiple steps and combine techniques rather than apply a single pattern. The fast/slow pointer finds the middle efficiently without knowing the length. Reversing the second half turns the "cannot go backwards" limitation of singly linked lists into a non-issue. And the alternating merge weaves everything together cleanly.Work through the dry runs carefully โ€” especially the pointer saving step in the merge. Once you see why each step is necessary and how they connect, this problem will always feel approachable no matter when it shows up in an interview.

LeetCodeJavaLinked ListTwo PointerFast Slow PointerMedium
Remove Nth Node From End โ€“ The Smart Way to Solve in One Pass (LeetCode 19)

Remove Nth Node From End โ€“ The Smart Way to Solve in One Pass (LeetCode 19)

๐Ÿš€ Try the ProblemPractice here:https://leetcode.com/problems/remove-nth-node-from-end-of-list/๐Ÿค” Letโ€™s Think Differentlyโ€ฆImagine this list:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5You are asked:๐Ÿ‘‰ Remove the 2nd node from the endSo counting from end:5 (1st), 4 (2nd) โŒ remove thisFinal list:1 โ†’ 2 โ†’ 3 โ†’ 5๐Ÿง  Problem in Simple WordsYou are given:Head of a linked listA number n๐Ÿ‘‰ Remove the nth node from the end๐Ÿ‘‰ Return the updated list๐Ÿ“ฆ Constraints1 <= number of nodes <= 300 <= Node.val <= 1001 <= n <= size of list๐Ÿงฉ First Thought (Counting Method)๐Ÿ’ก IdeaCount total nodesFind position from start:position = total - nTraverse again and remove that nodeโœ… Code (Counting Approach)class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { if(head == null) return head; // Step 1: Count nodes int co = 0; ListNode tempHead = head; while(tempHead != null){ co++; tempHead = tempHead.next; } // Step 2: If removing head if(co == n) return head.next; // Step 3: Find node before target int k = co - n; int con = 1; ListNode temp = head; while(con < k){ temp = temp.next; con++; } // Step 4: Remove node temp.next = temp.next.next; return head; }}โฑ๏ธ ComplexityTime ComplexityO(n) + O(n) = O(n)(two traversals)Space ComplexityO(1)โš ๏ธ Limitation of This Approach๐Ÿ‘‰ It requires two passesBut the problem asks:Can you solve it in one pass?๐Ÿš€ Optimal Approach: Two Pointer Technique (One Pass)Now comes the interesting part ๐Ÿ”ฅ๐Ÿง  Core IdeaWe use two pointers:fast pointerslow pointer๐ŸŽฏ Trick๐Ÿ‘‰ Move fast pointer n steps aheadThen move both pointers together until:fast reaches endAt that moment:๐Ÿ‘‰ slow will be at the node before the one to remove๐Ÿ“Œ Why This WorksBecause the gap between fast and slow is always n nodesSo when fast reaches end:๐Ÿ‘‰ slow is exactly where we need it๐Ÿ”ฅ Step-by-Step VisualizationList:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5n = 2Step 1: Move fast 2 stepsfast โ†’ 3slow โ†’ 1Step 2: Move both togetherfast โ†’ 4, slow โ†’ 2fast โ†’ 5, slow โ†’ 3fast โ†’ null, slow โ†’ 4๐Ÿ‘‰ Now slow is at node before target๐Ÿงผ Clean and Safe Approach (Using Dummy Node)Using dummy node avoids edge cases like removing head.๐Ÿ’ป Code (Optimal One Pass Solution)class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // Dummy node to handle edge cases ListNode dummy = new ListNode(0, head); ListNode fast = dummy; ListNode slow = dummy; // Move fast pointer n steps ahead for(int i = 0; i < n; i++){ fast = fast.next; } // Move both pointers while(fast.next != null){ fast = fast.next; slow = slow.next; } // Remove nth node slow.next = slow.next.next; return dummy.next; }}โฑ๏ธ ComplexityTime ComplexityO(n)(single pass)Space ComplexityO(1)โš–๏ธ Comparing ApproachesApproachPassesTimeSpaceDifficultyCounting2O(n)O(1)EasyTwo Pointer1O(n)O(1)OptimalโŒ Common MistakesForgetting to handle removing head nodeNot using dummy nodeOff-by-one errors in pointer movementMoving fast incorrectly๐Ÿ”ฅ Interview InsightThis problem is a classic example of:Fast & Slow Pointer TechniqueUsed in many problems like:Cycle DetectionMiddle of Linked ListPalindrome Linked List๐Ÿง  Final ThoughtAt first, counting feels naturalโ€ฆBut once you learn this trick:"Create a gap and move together"๐Ÿ‘‰ You unlock a powerful pattern.๐Ÿš€ ConclusionThe Remove Nth Node From End problem is not just about deletionโ€ฆIt teaches:Efficient traversalPointer coordinationOne-pass optimization๐Ÿ‘‰ Tip: Whenever you see โ€œfrom endโ€, think:"Can I use two pointers with a gap?"Thatโ€™s your shortcut to solving these problems like a pro ๐Ÿš€

Linked ListTwo PointersFast & Slow PointerOne Pass AlgorithmLeetCodeMedium
Reverse Only Letters โ€“ Two Pointer Strategy Explained (LeetCode 917)

Reverse Only Letters โ€“ Two Pointer Strategy Explained (LeetCode 917)

๐Ÿ”— Problem LinkLeetCode 917 โ€“ Reverse Only Letters ๐Ÿ‘‰ https://leetcode.com/problems/reverse-only-letters/IntroductionThis is a very clean two-pointer problem.The challenge is not just reversing a string โ€” itโ€™s reversing only the letters while keeping all non-letter characters in their original positions.This problem strengthens:Two pointer techniqueCharacter validation logicIn-place string manipulationLetโ€™s break it down.๐Ÿ“Œ Problem UnderstandingYou are given a string s.Rules:All non-English letters must remain at the same index.Only English letters (uppercase or lowercase) should be reversed.Return the modified string.Example 1Input: "ab-cd"Output: "dc-ba"Only letters are reversed. Hyphen stays at same position.Example 2Input: "a-bC-dEf-ghIj"Output: "j-Ih-gfE-dCba"Example 3Input: "Test1ng-Leet=code-Q!"Output: "Qedo1ct-eeLg=ntse-T!"Notice:Numbers, -, =, ! stay fixed.Only letters move.๐Ÿง  IntuitionThe first thought might be:Extract all lettersReverse themPut them backBut that would require extra space.Instead, we can solve this efficiently using Two Pointers.๐Ÿš€ Two Pointer ApproachWe use:i โ†’ starting from leftj โ†’ starting from rightSteps:Convert string into char array.Move i forward until it points to a letter.Move j backward until it points to a letter.Swap letters.Continue until i < j.๐Ÿ’ป Your Codeclass Solution { public String reverseOnlyLetters(String s) { int i = 0; int j = s.length() - 1; char arr[] = s.toCharArray(); while(i < j){ boolean l = ('A' <= s.charAt(i) && s.charAt(i) <= 'Z') || ('a' <= s.charAt(i) && s.charAt(i) <= 'z'); boolean r = ('A' <= s.charAt(j) && s.charAt(j) <= 'Z') || ('a' <= s.charAt(j) && s.charAt(j) <= 'z'); if(l && r){ char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; }else if(l){ j--; }else if(r){ i++; }else{ i++; j--; } } return new String(arr); }}๐Ÿ” Step-by-Step Explanation1๏ธโƒฃ Convert to Character Arraychar arr[] = s.toCharArray();Strings are immutable in Java. So we convert to char array to modify it.2๏ธโƒฃ Initialize Two Pointersint i = 0;int j = s.length() - 1;We start from both ends.3๏ธโƒฃ Check If Characters Are Lettersboolean l = ('A' <= s.charAt(i) && s.charAt(i) <= 'Z') || ('a' <= s.charAt(i) && s.charAt(i) <= 'z');You manually check ASCII ranges for uppercase and lowercase letters.Same logic for r.4๏ธโƒฃ Swap When Both Are Lettersif(l && r){ char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--;}Only swap letters.5๏ธโƒฃ Move Pointers When One Is Not LetterIf left is letter but right is not โ†’ move jIf right is letter but left is not โ†’ move iIf both are not letters โ†’ move bothThis ensures:Non-letter characters remain in their positions.๐ŸŽฏ Why This WorksWe never move non-letter characters.We only swap valid letters.Since we use two pointers:Time complexity stays linear.No extra array needed for letters.โฑ Complexity AnalysisTime Complexity: O(n)Each character is visited at most once.Space Complexity: O(n)Because we convert string to char array.(If considering output string creation, still O(n))๐Ÿ”ฅ Cleaner OptimizationInstead of manually checking ASCII ranges, Java provides:Character.isLetter(c)Cleaner version:class Solution { public String reverseOnlyLetters(String s) { int i = 0, j = s.length() - 1; char[] arr = s.toCharArray(); while(i < j){ if(!Character.isLetter(arr[i])){ i++; }else if(!Character.isLetter(arr[j])){ j--; }else{ char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } return new String(arr); }}Much cleaner and readable.๐Ÿ Final ThoughtsThis problem teaches:Two pointer patternIn-place string reversalCharacter validation logicClean pointer movement strategyItโ€™s a simple but powerful pattern that appears often in interviews.If you master this, you can easily solve:Reverse Vowels of a StringValid PalindromeReverse String IIPalindrome with One Removal

Two PointersString ManipulationLeetCodeEasy
Fast and Slow Pointer Technique in Linked List: Cycle Detection, Proof, and Complete Explanation

Fast and Slow Pointer Technique in Linked List: Cycle Detection, Proof, and Complete Explanation

๐Ÿš€ Before We StartTry these problems (optional but helpful):https://leetcode.com/problems/linked-list-cycle/https://leetcode.com/problems/linked-list-cycle-ii/๐Ÿค” Letโ€™s Talk Honestlyโ€ฆWhen you first learn this technique, youโ€™re told:๐Ÿ‘‰ โ€œSlow moves 1 step, fast moves 2 stepsโ€๐Ÿ‘‰ โ€œIf they meet โ†’ cycle existsโ€But your brain asks:โ“ Why 2 steps?โ“ Why do they meet at all?โ“ Why does resetting pointer find cycle start?โ“ Is this magic or logic?๐Ÿ‘‰ Letโ€™s answer each doubt one by one.๐Ÿงฉ Doubt 1: Why do we even use two pointers?โ“ Question:Why not just use one pointer?โœ… Answer:With one pointer:You can only move forwardYou cannot detect loops efficiently๐Ÿ‘‰ Two pointers create a relative motionThat relative motion is the key.๐Ÿงฉ Doubt 2: Why fast = 2 steps and slow = 1 step?โ“ Question:Why exactly 2 and 1?โœ… Answer:We need:Fast speed > Slow speedSo that:๐Ÿ‘‰ Fast catches up to slow๐Ÿง  Think like this:If both move same speed:Slow โ†’ 1 stepFast โ†’ 1 step๐Ÿ‘‰ They will NEVER meet โŒIf:Slow โ†’ 1 stepFast โ†’ 2 steps๐Ÿ‘‰ Fast gains 1 node every step๐Ÿ”ฅ Key Insight:Relative speed = fast - slow = 1๐Ÿ‘‰ This means fast is closing the gap by 1 node every step๐Ÿงฉ Doubt 3: Why do they ALWAYS meet in a cycle?โ“ Question:Okay, fast is fasterโ€ฆ but why guaranteed meeting?๐Ÿง  Imagine a Circular TrackInside a cycle, the list behaves like:Circle of length = ฮปNow:Slow moves 1 stepFast moves 2 steps๐Ÿ”„ Gap BehaviorEach step:Gap = Gap - 1Because fast is catching up.Eventually:Gap = 0๐Ÿ‘‰ They meet ๐ŸŽฏ๐Ÿ’ก Simple AnalogyTwo runners on a circular track:One is fasterOne is slower๐Ÿ‘‰ Faster runner will lap and meet slower runner๐Ÿงฉ Doubt 4: What if there is NO cycle?โ“ Question:Why does this fail without cycle?โœ… Answer:If no cycle:List ends โ†’ fast reaches null๐Ÿ‘‰ No loop โ†’ no meeting๐Ÿงฉ Doubt 5: Where do they meet?โ“ Question:Do they meet at cycle start?โŒ Answer:No, not necessarily.They meet somewhere inside the cycle๐Ÿงฉ Doubt 6: Then how do we find the cycle start?Now comes the most important part.๐ŸŽฏ SetupLetโ€™s define:a = distance from head to cycle startb = distance from cycle start to meeting pointc = remaining cycleCycle length:ฮป = b + c๐Ÿง  What happens when they meet?Slow distance:a + bFast distance:2(a + b)Using relation:2(a + b) = a + b + kฮปSolve:a + b = kฮป=> a = kฮป - b=> a = (k-1)ฮป + (ฮป - b)๐Ÿ’ฅ Final Meaninga = distance from meeting point to cycle start๐Ÿ”ฅ BIG CONCLUSION๐Ÿ‘‰ Distance from head โ†’ cycle start๐Ÿ‘‰ = Distance from meeting point โ†’ cycle start๐Ÿงฉ Doubt 7: Why resetting pointer works?โ“ Question:Why move one pointer to head?โœ… Answer:Because:One pointer is a away from startOther is also a away (via cycle)๐Ÿ‘‰ Move both 1 step:They meet at:Cycle Start ๐ŸŽฏ๐Ÿ”„ VisualizationHead โ†’ โ†’ โ†’ Cycle Start โ†’ โ†’ Meeting Point โ†’ โ†’ back to StartBoth pointers:One from headOne from meeting point๐Ÿ‘‰ Same distance โ†’ meet at start๐Ÿงฉ Doubt 8: Can we use fast = 3 steps?โ“ Question:Will this work?โœ… Answer:Yes, BUT:Math becomes complexHarder to reasonNo extra benefit๐Ÿ‘‰ So we use simplest:2 : 1 ratio๐Ÿง  Final Mental ModelThink in 3 steps:1. Different SpeedsFast moves faster โ†’ gap reduces2. Circular StructureCycle โ†’ positions repeat3. Guaranteed MeetingFinite positions + relative motion โ†’ collision๐Ÿงฉ TEMPLATE 1: Detect CycleListNode slow = head;ListNode fast = head;while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ return true; }}return false;๐Ÿงฉ TEMPLATE 2: Find Cycle StartListNode slow = head;ListNode fast = head;while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ slow = head; while(slow != fast){ slow = slow.next; fast = fast.next; } return slow; }}return null;๐Ÿงฉ TEMPLATE 3: Find Middle of Linked Listโ“ ProblemFind the middle node of a linked list.๐Ÿง  IntuitionFast moves twice as fast:When fast reaches end โ†’ slow reaches half๐Ÿ‘‰ Slow = middle๐Ÿ’ป CodeListNode slow = head;ListNode fast = head;while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next;}return slow;โš ๏ธ Even Length Case1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ 6๐Ÿ‘‰ Returns 4 (second middle)โ“ How to Get First Middle?while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next;}return slow;๐Ÿงฉ Where Else This Technique Is Used?Detect cycleFind cycle startFind middle nodeCheck palindrome (linked list)Split list (merge sort)Intersection problemsโš™๏ธ Time & Space ComplexityTime: O(n)Space: O(1)โŒ Common MistakesForgetting fast.next != nullThinking meeting point = cycle start โŒNot resetting pointer properly๐Ÿง  Final Mental ModelThink in 3 steps:1. Speed DifferenceFast moves faster โ†’ gap reduces2. Circular NatureCycle โ†’ repeated positions3. Guaranteed MeetingFinite nodes + relative motion โ†’ collision๐Ÿ”ฅ One Line to RememberFast catches slow because it reduces the gap inside a loop.๐Ÿš€ ConclusionNow you understand:โœ… Why fast moves fasterโœ… Why they meetโœ… Why meeting proves cycleโœ… Why reset gives cycle startโœ… How to find middle using same logic๐Ÿ‘‰ This is not just a trickโ€ฆItโ€™s a mathematical guarantee based on motion and cycles.๐Ÿ’ก Master this once, and youโ€™ll solve multiple linked list problems easily.

Linked ListFast & Slow PointerTwo Pointer TechniqueFloyd AlgorithmDSA PatternsDeep Intuition
LeetCode 2095. Delete the Middle Node of a Linked List โ€“ Fast and Slow Pointer Approach

LeetCode 2095. Delete the Middle Node of a Linked List โ€“ Fast and Slow Pointer Approach

๐Ÿ”— Try This Problemhttps://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/๐ŸŽฅ Video ExplanationProblem ExplanationYou are given the head of a singly linked list. The task is to remove the middle node and return the updated list.The middle node is defined using 0-based indexing:Middle Index = โŒŠn / 2โŒ‹Where n is the total number of nodes.ExampleInput: [1, 3, 4, 7, 1, 2, 6]Index: 0 1 2 3 4 5 6n = 7Middle index = 3Node to remove = 7Output: [1, 3, 4, 1, 2, 6]Approach 1: Brute Force (Two Traversals)IdeaTraverse the list to count total nodesCompute middle indexTraverse again to delete that nodeComplexityTime Complexity: O(n)Space Complexity: O(1)LimitationThis approach requires two passes, which is not optimal when a single traversal solution exists.Approach 2: Fast and Slow Pointer (Optimal)IntuitionUse two pointers:Slow pointer moves one step at a timeFast pointer moves two steps at a timeWhen the fast pointer reaches the end of the list:Slow pointer will be at the middle nodeImportant DetailTo remove the middle node, access to the previous node is required.Therefore, maintain an additional pointer:prev โ†’ tracks node before slowAlgorithm StepsInitialize:slow = headfast = headprev = nullTraverse:Move fast by 2 stepsMove slow by 1 stepUpdate prev = slow (before moving slow)When loop ends:slow points to middle nodeprev points to node before middleDelete node:prev.next = slow.nextDetailed Dry RunInput1 โ†’ 3 โ†’ 4 โ†’ 7 โ†’ 1 โ†’ 2 โ†’ 6Initial Stateslow = 1fast = 1prev = nullIteration 1fast โ†’ 4slow โ†’ 3prev โ†’ 1Iteration 2fast โ†’ 1slow โ†’ 4prev โ†’ 3Iteration 3fast โ†’ nullslow โ†’ 7prev โ†’ 4After Loopslow = 7 (middle node)prev = 4Deletionprev.next = slow.nextResult:1 โ†’ 3 โ†’ 4 โ†’ 1 โ†’ 2 โ†’ 6Optimized Code (Java)class Solution {public ListNode deleteMiddle(ListNode head) {// Edge case: single nodeif(head == null) return head;if(head.next == null) return null;ListNode slow = head;ListNode fast = head;ListNode prev = null;// Traverse using fast and slow pointerwhile(fast != null && fast.next != null){fast = fast.next.next;prev = slow;slow = slow.next;}// Remove middle nodeprev.next = slow.next;return head;}}Complexity AnalysisTime Complexity: O(n)Space Complexity: O(1)Only one traversal of the linked listNo extra data structures usedEdge CasesSingle NodeInput: [1]Output: []Two NodesInput: [2, 1]Output: [2]Even Length ListInput: [1, 2, 3, 4]n = 4Middle index = 2Node removed = 3Key TakeawaysFast and slow pointer reduces two-pass solution to one-passTracking the previous node is necessary for deletionWorks efficiently for large linked listsA fundamental pattern used in multiple linked list problemsConclusionThe fast and slow pointer technique provides an elegant and efficient way to identify and remove the middle node in a linked list. By leveraging different traversal speeds, the problem can be solved in a single pass with constant space, making it optimal for both interviews and practical implementations.

LeetCodeMediumLinked ListFast and Slow Pointer
LeetCode 234: Palindrome Linked List (Java) | Intuition, Dry Run & O(1) Space Solution

LeetCode 234: Palindrome Linked List (Java) | Intuition, Dry Run & O(1) Space Solution

๐Ÿงฉ Problem OverviewGiven the head of a singly linked list, determine whether it is a palindrome.A palindrome means the sequence reads the same forward and backward.Example:Input: [1,2,2,1] โ†’ Output: trueInput: [1,2] โ†’ Output: false๐ŸŽฏ Why This Problem MattersThis problem tests:Linked list traversalTwo-pointer technique (slow & fast)In-place reversalItโ€™s a must-know pattern for interviews.๐Ÿง  IntuitionA simple approach is to copy elements into an array and check for palindrome.But that uses extra space O(n).Optimal Idea:We can solve this in O(1) space by:Finding the middle of the linked listReversing the second halfComparing both halves๐ŸŽฅ Dry Run (Step-by-Step Explanation)๐Ÿ‘‰ Watch the complete dry run and pointer movement below:This video explains how slow and fast pointers work and how the comparison is performed.โš™๏ธ ApproachStep 1: Handle Edge CasesIf list has one node โ†’ return trueStep 2: Find MiddleUse slow and fast pointersFast moves 2 steps, slow moves 1Step 3: Reverse Second HalfReverse the list starting from the middleStep 4: Compare Both HalvesCompare values from:start of liststart of reversed half๐Ÿ’ป Java Solutionclass Solution {public ListNode reverse(ListNode head) {ListNode curr = head;ListNode prev = null;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;}public boolean isPalindrome(ListNode head) {if (head == null) return false;if (head.next == null) return true;ListNode slow = head;ListNode fast = head;// Find middlewhile (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;}// Reverse second halfListNode secondHalf = reverse(slow);// Compare both halvesListNode firstHalf = head;while (secondHalf != null) {if (secondHalf.val != firstHalf.val) {return false;}secondHalf = secondHalf.next;firstHalf = firstHalf.next;}return true;}}๐Ÿ” Dry Run (Quick Summary)Example:1 โ†’ 2 โ†’ 2 โ†’ 1Middle foundSecond half reversedCompare values one by oneResult โ†’ Palindrome โœ…โฑ๏ธ Time and Space ComplexityTime Complexity: O(n)Space Complexity: O(1)โš ๏ธ Edge CasesSingle nodeTwo nodesOdd length listEven length list๐Ÿ’ก Key TakeawaysFast & slow pointer technique is essentialReversing linked list is a reusable patternHelps optimize space complexity๐Ÿš€ Final ThoughtsThis is a classic interview problem combining multiple linked list techniques.Make sure you understand:Pointer movementReversal logicComparison step๐Ÿ‘‰ For full clarity, donโ€™t skip the video explanation above.

LinkedListFast and Slow PointerPalindromeEasy
LeetCode 328: Odd Even Linked List โ€“ Clean and Easy Explanation with Multiple Approaches

LeetCode 328: Odd Even Linked List โ€“ Clean and Easy Explanation with Multiple Approaches

Try the ProblemYou can practice the problem here:https://leetcode.com/problems/odd-even-linked-list/Problem Description (In Very Simple Words)You are given the head of a linked list.Your task is:๐Ÿ‘‰ Rearrange the list such that:All nodes at odd positions come firstFollowed by all nodes at even positionsImportant Clarificationโ— This problem is about positions (indices), NOT values.1st node โ†’ Odd2nd node โ†’ Even3rd node โ†’ Odd4th node โ†’ EvenExample WalkthroughExample 1Input:[1,2,3,4,5]Positions:1(odd), 2(even), 3(odd), 4(even), 5(odd)Output:[1,3,5,2,4]Example 2Input:[2,1,3,5,6,4,7]Output:[2,3,6,7,1,5,4]Constraints0 <= number of nodes <= 10^4-10^6 <= Node.val <= 10^6Core Idea of the ProblemWe need to:Separate nodes into two groups:Odd index nodesEven index nodesMaintain their original orderFinally:๐Ÿ‘‰ Attach even list after odd listThinking About Different ApproachesWhen solving this problem, multiple approaches may come to mind:Approach 1: Create New ListsIdeaTraverse the listCreate new nodes for odd and even positionsBuild two separate listsMerge themProblem with This ApproachโŒ Uses extra spaceโŒ Not optimal (violates O(1) space requirement)โŒ Code becomes messy and harder to maintainYour approach is logically correct, but:๐Ÿ‘‰ It is not optimal and can be simplified a lot.Approach 2: Brute Force Using Array/ListIdeaStore nodes in an arrayRearrange based on indicesRebuild linked listComplexityTime: O(n)Space: O(n) โŒ (Not allowed)Approach 3: Optimal In-Place Approach (Best Solution)This is the cleanest and most important approach.Optimal Approach: Two Pointer Technique (In-Place)IdeaInstead of creating new nodes:๐Ÿ‘‰ We rearrange the existing nodesWe maintain:odd โ†’ points to odd nodeseven โ†’ points to even nodesevenHead โ†’ stores start of even listVisualizationInput:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5We separate into:Odd: 1 โ†’ 3 โ†’ 5Even: 2 โ†’ 4Final:1 โ†’ 3 โ†’ 5 โ†’ 2 โ†’ 4Step-by-Step LogicStep 1: Initialize pointersodd = headeven = head.nextevenHead = head.nextStep 2: Traverse the listWhile:even != null && even.next != nullUpdate:odd.next = odd.next.nexteven.next = even.next.nextMove pointers forward:odd = odd.nexteven = even.nextStep 3: Mergeodd.next = evenHeadClean Java Implementation (Optimal)class Solution { public ListNode oddEvenList(ListNode head) { // Edge case if(head == null || head.next == null) return head; // Initialize pointers ListNode odd = head; ListNode even = head.next; ListNode evenHead = even; // Rearranging nodes while(even != null && even.next != null){ odd.next = odd.next.next; // Link next odd even.next = even.next.next; // Link next even odd = odd.next; even = even.next; } // Attach even list after odd list odd.next = evenHead; return head; }}Dry Run (Important)Input:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5Steps:Iteration 1:odd โ†’ 1, even โ†’ 2Link:1 โ†’ 32 โ†’ 4Iteration 2:odd โ†’ 3, even โ†’ 4Link:3 โ†’ 54 โ†’ nullFinal connection:5 โ†’ 2Result:1 โ†’ 3 โ†’ 5 โ†’ 2 โ†’ 4Time ComplexityO(n)We traverse the list once.Space ComplexityO(1)No extra space used.Why This Approach is BestFeatureResultExtra SpaceโŒ NoneClean Codeโœ… YesEfficientโœ… O(n)Interview Friendlyโœ… HighlyCommon MistakesโŒ Confusing values with positionsโŒ Creating new nodes unnecessarilyโŒ Forgetting to connect even list at the endโŒ Breaking the list accidentallyKey LearningThis problem teaches:In-place linked list manipulationPointer handlingList partitioningFinal ThoughtsThe Odd Even Linked List problem is a classic example of how powerful pointer manipulation can be.Even though creating new nodes might seem easier at first, the in-place approach is:๐Ÿ‘‰ Faster๐Ÿ‘‰ Cleaner๐Ÿ‘‰ Interview optimized๐Ÿ‘‰ Tip: Whenever you are asked to rearrange a linked list, always think:"Can I solve this by just changing pointers instead of creating new nodes?"Thatโ€™s the key to mastering linked list problems ๐Ÿš€

Linked ListPointer ManipulationIn-Place AlgorithmTwo PointersLeetCode Medium
Maximum Twin Sum of a Linked List (LeetCode 2130) โ€” Intuition, Dry Run & Optimal Approach

Maximum Twin Sum of a Linked List (LeetCode 2130) โ€” Intuition, Dry Run & Optimal Approach

๐Ÿ”— Try This ProblemPractice here:https://leetcode.com/problems/maximum-twin-sum-of-a-linked-list/๐Ÿ“Œ Problem OverviewYou are given a linked list of even length.Each node has a twin node defined as:Node at index i โ†’ Twin is at index (n - 1 - i)๐Ÿ‘‰ Example (n = 4):Index 0 โ†” Index 3Index 1 โ†” Index 2The twin sum is:node[i] + node[n - 1 - i]๐ŸŽฏ Goal:Return the maximum twin sum among all such pairs.๐Ÿง  Understanding the ProblemLetโ€™s take a simple example:Input: [4, 2, 2, 3]Pairs:4 + 3 = 72 + 2 = 4๐Ÿ‘‰ So the answer is:7๐Ÿ’ก Intuition โ€” How to Think About This ProblemAt first glance, it feels like we need to access:First nodeLast nodeSecond nodeSecond last node๐Ÿ‘‰ This naturally suggests:Two-directional accessBut linked lists donโ€™t allow backward traversal๐Ÿค” Possible Thinking Patterns๐Ÿ”น Idea 1: Use Extra SpaceStore values in an arrayUse two pointers from both endsโœ” WorksโŒ But uses extra space O(n)๐Ÿ”น Idea 2: Work Directly on Linked List (Better)We need a way to:Reach the middleAccess second half in reverse order๐Ÿ‘‰ That leads to a powerful idea:โ€œWhat if we reverse the second half of the list?โ€Now we can compare:First half (forward)Second half (also forward after reversal)๐ŸŽฅ Visual Intuition (Your Explanation Video)๐Ÿ‘‰ In this section, focus only on:ConceptVisualizationThought processโŒ Avoid code hereโœ” Build understandingโš™๏ธ Optimized Approach (Step-by-Step)Step 1: Find MiddleUse two pointers:slow โ†’ moves 1 stepfast โ†’ moves 2 steps๐Ÿ‘‰ When fast reaches end โ†’ slow is at middleStep 2: Reverse Second HalfStart from:slow.nextReverse the listStep 3: Compare Twin PairsNow:One pointer โ†’ start of listOne pointer โ†’ start of reversed halfCalculate:sum = left.val + right.valTrack maximum๐Ÿงช Dry RunExample:Input: [5, 4, 2, 1]Step 1: Find MiddleSlow โ†’ 4Fast โ†’ EndStep 2: Reverse Second HalfSecond half: 2 โ†’ 1Reversed: 1 โ†’ 2Step 3: Compare5 + 1 = 64 + 2 = 6โœ… Maximum = 6๐Ÿ’ป Optimized Code (Java)class Solution {public ListNode reverse(ListNode head){ListNode curr = head;ListNode prev = null;while(curr != null){ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;}public int pairSum(ListNode head) {ListNode slow = head;ListNode fast = head;// Find middlewhile(fast.next != null && fast.next.next != null){slow = slow.next;fast = fast.next.next;}// Reverse second halfListNode secondHalf = reverse(slow.next);// Compare pairsListNode firstHalf = head;int max = Integer.MIN_VALUE;while(secondHalf != null){int sum = firstHalf.val + secondHalf.val;max = Math.max(max, sum);firstHalf = firstHalf.next;secondHalf = secondHalf.next;}return max;}}โฑ๏ธ Complexity AnalysisTypeValueTime ComplexityO(n)Space ComplexityO(1)๐Ÿงฉ Key TakeawaysLinked list problems often require restructuring instead of extra spaceReversing half is a powerful trickFast & slow pointer is essential for mid-finding๐Ÿ Final ThoughtsThis problem is a perfect example of:Combining multiple conceptsOptimizing spaceThinking beyond direct access๐Ÿ‘‰ Once you understand this pattern, many linked list problems become easier.

LeetcodeMediumLinkedListFast and Slow Pointer
LeetCode 123 โ€” Best Time to Buy and Sell Stock III | At Most Two Transactions Explained

LeetCode 123 โ€” Best Time to Buy and Sell Stock III | At Most Two Transactions Explained

๐Ÿš€ Try This Problem First!Before reading the solution, attempt it yourself on LeetCode โ€” you will learn much more that way.๐Ÿ”— Problem Link: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/What Is the Problem Asking?You have a list of stock prices, one for each day. You are allowed to buy and sell the stock, but with one important restriction โ€” you can make at most two transactions in total. A transaction means one buy followed by one sell.You cannot hold two stocks at the same time, meaning you must sell whatever you are holding before you buy again.Your job is to find the maximum profit you can make by doing at most two transactions. If there is no way to make any profit, return 0.Example to build intuition: prices = [3, 3, 5, 0, 0, 3, 1, 4]If you buy on day 4 (price 0) and sell on day 6 (price 3), profit = 3. Then buy on day 7 (price 1) and sell on day 8 (price 4), profit = 3. Total profit = 6. That is the best you can do.Constraints:1 โ‰ค prices.length โ‰ค 10โต0 โ‰ค prices[i] โ‰ค 10โตWhy Is This Harder Than the Previous Stock Problems?In LeetCode 121, you could only make one transaction โ€” so you just found the best single buy-sell pair.In LeetCode 122, you could make unlimited transactions โ€” so you greedily collected every upward price movement.Here, you are limited to exactly two transactions. Greedy does not work anymore because sometimes skipping a small profit early on allows you to make a much bigger combined profit across two trades. You need a smarter strategy.The Big Idea Behind the SolutionHere is a simple way to think about it.If you are allowed two transactions, you can imagine drawing a dividing line somewhere in the price array. The first transaction happens entirely on the left side of that line. The second transaction happens entirely on the right side.So the problem becomes:For every possible dividing position, find the best profit on the left side and the best profit on the right side.Add them together.The maximum sum across all dividing positions is your answer.The challenge is doing this efficiently without checking every position with a nested loop, which would be O(Nยฒ) and too slow.Approach 1 โ€” Left-Right Prefix DPThe idea:Instead of trying every split point from scratch, precompute the answers in advance.Build a leftProfit array where leftProfit[i] stores the best single transaction profit you can make using prices from day 0 to day i.Build a rightProfit array where rightProfit[i] stores the best single transaction profit you can make using prices from day i to the last day.Then for every index i, the answer if you split at i is simply leftProfit[i] + rightProfit[i]. Take the maximum across all indices.How to build leftProfit:Go left to right. Keep track of the minimum price seen so far. At each day i, the best profit you could make ending at or before day i is either the same as the day before, or selling today at today's price minus the minimum price seen so far. Take whichever is larger.How to build rightProfit:Go right to left. Keep track of the maximum price seen so far from the right. At each day i, the best profit you could make starting at or after day i is either the same as the day after, or buying today at today's price and selling at the maximum price seen to the right. Take whichever is larger.Dry Run โ€” prices = [3, 3, 5, 0, 0, 3, 1, 4]Building leftProfit left to right, tracking minimum price seen:Day 0 โ†’ min=3, leftProfit[0] = 0 (nothing to compare yet) Day 1 โ†’ min=3, best profit = 3-3 = 0, leftProfit[1] = max(0, 0) = 0 Day 2 โ†’ min=3, best profit = 5-3 = 2, leftProfit[2] = max(0, 2) = 2 Day 3 โ†’ min=0, best profit = 0-0 = 0, leftProfit[3] = max(2, 0) = 2 Day 4 โ†’ min=0, best profit = 0-0 = 0, leftProfit[4] = max(2, 0) = 2 Day 5 โ†’ min=0, best profit = 3-0 = 3, leftProfit[5] = max(2, 3) = 3 Day 6 โ†’ min=0, best profit = 1-0 = 1, leftProfit[6] = max(3, 1) = 3 Day 7 โ†’ min=0, best profit = 4-0 = 4, leftProfit[7] = max(3, 4) = 4leftProfit = [0, 0, 2, 2, 2, 3, 3, 4]Building rightProfit right to left, tracking maximum price seen:Day 7 โ†’ max=4, rightProfit[7] = 0 (nothing to compare yet) Day 6 โ†’ max=4, best profit = 4-1 = 3, rightProfit[6] = max(0, 3) = 3 Day 5 โ†’ max=4, best profit = 4-3 = 1, rightProfit[5] = max(3, 1) = 3 Day 4 โ†’ max=4, best profit = 4-0 = 4, rightProfit[4] = max(3, 4) = 4 Day 3 โ†’ max=4, best profit = 4-0 = 4, rightProfit[3] = max(4, 4) = 4 Day 2 โ†’ max=5, best profit = 5-5 = 0, rightProfit[2] = max(4, 0) = 4 Day 1 โ†’ max=5, best profit = 5-3 = 2, rightProfit[1] = max(4, 2) = 4 Day 0 โ†’ max=5, best profit = 5-3 = 2, rightProfit[0] = max(4, 2) = 4rightProfit = [4, 4, 4, 4, 4, 3, 3, 0]Combining at each index: Index 0 โ†’ 0 + 4 = 4 Index 1 โ†’ 0 + 4 = 4 Index 2 โ†’ 2 + 4 = 6 Index 3 โ†’ 2 + 4 = 6 Index 4 โ†’ 2 + 4 = 6 Index 5 โ†’ 3 + 3 = 6 Index 6 โ†’ 3 + 3 = 6 Index 7 โ†’ 4 + 0 = 4Maximum = 6 โœ…Implementation code:class Solution { public int maxProfit(int[] prices) { int lefProf[] = new int[prices.length]; int rigProf[] = new int[prices.length]; int j = 1; int i = 0; int coL = 1; while (i < j && j < prices.length) { if (prices[i] > prices[j]) { i = j; if (coL - 1 != -1) { lefProf[coL] = lefProf[coL - 1]; } coL++; } else { int max = prices[j] - prices[i]; if (coL - 1 != -1) { lefProf[coL] = Math.max(lefProf[coL - 1], max); coL++; } else { lefProf[coL] = max; coL++; } } j++; } int ii = prices.length - 2; int jj = prices.length - 1; int coR = prices.length - 2; while (ii >= 0) { if (prices[ii] > prices[jj]) { jj = ii; if (coR + 1 < prices.length) { rigProf[coR] = rigProf[coR + 1]; } coR--; } else { int max = prices[jj] - prices[ii]; if (coR + 1 < prices.length) { rigProf[coR] = Math.max(rigProf[coR + 1], max); coR--; } else { rigProf[coR] = max; coR--; } } ii--; } int maxAns = 0; for (int k = 0; k < lefProf.length; k++) { maxAns = Math.max(maxAns, lefProf[k] + rigProf[k]); } return maxAns; }}The approach here is exactly right. You are building the left and right profit arrays using a two pointer scanning technique โ€” the same idea as LeetCode 121 but applied twice, once going forward and once going backward. The manual counters coL and coR make it a bit harder to follow, but the logic underneath is solid.Here is the same approach written in a cleaner way so it is easier to read and debug:class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[] leftProfit = new int[n]; int[] rightProfit = new int[n]; int minPrice = prices[0]; for (int i = 1; i < n; i++) { minPrice = Math.min(minPrice, prices[i]); leftProfit[i] = Math.max(leftProfit[i - 1], prices[i] - minPrice); } int maxPrice = prices[n - 1]; for (int i = n - 2; i >= 0; i--) { maxPrice = Math.max(maxPrice, prices[i]); rightProfit[i] = Math.max(rightProfit[i + 1], maxPrice - prices[i]); } int maxProfit = 0; for (int i = 0; i < n; i++) { maxProfit = Math.max(maxProfit, leftProfit[i] + rightProfit[i]); } return maxProfit; }}Time Complexity: O(N) โ€” three separate linear passes through the array. Space Complexity: O(N) โ€” two extra arrays of size N.Approach 2 โ€” Four Variable State Machine DPThe idea:Instead of building arrays, what if you could track everything in just four variables and solve it in a single pass?Think about what is happening at any point in time as you go through the prices. You are always in one of these situations:You have bought your first stock and are currently holding it. You have sold your first stock and are sitting on that profit. You have bought your second stock (using the profit from the first sale) and are holding it. You have sold your second stock and collected your total profit.These four situations are your four states. For each one, you always want to know โ€” what is the best possible profit I could have in this state up to today?Here is how each state updates as you look at a new price:buy1 โ€” This represents the best outcome after buying the first stock. Buying costs money, so this is negative. As you see each new price, you ask: is it better to have bought on some earlier day, or to buy today? You keep whichever gives you the least cost, which means the highest value of negative price.sell1 โ€” This represents the best profit after completing the first transaction. As you see each new price, you ask: is it better to have sold on some earlier day, or to sell today using the best buy1 we have? Selling today means adding today's price on top of buy1.buy2 โ€” This represents the best outcome after buying the second stock. The key insight here is that you are not starting from zero โ€” you already have the profit from the first transaction in your pocket. So buying the second stock costs today's price but you subtract it from sell1. As you see each new price, you ask: is it better to have bought the second stock earlier, or to buy today using the profit from sell1?sell2 โ€” This is your final answer. It represents the best total profit after completing both transactions. As you see each new price, you ask: is it better to have completed both transactions earlier, or to sell the second stock today using the best buy2 we have?The beautiful thing is that all four updates happen in order on every single day, in one pass through the array. Each variable feeds into the next โ€” buy1 feeds sell1, sell1 feeds buy2, buy2 feeds sell2.Dry Run โ€” prices = [3, 3, 5, 0, 0, 3, 1, 4]We start with buy1 and buy2 as very negative (not yet bought anything) and sell1 and sell2 as 0 (no profit yet).Day 0, price = 3: buy1 = max(-โˆž, -3) = -3. Best cost to buy first stock is spending 3. sell1 = max(0, -3+3) = 0. Selling immediately gives no profit. buy2 = max(-โˆž, 0-3) = -3. Best cost to buy second stock after first sale is 3. sell2 = max(0, -3+3) = 0. No total profit yet.Day 1, price = 3: Nothing changes โ€” same price as day 0.Day 2, price = 5: buy1 = max(-3, -5) = -3. Still best to have bought at price 3. sell1 = max(0, -3+5) = 2. Selling today at 5 after buying at 3 gives profit 2. buy2 = max(-3, 2-5) = -3. Buying second stock today costs too much relative to first profit. sell2 = max(0, -3+5) = 2. Completing both trades gives 2 so far.Day 3, price = 0: buy1 = max(-3, -0) = 0. Buying today at price 0 is the best possible first buy. sell1 = max(2, 0+0) = 2. Selling today at 0 gives nothing โ€” keep the 2 from before. buy2 = max(-3, 2-0) = 2. Using the profit of 2 and buying today at 0 gives buy2 = 2. sell2 = max(2, 2+0) = 2. Selling second stock today at 0 gives nothing extra.Day 4, price = 0: Nothing changes โ€” same price as day 3.Day 5, price = 3: buy1 = max(0, -3) = 0. Still best to have bought at price 0. sell1 = max(2, 0+3) = 3. Selling today at 3 after buying at 0 gives profit 3. buy2 = max(2, 3-3) = 2. Buying second stock today at 3 using sell1=3 gives 0. Keep 2. sell2 = max(2, 2+3) = 5. Selling second stock today at 3 after buy2=2 gives total 5.Day 6, price = 1: buy1 = max(0, -1) = 0. Still best to have bought at 0. sell1 = max(3, 0+1) = 3. Selling today at 1 gives less than 3 โ€” no change. buy2 = max(2, 3-1) = 2. Buying second stock at 1 using sell1=3 gives 2 โ€” same as before. sell2 = max(5, 2+1) = 5. No improvement yet.Day 7, price = 4: buy1 = max(0, -4) = 0. No change. sell1 = max(3, 0+4) = 4. Selling today at 4 after buying at 0 gives profit 4 โ€” new best. buy2 = max(2, 4-4) = 2. No improvement. sell2 = max(5, 2+4) = 6. Selling second stock today at 4 with buy2=2 gives total 6 โ€” new best.Output: 6 โœ…class Solution { public int maxProfit(int[] prices) { int buy1 = Integer.MIN_VALUE; int sell1 = 0; int buy2 = Integer.MIN_VALUE; int sell2 = 0; for (int price : prices) { buy1 = Math.max(buy1, -price); sell1 = Math.max(sell1, buy1 + price); buy2 = Math.max(buy2, sell1 - price); sell2 = Math.max(sell2, buy2 + price); } return sell2; }}Time Complexity: O(N) โ€” single pass through the array. Space Complexity: O(1) โ€” only four integer variables, no extra arrays.Approach 3 โ€” Generalizing to At Most K TransactionsOnce you understand Approach 2, there is a natural question โ€” what if instead of two transactions, you were allowed K transactions? That is exactly LeetCode 188.Instead of four hardcoded variables, you use two arrays of size K. buy[j] tracks the best outcome after the j-th buy and sell[j] tracks the best outcome after the j-th sell. The logic is identical to Approach 2, just in a loop.For this problem set K = 2 and it gives the same answer:class Solution { public int maxProfit(int[] prices) { int k = 2; int[] buy = new int[k]; int[] sell = new int[k]; Arrays.fill(buy, Integer.MIN_VALUE); for (int price : prices) { for (int j = 0; j < k; j++) { buy[j] = Math.max(buy[j], (j == 0 ? 0 : sell[j - 1]) - price); sell[j] = Math.max(sell[j], buy[j] + price); } } return sell[k - 1]; }}Time Complexity: O(N ร— K) โ€” for K=2 this is effectively O(N). Space Complexity: O(K) โ€” two small arrays of size K.If you understand this, LeetCode 188 becomes a five minute problem.Comparing All Three ApproachesApproach 1 โ€” Left-Right Prefix DP (Your Approach)You build two arrays โ€” one scanning left to right, one scanning right to left โ€” and combine them. This is the most visual and intuitive approach. It is easy to explain because you are essentially solving LeetCode 121 twice from opposite ends and adding the results. The downside is it uses O(N) extra space for the two arrays.Approach 2 โ€” Four Variable State MachineYou solve the entire problem in a single pass using just four variables. No arrays, no extra memory. This is the most efficient and elegant solution. Once you understand what each variable represents, the code almost writes itself. This is what most interviewers are hoping to see.Approach 3 โ€” General K-Transaction DPThis is Approach 2 extended to handle any number of transactions using arrays instead of hardcoded variables. Solving LeetCode 123 with this approach means you have already solved LeetCode 188 as a bonus.Mistakes That Catch Most PeopleTrying to use the greedy approach from LeetCode 122: Adding every upward price movement does not work when you are limited to two transactions. You need to pick the two best non-overlapping windows, not collect everything.Buying twice without selling in between: The state machine prevents this naturally โ€” buy2 depends on sell1, so you can never enter the second buy before completing the first sell.Initializing buy variables to 0: Both buy1 and buy2 must start at Integer.MIN_VALUE. Setting them to 0 implies you bought a stock for free, which is wrong and will give inflated profits.Returning the wrong variable: Always return sell2, not buy2 or sell1. sell2 is the only variable that represents a completed two-transaction profit.Where This Fits in the Stock SeriesLeetCode 121 โ€” One transaction only. Find the single best buy-sell pair. [ Blog is also avaliable on this - Read Now ]LeetCode 122 โ€” Unlimited transactions. Collect every upward price movement greedily. [ Blog is also avaliable on this - Read Now ]LeetCode 188 โ€” At most K transactions. Direct extension of Approach 3 above.LeetCode 309 โ€” Unlimited transactions but with a cooldown day after every sale.LeetCode 714 โ€” Unlimited transactions but with a fee charged on every transaction.Each problem adds one new constraint on top of the previous one. If you understand LeetCode 123 deeply โ€” especially Approach 2 โ€” every other problem in this series becomes a small modification of the same framework.Key Takeawaysโœ… When you are limited to two transactions, greedy fails. You need to find two non-overlapping windows that together give maximum profit.โœ… The left-right prefix DP approach is the most intuitive โ€” precompute the best single transaction from both ends, then combine at every split point.โœ… The four variable state machine solves it in one pass with O(1) space. Each variable tracks the best outcome for one state โ€” buy1 feeds sell1, sell1 feeds buy2, buy2 feeds sell2.โœ… Always initialize buy variables to Integer.MIN_VALUE to represent "not yet bought anything."โœ… Understanding Approach 3 here means LeetCode 188 is already solved โ€” just change the hardcoded 2 to K.Happy Coding! This problem is the turning point in the stock series where DP becomes unavoidable โ€” once you crack it, the rest of the series feels manageable. ๐Ÿš€

LeetCodeDynamic ProgrammingHardJavaDSAPrefix DPArraysStock Series
Longest Substring with K Unique Characters โ€“ Efficient Sliding Window Solution

Longest Substring with K Unique Characters โ€“ Efficient Sliding Window Solution

IntroductionFinding substrings with unique characters is a common problem in string manipulation and interview questions.GFGโ€™s Longest Substring with K Unique Characters problem challenges you to find the length of the longest substring containing exactly K distinct characters.The focus is not just on brute-force solutions, but on using sliding window with a HashMap to efficiently track character frequencies and window boundaries.If youโ€™d like to try solving the problem first, you can attempt it here: Try the problem on GeeksforGeeks: https://leetcode.com/problems/max-consecutive-ones-iii/Problem UnderstandingYou are given:A string s of lowercase lettersAn integer kYour task: Return the length of the longest substring containing exactly k distinct characters.Examples:Input: s = "aabacbebebe", k = 3Output: 7Explanation: "cbebebe" is the longest substring with 3 distinct characters.Input: s = "aaaa", k = 2Output: -1Explanation: No substring contains exactly 2 distinct characters.Input: s = "aabaaab", k = 2Output: 7Explanation: "aabaaab" has exactly 2 unique characters.A naive approach would try all possible substrings, count unique characters, and track the max length.Time Complexity: O(nยฒ)Inefficient for large stringsKey Idea: Sliding Window + HashMapInstead of recomputing the unique characters for every substring:Use a HashMap to store character frequencies in the current windowUse two pointers (i and j) to maintain the windowExpand the window by moving j and add characters to the mapShrink the window from the left when the number of unique characters exceeds kUpdate the max length whenever the window has exactly k unique charactersThis ensures each character is processed only once, giving a linear solution.Approach (Step-by-Step)Initialize a HashMap mp to store character countsUse two pointers i (window start) and j (window end)Iterate over the string with jAdd s[j] to the map and update its countCheck the map size:If size < k โ†’ move j forwardIf size == k โ†’ update max length, move j forwardIf size > k โ†’ shrink window from i until map size <= kAfter iterating, return max (or -1 if no valid substring exists)Optimization:Using a HashMap avoids recalculating the number of unique characters for every substringSliding window ensures O(n) complexityImplementation (Java)class Solution {public int longestKSubstr(String s, int k) {HashMap<Character,Integer> mp = new HashMap<>();int i = 0, j = 0;int max = -1;while (j < s.length()) {// Add current character to mapmp.put(s.charAt(j), mp.getOrDefault(s.charAt(j), 0) + 1);if (mp.size() < k) {j++;}else if (mp.size() == k) {// Update max length when exactly k unique charactersmax = Math.max(max, j - i + 1);j++;}else {// Shrink window until map size <= kwhile (mp.size() > k) {mp.put(s.charAt(i), mp.get(s.charAt(i)) - 1);if (mp.get(s.charAt(i)) == 0) {mp.remove(s.charAt(i));}i++;}max = Math.max(max, j - i + 1);j++;}}return max;}}Dry Run ExampleInput:s = "aabacbebebe", k = 3Execution:Window [0-4] = "aaba" โ†’ unique = 2 โ†’ continueWindow [1-6] = "abacb" โ†’ unique = 3 โ†’ max = 5Window [4-10] = "cbebebe" โ†’ unique = 3 โ†’ max = 7Output:7Complexity AnalysisTime Complexity: O(n) โ†’ Each character enters and leaves the window onceSpace Complexity: O(k) โ†’ HashMap stores at most k unique charactersEdge Cases Consideredk greater than number of unique characters โ†’ return -1Entire string has exactly k unique charactersString with repeated characters onlyMinimum string length (1)Sliding Window Pattern ImportanceThis problem reinforces a common sliding window + hashmap pattern used in:Longest substring problems with constraintsCounting substrings with exactly K conditionsOptimizing brute-force approaches to linear solutionsConclusionBy combining sliding window with HashMap frequency tracking, we reduce an O(nยฒ) problem to O(n).Once you master this approach, many string and substring problems with constraints on unique characters become much easier to solve efficiently.

SlidingWindowHashMapStringsGFG
Find Length of Loop in Linked List โ€” Complete Guide with Intuition, Dry Run & Floydโ€™s Cycle Algorithm

Find Length of Loop in Linked List โ€” Complete Guide with Intuition, Dry Run & Floydโ€™s Cycle Algorithm

๐Ÿ”— Try This ProblemPractice here:GeeksforGeeks link๐Ÿ“Œ Problem OverviewYou are given the head of a singly linked list.Your task is:Detect whether a loop (cycle) existsIf a loop exists โ†’ return the length of the loopIf no loop exists โ†’ return 0๐Ÿง  Understanding the ProblemA loop in a linked list means:๐Ÿ‘‰ A nodeโ€™s next pointer is pointing to a previous node, forming a cycle.Example1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5โ†‘ โ†“โ† โ† โ† โ†Here:Loop starts at node 3Loop nodes = 3 โ†’ 4 โ†’ 5Loop length = 3๐Ÿ’ก Intuition โ€” How to Think About This ProblemThere are two main challenges:๐Ÿ”น 1. Detect if a loop exists๐Ÿ”น 2. Find the length of that loop๐Ÿค” Brute Force ThinkingStore visited nodes in a HashSetIf node repeats โ†’ loop detectedโœ” WorksโŒ Extra space O(n)๐Ÿš€ Optimized Thinking (Floydโ€™s Cycle Detection)We use:Slow pointer โ†’ moves 1 stepFast pointer โ†’ moves 2 steps๐Ÿง  Key Idea๐Ÿ‘‰ If a loop exists:Fast pointer will eventually meet slow pointer๐Ÿ‘‰ If no loop:Fast pointer reaches null๐ŸŽฅ Visual Intuition & Dry Runโš™๏ธ Optimized Approach (Step-by-Step)Step 1: Detect LoopInitialize:slow = headfast = headMove:slow โ†’ 1 stepfast โ†’ 2 stepsIf:slow == fast โ†’ loop existsStep 2: Find Length of LoopOnce loop is detected:Keep one pointer fixedMove another pointer until it comes backCount stepsWhy This Works?Because:Inside a loop, traversal becomes circularSo eventually, we will return to the same node๐Ÿงช Dry Run (Manual Understanding)Example:Loop: 3 โ†’ 4 โ†’ 5 โ†’ 3Step 1: Detect LoopSlow and fast meet inside loopStep 2: Count Loop LengthStart from meeting point:Move one pointer:3 โ†’ 4 โ†’ 5 โ†’ 3Count = 3๐Ÿ’ป Code (Java)class Solution {public int lengthOfLoop(Node head) {Node slow = head;Node fast = head;// Step 1: Detect loopwhile(fast != null && fast.next != null){slow = slow.next;fast = fast.next.next;if(fast == slow){// Step 2: Count loop lengthint length = 1;Node temp = slow.next;while(temp != slow){temp = temp.next;length++;}return length;}}return 0; // No loop}}โฑ๏ธ Complexity AnalysisTypeValueTime ComplexityO(n)Space ComplexityO(1)๐Ÿ” Deep Insight โ€” Why Fast Meets Slow?Fast moves twice as fast as slowInside a loop โ†’ paths become circularDifference in speed guarantees collision๐Ÿ‘‰ This is a mathematical certainty in cyclic structuresโš ๏ธ Edge CasesNo loop โ†’ return 0Single node loop โ†’ return 1Large loop โ†’ still works efficiently๐Ÿงฉ Key TakeawaysFloydโ€™s Cycle Detection is powerfulLoop detection + loop length can be done in one traversalNo extra space needed๐Ÿ Final ThoughtsThis problem is a classic linked list concept and very important for interviews.๐Ÿ‘‰ Once you master this:Detect cycleFind loop lengthFind loop starting nodeAll become easier.

GeeksforGeeksLinkedListMediumFast and Slow Pointer
LeetCode 203: Remove Linked List Elements โ€“ Step-by-Step Guide for Beginners

LeetCode 203: Remove Linked List Elements โ€“ Step-by-Step Guide for Beginners

Try the ProblemYou can practice the problem here:https://leetcode.com/problems/remove-linked-list-elements/Problem Description (In Very Simple Words)You are given:The head of a linked listAn integer value valYour task is:๐Ÿ‘‰ Remove all nodes from the linked list whose value is equal to val.๐Ÿ‘‰ Return the updated head of the linked list.What is a Linked List? (Quick Recap)A linked list is a chain of nodes where each node contains:value + pointer to next nodeExample:1 โ†’ 2 โ†’ 6 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ 6 โ†’ nullExample WalkthroughExample 1Input:head = [1,2,6,3,4,5,6]val = 6Output:[1,2,3,4,5]ExplanationWe remove all nodes with value 6.1 โ†’ 2 โ†’ โŒ6 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ โŒ6Final list:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5Example 2Input:head = []val = 1Output:[]Example 3Input:head = [7,7,7,7]val = 7Output:[]All nodes are removed.Constraints0 <= number of nodes <= 10^41 <= Node.val <= 500 <= val <= 50Understanding the Problem DeeplyThe tricky part is:๐Ÿ‘‰ Nodes can appear anywhere:At the beginningIn the middleAt the endOr all nodes๐Ÿ‘‰ Linked list does NOT allow backward traversalSo we must carefully handle pointers.Thinking About the SolutionWhen solving this problem, multiple approaches may come to mind:Possible ApproachesTraverse the list and remove nodes manually.Handle head separately, then process the rest.Use a dummy node to simplify logic.Use recursion (less preferred for beginners).Approach 1: Without Dummy Node (Manual Handling)IdeaFirst, remove nodes from the start (head) if they match val.Then traverse the list using two pointers:prev โ†’ previous nodecurr โ†’ current nodeKey ChallengeHandling head separately makes code more complex.Codeclass Solution { public ListNode removeElements(ListNode head, int val) { // Step 1: Remove matching nodes from the beginning while(head != null && head.val == val){ head = head.next; } // If list becomes empty if(head == null) return null; ListNode prev = head; ListNode curr = head.next; while(curr != null){ if(curr.val == val){ // Skip the node prev.next = curr.next; } else { // Move prev only when node is not removed prev = curr; } curr = curr.next; } return head; }}Why This WorksWe ensure prev always points to the last valid nodeWhen we find a node to delete:prev.next = curr.nextThis skips the unwanted nodeProblem With This Approach๐Ÿ‘‰ Too many edge cases:Removing head nodesEmpty listAll nodes equalApproach 2: Dummy Node (Best & Clean Approach)IdeaWe create a fake node (dummy) before the head.dummy โ†’ head โ†’ rest of listThis helps us:๐Ÿ‘‰ Treat head like a normal node๐Ÿ‘‰ Avoid special casesVisualizationOriginal:1 โ†’ 2 โ†’ 6 โ†’ 3After dummy:-1 โ†’ 1 โ†’ 2 โ†’ 6 โ†’ 3Java Implementation (Best Approach)class Solution { public ListNode removeElements(ListNode head, int val) { // Step 1: Create dummy node ListNode dumm = new ListNode(-1, head); // Step 2: Start from dummy ListNode curr = dumm; while(curr.next != null){ // If next node needs to be removed if(curr.next.val == val){ // Skip that node curr.next = curr.next.next; } else { // Move forward only when no deletion curr = curr.next; } } // Step 3: Return new head return dumm.next; }}Step-by-Step Dry RunInput:1 โ†’ 2 โ†’ 6 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ 6val = 6After dummy:-1 โ†’ 1 โ†’ 2 โ†’ 6 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ 6Traversal:curr = -1 โ†’ 1 (keep)curr = 1 โ†’ 2 (keep)curr = 2 โ†’ 6 (remove)curr = 2 โ†’ 3 (keep)curr = 3 โ†’ 4 (keep)curr = 4 โ†’ 5 (keep)curr = 5 โ†’ 6 (remove)Final:1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5Time ComplexityO(n)We traverse the list once.Space ComplexityO(1)No extra space used.Why Dummy Node is PreferredWithout DummyWith DummyComplex edge casesClean logicSpecial handling for headNo special handlingError-proneSafe and readableApproach 3: Recursive Solution (Conceptual)IdeaWe process one node at a time and recursively solve for the rest.Codeclass Solution { public ListNode removeElements(ListNode head, int val) { if(head == null) return null; head.next = removeElements(head.next, val); if(head.val == val){ return head.next; } else { return head; } }}Time ComplexityO(n)Space ComplexityO(n)(due to recursion stack)Key TakeawaysLinked list problems are all about pointer manipulationAlways think about edge cases (especially head)Dummy node simplifies almost every linked list problemConclusionThe Remove Linked List Elements problem is perfect for understanding how linked lists work in real scenarios.While the manual approach works, the dummy node technique provides a much cleaner and safer solution.If you master this pattern, you will be able to solve many linked list problems easily in interviews.๐Ÿ‘‰ Tip: Whenever you feel stuck in linked list problems, try adding a dummy node โ€” it often simplifies everything!

Linked ListIterationDummy NodePointer ManipulationLeetCodeEasy
LeetCode 187 โ€“ Repeated DNA Sequences (Java Solution with Sliding Window and HashSet)

LeetCode 187 โ€“ Repeated DNA Sequences (Java Solution with Sliding Window and HashSet)

IntroductionIn this article, we will solve LeetCode 187: Repeated DNA Sequences using Java. This is a popular string problem that tests your understanding of the sliding window technique and efficient use of hash-based data structures.DNA sequences are composed of four characters:A (Adenine)C (Cytosine)G (Guanine)T (Thymine)The goal is to identify all 10-letter-long substrings that appear more than once in a given DNA string.You can try solving the problem directly on LeetCode here: https://leetcode.com/problems/repeated-dna-sequences/Problem StatementGiven a string s that represents a DNA sequence, return all the 10-letter-long substrings that occur more than once.Example 1Input: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"Output: ["AAAAACCCCC", "CCCCCAAAAA"]Example 2Input: s = "AAAAAAAAAAAAA"Output: ["AAAAAAAAAA"]Key ObservationsWe only need substrings of fixed length 10.The maximum length of the string can be up to 10^5.A brute-force solution checking all substrings multiple times would be inefficient.This problem can be solved efficiently using a sliding window and hash-based data structures.Approach 1: Sliding Window with HashSet (Given Solution)IdeaUse two pointers (i and j) to maintain a sliding window.Build a substring of size 10 dynamically.Store previously seen substrings in a HashSet.If a substring is already present in the set:Check if it is already in the result list.If not, add it to the result list.Slide the window forward and continue.Java Code (Your Implementation)class Solution { public List<String> findRepeatedDnaSequences(String s) { HashSet<String> ms = new HashSet<>(); List<String> lis = new ArrayList<>(); int i = 0; int j = 0; String tes = ""; while (j < s.length()) { tes += s.charAt(j); if (j - i + 1 < 10) { j++; } else { if (j - i + 1 == 10) { if (ms.contains(tes)) { boolean fl = false; for (String a : lis) { if (a.equals(tes)) { fl = true; } } if (!fl) { lis.add(tes); } } else { ms.add(tes); } tes = tes.substring(1); i++; j++; } } } return lis; }}ExplanationThe variable tes maintains the current substring.ms stores all previously seen substrings of length 10.If a substring already exists in ms, we manually check whether it has already been added to the result list.This avoids duplicate entries in the final output.Time ComplexitySliding through the string: O(n)Checking duplicates in the result list: O(n) in the worst caseOverall worst-case complexity: O(nยฒ)Space ComplexityHashSet storage: O(n)Limitation of Approach 1The manual duplicate check using a loop inside the result list introduces unnecessary overhead. This makes the solution less efficient.We can improve this by using another HashSet to automatically handle duplicates.Approach 2: Optimized Solution Using Two HashSetsIdeaUse one HashSet called seen to track all substrings of length 10.Use another HashSet called repeated to store substrings that appear more than once.Iterate from index 0 to s.length() - 10.Extract substring of length 10.If adding to seen fails, it means it has appeared before.Add it directly to repeated.This removes the need for a nested loop.Optimized Java Codeclass Solution { public List<String> findRepeatedDnaSequences(String s) { Set<String> seen = new HashSet<>(); Set<String> repeated = new HashSet<>(); for (int i = 0; i <= s.length() - 10; i++) { String substring = s.substring(i, i + 10); if (!seen.add(substring)) { repeated.add(substring); } } return new ArrayList<>(repeated); }}Why This Approach is BetterNo manual duplicate checking.Cleaner and more readable code.Uses HashSet properties efficiently.Each substring is processed only once.Time Complexity (Optimized)Single traversal of the string: O(n)Substring extraction of fixed length 10: O(1)Overall time complexity: O(n)Space ComplexityTwo HashSets storing substrings: O(n)ConclusionLeetCode 187 is a classic example of combining the sliding window technique with hash-based data structures.The first approach works but has unnecessary overhead due to manual duplicate checks.The second approach is more optimal, cleaner, and recommended for interviews.Always leverage the properties of HashSet to avoid redundant checks.This problem highlights the importance of choosing the right data structure to optimize performance.

JavaSliding WindowMedium
Maximum Average Subarray I โ€” Efficient Solution Using Sliding Window (LeetCode 643)

Maximum Average Subarray I โ€” Efficient Solution Using Sliding Window (LeetCode 643)

IntroductionLeetCode Problem 643: Maximum Average Subarray I is a classic problem that tests understanding of arrays and the sliding window technique.The task is simple in description but requires optimization to work efficiently for large inputs.We are given:An integer array numsAn integer kWe must find a contiguous subarray of length k that has the maximum average value, and return that average.If you want to try solving the problem yourself before reading further, you can attempt it here:๐Ÿ‘‰ Try the problem on LeetCode: https://leetcode.com/problems/maximum-average-subarray-i/Problem UnderstandingA brute-force solution would compute the sum for every subarray of length k and track the maximum average. However, recalculating sums repeatedly results in O(n ร— k) time complexity, which becomes inefficient for large arrays.Instead, we can use the sliding window technique to optimize the process.Key Idea: Sliding WindowInstead of recomputing sums:Compute the sum of the first window of size k.Slide the window forward by:Adding the next elementRemoving the element leaving the windowUpdate the maximum average at each step.This reduces time complexity to O(n).ApproachMaintain two pointers representing the window.Keep adding elements until window size becomes k.Compute average and update maximum.Slide the window by removing the left element.Continue until the end of the array.Implementation (Java)class Solution { public double findMaxAverage(int[] nums, int k) { double ans = Integer.MIN_VALUE; int i = 0; int j = 0; int sum = 0; while (j < nums.length) { sum += nums[j]; if (j - i + 1 < k) { j++; } else { ans = Math.max(ans, (double) sum / k); sum -= nums[i]; i++; j++; } } return ans; }}Dry Run ExampleInput:nums = [1,12,-5,-6,50,3], k = 4Windows examined:[1,12,-5,-6] โ†’ avg = 0.5[12,-5,-6,50] โ†’ avg = 12.75 (maximum)[-5,-6,50,3] โ†’ avg = 10.5Maximum average = 12.75Complexity AnalysisTime Complexity: O(n) Each element enters and leaves the window once.Space Complexity: O(1) No extra space is used apart from variables.Edge Cases ConsideredSingle element array (k = 1)All negative numbersLarge input sizeLarge positive or negative valuesWhy Sliding Window MattersSliding window is a crucial technique used in many interview problems:Subarray sum problemsLongest substring problemsFixed or variable window size optimizationsMastering this pattern greatly improves coding interview performance.ConclusionThis problem demonstrates how recognizing patterns like sliding window can transform a slow brute-force solution into an efficient one.If you are preparing for coding interviews, mastering sliding window problems is essential since they appear frequently.

LeetCodeSliding WindowEasy
Count Subarrays of Size K with Average โ‰ฅ Threshold โ€” Sliding Window Solution (LeetCode 1343)

Count Subarrays of Size K with Average โ‰ฅ Threshold โ€” Sliding Window Solution (LeetCode 1343)

IntroductionLeetCode 1343: Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold is another excellent problem to practice the sliding window technique.The goal is not just to compute values but to count how many subarrays satisfy a given condition efficiently.You are given:An integer array arrAn integer k representing subarray sizeA threshold valueYou must return the number of subarrays of size k whose average is greater than or equal to the threshold.If you'd like to try solving the problem first, you can attempt it here:๐Ÿ‘‰ Try the problem on LeetCode:https://leetcode.com/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/Problem UnderstandingA direct approach would calculate the average for every subarray of size k. However, recomputing sums repeatedly leads to unnecessary work.To optimize, we use a sliding window, allowing us to reuse previous computations and move efficiently across the array.Key Idea: Sliding Window OptimizationInstead of calculating sums repeatedly:Maintain the sum of the current window.Slide the window by:Adding the next elementRemoving the element leaving the windowCheck if the average satisfies the threshold.Count valid windows.This ensures each element is processed only once.ApproachMaintain two pointers to represent the window.Add elements until window size reaches k.Check if average meets the threshold.Move the window forward.Count valid subarrays.An important optimization: instead of computing average each time, we can compare the sum with threshold * k. However, your current implementation using division also works correctly.Implementation (Java)class Solution { public int numOfSubarrays(int[] arr, int k, int t) { int curr = 0; int i = 0; int j = 0; int co = 0; while (j < arr.length) { curr += arr[j]; if (j - i + 1 < k) { j++; } else { if (j - i + 1 == k) { if (curr / k >= t) { co++; } curr -= arr[i]; i++; j++; } } } return co; }}Dry Run ExampleInput:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4Valid subarrays:[2,5,5] โ†’ avg = 4[5,5,5] โ†’ avg = 5[5,5,8] โ†’ avg = 6Total valid subarrays = 3Complexity AnalysisTime Complexity: O(n)Each element enters and leaves the window once.Space Complexity: O(1)Only constant extra variables are used.Edge Cases ConsideredThreshold equals zeroMinimum array lengthLarge array sizesNon-integer averagesAll elements smaller than thresholdSliding Window Pattern ImportanceThis problem reinforces the sliding window pattern, which appears frequently in:Fixed-size window problemsMaximum or minimum subarray problemsCounting valid segmentsString window problemsMastering this pattern helps solve many interview questions efficiently.ConclusionThis problem is a strong example of turning a potentially expensive brute-force approach into an efficient linear solution using sliding window.Once you recognize this pattern, many array and string problems become significantly easier to solve.

LeetCodeSliding WindowMedium
Master LeetCode 92: Reverse Linked List II | Detailed Java Solution & Explanation

Master LeetCode 92: Reverse Linked List II | Detailed Java Solution & Explanation

If you are preparing for software engineering interviews, you already know that Linked Lists are a favourite topic among interviewers. While reversing an entire linked list is a standard beginner problem, reversing only a specific section of it requires a bit more pointer magic.In this blog post, we will tackle LeetCode 92. Reverse Linked List II. We will break down the problem in plain English, walk through a highly intuitive modular approach, and then look at an optimized one-pass technique.Letโ€™s dive in!Understanding the ProblemQuestion Statement:Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.Example:Input: head = [1,2,3,4,5], left = 2, right = 4Output: [1,4,3,2,5]In Simple Words:Imagine a chain of connected boxes. You don't want to flip the whole chain backwards. You only want to flip a specific middle section (from the 2nd box to the 4th box), while keeping the first and last boxes exactly where they are.Approach 1: The Intuitive Modular Approach (Find, Reverse, Connect)When solving complex linked list problems, breaking the problem into smaller helper functions is an excellent software engineering practice.In this approach, we will:Use a Dummy Node. This is a lifesaver when left = 1 (meaning we have to reverse from the very first node).Traverse the list to find the exact boundaries: the node just before the reversal (slow), the start of the reversal (leftNode), and the end of the reversal (rightNode).Pass the sub-list to a helper function that reverses it.Reconnect the newly reversed sub-list back to the main list.Here is the Java code for this approach:/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { // Helper function to reverse a portion of the list public ListNode reverse(ListNode LeftHead, ListNode rightHead){ ListNode curr = LeftHead; ListNode prev = rightHead; // Standard linked list reversal logic while(curr != rightHead){ ListNode newnode = curr.next; curr.next = prev; prev = curr; curr = newnode; } return prev; } public ListNode reverseBetween(ListNode head, int left, int right) { if(left == 1 && right == 1) return head; // Dummy node helps handle edge cases easily ListNode dummy = new ListNode(-1); dummy.next = head; ListNode leftNode = null; ListNode rightNode = null; ListNode curr = head; int leftC = 1; int rightC = 1; ListNode slow = dummy; // Traverse to find the exact bounds of our sublist while(curr != null){ if(leftC == left - 1){ slow = curr; // 'slow' is the node just before the reversed section } if(leftC == left){ leftNode = curr; } if(rightC == right){ rightNode = curr; } if(leftC == left && rightC == right){ break; // We found both bounds, no need to traverse further } leftC++; rightC++; curr = curr.next; } // Reverse the sublist and connect it back ListNode rev = reverse(leftNode, rightNode.next); slow.next = rev; return dummy.next; }}๐Ÿ” Dry Run of Approach 1Letโ€™s trace head = [1, 2, 3, 4, 5], left = 2, right = 4.Step 1: Initializationdummy = -1 -> 1 -> 2 -> 3 -> 4 -> 5slow = -1 (dummy node)Step 2: Finding Bounds (While Loop)We move curr through the list.When curr is at 1 (Position 1): left - 1 is 1, so slow becomes node 1.When curr is at 2 (Position 2): leftNode becomes node 2.When curr is at 4 (Position 4): rightNode becomes node 4. We break the loop.Step 3: The Helper ReversalWe call reverse(leftNode, rightNode.next), which means reverse(Node 2, Node 5).Inside the helper, we reverse the links for 2, 3, and 4.Because we initialized prev as rightHead (Node 5), Node 2's next becomes Node 5.The helper returns Node 4 as the new head of this reversed chunk. The chunk now looks like: 4 -> 3 -> 2 -> 5.Step 4: ReconnectionBack in the main function, slow (Node 1) is connected to the returned reversed list: slow.next = rev.Final List: dummy -> 1 -> 4 -> 3 -> 2 -> 5.Return dummy.next!Time & Space Complexity:Time Complexity: O(N). We traverse the list to find the pointers, and then the helper traverses the sub-list to reverse it. Since we visit nodes a maximum of two times, it is linear time.Space Complexity: O(1). We are only creating a few pointers (dummy, slow, curr, etc.), requiring no extra dynamic memory.Approach 2: The Optimized One-Pass SolutionLeetCode includes a follow-up challenge: "Could you do it in one pass?"While the first approach is incredibly readable, we can optimize it to reverse the nodes as we traverse them, eliminating the need for a separate helper function or a second sub-list traversal.In this approach, we:Move a prev pointer to the node just before left.Keep a curr pointer at left.Use a for loop to extract the node immediately after curr and move it to the front of the reversed sub-list. We do this exactly right - left times.class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if (head == null || left == right) return head; ListNode dummy = new ListNode(0); dummy.next = head; ListNode prev = dummy; // Step 1: Reach the node just before 'left' for (int i = 0; i < left - 1; i++) { prev = prev.next; } // Step 2: Start the reversal process ListNode curr = prev.next; for (int i = 0; i < right - left; i++) { ListNode nextNode = curr.next; // Node to be moved to the front curr.next = nextNode.next; // Detach nextNode nextNode.next = prev.next; // Point nextNode to the current front of sublist prev.next = nextNode; // Make nextNode the new front of the sublist } return dummy.next; }}๐Ÿ” Why this works (The Pointer Magic)If we are reversing [1, 2, 3, 4, 5] from 2 to 4:prev is at 1. curr is at 2.Iteration 1: Grab 3, put it after 1. List: [1, 3, 2, 4, 5].Iteration 2: Grab 4, put it after 1. List: [1, 4, 3, 2, 5].Done! We achieved the reversal in a strict single pass.Time & Space Complexity:Time Complexity: O(N). We touch each node exactly once.Space Complexity: O(1). Done entirely in-place.ConclusionReversing a portion of a linked list is a fantastic way to test your understanding of pointer manipulation.Approach 1 is amazing for interviews because it shows you can modularize code and break big problems into smaller, testable functions.Approach 2 is the perfect "flex" to show the interviewer that you understand optimization and single-pass algorithms.I highly recommend writing down the dry run on a piece of paper and drawing arrows to see how the pointers shift. That is the secret to mastering Linked Lists!Happy Coding! ๐Ÿ’ป๐Ÿš€

LeetCodeLinkedListJavaReverse LinkedList II
LeetCode 1021: Remove Outermost Parentheses โ€” Java Solution Explained

LeetCode 1021: Remove Outermost Parentheses โ€” Java Solution Explained

IntroductionLeetCode 1021 Remove Outermost Parentheses sounds intimidating with words like "primitive decomposition" but once you strip away the fancy terminology, it is a beautifully simple problem. It builds directly on the depth-counting technique from LeetCode 1614 and is a perfect example of how one clean insight can collapse a seemingly complex problem into just a few lines of code.Here is the Link of Question -: LeetCode 1021In this article we cover plain English explanation, what "primitive" really means, two Java approaches with detailed dry runs, complexity analysis, and everything you need to ace this in an interview.What Is the Problem Really Asking?Let us ignore the formal definition for a moment and think practically.A primitive string is the smallest self-contained valid bracket group. Think of it as a top-level unit โ€” it opens, closes completely, and is not part of a bigger bracket group.For "(()())(())":"(()())" is one primitive โ€” it opens and fully closes"(())" is another primitive โ€” it opens and fully closes afterNow from each primitive, remove only the outermost opening and closing bracket, keep everything inside."(()())" โ†’ remove outer โ†’ "()()""(())" โ†’ remove outer โ†’ "()"Combined โ†’ "()()()"That is literally the whole problem!Real Life Analogy โ€” Gift Boxes Inside a Shipping BoxImagine you ordered gifts online. They all arrive in one big shipping box. Inside that box are individual gift boxes. Inside some gift boxes are smaller boxes.The "primitive decomposition" is identifying each individual gift box inside the shipping box. Removing the outermost parentheses is like removing just the outer gift box wrapping but keeping everything inside it intact.You are not unpacking the inner boxes โ€” just peeling off one layer from each top-level group.Understanding Primitive Decomposition VisuallyFor s = "(()())(())(()(()))":( ( ) ( ) ) ( ( ) ) ( ( ) ( ( ) ) )|_________| |_______| |_____________| primitive1 primitive2 primitive3Each group that starts at depth 0 and returns to depth 0 is one primitive. Remove the outermost ( and ) from each and concatenate what remains.The depth analogy from LeetCode 1614 is the key โ€” a primitive starts when depth goes from 0 to 1 and ends when depth returns to 0.Approach 1: Counter With Substring (Your Solution) โœ…The IdeaTrack depth with a counter. Use start and end pointers to mark the boundaries of each primitive. When depth hits 0, you have found a complete primitive โ€” append everything between start+1 and end (skipping the outermost brackets) to the result.public String removeOuterParentheses(String s) { StringBuilder sb = new StringBuilder(); int c = 0; int start = 0; int end = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { c++; } else { c--; } if (c == 0) { // primitive found from index start to end (inclusive) sb.append(s.substring(start + 1, end)); // skip outermost ( and ) start = end + 1; // next primitive starts after current end } end++; } return sb.toString();}Dry Run โ€” s = "(()())(())"Tracking c, start, end at each step:i=0, ( โ†’ c=1, end=1i=1, ( โ†’ c=2, end=2i=2, ) โ†’ c=1, end=3i=3, ( โ†’ c=2, end=4i=4, ) โ†’ c=1, end=5i=5, ) โ†’ c=0 โ†’ primitive found! append s.substring(1, 5) = "()()" โ†’ start=6, end=6i=6, ( โ†’ c=1, end=7i=7, ( โ†’ c=2, end=8i=8, ) โ†’ c=1, end=9i=9, ) โ†’ c=0 โ†’ primitive found! append s.substring(7, 9) = "()" โ†’ start=10, end=10Result: "()()" + "()" = "()()()" โœ…Time Complexity: O(n) โ€” single pass plus substring operations Space Complexity: O(n) โ€” StringBuilder stores the resultApproach 2: Counter With Depth Filter (Cleaner & More Intuitive)The IdeaInstead of tracking start/end pointers, use depth directly to decide whether to include each character. The insight is:The outermost ( of each primitive is always encountered when c goes from 0 to 1 โ€” skip itThe outermost ) of each primitive is always encountered when c goes from 1 to 0 โ€” skip itEvery other character is inside some primitive โ€” include itpublic String removeOuterParentheses(String s) { StringBuilder sb = new StringBuilder(); int depth = 0; for (char c : s.toCharArray()) { if (c == '(') { if (depth > 0) { sb.append(c); // not the outermost (, include it } depth++; } else { depth--; if (depth > 0) { sb.append(c); // not the outermost ), include it } } } return sb.toString();}This is arguably the most elegant solution. No pointers, no substring, just one rule โ€” if depth is already greater than 0 when you see (, it is not the outer one. If depth is still greater than 0 after decrementing on ), it is not the outer one.Dry Run โ€” s = "()()" (Edge Case)( โ†’ depth=0, skip it, depth becomes 1) โ†’ depth becomes 0, depth=0 so skip it( โ†’ depth=0, skip it, depth becomes 1) โ†’ depth becomes 0, depth=0 so skip itResult: "" โœ… Both are primitives "()" and "()", removing outer from each gives empty string.Dry Run โ€” s = "(()())(())(()(()))"Let us trace just which characters get included vs skipped:For the first primitive "(()())":( at depth 0 โ†’ skip (outermost opener)( at depth 1 โ†’ include) at depth 2โ†’1 โ†’ include( at depth 1 โ†’ include) at depth 2โ†’1 โ†’ include) at depth 1โ†’0 โ†’ skip (outermost closer)Inner content "()()" included โœ…Same logic applies to each subsequent primitive. Final result: "()()()()(())" โœ…Time Complexity: O(n) โ€” single pass Space Complexity: O(n) โ€” StringBuilder stores resultApproach 3: Stack Based (Verbose but Clear)The IdeaUse an explicit stack to track open brackets. When the stack becomes empty after a ), you have found a complete primitive. Collect all characters between the outermost brackets.public String removeOuterParentheses(String s) { StringBuilder sb = new StringBuilder(); Stack<Character> st = new Stack<>(); int start = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { st.push('('); } else { st.pop(); } if (st.empty()) { // primitive ends at i, extract inner content sb.append(s.substring(start + 1, i)); start = i + 1; } } return sb.toString();}This is the most verbose but makes the primitive detection very explicit โ€” stack empty means primitive complete. Great for explaining your thought process in an interview before optimizing.Time Complexity: O(n) Space Complexity: O(n) โ€” stack storageApproach ComparisonThe Stack approach is clearest for explaining primitive detection. The pointer-based counter approach (your solution) is direct and efficient. The depth-filter counter approach is the most elegant โ€” no pointers, no substrings, just a single depth check per character. All three are O(n) time. The depth-filter approach wins on code clarity.How This Connects to the Full SeriesLooking at the bracket problem series you have been building:20 Valid Parentheses โ€” is the string valid? 1614 Maximum Nesting Depth โ€” how deep is the nesting? 1021 Remove Outermost Parentheses โ€” identify and strip top-level groupsEach problem adds one more layer of understanding about bracket strings. The depth counter technique from 1614 directly powers the optimal solution here. This is pattern building at its best.Common Mistakes to AvoidOff-by-one in substring indices In your solution, s.substring(start+1, end) skips the outermost ( by using start+1, and end (not end+1) naturally excludes the outermost ) since end points to it. Getting these indices wrong by even one position gives completely wrong output.Appending the outermost brackets in the depth-filter approach The condition order matters โ€” for (, check depth BEFORE incrementing. For ), check depth AFTER decrementing. Flipping these gives wrong results.Forgetting to update start after each primitive In pointer-based approaches, always set start = end + 1 (or start = i + 1) after appending each primitive, otherwise the next primitive's start index is wrong.FAQs โ€” People Also AskQ1. What is a primitive valid parentheses string in LeetCode 1021? A primitive is the smallest self-contained valid bracket group that cannot be split into two smaller valid groups. It starts when depth goes from 0 to 1 and ends when depth returns to 0. For example in "(()())(())", the primitives are "(()())" and "(())".Q2. What is the most elegant solution for LeetCode 1021? The depth-filter counter approach โ€” increment depth on (, decrement on ), and only append characters when depth is greater than 0 for ( and still greater than 0 after decrement for ). This skips outermost brackets naturally without tracking indices.Q3. What is the time complexity of LeetCode 1021? All approaches run in O(n) time with a single pass. Space complexity is O(n) for storing the output string in StringBuilder.Q4. How is LeetCode 1021 different from LeetCode 1614? LeetCode 1614 finds the maximum depth by tracking a counter. LeetCode 1021 uses the same counter technique but to identify primitive boundaries and strip their outermost characters. 1614 returns a number, 1021 returns a modified string.Q5. Is LeetCode 1021 asked in coding interviews? It appears occasionally as a follow-up to Valid Parentheses or Nesting Depth problems. The real skill it tests is understanding primitive decomposition โ€” identifying self-contained units within a bracket string โ€” which appears in real world parser and compiler design.Similar LeetCode Problems to Practice Next20. Valid Parentheses โ€” Easy โ€” validate bracket structure1614. Maximum Nesting Depth of Parentheses โ€” Easy โ€” find max depth1249. Minimum Remove to Make Valid Parentheses โ€” Medium โ€” remove minimum brackets394. Decode String โ€” Medium โ€” nested brackets with encoding32. Longest Valid Parentheses โ€” Hard โ€” longest valid substringConclusionLeetCode 1021 Remove Outermost Parentheses is a satisfying problem once you realize that "primitive decomposition" is just a fancy way of saying "find each top-level bracket group." The depth counter is the key โ€” depth 0 to 1 marks the start of a primitive, depth 1 to 0 marks its end.The depth-filter approach is the cleanest solution โ€” one pass, one counter, one condition. No substring, no pointers, just elegance. Master this alongside LeetCode 20 and 1614 and you will have a complete toolkit for any bracket string problem that comes your way.

StringStackEasyLeetCode
Search in Rotated Sorted Array II โ€“ Binary Search with Duplicates Explained (LeetCode 81)

Search in Rotated Sorted Array II โ€“ Binary Search with Duplicates Explained (LeetCode 81)

Try the QuestionBefore reading the explanation, try solving the problem yourself:๐Ÿ‘‰ https://leetcode.com/problems/search-in-rotated-sorted-array-ii/Practicing the problem first helps develop stronger problem-solving intuition, especially for binary search variations.Problem StatementYou are given an integer array nums that is sorted in non-decreasing order.Example of a sorted array:[0,1,2,4,4,4,5,6,6,7]Before being passed to your function, the array may be rotated at some pivot index k.After rotation, the structure becomes:[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]Example:Original array[0,1,2,4,4,4,5,6,6,7]Rotated at index 5[4,5,6,6,7,0,1,2,4,4]You are also given an integer target.Your task is to determine:Return true if the target exists in the arrayReturn false if the target does not existThe goal is to minimize the number of operations, which suggests using Binary Search.Example WalkthroughExample 1Inputnums = [2,5,6,0,0,1,2]target = 0OutputtrueExplanation:0 exists in the arrayExample 2Inputnums = [2,5,6,0,0,1,2]target = 3OutputfalseExplanation:3 does not exist in the arrayUnderstanding the Core ChallengeThis problem is very similar to the classic problem:Search in Rotated Sorted Array (LeetCode 33).However, there is an important difference.Difference Between the Two ProblemsProblemArray ValuesRotated Sorted ArrayAll elements are distinctRotated Sorted Array IIArray may contain duplicatesDuplicates introduce ambiguity during binary search.Why Duplicates Make the Problem HarderIn the previous problem, we relied on this rule:If nums[left] <= nums[mid]โ†’ left half is sortedBut duplicates can break this assumption.Example:nums = [1,0,1,1,1]If:left = 0mid = 2right = 4Then:nums[left] = 1nums[mid] = 1nums[right] = 1Here we cannot determine which half is sorted.This is the main complication introduced by duplicates.Key Idea to Handle DuplicatesWhen the values at left, mid, and right are the same, the algorithm cannot decide which half is sorted.To resolve this situation, we shrink the search space:left++right--This gradually removes duplicate values and allows binary search to continue.Modified Binary Search StrategyThe algorithm works as follows:Step 1Calculate the middle index.Step 2If the middle element equals the target:return trueStep 3If duplicates block decision-making:nums[left] == nums[mid] == nums[right]Then move both pointers inward:left++right--Step 4Otherwise, determine which half is sorted and apply normal binary search logic.Java Implementationclass Solution { public boolean search(int[] nums, int target) { int l = 0; int r = nums.length - 1; while (l <= r) { int mid = l + (r - l) / 2; if (nums[mid] == target) return true; // Handle duplicates if (nums[l] == nums[mid] && nums[mid] == nums[r]) { l++; r--; } // Left half sorted else if (nums[l] <= nums[mid]) { if (nums[l] <= target && target < nums[mid]) { r = mid - 1; } else { l = mid + 1; } } // Right half sorted else { if (nums[mid] < target && target <= nums[r]) { l = mid + 1; } else { r = mid - 1; } } } return false; }}Step-by-Step ExampleArray:[2,5,6,0,0,1,2]target = 0Iteration 1mid = 6value = 0Target found immediately.return trueTime ComplexityBest CaseO(log n)When duplicates do not interfere with binary search decisions.Worst CaseO(n)When many duplicate values force the algorithm to shrink the search space one element at a time.Example worst case:[1,1,1,1,1,1,1,1,1]Binary search cannot divide the array effectively.Space ComplexityO(1)The algorithm only uses a few variables and does not require extra memory.Follow-Up: How Do Duplicates Affect Runtime?Without duplicates, binary search always reduces the search space by half.Time Complexity โ†’ O(log n)With duplicates, we sometimes cannot determine which half is sorted.In such cases, we shrink the search space linearly:left++right--This may degrade performance to:Worst Case โ†’ O(n)However, in most practical cases the algorithm still performs close to logarithmic time.Key Takeawaysโœ” The array is sorted but rotatedโœ” Duplicates introduce ambiguity in binary searchโœ” Special handling is required when nums[left] == nums[mid] == nums[right]โœ” The algorithm combines binary search with duplicate handlingโœ” Worst-case complexity may degrade to O(n)Final ThoughtsThis problem is a natural extension of the rotated sorted array search problem. It tests your ability to adapt binary search to more complex conditions.Understanding this pattern is valuable because similar techniques appear in many interview problems involving:Rotated arraysBinary search edge casesHandling duplicates in sorted data structuresMastering this approach strengthens both algorithmic thinking and interview preparation.

LeetCodeBinary SearchRotated Sorted ArrayJavaMedium
Ai Assistant Kas