Where is my logic wrong in this dynamic programming problem?

In summary, The conversation is about solving a specific problem on HackerRank involving finding the sum of contiguous subarrays in an array. The code provided implements a solution using a helper class for fast lookup of the sum of elements in a given range. The main function uses a recursive approach to calculate the special sum needed for the problem. However, the solution may not be efficient enough and the coder asks for feedback on their coding style. There is also a discussion about the unnecessary use of a modulus for the final output. The expert suggests that the modulus is necessary due to the large numbers involved in the problem. Finally, the expert points out a small error in the comment about the divisor used in the code.
  • #1
SlurrerOfSpeech
141
11
I'm trying to solve https://www.hackerrank.com/challenges/summing-pieces and I think I have come up with an O(n) solution although the numbers aren't coming out exactly right. The problem is essentially to find a sum like

ABCD
---> All sets of sets of contiguous subarrays spanning ABCD is
{ {ABCD}, {ABC, D}, {A, BCD}, {AB, CD}, {AB, C, D}, {A, BC, D}, {A, B, CD}, {A, B, C, D} }
---> special sum is
4(A+B+C+D) + 3(A+B+C) + 1(D) + 1(A) + 3(BCD) + 2(AB) + 2(CD) + 2(AB) + 2(C) + 2(D) + 2(A) + 2(BC) + 2(D) + 2(A) + 2(B) + 2(CD) + 2(A) + 2(B) + 2(C) + 2(D)Let me know if you see anything wrong with my attempted C# solution, and I would appreciate any general comments on my coding style.

Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

/// <summary>
/// Helper class for finding the sum of elements in a contiguous section
/// of an array in O(1) time.
/// </summary>
public class RangeSummer
{
    /// <summary>
    /// _s[i] = arr[0] + ... + arr[i]
    /// </summary>
    private long[] _s;

    /// <summary>
    /// Length of _s and arr
    /// </summary>
    public int Length { get; private set; }

    /// <summary>
    /// Initializes using the source aray <param name="arr"></param>
    /// </summary>
    /// <remarks>
    /// Initialization takes O(n) time
    /// </remarks>
    public RangeSummer(long[] arr)
    {
        int n = arr.Length;
        Length = n;
        _s = new long[n];
        for (
            long sum = 0, i = 0;
            i < n;
            sum += arr[i], _s[i] = sum, ++i
        ) ;
    }

    /// <summary>
    /// this[i,j] = the sum of the elements of arr in the range [i, j)
    /// </summary>
    /// <remarks>
    /// Uses the fact that  arr[i] + ... + arr[j - 1] = s[j - 1] - s[i - 1]
    /// </remarks>
    public long this[int i, int j]
    {
        get
        {
            return i == 0 ? _s[i] : _s[j - 1] - _s[i - 1];
        }
    }
}

internal class Solver
{
    /// <remarks>
    /// The problem useslessly makes us mod our answer by 10^7+9 
    /// for the final output. 
    /// </remarks>
    private long _divisor = (long)Math.Pow(10.0, 9.0) + 7;

    /// <summary>
    /// Utility class for fast lookup of sum of elements
    /// of source array in a given range
    /// </summary>
    private RangeSummer _summer;

    /// <summary>
    /// Single constructor. Since the only purpose of this class
    /// is to solve a single problem, class could be made into
    /// a functor or could use a similar design pattern.
    /// </summary>
    /// <param name="arr">Source array</param>
    public Solver(long[] arr)
    {
        _summer = new RangeSummer(arr);
    }

    /// <summary>
    /// Wrapper function for recursive solution
    /// </summary>
    /// <returns>Final answer</returns>
    public long GetSpecialSum()
    {
        long sum = GetSpecialSum_Recursive(0, _summer.Length);
        return sum % _divisor; 
    }

    /// <summary>
    /// Inner function of recursive solution
    /// </summary>
    /// <remarks>
    /// Uses the fact that the solution f({A_0,...,A_n-1})
    /// is equal to 
    /// n * (A_0 + ... + A_n-1) + f({A_1, ..., A_n-1}) + A({A_0, ..., A_n-2})
    /// </remarks>
    /// <param name="i">starting index</param>
    /// <param name="j">1 off-the-end of second index</param>
    private long GetSpecialSum_Recursive(int i, int j)
    {
        return i >= j ? 0 :
            (j - i) * _summer[i, j] // (j - i) (arr[i] + ... + arr[j - 1])
            + GetSpecialSum_Recursive(i, j - 1)
            + GetSpecialSum_Recursive(i + 1, j);
    }
}

/// <summary>
/// Solution to the HackerRank problem [PLAIN]https://www.hackerrank.com/challenges/summing-pieces.[/PLAIN] 
/// This is for testing and debugging locally. The code pased into the solution box will 
/// be slightly different since it reads the input from the console.
/// </summary>
class Solution
{
    static void Main(String[] args)
    {
        // get test file
        string curdir = Directory.GetCurrentDirectory();
        System.IO.StreamReader file = new System.IO.StreamReader(
            Path.GetFullPath(
                Path.Combine(curdir, @"..\..\", "TestFiles\\TestTwo.txt")
            ) // second line is 4 2 9 10 1
        );

        // get input and solve
        int.Parse(file.ReadLine()); // don't need this line
        long[] arr = Array.ConvertAll(file.ReadLine().Split(' '), long.Parse);
        Console.WriteLine(new Solver(arr).GetSpecialSum());
    }
}
 
Last edited by a moderator:
Technology news on Phys.org
  • #2
/// <remarks>
/// The problem useslessly makes us mod our answer by 10^7+9
/// for the final output.
/// </remarks>
That is not useless. The actual sum is way too large for any reasonable number format for large n. It also means you have to take it into account in intermediate steps unless you want to store integers with hundreds of thousands of digits.I don't follow the logic in GetSpecialSum_Recursive and it looks like an O(2n ) implementation.

Before you write the first line of code: This is a problem that needs some thought on how to calculate the sum efficiently. O(n) is possible, but not by brute force.
 
  • #3
SlurrerOfSpeech said:
C:
/// The problem useslessly makes us mod our answer by 10^7+9
    /// for the final output.
    /// </remarks>
    private long _divisor = (long)Math.Pow(10.0, 9.0) + 7;
Make sure your comments are accurate descriptions of the code. _divisor is actually ##10^9 + 7##, not ##10^7 + 9## as stated in the comment. A comment that is inaccurate is worse than no comment at all.
 
Last edited:

FAQ: Where is my logic wrong in this dynamic programming problem?

What is dynamic programming?

Dynamic programming is a problem-solving technique used to solve complex problems by breaking them down into smaller, simpler subproblems. It involves finding an optimal solution by recursively solving subproblems and storing the solutions in a table to avoid repeated computations.

How do I know if dynamic programming is the right approach for my problem?

Dynamic programming is most commonly used for problems that exhibit optimal substructure and overlapping subproblems. In other words, if the problem can be broken down into smaller subproblems and the solutions to these subproblems can be reused, then dynamic programming may be a suitable approach.

What is the difference between top-down and bottom-up dynamic programming?

In top-down dynamic programming, the problem is solved by breaking it down into smaller subproblems and then recursively solving these subproblems. In contrast, bottom-up dynamic programming starts with the smallest subproblem and builds up to the larger problem by storing the solutions in a table.

How do I optimize my dynamic programming solution?

There are several techniques for optimizing a dynamic programming solution, such as memoization, which involves storing previously computed solutions in a table to avoid repeated computations. Another technique is to apply pruning, which involves eliminating unnecessary subproblems from the computation.

What is a common mistake made when using dynamic programming?

A common mistake in dynamic programming is not properly identifying and solving the subproblems. It is important to carefully analyze the problem and break it down into smaller subproblems that can be solved recursively. Additionally, not using an appropriate data structure or not considering all possible cases can also lead to incorrect solutions.

Similar threads

Back
Top