{"id":6126,"date":"2025-01-05T19:53:04","date_gmt":"2025-01-05T19:53:04","guid":{"rendered":"https:\/\/algocademy.com\/blog\/mastering-subarray-problem-solving-techniques-a-comprehensive-guide\/"},"modified":"2026-10-15T12:51:16","modified_gmt":"2026-10-15T12:51:16","slug":"mastering-subarray-problem-solving-techniques-a-comprehensive-guide","status":"publish","type":"post","link":"https:\/\/algocademy.com\/blog\/mastering-subarray-problem-solving-techniques-a-comprehensive-guide\/","title":{"rendered":"Subarray Problems: Sliding Window, Kadane, Prefix Sum"},"content":{"rendered":"<p>A subarray is a contiguous slice of an array, and almost every subarray problem is solved by one of four techniques. Use a <strong>sliding window<\/strong> when the values are non-negative or the window has a fixed size, <strong>Kadane&#8217;s algorithm<\/strong> for the best-sum subarray, <strong>prefix sums with a hash map<\/strong> when negatives are allowed and you need a target sum, and <strong>binary search on the answer<\/strong> when you must split an array into k pieces. This guide shows how to tell them apart, with code that has been run.<\/p>\n<p>A subarray is not a subsequence. For <code>[1, 2, 3, 4]<\/code>, <code>[2, 3]<\/code> is a subarray, but <code>[1, 3]<\/code> is not because it skips an element. An array of length n has n(n+1)\/2 subarrays, which is why checking all of them is O(n<sup>2<\/sup>) at best.<\/p>\n<h2>Which technique fits? A quick decision table<\/h2>\n<table>\n<thead>\n<tr>\n<th>The problem says<\/th>\n<th>Technique<\/th>\n<th>Time<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Best sum (or product) of any subarray<\/td>\n<td>Kadane&#8217;s algorithm<\/td>\n<td>O(n)<\/td>\n<\/tr>\n<tr>\n<td>Best subarray of exactly k elements<\/td>\n<td>Fixed sliding window<\/td>\n<td>O(n)<\/td>\n<\/tr>\n<tr>\n<td>Longest or shortest subarray with a condition, all values non-negative (or a count\/distinct-value condition)<\/td>\n<td>Variable sliding window<\/td>\n<td>O(n)<\/td>\n<\/tr>\n<tr>\n<td>Number of subarrays with sum k, negatives allowed<\/td>\n<td>Prefix sums + hash map<\/td>\n<td>O(n)<\/td>\n<\/tr>\n<tr>\n<td>Many range-sum queries on a fixed array<\/td>\n<td>Prefix sums<\/td>\n<td>O(n) build, O(1) per query<\/td>\n<\/tr>\n<tr>\n<td>Split into k subarrays, minimize the largest sum<\/td>\n<td>Binary search on the answer<\/td>\n<td>O(n log(sum))<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The most common mistake is reaching for a sliding window when the array contains negative numbers. A window works by saying &#8220;adding an element can only increase the sum, so if the sum is too big, shrink from the left&#8221;. With negatives that statement is false, and the window gives wrong answers.<\/p>\n<h2>Brute force: the baseline to state first<\/h2>\n<p>In an interview, say the brute force out loud first, then improve it. Fix the start index, extend the end index, and keep a running sum so you never re-add the same elements:<\/p>\n<pre><code class=\"language-python\">def max_sum_brute(a):\n    best = a[0]\n    for i in range(len(a)):\n        s = 0\n        for j in range(i, len(a)):\n            s += a[j]\n            best = max(best, s)\n    return best\n\nprint(max_sum_brute([-2, 1, -3, 4, -1, 2, 1, -5, 4]))  # 6\n<\/code><\/pre>\n<p>This is O(n<sup>2<\/sup>) time and O(1) space. Calling <code>sum(a[i:j+1])<\/code> inside the inner loop, as many tutorials do, makes it O(n<sup>3<\/sup>) and is a red flag in an interview.<\/p>\n<h2>Kadane&#8217;s algorithm: maximum sum subarray<\/h2>\n<p>At each index, the best subarray <em>ending here<\/em> is either the element alone or the element added to the best subarray ending at the previous index. If the previous best is negative, drop it and start fresh.<\/p>\n<pre><code class=\"language-python\">def kadane(a):\n    cur = best = a[0]\n    for x in a[1:]:\n        cur = max(x, cur + x)\n        best = max(best, cur)\n    return best\n\nprint(kadane([-2, 1, -3, 4, -1, 2, 1, -5, 4]))  # 6\n<\/code><\/pre>\n<p>Trace on <code>[-2, 1, -3, 4, -1, 2, 1, -5, 4]<\/code>:<\/p>\n<table>\n<thead>\n<tr>\n<th>x<\/th>\n<th>-2<\/th>\n<th>1<\/th>\n<th>-3<\/th>\n<th>4<\/th>\n<th>-1<\/th>\n<th>2<\/th>\n<th>1<\/th>\n<th>-5<\/th>\n<th>4<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>cur (best ending here)<\/td>\n<td>-2<\/td>\n<td>1<\/td>\n<td>-2<\/td>\n<td>4<\/td>\n<td>3<\/td>\n<td>5<\/td>\n<td>6<\/td>\n<td>1<\/td>\n<td>5<\/td>\n<\/tr>\n<tr>\n<td>best so far<\/td>\n<td>-2<\/td>\n<td>1<\/td>\n<td>1<\/td>\n<td>4<\/td>\n<td>4<\/td>\n<td>5<\/td>\n<td>6<\/td>\n<td>6<\/td>\n<td>6<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The answer is 6, the subarray <code>[4, -1, 2, 1]<\/code>. It runs in O(n) time and O(1) space. Initialize with <code>a[0]<\/code>, not 0, so an all-negative array returns its largest element instead of an empty sum. The same idea, tracking both a maximum and a minimum because a negative flips them, solves Maximum Product Subarray. There is a dedicated walkthrough in <a href=\"https:\/\/algocademy.com\/blog\/kadanes-algorithm-mastering-the-maximum-subarray-problem\/\">Kadane&#8217;s algorithm: the maximum subarray problem<\/a>.<\/p>\n<h2>Sliding window<\/h2>\n<p>A window is a pair of indices, <code>left<\/code> and <code>right<\/code>, with a running value for what is inside. Each element enters once and leaves at most once, so the total work is O(n) even though there is a loop inside a loop.<\/p>\n<h3>Fixed size: best sum of k consecutive elements<\/h3>\n<p>Add the new element on the right, subtract the one that fell off the left:<\/p>\n<pre><code class=\"language-python\">def max_sum_k(a, k):\n    s = sum(a[:k])\n    best = s\n    for i in range(k, len(a)):\n        s += a[i] - a[i - k]\n        best = max(best, s)\n    return best\n\nprint(max_sum_k([2, 1, 5, 1, 3, 2], 3))  # 9  (5 + 1 + 3)\n<\/code><\/pre>\n<h3>Variable size: shortest subarray with sum at least target<\/h3>\n<p>Grow the window with <code>right<\/code>. While the condition holds, record the length and shrink from the left. This is LeetCode 209 and it requires non-negative values:<\/p>\n<pre><code class=\"language-python\">def min_len_at_least(target, a):\n    left = s = 0\n    best = float('inf')\n    for right, x in enumerate(a):\n        s += x\n        while s &gt;= target:\n            best = min(best, right - left + 1)\n            s -= a[left]\n            left += 1\n    return 0 if best == float('inf') else best\n\nprint(min_len_at_least(7, [2, 3, 1, 2, 4, 3]))  # 2  ([4, 3])\n<\/code><\/pre>\n<p>The template is always the same: expand, then shrink while the window is invalid (or while it is valid, if you want the shortest). What changes between problems is the thing you keep inside the window: a sum, a count of distinct values, a frequency map. For more variants see <a href=\"https:\/\/algocademy.com\/blog\/mastering-the-sliding-window-technique-a-comprehensive-guide\/\">the sliding window guide<\/a>, and practice on the lesson <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=longest-subarray-with-sum-at-most-s&amp;solution=1\">Longest Subarray with Sum at most S<\/a>.<\/p>\n<h2>Prefix sums with a hash map: when negatives are allowed<\/h2>\n<p>Let <code>P[i]<\/code> be the sum of the first i elements. The sum of the subarray from index i to j-1 is <code>P[j] - P[i]<\/code>. So a subarray ending at the current position sums to k exactly when an earlier prefix equals <code>current_prefix - k<\/code>. Keep a count of every prefix seen so far:<\/p>\n<pre><code class=\"language-python\">def count_sum_k(a, k):\n    seen = {0: 1}          # the empty prefix\n    s = count = 0\n    for x in a:\n        s += x\n        count += seen.get(s - k, 0)\n        seen[s] = seen.get(s, 0) + 1\n    return count\n\nprint(count_sum_k([1, 2, 3, -3, 1, 1], 3))  # 4\n<\/code><\/pre>\n<p>The four subarrays are <code>[1, 2]<\/code>, <code>[3]<\/code>, <code>[1, 2, 3, -3]<\/code> and <code>[2, 3, -3, 1]<\/code>. It is O(n) time and O(n) space, and it works with negatives because it never assumes the sum moves in one direction. Forgetting <code>{0: 1}<\/code> is the usual bug: it makes you miss subarrays that start at index 0.<\/p>\n<p>If you only need many range sums on a static array, build the prefix array once and answer each query as <code>P[j] - P[i]<\/code> in O(1). More patterns are in <a href=\"https:\/\/algocademy.com\/blog\/when-to-use-a-prefix-sum-array-recognition-patterns\/\">when to use a prefix sum array<\/a>.<\/p>\n<p>Variations use the same idea with a different key. For &#8220;longest subarray with sum divisible by k&#8221;, store the first index at which each <code>prefix % k<\/code> remainder appeared; two equal remainders mean the sum between them is divisible by k.<\/p>\n<h2>Splitting an array into k subarrays: binary search on the answer<\/h2>\n<p>The problem (LeetCode 410, Split Array Largest Sum): cut an array of positive integers into k contiguous non-empty pieces so that the largest piece sum is as small as possible. Searches for this problem often phrase it as &#8220;divide the array into k contiguous subarrays&#8221;.<\/p>\n<p>Trying every way to cut is exponential. Instead, ask a yes\/no question: &#8220;can I split the array into at most k pieces if no piece may exceed <code>limit<\/code>?&#8221; That is answered by one greedy pass: keep adding to the current piece, and start a new one when the next element would break the limit. The answer is monotonic (a larger limit never needs more pieces), so binary search finds the smallest limit that works. The search range is from <code>max(a)<\/code> (a piece must hold the largest element) to <code>sum(a)<\/code> (one piece):<\/p>\n<pre><code class=\"language-python\">def split_array(a, k):\n    def pieces(limit):\n        n, cur = 1, 0\n        for x in a:\n            if cur + x &gt; limit:\n                n += 1\n                cur = 0\n            cur += x\n        return n\n\n    lo, hi = max(a), sum(a)\n    while lo &lt; hi:\n        mid = (lo + hi) \/\/ 2\n        if pieces(mid) &lt;= k:\n            hi = mid\n        else:\n            lo = mid + 1\n    return lo\n\nprint(split_array([7, 2, 5, 10, 8], 2))  # 18  ([7, 2, 5] and [10, 8])\nprint(split_array([1, 2, 3, 4, 5], 3))   # 6   ([1, 2, 3], [4], [5])\n<\/code><\/pre>\n<p>Each check is O(n) and the search takes O(log(sum)) steps, so the total is O(n log(sum)). The greedy check only works because all values are positive. The same &#8220;binary search on the answer&#8221; shape solves minimum shipping capacity and &#8220;koko eating bananas&#8221;. A refresher on the search itself is in the <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=binary-search&amp;solution=1\">Binary Search lesson<\/a>.<\/p>\n<h2>Common mistakes<\/h2>\n<ul>\n<li><strong>Sliding window with negatives.<\/strong> If values can be negative and the problem asks for an exact or minimum sum, use prefix sums and a hash map (or a monotonic deque for &#8220;shortest subarray with sum at least K&#8221; on LeetCode 862).<\/li>\n<li><strong>Empty subarray.<\/strong> Check whether the problem allows it. Starting Kadane at 0 instead of <code>a[0]<\/code> silently allows it.<\/li>\n<li><strong>Off-by-one on window length.<\/strong> The length is <code>right - left + 1<\/code>.<\/li>\n<li><strong>Integer overflow<\/strong> in C++ or Java: prefix sums over large arrays need <code>long<\/code>.<\/li>\n<li><strong>Subarray vs subsequence.<\/strong> Subsequence problems usually need dynamic programming, not these techniques.<\/li>\n<\/ul>\n<h2>What interviewers ask next<\/h2>\n<ul>\n<li>&#8220;Can you return the indices, not just the sum?&#8221; For Kadane, remember where <code>cur<\/code> last restarted.<\/li>\n<li>&#8220;What if the array is circular?&#8221; Take the larger of Kadane&#8217;s result and <code>total - minimum subarray<\/code>, with a special case for all-negative input.<\/li>\n<li>&#8220;What if the array is a stream?&#8221; Kadane and fixed windows need only O(1) or O(k) memory, so they work online.<\/li>\n<li>&#8220;What is the space cost?&#8221; Kadane and windows are O(1), the hash map version is O(n).<\/li>\n<\/ul>\n<h2>Practice order<\/h2>\n<p>Solve these in this order: Maximum Subarray, Maximum Average Subarray I, Minimum Size Subarray Sum, Subarray Sum Equals K, Maximum Product Subarray, Longest Subarray with Sum Divisible by K, Split Array Largest Sum, Sliding Window Maximum. If you want guided practice with hints, the lessons on <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=maximum-sum-subarray-of-length-k&amp;solution=1\">Maximum Sum Subarray of Length K<\/a> and <a href=\"https:\/\/algocademy.com\/link\/?lang=py&amp;problem=subarray-of-given-sum&amp;solution=2\">Subarray of Given Sum<\/a> cover the window and prefix-sum ideas from this page.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to pick the right technique for a subarray problem: sliding window, Kadane, prefix sums with a hash map, or binary search on the answer, with tested code.<\/p>\n","protected":false},"author":1,"featured_media":6125,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23],"tags":[],"class_list":["post-6126","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-problem-solving"],"_links":{"self":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/6126"}],"collection":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/comments?post=6126"}],"version-history":[{"count":2,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/6126\/revisions"}],"predecessor-version":[{"id":8932,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/6126\/revisions\/8932"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media\/6125"}],"wp:attachment":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media?parent=6126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/categories?post=6126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/tags?post=6126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}