Hello I am currently reading at the knight tour problem at Geeksforgeeks https://www.geeksforgeeks.org/the-knights-tour-problem-backtracking-1
I am testing the code bymyself and when I change the sequence of
knight moves at the code
let xMove = [ 2, 1, -1, -2, -2, -1, 1, 2 ];
let yMove = [ 1, 2, 2, 1, -1, -2, -2, -1 ];
to this
let xMove = [1,1,-1,-1,2,2,-2,-2]
let yMove = [2,-2,-2,2,-1,1,-1,1]
and the problem seems doesn't reach to solution. Does this probem relies on the sequence of the knight moves or what is the cause of it? as to my understanding, the recursion will search all possible moves so it should not be difference right?
...and the problem seems doesn't reach to solution
This is exactly the problem that is also mentioned on Wikipedia:
A brute-force search for a knight's tour is impractical on all but the smallest boards. For example, there are approximately 4×1051 possible move sequences on an 8×8 board, and it is well beyond the capacity of modern computers (or networks of computers) to perform operations on such a large set.
The order of moves in the implementation you quote from GfG, is a lucky order. With the order that you have tested with, the amount of backtracking is enormous. One can image that taking the right moves in the very beginning of the path is crucial. If one of the early moves is wrong, there will be a tremendous amount of backtracking taking place in the deeper nodes of the recursion tree.
There is a heuristic that greatly reduces the number of moves to consider, most of the time only 1: this is Warnsdorff's rule:
The knight is moved so that it always proceeds to the square from which the knight will have the fewest onward moves. When calculating the number of onward moves for each candidate square, we do not count moves that revisit any square already visited. It is possible to have two or more choices for which the number of onward moves is equal; there are various methods for breaking such ties,...
In the case of an 8×8 board there is no practical need for breaking ties: the backtracking will resolve wrong choices. But as now the search tree is very narrow, this does not lead to a lot of backtracking, even if we're unlucky.
Here is an implementation in a runnable JavaScript snippet. It intentionally shuffles the list of moves randomly, and prints "backtracking" whenever it needs to backtrack, so that you can experiment on different runs. This will show that this always finds the solution with hardly any backtracking on average:
class Solver {
constructor(size) {
this.size = size;
this.grid = Array.from({length: size}, () => Array(size).fill(-1));
}
toString() {
return this.grid.map(row =>
row.map(i => (i + "").padStart(2, "0")).join(" ")
).join("\n");
}
liberty(x, y) {
// Count the number of legal moves
return [...this.getMoveList(x, y)].length;
}
*getMoveList(x, y) {
// Get list of legal moves, in random order
let moves = [[1, 2], [1, -2], [-1, -2], [-1, 2],
[2, -1], [2, 1], [-2, -1], [-2, 1]];
// Shuffle moves randomly
for (var i = moves.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
[moves[i], moves[j]] = [moves[j], moves[i]]; // Swap
}
// Yield the valid positions that can be reached
for (let [moveX, moveY] of moves) {
if (this.grid[x + moveX]?.[y + moveY] == -1) {
yield [x + moveX, y + moveY];
}
}
}
getBestMoveList(x, y) {
// Get list of move(s) that have the least possible follow-up moves
let minLiberty = 100000;
const bestMoves = [];
// Consider every legal move:
for (let [nextX, nextY] of this.getMoveList(x, y)) {
let liberty = this.liberty(nextX, nextY);
if (liberty < minLiberty) {
minLiberty = liberty;
bestMoves.length = 0; // Empty the array
}
if (liberty == minLiberty) bestMoves.push([nextX, nextY]);
}
if (Math.random() >= 0.5) bestMoves.reverse();
return bestMoves;
}
solve(x, y, moveCount=0) {
this.grid[x][y] = moveCount++;
if (moveCount == this.size * this.size) return true;
// Try all of the BEST next moves from the current coordinate x, y
for (let [nextX, nextY] of this.getBestMoveList(x, y)) {
if (this.solve(nextX, nextY, moveCount)) return true;
}
console.log("backtrack");
this.grid[x][y] = -1;
return false;
}
}
// Driver code
const solver = new Solver(8);
let success = solver.solve(0, 0);
console.log(success ? solver.toString() : "Solution does not exist");
Related
I was asked this during a coding interview but wasn't able to solve this. Any pointers would be very helpful.
I was given an integer list (think of it as a number line) which needs to be rearranged so that the difference between elements is equal to M (an integer which is given). The list needs to be rearranged in such a way that the value of the max absolute difference between the elements' new positions and the original positions needs to be minimized. Eventually, this value multiplied by 2 is returned.
Test cases:
//1.
original_list = [1, 2, 3, 4]
M = 2
rearranged_list = [-0.5, 1.5, 3.5, 5.5]
// difference in values of original and rearranged lists
diff = [1.5, 0.5, 0.5, 1.5]
max_of_diff = 1.5 // list is rearranged in such a way so that this value is minimized
return_val = 1.5 * 2 = 3
//2.
original_list = [1, 2, 4, 3]
M = 2
rearranged_list = [-1, 1, 3, 5]
// difference in values of original and rearranged lists
diff = [2, 1, 1, 2]
max_of_diff = 2 // list is rearranged in such a way so that this value is minimized
return_val = 2 * 2 = 4
Constraints:
1 <= list_length <= 10^5
1 <= M <= 10^4
-10^9 <= list[i] <= 10^9
There's a question on leetcode which is very similar to this: https://leetcode.com/problems/minimize-deviation-in-array/ but there, the operations that are performed on the array are mentioned while that's not been mentioned here. I'm really stumped.
Here is how you can think of it:
The "rearanged" list is like a straight line that has a slope that corresponds to M.
Here is a visualisation for the first example:
The black dots are the input values [1, 2, 3, 4] where the index of the array is the X-coordinate, and the actual value at that index, the Y-coordinate.
The green line is determined by M. Initially this line runs through the origin at (0, 0). The red line segments represent the differences that must be taken into account.
Now the green line has to move vertically to its optimal position. We can see that we only need to look at the difference it makes with the first and with the last point. The other two inputs will never contribute to an extreme. This is generally true: there are only two input elements that need to be taken into account. They are the points that make the greatest (signed -- not absolute) difference and the least difference.
We can see that we need to move the green line in such a way that the signed differences with these two extremes are each others opposite: i.e. their absolute difference becomes the same, but the sign will be opposite.
Twice this absolute difference is what we need to return, and it is actually the difference between the greatest (signed) difference and the least (signed) difference.
So, in conclusion, we must generate the values on the green line, find the least and greatest (signed) difference with the data points (Y-coordinates) and return the difference between those two.
Here is an implementation in JavaScript running the two examples you provided:
function solve(y, slope) {
let low = Infinity;
let high = -Infinity;
for (let x = 0; x < y.length; x++) {
let dy = y[x] - x * slope;
low = Math.min(low, dy);
high = Math.max(high, dy);
}
return high - low;
}
console.log(solve([1, 2, 3, 4], 2)); // 3
console.log(solve([1, 2, 4, 3], 2)); // 4
I only heard of this question, so I don't know the exact limits. You are given a list of positive integers. Each two consecutive values form a closed interval. Find the number that appears in most intervals. If two values appear the same amount of times, select the smallest one.
Example: [4, 1, 6, 5] results in [1, 4], [1, 6], [5, 6] with 1, 2, 3, 4, 5 each showing up twice. The correct answer would be 1 since it's the smallest.
I unfortunately have no idea how this can be done without going for an O(n^2) approach. The only optimisation I could think of was merging consecutive descending or ascending intervals, but this doesn't really work since [4, 3, 2] would count 3 twice.
Edit: Someone commented (but then deleted) a solution with this link http://www.zrzahid.com/maximum-number-of-overlapping-intervals/. I find this one the most elegant, even though it doesn't take into account the fact that some elements in my input would be both the beginning and end of some intervals.
Sort intervals based on their starting value. Then run a swipe line from left (the global smallest value) to the right (the global maximum value) value. At each meeting point (start or end of an interval) count the number of intersection with the swipe line (in O(log(n))). Time complexity of this algorithm would be O(n log(n)) (n is the number of intervals).
The major observation is that the result will be one of the numbers in the input (proof left to the reader as simple exercise, yada yada).
My solution will be inspired by #Prune's solution. The important step is mapping the input numbers to their order within all different numbers in the input.
I will work with C++ std. We can first load all the numbers into a set. We can then create map from that, which maps a number to its order within all numbers.
int solve(input) {
set<int> vals;
for (int n : input) {
vals.insert(n);
}
map<int, int> numberOrder;
int order = 0;
for (int n : vals) { // values in a set are ordered
numberOrder[n] = order++;
}
We then create process array (similar to #Prune's solution).
int process[map.size() + 1]; // adding past-the-end element
int curr = input[0];
for (int i = 0; i < input.size(); ++i) {
last = curr;
curr = input[i];
process[numberOrder[min(last, curr)]]++;
process[numberOrder[max(last, curr)] + 1]--;
}
int appear = 0;
int maxAppear = 0;
for (int i = 0; i < process.size(); ++i) {
appear += process[i];
if (appear > maxAppear) {
maxAppear = appear;
maxOrder = i;
}
}
Last, we need to find our found value in the map.
for (pair<int, int> a : numberOrder) {
if (a.second == maxOrder) {
return a.first;
}
}
}
This solution has O(n * log(n)) time complexity and O(n) space complexity, which is independent on maximum input number size (unlike other solutions).
If the maximum number in the range array is less than the maximum size limit of an array, my solution will work with complexity o(n).
1- I created a new array to process ranges and use it to find the
numbers that appears most in all intervals. For simplicity let's use
your example. the input = [1, 4], [1, 6], [5, 6]. let's call the new
array process and give it length 6 and it is initialized with 0s
process = [0,0,0,0,0,0].
2-Then loop through all the intervals and mark the start with (+1) and
the cell immediately after my range end with (-1)
for range [1,4] process = [1,0,0,0,-1,0]
for range [1,6] process = [2,0,0,0,-1,0]
for range [5,6] process = [2,0,0,0,0,0]
3- The p rocess array will work as accumulative array. initialize a
variable let's call it appear = process[0] which will be equal to 2
in our case. Go through process and keep accumulating what can you
notice? elements 1,2,3,4,5,6 will have appear =2 because each of
them appeared twice in the given ranges .
4- Maximize while you loop through process array you will find the
solution
public class Test {
public static void main(String[] args) {
int[] arr = new int[] { 4, 1, 6, 5 };
System.out.println(solve(arr));
}
public static int solve(int[] range) {
// I assume that the max number is Integer.MAX_VALUE
int size = (int) 1e8;
int[] process = new int[size];
// fill process array
for (int i = 0; i < range.length - 1; ++i) {
int start = Math.min(range[i], range[i + 1]);
int end = Math.max(range[i], range[i + 1]);
process[start]++;
if (end + 1 < size)
process[end + 1]--;
}
// Find the number that appears in most intervals (smallest one)
int appear = process[0];
int max = appear;
int solu = 0;
for (int i = 1; i < size; ++i) {
appear += process[i];
if (appear > max){
solu = i;
max = appear;
}
}
return solu;
}
}
Think of these as parentheses: ( to start and interval, ) to end. Now check the bounds for each pair [a, b], and tally interval start/end markers for each position: the lower number gets an interval start to the left; the larger number gets a close interval to the right. For the given input:
Process [4, 1]
result: [0, 1, 0, 0, 0, -1]
Process [1, 6]
result: [0, 2, 0, 0, 0, -1, 0, -1]
Process [6, 5]
result: [0, 2, 0, 0, 0, -1, 1, -2]
Now, merely make a cumulative sum of this list; the position of the largest value is your desired answer.
result: [0, 2, 0, 0, 0, -1, 1, -2]
cumsum: [0, 2, 2, 2, 2, 1, 2, 0]
Note that the final sum must be 0, and can never be negative. The largest value is 2, which appears first at position 1. Thus, 1 is the lowest integer that appears the maximum (2) quantity.
No that's one pass on the input, and one pass on the range of numbers. Note that with a simple table of values, you can save storage. The processing table would look something like:
[(1, 2)
(4, -1)
(5, 1)
(6, -2)]
If you have input with intervals both starting and stopping at a number, then you need to handle the starts first. For instance, [4, 3, 2] would look like
[(2, 1)
(3, 1)
(3, -1)
(4, -1)]
NOTE: maintaining a sorted insert list is O(n^2) time on the size of the input; sorting the list afterward is O(n log n). Either is O(n) space.
My first suggestion, indexing on the number itself, is O(n) time, but O(r) space on the range of input values.
[
I ran into a problem where I wanted to add a little feature to my homework and it turned out to be overwhelming for me (read bold sentences for question without context).
My program has a list of about 35 items in it, containing information about the map I'm supposed to work with. It can have the following elements:
"Wall" with its coordinates (X, Y), in the dijkstra it is supposed to have weight 100.
"Tree" with cords (X, Y), weight 3
I have a 10x10 map laid out like a chessboard, which means 100 tiles, and 35 items. "Nothing" in the list means weight 1 in dijkstra (it means a normal route)
To make the dijkstra work and be able to find a shortest path between two tiles, I have to build an adjacency graph. My problem here is, how to define tiles "around" the current tile, if all I have is that list?
Only adjacent tiles in the shape of "+" have edges in the graph between them, but I have to run through the list every time to check if there is something on it?
Any lead on the problem would be greatly appreciated, also if you can point me to a source with a code sample, that could also do. I only see really messy code with a lot of "if-elseif-elseif..." to solve that.
Thank you for your time!
Edit: I ended up using #kraskevich 's suggested way, and it works excellent, but all the answers and suggestions were really useful, thank you very much everyone!
You don't really need to build a graph. Just create a 10x10 table and put the weight of the corresponding items into it:
board = 10x10 array filled with 1
for item in list:
if item is a tree:
board[item.row][item.column] = 3
else if item is a wall:
board[item.row][item.column] = 100
After that, you can treat pairs (row, col) of coordinates as vertices and update the distance for 4 adjacent cells when you process a tile. That's it. Sure, you can also create a graph with 100 vertices and add edges explicitly from a tile to all 4 adjacent cells (the weight is the weight of the end of the edge tile) and use a standard implementation.
The most convenient way to iterate over adjacent cells is as follows:
delta_rows = [-1, 1, 0, 0]
delta_cols = [0, 0, -1, 1]
...
for direction = 0 .. 3
new_row = row + delta_rows[direction]
new_col = col + delta_cols[direction]
if is_valid(new_row, new_col)
// do something
It should be straightforward to implement Dijkstra based on this generic graph interface:
interface Graph<T> {
Iterable<T> adjacentNodes(T node);
double getDistance(T node, T adjacent);
}
So now all we need to do is to fill it for your case:
class Field {
final int x;
final int y;
int value = 1;
Field (int x, int y) {
this.x = x;
this.y = y;
}
}
class ChessboardGraph implements Graph<Field> {
Field[][] board = new Filed[10][10];
ChessboardGraph(List<Entry> list) {
for (int x = 0; x < 10; x++) {
for (int y = 0; y < 10; y++) {
board[x][y] = new Field(x, y);
}
}
for (Entry e: list) {
board[e.x][e.y].value = e.value == TREE ? 3 : 100;
}
}
Iterable<Field> adjacentNodes(Field node) {
ArrayList result = new ArrayList<>();
int x = node.x;
int y = node.y;
if (x > 0) {
if (y > 0) {
result.add(board[x - 1][y - 1]);
}
if (y < 9) {
result.add(board[x - 1][y + 1]);
}
}
if (x < 9) {
if (y > 0) {
result.add(board[x + 1][y - 1]);
}
if (y < 9) {
result.add(board[x + 1][y + 1]);
}
}
}
double getDistance(Field node, Field adjacent) {
assert Math.abs(node.x - adjacent.x) + Math.abs(node.y - adjacent.y) == 1;
return board[adjacent.x][adjacent.y];
}
}
My problem can be simplified as follows.
There're s bins, and within each bin there're k numbers.
A combination consists of one number from each bin, so in total there're k^s possible combinations.
The score of a combination is the sum of s numbers it contains.
How can I find all the combinations with score less than some value r?
Right now what I'm doing is,
1) sort the numbers in each bin.
2) start with a priority queue that only contains the combination of the smallest number from each bin.
3) pop a combination from the queue, add s children of that combination to to queue. (a child of a combination is made of replacing one number of the combination to the next larger number in the same bin, so there're s children of a combination.)
4) repeat 3) till the combination popped is larger than r.
Suppose we find n combinations smaller than r, the time complexity of this algorithm is then O(nlog(s-1)n + sklogk).
Of course this algorithm is not optimal. For example instead of starting with the smallest combination, we can start with a known lower bound. And I sort of have a feeling that dynamic programming can be applied here too, but I didn't figure out how to do it.
Any suggestions are welcome, thanks.
After having sorted the bins, you could use a recursive algorithm, that extends a partial selection with an element from the next bin until the selection is complete (or overruns the sum limit). When complete, it is added to the result. Through backtracking all the valid selections get added to the result.
Here is some pseudo code. The last argument is both input and output:
function combinations(int[][] bins, int r, int[] selection, int[][] result):
if r < 0 then:
return
if selection.length >= bins.length then:
result.add(selection)
return
bin = bins[selection.length]
for (i = 0; i < bin.length; i++):
# Concatenate the i-th value from the bin to a new selection array
combinations(bins, r - bin[i], selection + bin[i], result)
Here is an implementation in JavaScript:
function sortBins(bins) {
for (bin of bins) {
bin.sort(function (a,b) { return a-b; });
}
}
function combinations(bins, r, selection, result) {
if (r < 0) return result; // nothing added to result
if (selection.length >= bins.length) return result.concat([selection]);
var bin = bins[selection.length];
for (var i = 0; i < bin.length; i++)
result = combinations(bins, r - bin[i], selection.concat([bin[i]]), result);
return result;
}
// Test data:
var r = 13;
var bins = [
[5, 2, 3],
[9, 4, 1],
[6, 5, 7]
];
// Get solution:
sortBins(bins);
var result = combinations(bins, r, [], []);
// Output results:
console.log(result);
The following is a practice interview question that was given to me by someone, and I'm not sure what the best solution to this is:
Given a set of ranges:
(e.g. S = {(1, 4), (30, 40), (20, 91) ,(8, 10), (6, 7), (3, 9), (9, 12), (11, 14)}. And given a target range R (e.g. R = (3, 13) - meaning the range going from 3 to 13). Write an algorithm to find the smallest set of ranges that covers your target range. All of the ranges in the set must overlap in order to be considered as spanning the entire target range. (In this example, the answer would be {(3, 9), (9, 12), (11, 14)}.
What is the best way to solve this? I was thinking this would be done using a greedy algorithm. In our example above, we would look for all of the numbers that intersect with 3, and pick from those the one with the highest max. Then we would do the same thing with the one we just picked. So, since we picked (3, 9) we now want to find all of the ranges that intersect 9, and among those, we pick the one with the highest max. In that iteration, we picked (9, 12). We do the same thing to that one, and we find that the next range that intersects 12, with the highest max is (11, 14).
After that iteration, we see that 14 is greater than 13 (the max of our range), so we can stop.
The problem I'm having with this algorithm is, how do efficiently query the intersecting ranges? If we try a linear search, we end up with an algorithm that is O(n^2). My next thought was to cross off any of our intersecting ranges from our list each time we run through the loop. So in the first iteration, we cross of (1, 4) and (3, 9). In our next iteration we cross of (9, 12), (3, 9), and (8, 10). So by the last iteration, all we have to look through is {(30, 40), (20, 91), (6, 7)}. We could make this even more efficient by also crossing out everything that has a min > 13, and a max < 3. The problem is this still might not be enough. There is still the potential problem of having lots of duplicate sequences within the bounds of our range. If our list of ranges contained something like {(6, 7), (6, 7), (6, 7), (6, 7), (6, 7)} we would have to look through those each time, even though they aren't useful to us. Even if we were only to store unique values (by putting them all in a set), we might have a really big range, with a bunch of ranges that are inside of our target range, but we also have one range inside that spans almost the entire target range.
What would be an efficient way to query our ranges? Or possibly, what would be a more efficient algorithm to solving this problem?
How about using an interval tree for queries? (https://en.m.wikipedia.org/wiki/Interval_tree) I'm not sure if greedy could work here or not. If we look at the last set of choices, overlapping with the high point in R, there's a possibility of overlap between the earlier choices for each one of those, for example:
R = (2,10) and we have (8,10) and (7,10) both overlapping with (6,8)
In that case, we only need to store one value for (6,8) as a second leg of the path; and visiting (6,8) again as we make longer paths towards the low point in R would be superfluous since we already know (6,8) was visited with a lower leg count. So your idea of eliminating intervals as we go makes sense. Could something like this work?
leg = 1
start with the possible end (or beginning) intervals
label these intervals with leg
until end of path is reached:
remove the intervals labeled leg from the tree
for each of those intervals labeled leg:
list overlapping intervals in the chosen direction
leg = leg + 1
label the listed overlapping intervals with leg
I can suggest following algorithm with complexity O(n log n) without using Intervals trees.
Let introduce some notation. We should cover a range (X,Y) by intervals (x_i,y_i).
First sort given intervals (x_i,y_i) by start point. It will take O(n log n)
Let select from intervals (x_i,y_i) with x_i <= X interval (x_k,y_k) with maximum of y_i. Because interval already sorted by start point, we can just increment index, while interval satisfies condition. If y_k less than X, there are no solution for given set and range. In other case interval (x_k,y_k) contains 'X' and has maximal end point among intervals containing X.
Now we need to cover an interval (y_k, Y), to satisfy overlapping condition. Because for all intervals containing X has end point less than y_k+1, we can start from last interval from the previous step.
Each interval was used only once in this stage, so the time complexity of this part is O(n) and in total O(n log n).
Following code snippet for solution:
intervals // given intervals from set S
(X, Y) // range to cover
sort intervals
i = 0 // start index
start = X // start point
result_set // set to store result
while start <= Y && i < len(intervals):
next_start = intervals[i].y
to_add = intervals[i]
while intervals[i].x <= start && i < len(intervals):
if next_start > intervals[i].y:
next_start = intervals[i].y
to_add = intervals[i]
i++
if(next_start < start):
print 'No solution'
exit
start = next_start
result_set add to_add
Ok, after trying a bunch of different things, here is my solution. It runs in O(nlogn) time, and doesn't require the use of an Interval Tree (although I would probably use it if I could memorize how to implement one for an interview, but I think that would take too long without providing any real benefit).
The bottleneck of this algorithm is in the sorting. Every item is only touched once, but it only works with a sorted array, so that is the first thing we do. Thus the O(nlogn) time complexity. Because it modifies the original array , it has an O(1) space complexity, but if we were not allowed to modify the original array, we can just make a copy of it, and keep the rest of the algorithm the same, making the space complexity O(n).
import java.util.*;
class SmallestRangingSet {
static class Interval implements Comparable<Interval>{
Integer min;
Integer max;
public Interval(int min, int max) {
this.min = min;
this.max = max;
}
boolean intersects(int num) {
return (min <= num && max >= num);
}
//Overrides the compareTo method so it will be sorted
//in order relative to the min value
#Override
public int compareTo(Interval obj) {
if (min > obj.min) return 1;
else if (min < obj.min) return -1;
else return 0;
}
}
public static Set<Interval> smallestIntervalSet(Interval[] set, Interval target) {
//Bottleneck is here. The array is sorted, giving this algorithm O(nlogn) time
Arrays.sort(set);
//Create a set to store our ranges in
Set<Interval> smallSet = new HashSet<Interval>();
//Create a variable to keep track of the most optimal range, relative
//to the range before it, at all times.
Interval bestOfCurr = null;
//Keep track of the specific number that any given range will need to
//intersect with. Initialize it to the target-min-value.
int currBestNum = target.min;
//Go through each element in our sorted array.
for (int i = 0; i < set.length; i++) {
Interval currInterval = set[i];
//If we have already passed our target max, break.
if (currBestNum >= target.max)
break;
//Otherwise, if the current interval intersects with
//our currBestNum
if (currInterval.intersects(currBestNum)) {
//If the current interval, which intersects currBestNum
//has a greater max, then our current bestOfCurr
//Update bestOfCurr to be equal to currInterval.
if (bestOfCurr == null || currInterval.max >= bestOfCurr.max) {
bestOfCurr = currInterval;
}
}
//If our range does not intersect, we can assume that the most recently
//updated bestOfCurr is probably the most optimal new range to add to
//our set. However, if bestOfCurr is null, it means it was never updated,
//because there is a gap somewhere when trying to fill our target range.
//So we must check for null first.
else if (bestOfCurr != null) {
//If it's not null, add bestOfCurr to our set
smallSet.add(bestOfCurr);
//Update currBestNum to look for intervals that
//intersect with bestOfCurr.max
currBestNum = bestOfCurr.max;
//This line is here because without it, it actually skips over
//the next Interval, which is problematic if your sorted array
//has two optimal Intervals next to eachother.
i--;
//set bestOfCurr to null, so that it won't run
//this section of code twice on the same Interval.
bestOfCurr = null;
}
}
//Now we should just make sure that we have in fact covered the entire
//target range. If we haven't, then we are going to return an empty list.
if (currBestNum < target.max)
smallSet.clear();
return smallSet;
}
public static void main(String[] args) {
//{(1, 4), (30, 40), (20, 91) ,(8, 10), (6, 7), (3, 9), (9, 12), (11, 14)}
Interval[] interv = {
new Interval(1, 4),
new Interval(30, 40),
new Interval(20, 91),
new Interval(8, 10),
new Interval(6, 7),
new Interval(3, 9),
new Interval(9, 12),
new Interval(11, 14)
};
Set<Interval> newSet = smallestIntervalSet(interv, new Interval(3,14));
for (Interval intrv : newSet) {
System.out.print("(" + intrv.min + ", " + intrv.max + ") ");
}
}
}
Output
(3, 9) (9, 12) (11, 14)
Your assignment intrigued me, so I wrote a C++ program that solves the problem by iterating through the ranges that overlap the left side of the target range, and recursively searches for the smallest number of ranges that covers the remaining (right side) of the target range.
A significant optimization to this algorithm (not shown in this program) would be to, for each recursive level, use the range that overlaps the left side of the target range by the largest amount, and discarding from further consideration all ranges that overlap the left side by smaller amounts. By employing this rule, I believe there would be at most a single descent into the recursive call tree. Such an optimization would produce an algorithm having complexity O(n log(n)). (n to account for the depth of recursion, and log(n) to account for the binary search to find the range having the most overlap.)
This program produces the following as output:
{ (3, 9) (9, 12) (11, 14) }
Here is the program:
#include <utility> // for std::pair
#include <vector> // for std::vector
#include <iostream> // for std::cout & std::endl
typedef std::pair<int, int> range;
typedef std::vector<range> rangelist;
// function declarations
rangelist findRanges (range targetRange, rangelist candidateRanges);
void print (rangelist list);
int main()
{
range target_range = { 3, 13 };
rangelist candidate_ranges =
{ { 1, 4 }, { 30, 40 }, { 20, 91 }, { 8, 10 }, { 6, 7 }, { 3, 9 }, { 9, 12 }, { 11, 14 } };
rangelist result = findRanges (target_range, candidate_ranges);
print (result);
return 0;
}
// Recursive function that returns the smallest subset of candidateRanges that
// covers the given targetRange.
// If there is no subset that covers the targetRange, then this function
// returns an empty rangelist.
//
rangelist findRanges (range targetRange, rangelist candidateRanges)
{
rangelist::iterator it;
rangelist smallest_list_so_far;
for (it = candidateRanges.begin (); it != candidateRanges.end (); ++it) {
// if this candidate range overlaps the beginning of the target range
if (it->first <= targetRange.first && it->second >= targetRange.first) {
// if this candidate range also overlaps the end of the target range
if (it->second >= targetRange.second) {
// done with this level - return a list of ranges consisting only of
// this single candidate range
return { *it };
}
else {
// prepare new version of targetRange that excludes the subrange
// overlapped by the present range
range newTargetRange = { it->second + 1, targetRange.second };
// prepare new version of candidateRanges that excludes the present range
// from the list of ranges
rangelist newCandidateRanges;
rangelist::iterator it2;
// copy all ranges up to but not including the present range
for (it2 = candidateRanges.begin (); it2 != it; ++it2) {
newCandidateRanges.push_back (*it2);
}
// skip the present range
it2++;
// copy the remainder of ranges in the list
for (; it2 != candidateRanges.end(); ++it2) {
newCandidateRanges.push_back (*it2);
}
// recursive call to find the smallest list of ranges that cover the remainder
// of the target range not covered by the present range
rangelist subList = findRanges (newTargetRange, newCandidateRanges);
if (subList.size () == 0) {
// no solution includes the present range
continue;
}
else if (smallest_list_so_far.size () == 0 || // - first subList that covers the remainder of the target range
subList.size () < smallest_list_so_far.size ()) // - this subList is smaller than all previous ones checked
{
// add the present range to the subList, which represents a solution
// (though possibly not optimal yet) at the present level of recursion
subList.push_back (*it);
smallest_list_so_far = subList;
}
}
}
}
return smallest_list_so_far;
}
// print list of ranges
void print (rangelist list)
{
rangelist::reverse_iterator rit;
std::cout << "{ ";
for (rit = list.rbegin (); rit != list.rend (); ++rit) {
std::cout << "(" << rit->first << ", " << rit->second << ") ";
}
std::cout << "}" << std::endl;
}