6

The problem is I have to print all combinations of a sequence of numbers from 1 to N that will always result to zero. It is allowed to insert "+" (for adding) and "-" (for subtracting) between each numbers so that the result will be zero.

//Output
N = 7

1 + 2 - 3 + 4 - 5 - 6 + 7 = 0
1 + 2 - 3 - 4 + 5 + 6 - 7 = 0
1 - 2 + 3 + 4 - 5 + 6 - 7 = 0
1 - 2 - 3 - 4 - 5 + 6 + 7 = 0

So how can I implement this? I am not asking for the actual codes to do this, just a hint and ideas to solve this will do. Thank you..

yuvgin
  • 1,322
  • 1
  • 12
  • 27
robert
  • 249
  • 4
  • 11
  • I suppose that your constraint is that numbers cannot be repeated and always begin with the smallest and finish with the biggest, I would try to find any recursive solution you know that you have to stop when n is equal to N and only print when the result is 0. – vmrvictor Mar 26 '19 at 10:05
  • Can the numbers be repeated? Can you add the minus before the first number? – Karol Dowbecki Mar 26 '19 at 10:07
  • @KarolDowbecki no just conscecutive integer from 1 to N – robert Mar 26 '19 at 10:08
  • @robert If it can't, please update your question to fill in this extra constraint. – tryman Mar 26 '19 at 10:10
  • @robert so 1 will always be positive? or can you change the order of numbers, like `2-1+3+6-4-5+7`? – Mikel Ferreiro Mar 26 '19 at 10:11
  • Are negative numbers like `-1 + -2 - -3 ...` allowed? – Lino Mar 26 '19 at 10:12
  • @MikelFerreiro no it has to be in a increasing order – robert Mar 26 '19 at 10:12
  • 1
    @Lino what it was exactly stated is the sum of consecutive integers that will always result to zero. It says that we can either insert `+` or `-` between each numbers. So the number 1 will always be positive... – robert Mar 26 '19 at 10:17
  • It's fairly easy to show that only odd numbers can affect the last bit of the sum, so N must be odd. E.g. Both 1+2 and 1-2 are odd, so they're not 0, therefore N can't be 2. – MSalters Mar 26 '19 at 15:15
  • @MSalters I'm not sure if I understand your comment, but it is true that the output should be empty (no solutions exist) if `N mod 4` is either `1` or `2`...and the reason is simply that the sum of the first `N` positive integers is odd if `N mod 4` is either `1` or `2`. http://oeis.org/A058377 – mathmandan Mar 27 '19 at 18:39

8 Answers8

11

You could also use recursion here. Just remember your current integer, your max integer, your current sum and some kind of history of operations (could also be your final sequence). In every level you proceed the path in two dirdctions: adding to your sum and substracting from it.

I did a quick implementation in Python, but it should be easy to transfer this to Java or whatever you are using.

def zero_sum(curr, n, seq, sum):
    if curr == n and sum == 0:
        print(seq)
    elif curr < n:
        zero_sum(curr + 1, n, seq + " - " + str(curr + 1), sum - (curr + 1))
        zero_sum(curr + 1, n, seq + " + " + str(curr + 1), sum + (curr + 1))

zero_sum(1, 7, "1", 1)

Hopefully you get the idea.

holidayfun
  • 356
  • 1
  • 8
  • 2
    You can not actually return anything using this approach, for eg: output `String`. –  Mar 26 '19 at 13:45
  • The OP asked for printing the valid combinations. If it was necessary to return a long string of valid combinations you could return a single valid combination when ending the recursion and the levels above could concatenate the results of the two recursive calls. – holidayfun Mar 26 '19 at 13:52
  • What about using `yield seq` instead of `print(seq)` and `yield from zero_sum(...)` instead of `zero_sum(...)` to make it more reusable? – Solomon Ucko Mar 26 '19 at 15:04
4

The first step is to turn the problem into an entirely regularly formed problem:

 n
 ∑  ±i = -1
i=2

n-2
 ∑  ±(i+2) = -1
i=0

The term 1 at the start has no prefix +/-. And the walking index better runs from 0 when using a Java array.

So one has n-1 coefficients -1 or +1 for the possible values.

A brute force approach would be to start with the highest values, i = n-2.

The upper/lower bounds for j = 0, ..., i would be ± (i + 1) * (2 + i + 2) / 2, so one can cut the evaluation there - when the till then calculated sum can no longer reach -1.

To represent the coefficients, one could make a new int[n - 1] or simply a new BitSet(n-1).

public void solve(int n) {
    int i = n-2;
    int sumDone = 0;
    BigSet negates = new BitSet(n - 1);
    solveRecursively(i, sumDone, negates);
}

private void solveRecursively(int i, int SumDone, BitSet negates) {
    if (i < 0) {
        if (sumDone == -1) {
            System.out.println("Found: " + negates);
        }
        return;
    }
    ...
}

The interesting, actual (home) work I leave to you. (With BitSet better i = n, ... , 2 by -1 seems simpler though.)

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
4

The question here is how much efficiency matters. If you're content to do a brute-force approach, a regression method like the one indicated by holidayfun is a fine way to go, though this will become unwieldy as n gets large.

If performance speed matters, it may be worth doing a bit of math first. The easiest and most rewarding check is whether such a sum is even possible: since the sum of the first n natural numbers is n(n+1)/2, and since you want to divide this into two groups (a "positive" group and a "negative" group) of equal size, you must have that n(n+1)/4 is an integer. Therefore if neither n nor n+1 is divisible by four, stop. You cannot find such a sequence that adds to zero.

This and a few other math tricks might speed up your application significantly, if speed is of the essence. For instance, finding one solution will often help you find others, for large n. For instance, if n=11, then {-11, -10, -7, -5} is one solution. But we could swap the -5 for any combination that adds to 5 that isn't in our set. Thus {-11, -10, -7, -3, -2} is also a solution, and similarly for -7, giving {-11, -10, -5, -4, -3} as a solution (we are not allowed to use -1 because the 1 must be positive). We could continue replacing the -10, the -11, and their components similarly to pick up six more solutions.

This is probably how I'd approach this problem. Use a greedy algorithm to find the "largest" solution (the solution using the largest possible numbers), then keep splitting the components of that solution into successively smaller solutions. It is again fundamentally a recursion problem, but one whose running time decreases with the size of the component under consideration and which at each step generates another solution if a "smaller" solution exists. That being said, if you want every solution then you still have to check non-greedy combinations of your split (otherwise you'd miss solutions like {-7, -4, -3} in your n=7 example). If you only wanted a lot of solutions it would definitely be faster; but to get all of them it may be no better than a brute-force approach.

Galendo
  • 163
  • 1
  • 5
3

If I were you I would go for a graph implementation, and DFS algorithm. Imagine you have N nodes that are representing your numbers. Each number is connected to another via an "add" edge, or a "subtract" edge. So you have a fully connected graph. You can start from a node and compute all dfs paths that lead to zero.

For more information about DFS algorithm, you can see the wikipage.

Edit: In order to clarify my solution, the graph you will end up having will be a multigraph, which means that it has more than one edge between nodes. DFS in a multigraph is slightly more complicated, but it is not that hard.

Community
  • 1
  • 1
Farhood ET
  • 1,432
  • 15
  • 32
  • @robert You can implement your graph with an Adjacency List. https://www.geeksforgeeks.org/graph-and-its-representations/ – Farhood ET Mar 26 '19 at 10:12
  • 1
    Is there a little bit more simpler than that? To be honest I don't know anything about a graph in java yet. This problem that I am asking is just included in the recursion section of our java class specifically about permutations, so I am expecting any ideas that relates in implementing permutations and combinations or something like that.... – robert Mar 26 '19 at 10:24
  • 1
    @robert check holidayfun's answer. – Farhood ET Mar 26 '19 at 10:44
