<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yovo Manolov</title>
    <description>The latest articles on DEV Community by Yovo Manolov (@yovo_manolov).</description>
    <link>https://dev.to/yovo_manolov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3150179%2F54fda11e-1f75-4a9f-8978-9701d8d2fe63.jpg</url>
      <title>DEV Community: Yovo Manolov</title>
      <link>https://dev.to/yovo_manolov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yovo_manolov"/>
    <language>en</language>
    <item>
      <title>Fraudulent activity problem from Hackerrank - sorting problems. Three different solutions. ( min-max heap, counting sort)</title>
      <dc:creator>Yovo Manolov</dc:creator>
      <pubDate>Thu, 29 May 2025 21:38:38 +0000</pubDate>
      <link>https://dev.to/yovo_manolov/fraudulent-activity-problem-from-hackerrank-sorting-problems-three-different-solutions--15oe</link>
      <guid>https://dev.to/yovo_manolov/fraudulent-activity-problem-from-hackerrank-sorting-problems-three-different-solutions--15oe</guid>
      <description>&lt;p&gt;Reference link to the problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hackerrank.com/challenges/fraudulent-activity-notifications/problem" rel="noopener noreferrer"&gt;https://www.hackerrank.com/challenges/fraudulent-activity-notifications/problem&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The problem is the following: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We need to monitor a client's expenditure over a period and issue notifications if the expenditure for a given day is at least twice the median of the expenditure for the preceding 'd' days. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So what is a median: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The median of a finite list of numbers is the "middle" number when those numbers are listed in order from smallest to greatest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ref:&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/Median" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Median&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution I tried before looking out for optimization options is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
     * Complete the 'activityNotifications' function below.
     *
     * The function is expected to return an INTEGER.
     * The function accepts following parameters:
     *  1. INTEGER_ARRAY expenditure  
     *  2. INTEGER d
     */


    public static int activityNotifications(List&amp;lt;Integer&amp;gt; expenditure, int d) {
        int noticesNumber = 0;

        for (int currentDay = d; currentDay &amp;lt; expenditure.size(); currentDay++) {
            // Extract and sort the sublist of the last 'd' expenditures
            List&amp;lt;Integer&amp;gt; sublistToCheck = expenditure
                    .subList(currentDay - d, currentDay)
                    .stream()
                    .sorted()
                    .collect(Collectors.toList());

            // Calculate the median
            double medianValue;
            if (d % 2 == 1) {
                // Odd number of days: single middle value
                medianValue = sublistToCheck.get(d / 2);
            } else {
                // Even number of days: average of the two middle values
                medianValue = (sublistToCheck.get(d / 2 - 1) 
                                + sublistToCheck.get(d / 2)) / 2.0;
            }

            // Check if the current day's expenditure is at least twice the median
            if (expenditure.get(currentDay) &amp;gt;= medianValue * 2) {
                noticesNumber++;
            }
        }

        return noticesNumber;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;Non-optimized algorithm analysis: *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialization
`
'noticesNumber' is initialized to 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main loop runs from "currentDay=d" because we need at least 'd' days of data to consider issuing a notification. &lt;br&gt;
`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract and sort a sublist:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;` For each day 'currentDay' we extract the sublist from 'currentDay - d' to 'currentDay'. &lt;/p&gt;

&lt;p&gt;The sublist represents the last 'd' days' expenditures. &lt;/p&gt;

&lt;p&gt;We sort this sublist to facilitate median calculation. &lt;br&gt;
`&lt;br&gt;
Median calculation: &lt;/p&gt;

&lt;p&gt;`if 'd' is odd, the median is the middle element of the sorted sublist. &lt;/p&gt;

&lt;p&gt;if 'd' is even, the median is the average of the two middle elements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note that for even 'd', the average should be calculated using floating point division to ensure precision.`&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notification check : &lt;/p&gt;

&lt;p&gt;` We compare the current day's expenditure twice the calculated median. &lt;/p&gt;

&lt;p&gt;If the current day's expenditure is at least twice the median, we increment 'noticesNumber'. &lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Time complexity analysis of the non-optimized solution: *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;main loop&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main loop runs n-d times if 'expenditure.size()' is equal to 'n'&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;em&gt;sublist extraction and sorting *&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each iteration of the loop, we extract a sublist of size 'd' from 'expenditure'. This operation has a time complexity of O(d). &lt;/p&gt;

&lt;p&gt;The sublist is then sorted using stream.sorted(), which has time complexity of O(dlogd). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;median calculation &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Calculating the median involves accessing one or two elements of the sorted sublist. The operation is O(1).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notification check &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comparison of the current day's expenditure with twice the median value is simple arithmetic operation, which is O(1)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total time complexity &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sublist extraction and sorting: O(d) + O(dlogd) = O(dlogd) for each iteration.&lt;/p&gt;

&lt;p&gt;Median calculation and comparison: O(1) for each iteration. &lt;/p&gt;

&lt;p&gt;Since the dominant term is O(dlogd) for each iteration, &lt;/p&gt;

&lt;p&gt;The total time complexity of the entire loop is: &lt;/p&gt;

&lt;p&gt;_(n-d) * O(dlogd) = O((n-d) * dlogd) _&lt;/p&gt;

&lt;p&gt;Which simplified is &lt;strong&gt;O(n*dlogd)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis of Min-Max Heap optimized solution&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static int activityNotifications(List&amp;lt;Integer&amp;gt; expenditure, int d) {
        if (expenditure == null || expenditure.size() &amp;lt; d) return 0;

        int noticesNumber = 0;
        PriorityQueue&amp;lt;Integer&amp;gt; minHeap = new PriorityQueue&amp;lt;&amp;gt;();
        PriorityQueue&amp;lt;Integer&amp;gt; maxHeap = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());

        for (int i = 0; i &amp;lt; d; i++) {
            addNumber(expenditure.get(i), maxHeap, minHeap);
        }

        for (int i = d; i &amp;lt; expenditure.size(); i++) {
            double median = getMedian(maxHeap, minHeap);

            if (expenditure.get(i) &amp;gt;= 2 * median) {
                noticesNumber++;
            }

            if (!maxHeap.remove(expenditure.get(i - d))) {
                minHeap.remove(expenditure.get(i - d));
            }

            addNumber(expenditure.get(i), maxHeap, minHeap);
        }

        return noticesNumber;
    }

    private static void addNumber(int num, PriorityQueue&amp;lt;Integer&amp;gt; maxHeap, PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {
        if (maxHeap.isEmpty() || num &amp;lt;= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }

        if (maxHeap.size() &amp;gt; minHeap.size() + 1) {
            minHeap.add(maxHeap.poll());
        } else if (minHeap.size() &amp;gt; maxHeap.size()) {
            maxHeap.add(minHeap.poll());
        }
    }

    private static double getMedian(PriorityQueue&amp;lt;Integer&amp;gt; maxHeap, PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {
        if (maxHeap.size() == minHeap.size()) {
            return ((double) maxHeap.peek() + minHeap.peek()) / 2;
        } else {
            return maxHeap.peek();
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The solution above uses two heaps : 
&lt;/h2&gt;

&lt;p&gt;a min-heap : &lt;/p&gt;

&lt;p&gt;and a max heap &lt;/p&gt;

&lt;p&gt;You can brush up your heap knowledge from this article: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.geeksforgeeks.org/difference-between-min-heap-and-max-heap/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/difference-between-min-heap-and-max-heap/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In a Min-Heap the minimum key element is present at the root.&lt;/p&gt;
&lt;h2&gt;
  
  
  - In a Max-Heap the maximum key element is present at the root.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Algorithm breakdown:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialization&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;'noticesNumber' is initialized at 0; &lt;/p&gt;

&lt;p&gt;Two heaps are used: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; maxHeap (a max-heap) stores the lower half of the numbers.&lt;/li&gt;
&lt;li&gt; minHeap (a min-heap) stores the upper half of the numbers. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Initial Population&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ...
        for (int i = 0; i &amp;lt; d; i++) {
            addNumber(expenditure.get(i), maxHeap, minHeap);
        }
       ...
          private static void addNumber(int num, 
                   PriorityQueue&amp;lt;Integer&amp;gt; maxHeap,
                   PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {

         /*
         peek - Retrieves, but does not remove, the head of this queue, 
                         or returns null if  this queue is empty. 
        */
        if (maxHeap.isEmpty() || num &amp;lt;= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }

        if (maxHeap.size() &amp;gt; minHeap.size() + 1) {

            /* poll - Retrieves and removes the head of this queue, 
                  or returns null if this queue is empty  */ 

            minHeap.add(maxHeap.poll());
        } else if (minHeap.size() &amp;gt; maxHeap.size()) {
            maxHeap.add(minHeap.poll());
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the first 'd' days, add each expenditure to the appropriate heap using 'addNumber' method. &lt;/p&gt;

&lt;h2&gt;
  
  
  - Processing subsequent days
&lt;/h2&gt;

&lt;p&gt;For each day from 'd' to the end of the expenditure list: &lt;/p&gt;

&lt;p&gt;Compute the median using the 'getMedian' method: &lt;/p&gt;

&lt;p&gt;Check if the current day's expenditure is at least twice the median. If so increment 'noticesNumber' &lt;/p&gt;

&lt;p&gt;Remove the expenditure From 'd' days ago from the appropriate heap. &lt;/p&gt;

&lt;h2&gt;
  
  
   Add the current day's expenditure to the appropriate heap. 
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Heap operations:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;'addNumber': Adds a number to one of the heaps and balances the heaps if necessary&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; private static void addNumber(int num, PriorityQueue&amp;lt;Integer&amp;gt; maxHeap, PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {
        if (maxHeap.isEmpty() || num &amp;lt;= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }

        if (maxHeap.size() &amp;gt; minHeap.size() + 1) {
            minHeap.add(maxHeap.poll());
        } else if (minHeap.size() &amp;gt; maxHeap.size()) {
            maxHeap.add(minHeap.poll());
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;'getMedian': Computes the median based on the sizes and top elements of the heap&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; private static double getMedian(PriorityQueue&amp;lt;Integer&amp;gt; maxHeap, PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {
        if (maxHeap.size() == minHeap.size()) {
            return ((double) maxHeap.peek() + minHeap.peek()) / 2;
        } else {
            return maxHeap.peek();
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time complexity analysis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heap operation &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Insertion('addNumber'): Each insertion operation into a heaps takes O(logd)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The initial population of Heaps&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;For the first 'd' elements, we perform 'addNumber' which is O(logd) per insertion, therefore it takes *&lt;em&gt;O(dlogd) *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; private static void addNumber(int num, PriorityQueue&amp;lt;Integer&amp;gt; maxHeap, PriorityQueue&amp;lt;Integer&amp;gt; minHeap) {
        if (maxHeap.isEmpty() || num &amp;lt;= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }

        if (maxHeap.size() &amp;gt; minHeap.size() + 1) {
            minHeap.add(maxHeap.poll());
        } else if (minHeap.size() &amp;gt; maxHeap.size()) {
            maxHeap.add(minHeap.poll());
        }
    }  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;** Time complexity of insertion into a heap: **&lt;/p&gt;

&lt;p&gt;Inserting an element into a heap takes O(logd) for a heap of size 'd' because the heap needs to maintain its properties (binary tree structure and heap property)&lt;/p&gt;

&lt;p&gt;Rebalancing heaps: &lt;/p&gt;

&lt;p&gt;When 'maxHeap' and 'minHeap' are unbalanced, the method involves two primary operations: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removing the root of one heap, which takes O(logd)&lt;/li&gt;
&lt;li&gt;Inserting this element into the other heap, which also takes *&lt;em&gt;O(log d) *&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since rebalancing involves one removal and one insertion, the total time for rebalancing is: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;O(logd) + O(logd) =2*O(logd) = O(logd)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In conclusion the Total time complexity for each call to 'addNumber' involves: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; One insertion into a heap: O(logd)&lt;/li&gt;
&lt;li&gt; Potentially one rebalancing operation O(logd) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since these operations are sequential and not nested, &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;the total time complexity for each call to 'addNumber' remains O(logd) *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Processing subsequent days&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each of the remaining n - d days: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute the median O(1)&lt;/li&gt;
&lt;li&gt;Remove the old expenditure O(logd)&lt;/li&gt;
&lt;li&gt;Add the new expenditure: O(logd)&lt;/li&gt;
&lt;li&gt;Each day's operation takes O(logd), and we perform these operations for n-d days, resulting in O((n-d) logd)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Overall the total time complexity of the min-max heap solution is &lt;/p&gt;

&lt;p&gt;Initial heap population: O(dlogd)&lt;/p&gt;

&lt;p&gt;processing remaining days: O((n-d)logd)&lt;/p&gt;

&lt;p&gt;=&amp;gt; O(dlogd) + O((n-d)*logd) = O(nlogd)&lt;/p&gt;

&lt;p&gt;Which is significantly more efficient than the O(n*dlogd) time complexity of the non-optimized solution.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Summary of the min-max heap solution: *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The min-max heap solution efficiency maintains the median for each sliding window of size 'd' using 2 heaps. By leveraging the properties of the heaps, it achieves a time complexity of O(nlogd), making it well-suited for large datasets, compared to the non-optimized sorting-based approach. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counting sort solution&lt;/strong&gt;&lt;br&gt;
The implementation of the Counting sort optimization is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.util.List;

public class Result {

    public static int activityNotifications(List&amp;lt;Integer&amp;gt; expenditure, int d) {
        int noticesNumber = 0;
       // Assuming expenditures are between 0 and 200 
       // as stated nin th task description. 
       int[] count = new int[201]; 

        // Initialize the count array with the first 'd' expenditures
        for (int i = 0; i &amp;lt; d; i++) {
            count[expenditure.get(i)]++;
        }

        for (int i = d; i &amp;lt; expenditure.size(); i++) {
            int medianValue = getMedian(count, d);

            if (expenditure.get(i) &amp;gt;= 2 * medianValue) {
                noticesNumber++;
            }

            // Update the count array for the sliding window
            count[expenditure.get(i)]++;
            count[expenditure.get(i - d)]--;
        }

        return noticesNumber;
    }


       /*
        If d is even, we need to find the two middle values and take their average.
      If d is odd, we find the single middle value.
      We traverse the count array, accumulating counts until we reach the required                positions to determine the median.
         */


    private static int getMedian(int[] count, int d) {
        int sum = 0;

       int median1 = -1;
        int median2 = -1;

        if (d % 2 == 0) { 

            for (int i = 0; i &amp;lt; count.length; i++) {
                sum += count[i];
                if (median1 == -1 &amp;amp;&amp;amp; sum &amp;gt;= d / 2) {
                    median1 = i;
                }
                if (sum &amp;gt;= d / 2 + 1) {
                    median2 = i;
                    break;
                }
            }
            return (median1 + median2) / 2;
        } else {  
            for (int i = 0; i &amp;lt; count.length; i++) {
                sum += count[i];
                if (sum &amp;gt; d / 2) {
                    return i;
                }
            }
        }

        return 0;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Algorithm explanation: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialization:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;'noticesNumber' initialized to 0. &lt;/p&gt;

&lt;p&gt;A count array of size 201 is used to keep track of the frequency of expenditures which are restricted by the task conditions within a range of 0 to 200. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The initial population of the count array: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;for (int i = 0; i &amp;lt; d; i++) {&lt;br&gt;
            count[expenditure.get(i)]++;&lt;br&gt;
        }&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The first 'd' elements of the 'expenditure' list are used to populate the 'count' array.&lt;/p&gt;

&lt;p&gt;Processing subsequent days: &lt;/p&gt;

&lt;p&gt;` for (int i = d; i &amp;lt; expenditure.size(); i++) {&lt;br&gt;
            int medianValue = getMedian(count, d);&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        if (expenditure.get(i) &amp;gt;= 2 * medianValue) {
            noticesNumber++;
        }

        // Update the count array for the sliding window
        count[expenditure.get(i)]++;
        count[expenditure.get(i - d)]--;
    }`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For each day from 'd' to the end of the 'expenditure' list: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Compute the median using the 'getMedian' method&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check if the current day's expenditure is at least twice the median. If so, increment 'noticesNumber' &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the 'count' array to reflect the new sliding window&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;Median calculation *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static int getMedian(int[] count, int d) {
        int sum = 0;

       int median1 = -1;
        int median2 = -1;

        if (d % 2 == 0) { 

            for (int i = 0; i &amp;lt; count.length; i++) {
                sum += count[i];
                if (median1 == -1 &amp;amp;&amp;amp; sum &amp;gt;= d / 2) {
                    median1 = i;
                }
                if (sum &amp;gt;= d / 2 + 1) {
                    median2 = i;
                    break;
                }
            }
            return (median1 + median2) / 2;
        } else {  
            for (int i = 0; i &amp;lt; count.length; i++) {
                sum += count[i];
                if (sum &amp;gt; d / 2) {
                    return i;
                }
            }
        }

        return 0;
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The median method traverses the 'count' array to find the median&lt;/p&gt;

&lt;p&gt;For even 'd', it finds the two median values and returns their average&lt;/p&gt;

&lt;p&gt;For odd 'd' it finds the single middle value. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time complexity analysis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The initial population of the count array &lt;/p&gt;

&lt;p&gt;Involves iterating through the first 'd' elements of the expenditure list: ** O(d)**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing subsequent days: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each of the remaining n-d days: &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Median Calculation ('getMedian' method) *&lt;/em&gt; : &lt;/p&gt;

&lt;p&gt;Traversing the 'count' array to find the median takes O(R), where R is the range of possible expenditures (201 in our case)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Updating the count array&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Updating the count array involves two constant-time operations for each day.&lt;/p&gt;

&lt;p&gt;Time complexity for each day: O(R) + O(1) = O(R) &lt;/p&gt;

&lt;p&gt;Since we perform these operations for n-d days, the total time complexity of the loop is O((n-d)*R)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total time complexity - O(n)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining the complexities, we get: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Initial population of the count array: O(d)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Processing the remaining days : O((n-d) *R) &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overal, the total time complexity is: &lt;/p&gt;

&lt;p&gt;O(d) + O((n-d) R) = O(d) + (n*R) = O(n*R) &lt;/p&gt;

&lt;p&gt;and given that R is 201 we can simplify to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O(n)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison of the min-max heap solution to the counting sort solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on time complexity:&lt;/p&gt;

&lt;p&gt;The counting sort approach has a time complexity of O(nd)&lt;/p&gt;

&lt;p&gt;while the min-max heap approach has a time complexity of O(nlogd)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Winner: The min-max heap approach is generally more efficient in terms of time complexity, especially when 'd' is large.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most practical scenarios where efficiency is crucial, and 'd' is relatively large, the min-max heap approach is better. However, for cases with a small fixed range of expenditures, the counting sort approach might be sufficient and simpler to implement.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>dsa</category>
      <category>java</category>
    </item>
    <item>
      <title>Sherlock and anagrams (hacker rank) from the Dictionaries and Hashmaps section + extensive time and space complexity analysis.</title>
      <dc:creator>Yovo Manolov</dc:creator>
      <pubDate>Thu, 29 May 2025 21:10:30 +0000</pubDate>
      <link>https://dev.to/yovo_manolov/sherlock-and-anagrams-hacker-rank-from-the-dictionaries-and-hashmaps-section-extensive-time-and-1hdg</link>
      <guid>https://dev.to/yovo_manolov/sherlock-and-anagrams-hacker-rank-from-the-dictionaries-and-hashmaps-section-extensive-time-and-1hdg</guid>
      <description>&lt;p&gt;Hi, guys, today we will discuss the problem "Sherlock and anagrams" &lt;/p&gt;

&lt;p&gt;The link to the problem is the following: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hackerrank.com/challenges/sherlock-and-anagrams/problem?h_l=interview&amp;amp;playlist_slugs%5B%5D=interview-preparation-kit&amp;amp;playlist_slugs%5B%5D=dictionaries-hashmaps" rel="noopener noreferrer"&gt;https://www.hackerrank.com/challenges/sherlock-and-anagrams/problem?h_l=interview&amp;amp;playlist_slugs%5B%5D=interview-preparation-kit&amp;amp;playlist_slugs%5B%5D=dictionaries-hashmaps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Short description of the problem: &lt;/p&gt;

&lt;p&gt;Anagram definition:&lt;br&gt;
 Two strings are &lt;strong&gt;anagrams&lt;/strong&gt; of each other if the letters of one string can be rearranged to form the other string.&lt;/p&gt;

&lt;p&gt;The task is to: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find the number of pairs of substrings of a given string that are anagrams of each other.&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;Example 1: &lt;/p&gt;

&lt;p&gt;string = mom &lt;/p&gt;

&lt;p&gt;The list of all anagrammatic pairs is: [m,m], [mo, om] at positions [[0],[2]], [[0.1], [1,2]] respectivly. The result will be 2, as we have 2 anagrammatic pairs.&lt;/p&gt;



&lt;p&gt;We have a function sherlockAndAnagrams that has the following parameter(s):&lt;/p&gt;

&lt;p&gt;string s: the given string&lt;/p&gt;
&lt;h2&gt;
  
  
  The function should return the number of unordered anagrammatic pairs of substrings in s
&lt;/h2&gt;

&lt;p&gt;Example 2:&lt;/p&gt;

&lt;p&gt;string: abba &lt;/p&gt;

&lt;p&gt;anagrammatic pairs: ( a, a | b, b | ab, ba| abb, bba ) which are 4 in total. &lt;/p&gt;



&lt;p&gt;So, the important thing for me is the thought process when I solve those tasks. &lt;/p&gt;

&lt;p&gt;What I was initially thinking when I read the problem description was the following: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Those are dictionary problems, so we need to use dictionary data structures. &lt;/li&gt;
&lt;li&gt;We need to convert the string to a char array to manipulate it easier. &lt;/li&gt;
&lt;li&gt;We need to store the substrings with equal length in separate hashmaps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;... but in order to make the processing easy we may use embedded hashmap. The outer hashmap will store the length of the substrings in the inner hashmap, and the inner hashmap will store the strings themselves as a key and the occurrence count as a value. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The hardest thing for me to figure out was that, to count the pairs, they need to be equal as a value. Otherwise, I couldn't do it, so, before adding the strings to the HashMap with correspondent length, we need to use sorting on the substring, to make the anagram pairs look alike. If we represent the substrings as a char array, the sorting could be easily done in Java using the method java.util.Arrays.sort(string).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And the last thing is that if we keep the count of equal substrings with the same length we need a way to calculate the pairs. This can be achieved using this formula :&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;**  pairsCount = (numberOfEqualSubstrings * (numberOfEqualSubstrings - 1) )/2&lt;br&gt;&lt;br&gt;
** &lt;br&gt;
or  &lt;strong&gt;pc = (n*(n-1))/2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;let's see how this works: &lt;/p&gt;

&lt;p&gt;if we have the substrings - "a" "a" "a" "a" in the inner HashMap &lt;/p&gt;

&lt;p&gt;with values HashMap&amp;lt;"a", 6&amp;gt; for example then we shoud have 6 anagram pairs&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;(4*3)/2 = 6 *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;the 6 anagrammatic pairs are &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kx78v929ry7hrpmxulb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kx78v929ry7hrpmxulb.png" alt="Image description" width="318" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;//letter(index)&lt;/p&gt;

&lt;p&gt;a(0) a(1) &lt;/p&gt;

&lt;p&gt;a(1) a(2)&lt;/p&gt;

&lt;p&gt;a(2) a(3)&lt;/p&gt;

&lt;p&gt;a(0) a(2)&lt;/p&gt;

&lt;p&gt;a(1) a(3)&lt;/p&gt;

&lt;p&gt;a(0) a(3)&lt;/p&gt;

&lt;p&gt;Which are 6 pairs &lt;/p&gt;

&lt;p&gt;So an example solution to the problem is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static int sherlockAndAnagrams(String s) {

    // HashMap&amp;lt;AnagramLength, HashMap&amp;lt;SortedSubstring, Counter&amp;gt;&amp;gt;
    HashMap&amp;lt;Integer, HashMap&amp;lt;String, Integer&amp;gt;&amp;gt; hmAnagrams = new HashMap&amp;lt;&amp;gt;();


    //mininum length of substring is 1 and max is length-1     
    for (int substrLength = 1; substrLength &amp;lt; s.length(); substrLength++) {

        //inner HashMap&amp;lt;equalSubstrings, equalSubstringsCounter&amp;gt;
        HashMap&amp;lt;String, Integer&amp;gt; stringCounterHashMap = new HashMap&amp;lt;&amp;gt;();

        // from index 0 to index (stringLength - substringLength) 
        //so we don't get IndexOutOfBounds exception.
        for (int start = 0; start &amp;lt;= s.length() - substrLength; start++) {

            String substrValue = s.substring(start, start + substrLength);

            String sortedSubstrValue = sortString(substrValue);

            stringCounterHashMap.put(sortedSubstrValue, 
                                    stringCounterHashMap.
                                        getOrDefault(sortedSubstrValue, 0) + 1);
        }

        hmAnagrams.put(substrLength, stringCounterHashMap);
    }

    // Calculate the number of anagrammatic pairs
    int count = 0;
    for (HashMap&amp;lt;String, Integer&amp;gt; map : hmAnagrams.values()) {
        for (int value : map.values()) {
            // if value is 1  we don't have a pair.
            if (value &amp;gt; 1) { 
                // If there are n occurrences of a substring,
                // we can form n * (n - 1) / 2 pairs
                count += (value * (value - 1)) / 2;
            }
        }
    }

    return count;
}

// Helper function to sort characters of a string
private static String sortString(String inputString) {
    char[] tempArray = inputString.toCharArray();
    java.util.Arrays.sort(tempArray);
    return new String(tempArray);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time and Space complexity analysis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this article, I'll dive a little deeper into time and space complexity analysis, so if you don't enjoy this part as much you're free to skip it.&lt;/p&gt;

&lt;p&gt;Time complexity: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outer loop over Substring lengths: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;for (int substrLength = 1; substrLength &amp;lt; s.length(); substrLength++) {&lt;br&gt;
...&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This loop runs in &lt;strong&gt;n - 1&lt;/strong&gt; times where &lt;strong&gt;n&lt;/strong&gt; is the length of the input string.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inner loop over given string indexes:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;for (int start = 0; start &amp;lt;= s.length() - substrLength; start++) {&lt;br&gt;
...&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For each substring length "substrLength", the inner loop runs n - substrLength +1 times. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Substring and sorting: &lt;br&gt;
&lt;code&gt;String substrValue = s.substring(start, start + substrLength);&lt;br&gt;
String sortedSubstrValue = sortString(substrValue);&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple logarithm examples: &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25oes2qeqo3k3ezau54r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25oes2qeqo3k3ezau54r.png" alt="Image description" width="566" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To refresh your log knowledge refer to the link in the image description above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for reference here is the algorithms time complexity chart: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuv1ob8qb3pbca1l78yab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuv1ob8qb3pbca1l78yab.png" alt="Image description" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HashMap put and getOrDefault operations take O(1) constant runtime complexity on average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining all these steps, the time complexity for processing each substring can be approximated by: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if we represent "substrLength" as n , then &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O(n + nlogn) = O(nlogn) &lt;/p&gt;

&lt;p&gt;So why is this expression true?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The expression O(n+nlogn) simplifies to O(n log n) due to the properties of the Big-O notation. 

Big O notation describes the upper bound of an algorithm's complexity, focusing on the dominant term(s) as input size n grows. Here is why O(n + nlogn) = (Onlogn): 

1. Dominant Term: In the expression O(n+ nlogn)  

* n grows learly 
*  nlogn grows faster than n for sufficiently large n because log n 
(though it grows slower than any polynomial ) 
is still an icreasing function. 

(definition of polynomial function: A polynomial function is a function such as a quadratic, a cubic, a quartic, and so on, involving only non-negative integer powers of x)

2. Asymptotic Behaviour: 

As n becomes very large, the nlogn term will dominate the n term. 
This is because the logarithmic factor makes nlogn grow faster than n alone. 

For example: 

When n = 10, log n ≈ 3.3, so nlog ≈ 33
When n = 100, log n  ≈ 6.6 so nlogn ≈ 66
When n = 1000, log n ≈ 1000 so nlogn ≈ 9900 

In each case nlogn grows significantly faster than n. 

3. Big O simplification 

In Big-O notation, we only consider the term with the highest growth rate because it dominates the overall complexity as n increases. Lower-order terms and constant factors are omitted. Therefor,  O(n+nlogn) simplifies to O(nlogn) because : 

n+nlogn &amp;lt;= c * nlogn for same constant c and sufficiently large n.

In conclusion, O(n+nlogn) = O(n logn) because the nlogn term dominates the liner n term as n grows larger.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we only discuss the inner for loop that processes substrings of equal length then we can look at this logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (int start = 0; start &amp;lt;= s.length() - substrLength; start++) {
       String substrValue = s.substring(start, start + substrLength);

            String sortedSubstrValue = sortString(substrValue);

            stringCounterHashMap.put(sortedSubstrValue, 
                                    stringCounterHashMap.
                                        getOrDefault(sortedSubstrValue, 0) + 1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl87ulev80xagq33shbya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl87ulev80xagq33shbya.png" alt="Image description" width="628" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the inner for loop time complexity is О(n^2logn) and the Outer loop has approximate &lt;/p&gt;

&lt;p&gt;time complexity of O(n), so we can say that the total time complexity is :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiji274be2xaxf8sqb31f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiji274be2xaxf8sqb31f.png" alt="Image description" width="256" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  *&lt;em&gt;Space Complexity: *&lt;/em&gt;
&lt;/h1&gt;

&lt;p&gt;*&lt;em&gt;HashMaps: *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hmAnagrams stores a HashMap for each substring length with all unique sorted substrings as keys. &lt;/li&gt;
&lt;li&gt;The number of unique substrings of any length is at most n(n+1)/2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnwu3i3kc7c3x5nnez3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnwu3i3kc7c3x5nnez3u.png" alt="Image description" width="539" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Substrings and Sorted substrings: *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each sorted substring is stored in the inner HashMap, leading to O(n^2) space for substrings &lt;/li&gt;
&lt;li&gt;The space complexity for all substrings combined is O(n^3) in the worst case because we store O(n^2) substrings, each of length up to n&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz8cga4kzy6jy5osd77d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz8cga4kzy6jy5osd77d.png" alt="Image description" width="694" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auxiliary Space:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temp variables, counters, and all other small variables contribute negligible space compared to the HashMaps. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed this one as well. &lt;/p&gt;

</description>
      <category>algs</category>
      <category>dsa</category>
    </item>
    <item>
      <title>Array Manipulation Hackerrank problem analysis and solution optimization (prefix sum + difference array)</title>
      <dc:creator>Yovo Manolov</dc:creator>
      <pubDate>Thu, 29 May 2025 20:50:34 +0000</pubDate>
      <link>https://dev.to/yovo_manolov/array-manipulation-hackerrank-problem-analysis-and-solution-optimization-prefix-sum-difference-5546</link>
      <guid>https://dev.to/yovo_manolov/array-manipulation-hackerrank-problem-analysis-and-solution-optimization-prefix-sum-difference-5546</guid>
      <description>&lt;p&gt;Hi, guys, today I want to analyze the Array Manipulation problem from Hackerrank. We will take a look at a suboptimal solution, which is naturally easiest to figure out. Then we will discuss, analyze, and understand an optimized version that solves this problem which uses prefix sum and a difference array to achieve the needed time complexity.&lt;/p&gt;

&lt;p&gt;For starters, this is the link to the problem: &lt;a href="https://www.hackerrank.com/challenges/crush/problem?h_l=interview%5B%5D=interview-preparation-kit&amp;amp;playlist_slugs%5B%5D=arrays" rel="noopener noreferrer"&gt;https://www.hackerrank.com/challenges/crush/problem?h_l=interview%5B%5D=interview-preparation-kit&amp;amp;playlist_slugs%5B%5D=arrays&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Short problem description:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo3mn9prmisi2tl0jnbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo3mn9prmisi2tl0jnbr.png" alt="Image description" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, we have "n" which is the length of the array that we will populate, taking the data from the double LinkedList queries parameter. &lt;/p&gt;

&lt;p&gt;For example, the linked list contains the following data: &lt;/p&gt;

&lt;p&gt;queries = [[1,5,3],[4,8,7],[6,9,1]]&lt;/p&gt;

&lt;p&gt;each sublist has 3 integers e.g. [1,5,3] &lt;/p&gt;

&lt;p&gt;(1) is the one-based index, from which we need to start populating the n-length array. &lt;br&gt;
(5) is also a one-based index where we need to stop populating the array. &lt;br&gt;
(3) that's the value that we will use to populate the array. &lt;/p&gt;

&lt;p&gt;When I say populate In the task the population is an addition operation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpmch5pvfm1t9a8r1wxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpmch5pvfm1t9a8r1wxw.png" alt="Image description" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The expected return value of the method after all iterations is to return the max element value from the array with length n&lt;/p&gt;

&lt;p&gt;In the example above that's the value 10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suboptimal solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So my solution was not optimal but it demonstrates the basic logic that needs to be applied to solve the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static long arrayManipulation(int n, List&amp;lt;List&amp;lt;Integer&amp;gt;&amp;gt; queries) {

        long maxVal = Integer.MIN_VALUE;

        int[] calcArray = new int[n];  // the array in which the data will be calculated

        for(List&amp;lt;Integer&amp;gt; q : queries){  //queries length  can be up to 2*10^5 
            int fromIndex = q.get(0);        
            int toIndex = q.get(1);
            int valueToApply= q.get(2);

             // here I use the same array to do the addition operation as well as 
            // finding the max value in the array.
            for(int j=fromIndex-1 ;j &amp;lt; toIndex  ;j++){
                calcArray[j] += valueToApply;    
                if(maxVal &amp;lt; calcArray[j]) maxVal = calcArray[j];
            }        
        }

        return maxVal;
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worst case time complexity of this solution is O(m*n) &lt;/p&gt;

&lt;p&gt;where:&lt;/p&gt;

&lt;p&gt;m - number of queries&lt;/p&gt;

&lt;p&gt;n - is the scope (fromIndex-1 - toIndex-1)&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Space complexity: *&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The array:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;calcArray is of size n, so it requires O(n) space.&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Queries: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;queries list contains 'm' sublists each of 3 integers. The list requires O(m) space.&lt;br&gt;
So the overall space complexity is O(n+m)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So the overall space complexity is &lt;strong&gt;O(n+m)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The optimised approach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The optimized approach requires a technique named "Prefix sum"&lt;/p&gt;

&lt;p&gt;So what is prefix sum:&lt;/p&gt;

&lt;p&gt;`Input: arr[] = {10, 20, 10, 5, 15}&lt;br&gt;
Output: prefixSum[] = {10, 30, 40, 45, 60}&lt;/p&gt;

&lt;p&gt;Explanation: While traversing the array, update the element by adding it with its previous element. &lt;/p&gt;

&lt;p&gt;prefixSum[0] = 10, &lt;br&gt;
prefixSum[1] = prefixSum[0] + arr[1] = 30, &lt;br&gt;
prefixSum[2] = prefixSum[1] + arr[2] = 40 and so on.`&lt;/p&gt;

&lt;p&gt;How do we combine the prefix sum approach with diffArray to achieve time complexity of O(n+m) for the final updated array, instead of O(n*m) for the previous example&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The purpose of the "difference array" in the prefix sum approach is to efficiently handle range updates on an array, instead of directly updating each element in the specified range of each query. The differenceArray allows us to make constant-time updates, which can be processed to get the final array values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a look at a step-by-step explanation of &lt;strong&gt;how the difference array works&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialization:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we create a difference array of size n+1 to handle the range updates at the boundaries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applying updates: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a query that adds a value v from index a to b:&lt;/p&gt;

&lt;p&gt;Increment diffArray[a-1] by v &lt;br&gt;
(starting the increment at the index a).&lt;/p&gt;

&lt;p&gt;Decrement diffArray[b] by v &lt;br&gt;
(ending the increment after the index b).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculating final values:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use the prefix sum approach described above applied to the difference array to calculate the final array.&lt;/p&gt;

&lt;p&gt;The prefix sum of the &lt;strong&gt;diffArray&lt;/strong&gt; at any point gives us the net effect of all updates up to that index.&lt;/p&gt;

&lt;p&gt;Let's see a simple example of the diff array and prefix sum methods combined: &lt;/p&gt;

&lt;p&gt;If we have the following conditions:&lt;br&gt;
`n = 5  // size of the array&lt;/p&gt;

&lt;p&gt;queries = [&lt;br&gt;
    [1, 2, 100],&lt;br&gt;
    [2, 5, 100],&lt;br&gt;
    [3, 4, 100]&lt;br&gt;
]`&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;we initialize the diffArray which must have a size of n+1 = 6 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;diffArray = [0, 0, 0, 0, 0, 0]&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; We apply the first query [1,2,100]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`//Increment diffArray[a -1] by v  (index 0 by 100)&lt;br&gt;
// Decrement diffArray[b] by v (decr index 2 by 100)&lt;/p&gt;

&lt;p&gt;diffArray = [100, 0, -100, 0, 0, 0]`&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We apply the second query [2,5,100]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`//Increment diffArray[a-1] by v    ( index 1 by 100 ) &lt;br&gt;
// Decrement diffArray[b] by v  (decr index 5 by 100 )&lt;/p&gt;

&lt;p&gt;diffArray = [100, 100, -100, 0, 0, -100]`&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We apply the third query [3,4, 100]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`//Increment diffArray[a-1] by v    ( index 2 by 100 )  from -100 to 0&lt;br&gt;
// Decrement diffArray[b] by v (decr index 4 by 100 ) from 0 to -100&lt;/p&gt;

&lt;p&gt;diffArray = [100, 100, 0, 0, -100, -100]`&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calculate the final values using the prefix sum: 
`currentSum = 0
finalArray[0] = currentSum += diffArray[0] ;
finalArray[1] = currentSum += diffArray[1] ;
finalArray[2] = currentSum += diffArray[2] ;
finalArray[3] = currentSum += diffArray[3] ;
finalArray[4] = currentSum += diffArray[4] ;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;result: &lt;br&gt;
finalArray = [100, 200, 200, 200, 100] `&lt;/p&gt;

&lt;p&gt;Using the difference array, each query update is done in O(1) time complexity, and calculating the final values from the difference array takes O(n) time, making the overall time complexity O(n+m), which makes this approach significantly more time efficient than directly updating the array for each query. The space complexity is O(n) for the diff array and O(m) for the queries.&lt;/p&gt;

&lt;p&gt;Thus, the optimized space complexity remains O(n+m)&lt;/p&gt;

&lt;p&gt;This is an example of the prefix sum + difference array problem solution :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static long arrayManipulation(int n, List&amp;lt;List&amp;lt;Integer&amp;gt;&amp;gt; queries) {
    long[] diffArray = new long[n + 1];  // Using a diff array for range updates

    // Process each query and update the diffArray
    for (List&amp;lt;Integer&amp;gt; q : queries) {
        int fromIndex = q.get(0) - 1;
        int toIndex = q.get(1);
        int valueToApply = q.get(2);

        diffArray[fromIndex] += valueToApply;
        if (toIndex &amp;lt; n) {
            diffArray[toIndex] -= valueToApply;
        }
    }

    // Find the maximum value using the prefix sum array
    long maxVal = Long.MIN_VALUE;
    long currentSum = 0;
    for (int i = 0; i &amp;lt; n; i++) {
        currentSum += diffArray[i];
        if (currentSum &amp;gt; maxVal) {
            maxVal = currentSum;
        }
    }

    return maxVal;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation tips for Java: &lt;/p&gt;

&lt;p&gt;the parallel prefix implementation could be replaced by the&lt;br&gt;
&lt;code&gt;prefixedSumArray = Arrays.parallelPrefix(diffArray, Math::addExact);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;also extracting the max value could be replaced by &lt;br&gt;
&lt;code&gt;Collections.max(Arrays.asList(arr));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you reached the end, I'm glad the article was interesting for you : )&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>dsa</category>
    </item>
    <item>
      <title>New Year Chaos - HackerRank algorithmic problem - clarifying comments</title>
      <dc:creator>Yovo Manolov</dc:creator>
      <pubDate>Tue, 20 May 2025 22:47:40 +0000</pubDate>
      <link>https://dev.to/yovo_manolov/test1-4ne4</link>
      <guid>https://dev.to/yovo_manolov/test1-4ne4</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   The name of the problem is New Year Chaos. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is a link to the problem in HackerRank:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hackerrank.com/challenges/new-year-chaos" rel="noopener noreferrer"&gt;https://www.hackerrank.com/challenges/new-year-chaos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem description :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xeav90oims4203v2ns7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xeav90oims4203v2ns7.png" alt="Image description" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initially, I tried to solve it independently but failed miserably. &lt;/p&gt;

&lt;p&gt;Then I found this video with a good explanation: &lt;/p&gt;

&lt;p&gt;Credits to: &lt;a class="mentioned-user" href="https://dev.to/aditya"&gt;@aditya&lt;/a&gt; Pandey for the solution that I used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=RGv_K72p86M" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=RGv_K72p86M&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I still missed some ideas that the solver (Aditya) had in his mind, so I added some additional comments to his solution when I fully understood the way it works :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; /*
     * Complete the 'minimumBribes' function below.
     *
     * The function accepts INTEGER_ARRAY q as parameter.
     */

    /* If we take an input array of numbers:
              Integer[] arr = {2, 1, 5, 3, 4};
            minimumBribes(Arrays.asList(arr));

    Then the comments in this method apply to the above input array.
    */
    private static void minimumBribes(List&amp;lt;Integer&amp;gt; q) {

        int bribesCounter = 0;
        boolean flag = false;

        for(int i = q.size(); i&amp;gt;=1; i--){

            if(q.get(i-1) != i) { //is the last element different than i (5)

                if((i-2)&amp;gt;=0 &amp;amp;&amp;amp; q.get(i-2)==i){
                    //we have single bribe occurring

                    /*this will handle the first two elements
                    at the second iteration of the base for loop.*/

                    //swap the values when we have single bribe
                    int swap1 = q.get(i-1); //latest unordered element that is not in the correct position
                    int swap2 = q.get(i-2); //the element next to the latest element also not in the correct position.
                    q.set(i-1, swap2);
                    q.set(i-2, swap1);

                    bribesCounter++;
                } else if ((i-3) &amp;gt;= 0 &amp;amp;&amp;amp; q.get(i-3) == i){
                    /*first iteration of the base for loop will first
                     fix the last three numbers in the array: which are 5 ,3 and 2
                    */

                    //double bribe

                    //i-3 is equal to i and i-2 is not
                    //e.g. 2 1 | 5 (i-3)  3 (i-2) 4 (i-1)

                    //swap operations
                    int swap1 = q.get(i-1); //4
                    int swap2 = q.get(i-2); //3
                    int swap3 = q.get(i-3); //5

                    q.set(i-1, swap3); // 5
                    q.set(i-2, swap1); // 4
                    q.set(i-3, swap2); // 3

                    bribesCounter+=2;

                } else {
                    //more than 2 consecutive bribes
                    flag = true;
                    break;
                }
            }
        }

        System.out.println(flag ? "Too chaotic" : bribesCounter);

    }

    public static void main(String[] args) throws IOException {
        Integer[] arr = {2, 1, 5, 3, 4};
        minimumBribes(Arrays.asList(arr));

        Integer[] arr2 = {2, 5, 1, 3, 4};
        minimumBribes(Arrays.asList(arr2));
    }

    /*output :
     ==========  
     3
     Too chaotic
     ===========
     */ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>test</category>
    </item>
  </channel>
</rss>
