Reputation: 53
I'm working through Cracking the Coding Interview 6th ed, and I'm having trouble with the Circus Tower problem (number 17.8). I have a solution that I think runs in O(N logN) time, but the book's solution (which is different) says that the O(N logN) solution is very complex, but my code is not. I'd like some help figuring out if my solution is correct, and if it actually runs in O(N logN) time. I want to understand why I am wrong (or right), so any details would be helpful.
A circus is designing a tower routine consisting of people standing atop one another's shoulders. For practical and aesthetic reasons, each person must be both shorter and lighter than the person below him or her. Given the heights and weights of each person in the circus, write a method to compute the largest possible number of people in such a tower.
def circus_tower(people):
# People is a list of tuples of length 2, where item[0] is their height, and item[1] is their weight.
if len(people) == 0:
return 0
people = sorted(people, key=lambda x: x[0]) # Sort people by heights. O(P*logP).
start = 0
end = 0
longest_sequence = 0
for i in range(1, len(people)): # O(P).
if people[i][1] > people[i-1][1]: # Weight check.
end = i
else:
longest_sequence = end - start + 1
start = i
end = i
return max(longest_sequence, end - start + 1)
Here are some sample inputs and what my code returns:
circus_tower([(65, 100), (70, 150), (56, 90), (75, 190), (60, 95), (68, 110)])
6
circus_tower([(65, 100), (70, 150), (56, 90), (75, 190), (65, 95), (68, 110)])
4
circus_tower([(56, 90), (65, 95), (72, 100), (68, 90), (70, 150), (75, 190)])
2
circus_tower([])
0
Upvotes: 3
Views: 2821
Reputation: 1
I went through this thread and decided I can attempt to clarify this. Consider this as an example of those cases where a greedy approach does not work because we are not able to decide for sure if taking a particular element is invariably the better solution that not taking it. We have seen a similar problem with Knapsack 0/1 problem. If you are given the weights: [1,2,3,4] and profits [80, 100, 20, 60] and your capacity is 5, then greedily you can choose 120 (100+20), but then you will realise that leaving out 100 was the better choice, since you could have got 140 (60+80).
To solve this problem, we need to assume a somewhat similar approach: first let's sort the array of people, because unlike in case of Knapsack, here the order of taking elements matters. But both of these approaches boils down to a similar idea: to optimize the output, should you take the nth element or leave it?
Here is my Java recursive solution which runs in O(2^n) complexity:
Approach-1: Recusrive/Backtracking solution
public static void makeTower(List<Person> list, List<Person> tower, int index)
{
//Base case
if(index==-1)
{
printTower(tower);
return;
}
if(tower.get(tower.size()-1).height > list.get(index).height && tower.get(tower.size()-1).weight > list.get(index).weight) {
tower.add(list.get(index));
//if it is possible to add this person to the tower, add him
makeTower(list, new ArrayList<>(tower), index - 1);
}
//OR, choose to exclude the person
tower.remove(list.get(index));
makeTower(list, new ArrayList<>(tower), index-1);
}
Where "Person" class is just the encapsulation of his height and weight
public class Person {
int height;
int weight;
public Person(int _h, int _w)
{
this.height = _h;
this.weight = _w;
}
}
And "printTower" method does nothing but just print the possible tower to you after every combination
public static void printTower(List<Person> t)
{
for(Person p : t)
System.out.println(p.height+" , "+p.weight);
System.out.println("-----END-----");
}
Test input and output:
So if we consider this example:
The people (height, weight) : [20, 80] [10, 40] [80, 40] First step, sort by heights -> [10, 40] [20, 80] [80, 40] Then, we can possibly construct these towers:
[10, 40] on top of [20, 80]
This is the only tower possible from all the combinations.
So finally, the main (driver) method:
public static void main(String args[])
{
List<Person> people = new ArrayList();
people.add(new Person(20,80));
people.add(new Person(10, 40));
people.add(new Person(80, 40));
Collections.sort(people, new Comparator<Person>()
{
@Override
public int compare(Person one, Person two)
{
return (one.height-two.height);
}
});
List<Person> t = new ArrayList<>();
t.add(new Person(1000,1000));
makeTower(people, t, people.size()-1);
}
As mentioned, the runtime complexity of this algorithm is exponential, but it turns out that there is a better method for this which suits only this problem. This solution is explained in Cracking the coding interview.
Approach 2 - Dynamic Programming
The second approach runs in O(n^2) complexity, not pretty but definitely a lot of improvement over exponential. The approach is that we will take turns to consider each person as the last person in the tower. How long will the tower run if person-0 is the last person? And so on till person-n. In our case,
length of tower(given person-0 is at base) : 2 (highest) length of tower(given person-1 is at base) : 1 length of tower(given person-2 is at base) : 1
So 2 is our answer (maximum tower size). Bam! That's our answer. Much less fuss, but as with most DP approaches, the problem with this approach is that its a much more specific type of solution that may/may not suit other similar types of problems, and hence a tad bit more difficult to come up with.
Since this post has already run so long, let me know if anyone would need the code for approach-2 with explanation, would be glad to help! :)
Upvotes: 0
Reputation: 677
I have divided the problem into three parts.
Insert the data HeightWeight object and then sort with height or width. I have sorted with height
After that insert the values into the map to get unique heights with minimum weight.
After that I have found the Longest Increasing subsequence for weight.
import java.util.*;
public class CircusTower {
private class HeightWeight implements Comparable<HeightWeight>{
int height;
int weight;
HeightWeight(int height, int weight) {
this.height = height;
this.weight = weight;
}
@Override
public int compareTo(HeightWeight other) {
if(this.height == other.height){
return this.weight - other.weight;
}else{
return this.height - other.height;
}
}
}
public static void main(String[] args) {
int[][] arr = {{1,1},{1,7},{1,9},{2,2},{2,6},{3,3},{3,5},{4,4}};
CircusTower ct = new CircusTower();
System.out.println(ct.getMaxHeightTower(arr));
}
public int getMaxHeightTower(int[][] arr){
List<HeightWeight> list = new ArrayList<>();
int i =0;
for(i =0; i<arr.length; i++){
list.add(new HeightWeight(arr[i][0], arr[i][1]));
}
Collections.sort(list);
Map<Integer, Integer> map = new HashMap<>();
for(i =0; i<list.size(); i++){
HeightWeight current = list.get(i);
if(!map.containsKey(current.height)){
map.put(current.height, current.weight);
}
}
int[] nums = map.values().stream().mapToInt(Integer::intValue).toArray();
return getLIS(nums);
}
public int getLIS(int[] nums){
int _l = nums.length;
int[] out = new int[_l];
int mx = Integer.MIN_VALUE;
/*
we initialize the array with ones because
a single character has a subsequence of length one
*/
Arrays.fill(out, 1);
for(int i = 0; i < _l; i++)
for(int j = i + 1; j < _l; j++){
/*
every iteration, all we're doing is checking what is the longest
increasing subsequence so far, till this point
*/
if(nums[j] > nums[i])
out[j] = Math.max(out[j], out[i] + 1);
/*
we keep checking for a max value because the longest
subsequence could exist before the end of the string
*/
mx = Math.max(out[j], mx);
}
return mx == Integer.MIN_VALUE ? 1 : mx;
}
}
Upvotes: 0
Reputation: 485
There is a correct solution
module CircusTowerSo
class Person
include Comparable
attr_reader :height, :weight
def initialize(height, weight)
@height = height
@weight = weight
end
def <=>(other)
res = height <=> other.height
if res == 0
weight <=> other.weight
else
res
end
end
def smaller?(other)
height < other.height && weight < other.weight
end
def to_s
"(#{height}, #{weight})"
end
def inspect
to_s
end
end
# Time=O(n * n) Mem=O(n * n)
def solve_b(people)
sorted = people.sort
find_lis_by_weight(sorted).size
end
def find_lis_by_weight(people)
longest_by_index_cache = people.each_with_index.map { |person, i| [i, [person]] }.to_h
longest = []
people.each_with_index do |person, index|
res = longest_for_index(longest_by_index_cache, index, person)
if res.size > longest.size
longest = res
end
longest_by_index_cache[index] = res
end
longest
end
def longest_for_index(longest_by_index_cache, index, person)
longest_prev_seq = []
index.times do |i|
prev_seq = longest_by_index_cache[i]
if prev_seq.last.smaller?(person) && prev_seq.size > longest_prev_seq.size
longest_prev_seq = prev_seq
end
end
longest_prev_seq + [person]
end
RSpec.describe 'CircusTower' do
include CircusTower
subject { solve_b(people) }
context 'book example' do
let(:people) do
[
Person.new(65, 100),
Person.new(70, 150),
Person.new(56, 90),
Person.new(75, 190),
Person.new(60, 95),
Person.new(68, 110),
]
end
it { is_expected.to eq 6 }
end
context 'tricky example' do
let(:people) do
[
Person.new(1, 1),
Person.new(1, 7),
Person.new(1, 9),
Person.new(2, 2),
Person.new(2, 6),
Person.new(3, 3),
Person.new(3, 5),
Person.new(4, 4),
]
end
it { is_expected.to eq 4 }
end
context 'more tricky example' do
let(:people) do
[
Person.new(1, 1),
Person.new(2, 2),
Person.new(3, 3),
Person.new(4, 1),
]
end
it { is_expected.to eq 3 }
end
end
end
see more solutions for CTCI on https://github.com/holyketzer/ctci_v6
Upvotes: 0
Reputation: 485
I also "found" a simple solution and can't understand where I'm wrong:
module CircusTower
class Person
include Comparable
attr_reader :height, :weight
def initialize(height, weight)
@height = height
@weight = weight
end
def <=>(other)
res = height <=> other.height
if res == 0
weight <=> other.weight
else
res
end
end
end
# Time=O(n * log n) Mem=O(n)
def solve_b(people)
sorted = people.sort.reverse
res = []
sorted.each do |person|
if res.size == 0
res << person
else
if res.last.height > person.height && res.last.weight > person.weight
res << person
end
end
end
res.size
end
RSpec.describe 'CircusTower' do
include CircusTower
subject { solve_b(people) }
context 'book example' do
let(:people) do
[
Person.new(65, 100),
Person.new(70, 150),
Person.new(56, 90),
Person.new(75, 190),
Person.new(60, 95),
Person.new(68, 110),
]
end
it { is_expected.to eq 6 }
end
context 'tricky example' do
let(:people) do
[
Person.new(1,1),
Person.new(1,7),
Person.new(1,9),
Person.new(2,2),
Person.new(2,6),
Person.new(3,3),
Person.new(3,5),
Person.new(4,4),
]
end
it { is_expected.to eq 4 }
end
end
end
Upvotes: 0
Reputation: 154
Your solution is wrong. If you run
circus_tower([[1,1],[1,7],[1,9],[2,2],[2,6],[3,3],[3,5],[4,4]])
it returns 2
while the longest subsequence ([1,1]<[2,2]<[3,3]<[4,4]
) has length 4.
The problem with your code is that you only find contiguous subsequences.
Upvotes: 2