Explanation of the neat Sort+Insert solution


  • 67

    Below is my explanation of the following neat solution where we sort people from tall to short (and by increasing k-value) and then just insert them into the queue using their k-value as the queue index:

    def reconstructQueue(self, people):
        people.sort(key=lambda (h, k): (-h, k))
        queue = []
        for p in people:
            queue.insert(p[1], p)
        return queue
    

    I didn't come up with that myself, but here's my own explanation of it, as I haven't seen anybody explain it (and was asked to explain it):

    People are only counting (in their k-value) taller or equal-height others standing in front of them. So a smallest person is completely irrelevant for all taller ones. And of all smallest people, the one standing most in the back is even completely irrelevant for everybody else. Nobody is counting that person. So we can first arrange everybody else, ignoring that one person. And then just insert that person appropriately. Now note that while this person is irrelevant for everybody else, everybody else is relevant for this person - this person counts exactly everybody in front of them. So their count-value tells you exactly the index they must be standing.

    So you can first solve the sub-problem with all but that one person and then just insert that person appropriately. And you can solve that sub-problem the same way, first solving the sub-sub-problem with all but the last-smallest person of the subproblem. And so on. The base case is when you have the sub-...-sub-problem of zero people. You're then inserting the people in the reverse order, i.e., that overall last-smallest person in the very end and thus the first-tallest person in the very beginning. That's what the above solution does, Sorting the people from the first-tallest to the last-smallest, and inserting them one by one as appropriate.

    Now that's my explanation. If you have a different one, I'm interested to see it :-)


  • 0
    D

    Actually, I solved it by sorting from tall to short (i.e. non-increasing order or height) and breaking ties by putting the smaller k-values first. So, people with equal height are sorted (among themselves) in increasing k-value order. After that I did the same thing you did.


  • 3

    @dribvurhd That's exactly how I sort, no?


  • 0
    D

    err... I got confused by the (-h, k) transform. You got me!


  • 29

    Nice explanation. I actually came to this solution myself, and pretty quickly, only to be completely baffled by the next rain problem. My code was a bit different, though, because I totally forgot about the key parameter:

            res = []
            for p in sorted((-x[0], x[1]) for x in people):
                res.insert(p[1], [-p[0], p[1]])
            return res
    

    My thinking process, though, was completely backwards. At first I remembered the Russian Doll Envelopes problem, which had me baffled for a while, and this awesome solution to it. So I immediately thought about sorting in various orders by various keys. It became instantly obvious that persons with the same height should be sorted by the second value: if they have the same height, then they have the same selection criteria for counting, and therefore the one with more selected persons in front of them should be behind.

    Then I thought about height. Suppose I take only the tallest persons, all having the same maximum height. Their second values must be 0, 1, 2, 3... with no gaps at all, because they only count each other. Therefore, if there were no other persons at all, their second value must be their final index. What about the persons with second maximum height then? Suppose there are only tallest persons and just one more person who has slightly smaller height. What would be his position? Well, since he obviously only count tallest persons, his position would still be his second value. The next person of the same height counts only the previous person and all the tallest ones, but since they are all already in the queue, his second value would also be his index.

    Then I realized that I could go on forever like that because each time I put a person in the queue and go to the next person, all persons counted by the next one are already there, so I instantly know the right index and I know that the person I put in the queue doesn't really care about where I put all subsequent persons because they are outside of his selection criteria.


  • 0
    X

    @SergeyTachenov This is a really detailed and good thinking process. Thanks!


  • 0
    R

    My solution is very similar to yours. Only different thing is I use heap instead sorting. But idea is mostly the same.

    class Solution(object):
        def reconstructQueue(self, people):
            """
            :type people: List[List[int]]
            :rtype: List[List[int]]
            """
            hp = []
            for p in people:
                heapq.heappush(hp, (-p[0],p[1]))
            ans = []
            while hp:
                p = heapq.heappop(hp)
                ans.insert(p[1], [-p[0],p[1]])
            return ans
    

  • 0
    M

    @StefanPochmann Can you please explain why doesn't it work in the reverse, that is sort by ascending order on tie, sort them by ascending on k?


  • 0
    S

    Neat and clean java implementation of the above python code.

    public int[][] reconstructQueue(int[][] people) {
    		ArrayList<int[]> list = new ArrayList<>();
    		Arrays.sort(people, new Comparator<int[]>() {
    			@Override
    			public int compare(int[] o1, int[] o2) {
    				if (o1[0] > o2[0])
    					return -1;
    				else if (o1[0] < o2[0])
    					return 1;
    				else{
    					if(o1[1]>o2[1]){
    						return 1;
    					}else if(o1[1]<o2[1]){
    						return -1;
    					}else{
    						return 0;
    					}
    				}
    			}
    		});
    
    		for (int i = 0; i < people.length; i++) {
    			list.add(people[i][1], new int[] { people[i][0], people[i][1] });
    		}
    
    		int res[][] = new int[people.length][2];
    		for(int i=0; i<list.size(); i++){
    			res[i][0] = list.get(i)[0];
    			res[i][1] = list.get(i)[1];
    		}
    		return res;
    	}
    

  • 0
    Z
    This post is deleted!

  • 1

    Your lambda is brilliant!


  • 0
    M

    This is my approach to the problem. I sorted the array in ascending order for all the elements. Then I am traversing from end(second last element) to start and checking how far it is from its position and then transferring it there.
    Here is my naive code to do it.

    def reconstructQueue(self, people):
        """
        :type people: List[List[int]]
        :rtype: List[List[int]]
        """
        if len(people) < 2:
            return people
        p = sorted(people)
        for i in range(len(p)-2,-1,-1):
            b = p[i][1]
            if b ==0:
                continue
            cnt = 0
            j = i-1
            while j >= 0:
                if p[j][0] == p[i][0]:
                    cnt+=1
                else:
                    break
                j-=1
            if cnt < b:
                for t in range(b-cnt):
                    p[i+t],p[i+t+1] = p[i+t+1],p[i+t]
        return p

  • 0
    S

    I guess this part could be cleaner
    @Override
    public int compare(int[] a, int[] b){
    if(b[0]==a[0]){
    return a[1]-b[1];
    }
    else return b[0]-a[0];
    }


  • 0

    i hate code-only posts! this thinking-process explanation should be awarded a golden badge.

    @SergeyTachenov said in Explanation of the neat Sort+Insert solution:

    Nice explanation. I actually came to this solution myself, and pretty quickly, only to be completely baffled by the next rain problem. My code was a bit different, though, because I totally forgot about the key parameter:

            res = []
            for p in sorted((-x[0], x[1]) for x in people):
                res.insert(p[1], [-p[0], p[1]])
            return res
    

    My thinking process, though, was completely backwards. At first I remembered the Russian Doll Envelopes problem, which had me baffled for a while, and this awesome solution to it. So I immediately thought about sorting in various orders by various keys. It became instantly obvious that persons with the same height should be sorted by the second value: if they have the same height, then they have the same selection criteria for counting, and therefore the one with more selected persons in front of them should be behind.

    Then I thought about height. Suppose I take only the tallest persons, all having the same maximum height. Their second values must be 0, 1, 2, 3... with no gaps at all, because they only count each other. Therefore, if there were no other persons at all, their second value must be their final index. What about the persons with second maximum height then? Suppose there are only tallest persons and just one more person who has slightly smaller height. What would be his position? Well, since he obviously only count tallest persons, his position would still be his second value. The next person of the same height counts only the previous person and all the tallest ones, but since they are all already in the queue, his second value would also be his index.

    Then I realized that I could go on forever like that because each time I put a person in the queue and go to the next person, all persons counted by the next one are already there, so I instantly know the right index and I know that the person I put in the queue doesn't really care about where I put all subsequent persons because they are outside of his selection criteria.


  • 0

    Remarkable idea to relate to this algorithm in the perspective of recursion, thank you for sharing this.


  • 0
    J

    @SergeyTachenov Your explanation is awesome!


Log in to reply
 

Looks like your connection to LeetCode Discuss was lost, please wait while we try to reconnect.