2

I would suggest a straight forward solution because as you mentioned you are dealing with consecutive integer from 1 to N which are fixed. The only things that vary are the operators in between.

Let's look at your example before we implement a general solution:

For n = 7 you need somehow to produce all possible combinations:

1+2+3+4+5+6+7
1+2+3+4+5+6-7
1+2+3+4+5-6+7
1+2+3+4+5-6-7
...
1-2-3-4-5-6+7
1-2-3-4-5-6-7

If we remove the numbers from above strings/expressions then we'll have:

++++++
+++++-
++++-+
++++--
...
----+-
-----+
------

Which reminds on binary numbers; if we interpret + as 0 and - as 1 the above can be mapped to the binary numbers from 000000 to 111111.

For an input n you'll have n-1 operators inbetween, which means the count of all possible combinations will be 2^n-1.

Putting all the above together something like below can be used to print those which sums are zero:

public static void main(String args[]) throws IOException{
    permute(7);
}
public static void permute(int n){
    int combinations = (int)Math.pow(2, n-1);
    for(int i = 0; i < combinations; i++){
        String operators =String.format("%"+(n-1)+"s", Integer.toBinaryString(i)).replace(' ', '0');

        int totalSum = 1;
        StringBuilder sb = new StringBuilder();

        for(int x = 0; x< operators.length(); x++){
            sb.append(x+1);
            if(operators.charAt(x)=='0'){
                sb.append("+");
                totalSum = totalSum + (x+2);
            }
            else{
                sb.append("-");
                totalSum = totalSum-(x+2);
            }                
        }
        sb.append(n);
        if(totalSum == 0){
            System.out.println(sb.toString() + " = " + totalSum);
        }
    }
}

Note/Example: String.format("%6s", Integer.toBinaryString(13)).replace(' ', '0') will produce a string with length = 6 from the binary representation of 13 with leading zeros, i.e 001101 instead of 1101 so that we get the required length of the operators.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Eritrean
  • 15,851
  • 3
  • 22
  • 28
  • Yes this is a valid naive solution which also tries many impossible cases. I have a feeling like a dynamical programming approach would yield a much efficient solution though. – Redu Apr 07 '19 at 21:57
1

This is an interesting question. It involves more math than programming because only if you discover the math portion then you may implement an efficient algorithm.

However even before getting into the math we must actually understand what exactly the question is. The question can be rephrased as

Given array [1..n], find all possible two groups (2 subarrays) with equal sum.

So the rules;

  1. sum of [1..n] is n*n(+1)/2
  2. If n*(n+1)/2 is odd then there is no solution.
  3. If your target sum is t then you should not iterate further for lower values than Math.ceil((Math.sqrt(8*t+1)-1)/2) (by solving n from n(n+1)/2 = t equation)

Sorry... I know the question requests Java code but I am not fluent in Java so the code below is in JavaScript. It's good though we can see the results. Also please feel free to edit my answer to include a Java version if you would like to transpile.

So here is the code;

function s2z(n){
    function group(t,n){                           // (t)arget (n)umber
        var e = Math.ceil((Math.sqrt(8*t+1)-1)/2), // don't try after (e)nd
            r = [],                                // (r)esult
            d;                                     // (d)ifference
        while (n >= e){
            d = t-n;
            r = d ? r.concat(group(d, d < n ? d : n-1).map(s => s.concat(n)))
                  : [[n]];
            n--;
        }
        return r;
    }
    var sum = n*(n+1)/2;              // get the sum of series [1..n] 
    return sum & 1 ? "No solution..!" // if target is odd then no solution
                   : group(sum/2,n);
}
console.log(JSON.stringify(s2z(7)));

So the result should be [[1,6,7],[2,5,7],[3,4,7],[1,2,4,7],[3,5,6],[1,2,5,6],[1,3,4,6],[2,3,4,5]].

what does this mean..? If you look into that carefuly you will notice that

  1. These are all the possible groups summing up to 14 (half of 28 which is the sum of [1..7].
  2. The first group (at index 0) is complemented by the last group (at index length-1) The second is complemented with the second last and so on...

Now that we have the interim results it's up to us how to display them. This is a secondary and trivial concern. My choice is a simple one as follows.

var arr = [[1,6,7],[2,5,7],[3,4,7],[1,2,4,7],[3,5,6],[1,2,5,6],[1,3,4,6],[2,3,4,5]],
    res = arr.reduce((r,s,i,a) => r+s.join("+")+"-"+a[a.length-1-i].join("-")+" = 0 \n","");
    
console.log(res);

Of course you may put the numbers in an order or might stop halfway preventing the second complements taking positive values while the firsts taking negative values.

This algorithm is not hard tested and i might have overlooked some edges but I believe that this should be a very efficient algorithm. I have calculated up to [1..28] in a very reasonable time resulting 2399784 uniques groups to be paired. The memory is only allocated for the constructed result set despite this is a resursive approach.

Redu
  • 25,060
  • 6
  • 56
  • 76
0

It is a good question, but first you must have to try to solve it and show us what you tried so we can help you in the solution, this way you will improve more effectively.

However, the below code is a solution I have write before years, I think the code need improvement but it will help..

public static void main(String[] args) {
    String plus = " + ", minus = " - ";
    Set<String> operations = new HashSet<>();
    operations.add("1" + plus);
    operations.add("1" + minus);

    // n >= 3
    int n = 7;

    for (int i = 1; i < n - 1; i++) {
        Set<String> newOperation = new HashSet<>();
        for (String opt : operations) {
            if ((i + 2) == n) {
                newOperation.add(opt + (i + 1) + plus + n);
                newOperation.add(opt + (i + 1) + minus + n);
            } else {
                newOperation.add(opt + (i + 1) + plus);
                newOperation.add(opt + (i + 1) + minus);
            }
        }
        operations.clear();
        operations.addAll(newOperation);
    }
    evalOperations(operations);
}

private static void evalOperations(Set<String> operations) {

    // from JDK1.6, you can use the built-in Javascript engine.
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    try {
        for (String opt : operations) {
            if ((int) engine.eval(opt) == 0) {
                System.out.println(opt + " = 0");
            }
        }
    } catch (ScriptException e) {
        e.printStackTrace();
    }
} 
Ebraheem Alrabeea
  • 2,130
  • 3
  • 23
  • 42
0

First, the question is the special case of sum to N. Second, sum a list to N, could be devided to the first element plus sublist and minus sublist. Third, if there are only one element in the list, check if n equals the element. Fourth, make recursion.

Here's the scala implementation, try finishing your java version:

def nSum(nums: List[Int], n: Int, seq: String, res: ListBuffer[String]): Unit =
  nums match {
    case Nil => if (n == 0) res.append(seq)
    case head :: tail => {
      nSum(tail, n - head, seq + s" + $head", res)
      nSum(tail, n + head, seq + s" - $head", res)
    }
  }

def zeroSum(nums: List[Int]): List[String] = {
  val res = ListBuffer[String]()
  nSum(nums.tail, -nums.head, s"${nums.head}", res)
  res.map(_ + " = 0").toList
}

val expected = List(
  "1 + 2 - 3 + 4 - 5 - 6 + 7 = 0",
  "1 + 2 - 3 - 4 + 5 + 6 - 7 = 0",
  "1 - 2 + 3 + 4 - 5 + 6 - 7 = 0",
  "1 - 2 - 3 - 4 - 5 + 6 + 7 = 0")

assert(expected == zeroSum((1 to 7).toList))
Ruoyu Dai
  • 53
  • 4
  • [1]It's not a hard to convert Scala code to Java. [2]You are right, I will update to talk about the idea. – Ruoyu Dai Mar 29 '19 at 06:27