10

I was reading through a problem and was trying to solve this problem.

You've invited N people over for dinner. Let's say 4.

You have a circular dinner table and you wish to seat everyone around it. Unfortunately, not all of your friends are friends with each other, but you'd like to seat everyone optimally so that as many people as possible are seated next to people they consider friends and not enemies.

You've charted everyone's friendships and hatreds in a matrix of size NxN and represented friendships with the integer 1, hatreds with -1, and sheer indifference with 0.

[[ 0, 1, 1, 1, 1],    ← yes you like all your friends
 [-1, 0, 1,-1, 0],
 [-1, 1, 0, 1, 0],
 [ 1, 1, 1, 0,-1],
 [ 1, 0, 0,-1, 0]]

Question:

-> Write a Javascript method that computes an optimal seating arrangement as an Array, e.g. [0,4,2,1,3], for a given input matrix. (assuming indexes 0 and N-1 are seated adjacently). What is the time complexity for the solution? Add thoughts on possible optimizations.

I've tried solving this manually however, I didn't understand the question's example [0,4,2,1,3] for the given input matrix.

Can someone Enlighten me?

How did he/she come up with [0,4,2,1,3]?

Thanks and very much appreciate your time.

SFer
  • 109
  • 4
  • 1
    You are row and column 0, you like all your friends, but only your friends 2 and 3 like you, "friends" 1 and 2 dislike you (-1 in column 0 - that's your own popularity). The expample means the sitting order: You, next to you friend 4, then 2, 1, 3 which is on the other side next to you. – Pinke Helga Feb 04 '19 at 00:03
  • 3
    The problem statement seems too vague; if the goal is that "as many people as possible are seated next to people they consider friends and not enemies", how do we quantify that? For example: suppose that one arrangement has each guest adjacent to one person they like and one person they hate, and a different arrangement has each guest adjacent to two people they're neutral about. Which of these is considered better? – ruakh Feb 04 '19 at 00:04
  • Since this task smells like a permutation, the complexity will be O(N!). – Pinke Helga Feb 04 '19 at 00:18
  • @Emma Possibly. Even I'm trying to figure out the same. If you have a solution in mind that would be great. – SFer Feb 04 '19 at 00:24
  • 2
    @ruakh sure. I'm guessing its an open ended question. Can you make an assumption and come up with a possible outcome? – SFer Feb 04 '19 at 00:25
  • 3
    I'm not sure if I want to have dinner with these folks, not if my first two "friends" don't like me back! – Scott Sauyet Feb 04 '19 at 00:45
  • It's unclear whether [+1,-1] neighbours are preferable to [0,0] neighbours; they both add up to zero, but is avoiding the -1 more important? – m69's been on strike for years Feb 04 '19 at 01:28
  • @m69 It does not look like. The task could have given values like +1/-2 as well. Thus those cases seem to be equaly weighted. – Pinke Helga Feb 04 '19 at 01:33
  • @m69: That's the same question I asked -- see the second comment above -- and SFer has already replied. (I don't find his/her reply very satisfying, but repeating the question probably won't change anything . . .) – ruakh Feb 04 '19 at 01:35
  • @ruakh Oh, I'd overlooked that; sorry. – m69's been on strike for years Feb 04 '19 at 01:36
  • *'as an Array, **e.g.** [0,4,2,1,3], for a given input matrix'*. As you state yourself, it is an **example**, not a *solution*. – Pinke Helga Feb 04 '19 at 02:46
  • 2
    Incidentally, this problem is NP-hard, by reduction from the [Hamiltonian cycle problem](https://en.wikipedia.org/wiki/Hamiltonian_path_problem), so you're unlikely to find an efficient solution for large numbers of guests. – user2357112 Feb 04 '19 at 03:14
  • This is a directed circular graph represented by matrix. Need to find a circular path that contains each node, the weight of the circle plus the weight of its reversed circle should be minimal. – Eric Feb 04 '19 at 08:23

2 Answers2

4

How did he/she come up with [0,4,2,1,3]?

That permutation certainly isn't the right answer for the example input (see reasoning below), so I think that Emma's comment above is spot-on: the problem statement is just demonstrating what a "seating arrangement as an Array" should look like in general, not specifically demonstrating the optimal seating arrangement for the example input.


As for why I say that [0,4,2,1,3] certainly isn't the right answer for the example you've given . . . I don't completely understand how we decide whether one permutation is better than another, but it's clear that [0,4,1,2,3] is better by any measure. For both [0,4,2,1,3] and [0,4,1,2,3], the first person (0) likes both neighbors; the second person (4) is neutral toward both neighbors; and the third and fifth people (2 and 3 in the former, 1 and 3 in the latter) each like one neighbor and are neutral toward the other. The only difference between the two permutations is that in [0,4,2,1,3], the fourth person (1) is neutral toward one neighbor and dislikes the other, whereas in [0,4,1,2,3], the fourth person (2) is neutral toward one neighbor and likes the other. So the latter is obviously superior, no matter whether we consider it more important to increase likes or to decrease dislikes.

ruakh
  • 175,680
  • 26
  • 273
  • 307
4

Checking all possible orders is a classical permutation task even if there might be a more efficient algorithm for this specific problem.

One optimization can be done by reducing the permutation to array length-1 since in circular orders e.g. 0,1,2,3,4 and 4,0,1,2,3 (and all further rotations) are the same. You can view the order from you own seat always starting at position 0.

(function ()
{
  'use strict';

  let popularity =
  [
    [ 0, 1, 1, 1, 1],   // ← yes you like all your friends
    [-1, 0, 1,-1, 0],
    [-1, 1, 0, 1, 0],
    [ 1, 1, 1, 0,-1],
    [ 1, 0, 0,-1, 0],
  ];

  function permutation(arr)
  {
    let
      l = arr.length,
      perms = []
    ;

    if(l<2)
      return [arr];

    for(let i=0; i<l; i++)
    {
      let
        cpy    = Array.from(arr),
        [perm] = cpy.splice(i, 1)
      ;
      perms.push(...permutation(cpy).map(v => [perm, ...v]));
    }

    return perms;
  }


  let
    keys = Array.from(popularity.keys()).slice(1),
    permutations = permutation(keys),
    rating = permutations.map(v =>
    {
      let
        last = v.length -1,

        // start with our own relationships to the left and right neighbour
        // (each: we like him, he likes us)
        rate =
            popularity [0]       [v[0]]
          + popularity [v[0]]    [0]
          + popularity [0]       [v[last]]
          + popularity [v[last]] [0]
      ;

      for(let i = 0; i<last; i++)
        rate += popularity[v[i]][v[i+1]] + popularity[v[i+1]][v[i]];

      return [rate, [0, ...v]];
    }
  ).sort( (v1, v2) => ( v1[0] === v2[0] ? 0 : (v1[0] > v2[0] ? -1 : 1))  );

  console.log(rating);

})();

output:

[ [ 8, [ 0, 4, 1, 2, 3 ] ],
  [ 8, [ 0, 3, 2, 1, 4 ] ],
  [ 6, [ 0, 3, 1, 2, 4 ] ],
  [ 6, [ 0, 4, 2, 1, 3 ] ],
  [ 4, [ 0, 1, 4, 2, 3 ] ],
  [ 4, [ 0, 1, 2, 3, 4 ] ],
  [ 4, [ 0, 4, 1, 3, 2 ] ],
  [ 4, [ 0, 1, 3, 2, 4 ] ],
  [ 4, [ 0, 2, 3, 1, 4 ] ],
  [ 4, [ 0, 3, 2, 4, 1 ] ],
  [ 4, [ 0, 4, 2, 3, 1 ] ],
  [ 4, [ 0, 4, 3, 2, 1 ] ],
  [ 2, [ 0, 3, 4, 2, 1 ] ],
  [ 2, [ 0, 3, 1, 4, 2 ] ],
  [ 2, [ 0, 2, 4, 1, 3 ] ],
  [ 2, [ 0, 4, 3, 1, 2 ] ],
  [ 2, [ 0, 3, 4, 1, 2 ] ],
  [ 2, [ 0, 1, 2, 4, 3 ] ],
  [ 2, [ 0, 2, 1, 4, 3 ] ],
  [ 2, [ 0, 2, 1, 3, 4 ] ],
  [ 0, [ 0, 1, 4, 3, 2 ] ],
  [ 0, [ 0, 2, 3, 4, 1 ] ],
  [ -2, [ 0, 1, 3, 4, 2 ] ],
  [ -2, [ 0, 2, 4, 3, 1 ] ] ]

As we can see, there still are reversed permutations combined with yourself (0) having the same rating of course. Eleminating mirrored orders, i.e. reversed permutations, would be another optimization.

I did this for demonstration in single steps to have a more readable code facing single problems step by step. You could refactor the rating calculation directly into the permutation algorithm.

Properly calculating the time complexity does not seem to be that easy. Please read the discussion in the comments below.

Pinke Helga
  • 6,378
  • 2
  • 22
  • 42
  • "Find each permutation and rate it" is the right approach overall -- it's the approach I used to find the answer I wrote above -- but I don't think your last paragraph is quite right; it's true that there are only (*n*-1)!/2 distinct permutations, but since they have length *n*, the complexity of generating each is at least O(n) rather than O(1), so the total complexity for the permutation part is at least O(n!). – ruakh Feb 04 '19 at 04:46
  • @ruakh I did not get it right yet, or you did not. The permutation runs on the sliced keys only `Array.from(popularity.keys()).slice(1)`. How is it length *n*? 0 (self) is added in the `map` callback later for output purposes only `[rate, [0, ...v]]`, `v` was the permutation result of 1..4 – Pinke Helga Feb 04 '19 at 04:49
  • Your comment doesn't make sense, sorry. Even ignoring the `[0, ...v]` in your code, and therefore granting that you're only generating permutations of length *n*-1, that's still a complexity of O(n-1) = O(n), so the total complexity for the permutation part is at least O((n-1)(n-1)!) = O(n!). (N.B. I had to work this out on paper to confirm my last equation, but it's correct; the ratio of (n-1)(n-1)! to n! converges to 1 as n goes to infinity, so they're asymptotically equivalent.) – ruakh Feb 04 '19 at 05:04
  • @ruakh Do you have the rep to open a chat? I'ld like to go deeper into this problem. The output of `permutations` array is `[ [ 1, 2, 3, 4 ], [ 1, 2, 4, 3 ], [ 1, 3, 2, 4 ], [ 1, 3, 4, 2 ], [ 1, 4, 2, 3 ], [ 1, 4, 3, 2 ], [ 2, 1, 3, 4 ], [ 2, 1, 4, 3 ], [ 2, 3, 1, 4 ], [ 2, 3, 4, 1 ], [ 2, 4, 1, 3 ], [ 2, 4, 3, 1 ], [ 3, 1, 2, 4 ], [ 3, 1, 4, 2 ], [ 3, 2, 1, 4 ], [ 3, 2, 4, 1 ], [ 3, 4, 1, 2 ], [ 3, 4, 2, 1 ], [ 4, 1, 2, 3 ], [ 4, 1, 3, 2 ], [ 4, 2, 1, 3 ], [ 4, 2, 3, 1 ], [ 4, 3, 1, 2 ], [ 4, 3, 2, 1 ] ]` => 24 rows = 4! – Pinke Helga Feb 04 '19 at 05:07
  • I have the rep for anything that requires rep, but I actually have no idea how to open a chat, sorry! :-P . . . But my point is, `permutations` has 4! elements, *each of which has 4 elements*. That comes out to O((n-1)(n-1)!), which is the same as O(n!). – ruakh Feb 04 '19 at 05:22
  • @ruakh As I just reread the question, it actually contains the 'time complexity'. That's what you're calculating, the runtime c. O((n-1)!) is the theoretical complexity. Did I get it correct? There was a Q asking for that: https://stackoverflow.com/questions/39125471/why-time-complexity-of-permutation-function-is-on/39126141 According to your comments this would mean, running a n! permutation would result into O((n+1)!) r.t.c.? – Pinke Helga Feb 04 '19 at 05:38
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187832/discussion-between-ruakh-and-quasimodos-clone). – ruakh Feb 04 '19 at 05:43