简体   繁体   中英

Tough recursive task

I've been struggle with question I'm trying to solve as part of test preparation, and I thought I could use your help. I need to write a Boolean method that takes array with integers (positive and negative), and return true if the array can be split to two equals groups, that the amount of every group's numbers is equals to the other group. For exmaple, for this array:

int[]arr = {-3, 5, 12, 14, -9, 13};

The method will return true, since -3 + 5 + 14 = 12 + -9 + 13.

For this array:

int[]arr = {-3, 5, -12, 14, -9, 13};

The method will return false since even though -3 + 5 + 14 + -12 = -9 + 13, the amount of numbers in every side of the equation isn't equals.

For the array:

int[]arr = {-3, 5, -12, 14, -9};

The method will return false since array length isn't even.

The method must be recursive, overloading is allowed, every assist method must be recursive too, and I don't need to worry about complexity.

I've been trying to solve this for three hours, I don't even have a code to show since all the things I did was far from the solution.

If someone can at least give me some pseudo code it will be great.

Thank you very much!

The problem described is a version of the Partition problem. First note that your formulation is equivalent to deciding whether there is a subset of the input which sums up to half of the sum of all elements (which is required to be an integral number, otherwise the instance cannot be solved, but this is easy to check). Basically, in each recursive step, it is to be decided whether the first number is to be selected into the subset or not, resulting in different recursive calls. If n denotes the number of elements, there must be n/2 (which is required to be integral again) items selected.

Let Sum denote the sum of the input and let Target := Sum / 2 which in the sequel is assumed to be integral. if we let

f(arr,a,count) := true
                  if there is a subset of arr summing up to a with
                  exactly count elements
                  false
                  otherwise

we obtain the following recursion

f(arr,a,count) = (arr[0] == a && count == 1)
                 ||
                 (a == 0 && count == 0)
                 if arr contains only one element
                 f(arr\arr[0], a, count)
                 ||
                 f(arr\arr[0], a - arr[0], count -1)
                 if arr contains more than one element

where || denotes logical disjuction, && denoted logical conjunction and \\ denotes removal of an element. The two cases for a non-singleton array correspond to chosing the first element of arr into the desired subset or its relative complement. Note that in an actual implementation, a would not be actually removed from the array; a starting index, which is used as an additional argument, would be initialized with 0 and increased in each recursive call, eventually reaching the end of the array.

Finally, f(arr,Target,n/2) yields the desired value.

You asked for pseudocode, but sometimes it's just as easy and clear to write it as Java.

The general idea of this solution is to try adding each number to either the left or the right of the equation. It keeps track of the count and sum on each side at each step in the recursion. More explanation in comments:

class Balance {
  public static void main(String[] args) {
    System.out.println(balanced(-3, 5, 12, 14, -9, 13));   // true
    System.out.println(balanced(-3, 5, -12, 14, -9, 13));  // false
  }

  private static boolean balanced(int... nums) {
    // First check if there are an even number of nums.
    return nums.length % 2 == 0
        // Now start the recursion:
        && balanced(
            0, 0,  // Zero numbers on the left, summing to zero.
            0, 0,  // Zero numbers on the right, summing to zero.
            nums);
  }

  private static boolean balanced(
      int leftCount, int leftSum,
      int rightCount, int rightSum,
      int[] nums) {
    int idx = leftCount + rightCount;
    if (idx == nums.length) {
      // We have attributed all numbers to either side of the equation.
      // Now check if there are an equal number and equal sum on the two sides.
      return leftCount == rightCount && leftSum == rightSum;
    } else {
      // We still have numbers to allocate to one side or the other.
      return
          // What if I were to place nums[idx] on the left of the equation?
          balanced(
              leftCount + 1, leftSum + nums[idx],
              rightCount, rightSum,
              nums)
          // What if I were to place nums[idx] on the right of the equation?
          || balanced(
              leftCount, leftSum,
              rightCount + 1, rightSum + nums[idx],
              nums);
    }
  }
}

This is just a first idea solution. It's O(2^n), which is obviously rather slow for large n , but it's fine for the size of problems you have given as examples.

Your strategy for this should be to try all combinations possible. I will try to document how I would go about to get to this.

NOTE that I think the requirement: make every function use recursion is a bit hard, because I would solve that by leaving out some helper functions that make the code much more readable, so in this case I wont do it like that.

With recursion you always want to make progression towards a final solution, and detect when you are done. So we need two parts in our function:

  1. The recursive step: for which we will take the first element of the input set, and try what happens if we add it to the first set, and if that doesn't find a solution we'll try what happens when we add it to the second set.
  2. Detect when we are done, that is when the input set is empty, in that case we either have found a solution or we have not.

A trick in our first step is that after taking the first element of our set, if we try to partition the remainder, we don't want the 2 sets being equal anymore, because we already assigned the first element to one of the sets.

This leads to a solution that follows this strategy:

public boolean isValidSet(MySet<int> inputSet, int sizeDifferenceSet1minus2)
{
    if (inputSet.isEmpty())
    {
         return sizeDifferenceSet1minus2== 0;
    }

    int first = inptuSet.takeFirst();
    return isValidSet(inputSet.copyMinusFirst(), sizeDifferenceSet1minus2+ first)
              || isValidSet(inputSet.copyMinusFirst(), sizeDifferenceSet1minus2+ -1 * first);
}

This code requires some help functions that you will still need to implement.

What it does is first test if we have reached the end condition, and if so returns if this partition is successful. If we still have elements left in the set, we try what happens if we add it to the first set and then what happens when adding it to the second set. Note that we don't actually keep track of the sets, we just keep track of the size difference between set 1 minus 2, decreasing the (but instead you could pass along both sets).

Also note that for this implementation to work, you need to make copies of the input set and not modify it!

For some background information: This problem is called the Partition Problem , which is famous for being NP-complete (which means it probably is not possible to solve it efficiently for large amounts of input data, but it is very easy to verify that a partitioning is indeed a solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM