Pathfind to edge / find hole algorithm? - performance

I'm looking for an algorithm that can search a 3D array and figure out the quickest way to pathfind from a single point to any edge of the array. More specifically, I am working on checking if a hollow structure within a 3 dimensional space is "airtight" and if it would be possible for something within this structure to escape through a hole.
Could anyone recommend good sources, documents, or articles that I could read about this stuff? I'm not looking for a specific copy/paste solution since I really want to understand how it works but my searches have been turning up pretty dry.
There's also I high chance that I'm approaching this from a completely wrong angle and actually need to change how I go about looking for information on this, if you have any recommendations, let me know!
Also, the max size of the 3D array will be [256,256,256] but the structures within will usually be much smaller. Furthermore, each element within the array will be like an empty room with 6 possible walls on each side.
I've tried searching for some time but most of my findings have been about "shortest-path" or "path of least resistance" which is not what I need. I want the least computationally expensive way to check if I can get from the inside of the structure to the outside, even if it is not the shortest path.
#Update: Round About Solution#
I did not find an actual answer to my question but I found a way to solve one of my problems so I'm adding this as an edit rather than as an answer because I still want to know how to deal with the original question.
After some research, I discovered flood-fill algorithms which gave me some ideas, the only problem with them was that most of the flood-fill methods were recursive. While recursion was a fast and simple solution, it quickly caused a stack overflow and could not even get close to the depth that I would potentially need to reach (it could get about 18,000 but I needed > 256^3). I then discovered scan-line filling, which was good, but I was having trouble understanding it and could only find 2D implementations for pixel-based graphics or 3D point-based models. After learning information about both uses, I managed to scrounge together my own 3D scan-line filler that can completely fill in a 256^3 area in about 2 seconds.
A "block" is an element in the 3d array.
Whether or not it "isSealed" could better be about checking if that block has been iterated over yet.
The stack is used to queue up empty elements that need to be checked.
This is in c# btw.
private void MyScanLine3DFill(int x, int y, int z)
{
Stack<Block> blocks = new Stack<Block>(); //create a stack
blocks.Push(grid[x, y, z]); //initialize the stack to check from the specified starting position
bool spanWest;
bool spanEast;
bool spanSouth;
bool spanNorth;
while (blocks.Count != 0) //loop through the stack as long as it is not empty
{
Block temp = blocks.Pop(); //get the block on top of the stack
int y1 = temp.coords.Item2; //get the y coord of the block
while (y1 >= 0 && grid[temp.coords.Item1, y1, temp.coords.Item3].isSealed == false) //go down the column until you hit a sealed block
{
y1--; //go down a block in the column
}
y1++; //go up one block
spanWest = false; //reset the spans for this iteration of the loop
spanEast = false;
spanSouth = false;
spanNorth = false;
while (y1 < maxY && grid[temp.coords.Item1, y1, temp.coords.Item3].isSealed == false) //go up the column until you hit a sealed block
{
grid[temp.coords.Item1, y1, temp.coords.Item3].isSealed = true; //set the block in the current iteration to true
//check the west block
if (!spanWest && temp.coords.Item1 > 0 && grid[temp.coords.Item1 - 1, y1, temp.coords.Item3].isSealed == false) //if there is an unsealed block to the west
{
blocks.Push(new Block(airBlock, (temp.coords.Item1 - 1, y1, temp.coords.Item3))); //add the unsealed block to the west to the stack
spanWest = true;
}
else if (spanWest && temp.coords.Item1 - 1 == 0 && grid[temp.coords.Item1 - 1, y1, temp.coords.Item3].isSealed != false) //if there is a sealed block to the west
{
spanWest = false;
}
//check the east block
if (!spanEast && temp.coords.Item1 < maxX - 1 && grid[temp.coords.Item1 + 1, y1, temp.coords.Item3].isSealed == false) //if there is an unsealed block to the east
{
blocks.Push(new Block(airBlock, (temp.coords.Item1 + 1, y1, temp.coords.Item3))); //add the unsealed block to the east to the stack
spanEast = true;
}
else if (spanEast && temp.coords.Item1 < maxX - 1 && grid[temp.coords.Item1 + 1, y1, temp.coords.Item3].isSealed != false) //if there is a sealed block to the east
{
spanEast = false;
}
//check the south block
if (!spanSouth && temp.coords.Item3 > 0 && grid[temp.coords.Item1, y1, temp.coords.Item3 - 1].isSealed == false) //if there is an unsealed block to the south
{
blocks.Push(new Block(airBlock, (temp.coords.Item1, y1, temp.coords.Item3 - 1))); //add the unsealed block to the south to the stack
spanSouth = true;
}
else if (spanSouth && temp.coords.Item3 - 1 == 0 && grid[temp.coords.Item1, y1, temp.coords.Item3 - 1].isSealed != false) //if there is a sealed block to the south
{
spanSouth = false;
}
//check the north block
if (!spanNorth && temp.coords.Item3 < maxZ - 1 && grid[temp.coords.Item1, y1, temp.coords.Item3 + 1].isSealed == false) //if there is an unsealed block to the north
{
blocks.Push(new Block(airBlock, (temp.coords.Item1, y1, temp.coords.Item3 + 1))); //add the unsealed block to the north to the stack
spanNorth = true;
}
else if (spanNorth && temp.coords.Item3 < maxZ - 1 && grid[temp.coords.Item1, y1, temp.coords.Item3 + 1].isSealed != false) //if there is a sealed block to the north
{
spanNorth = false;
}
y1++; //go up a block
}
}
//refresh mesh if applicable
}

I don't think you can really beat the basic conceptual idea of a breadth-first or depth-first search in this case. The base case that terminates our search is unknown in advance. You can't really get too fancy for an unknown destination in an algorithm (a heuristic) except to linearly search for the solution one step at a time.
A Potential Optimization
I was thinking about this problem a bit further the other day, and while this is still the same overall algorithmic idea, it should be considerably more efficient than testing one cell at a time for occupancy (getting closer to 256^2 tests than 256^3 tests).
The idea is to store and maintain 3 separate sets of 3D occupancy bitsets (the second transposed in a way such that the next bit is the next element along Y, and the third along Z) requiring only one bit to indicate an occupied cell.
This way instead of testing one cell at a time in your search, you can easily test 64 at a time or more on a 64-bit architecture (32 or more on 32-bit, and up to 512 at a time with SIMD). With a 256x256x256 grid, that should consume the data extremely quickly. Using 256-bit registers or larger, you can test all the cells of a 1D slice of the grid along a axis from a given point to the nearest occupied cell in a single FFS (find first set bit) test which is generally an efficient intrinsic on lots of hardware. Some handy code I use a lot:
// Returns the index of the first set bit or 64 if there are
// no set bits.
int32_t ffs(uint64_t bits)
{
#if defined(_MSC_VER) && defined(_WIN64)
// MSVC 64-bit.
unsigned long index = 0;
return _BitScanForward64(&index, bits) ? index : 64;
#elif defined(_MSC_VER) && defined(_WIN32)
// MSVC 32-bit.
unsigned long index = 0;
if (_BitScanForward(&index, bits & 0xffffffff))
return index;
else if (_BitScanForward(&index, bits >> 32))
return index + 32;
return 64;
#else
// GCC
return bits != 0 ? ffsll(bits): 64;
#endif
}
// Returns the index of the first set bit starting from the nth
// position or 64 if there are no set bits starting from
// that position.
int32_t ffs(uint64_t bits, int32_t n)
{
if (n < 64)
{
// Clear to nth bit.
bits &= ~((1ull << n) - 1);
// Return next set bit.
return ffs(bits);
}
return 64;
}
The rest is similar to your vanilla BFS/DFS. At least I haven't been able to think up a better solution. Visual illustration:
It's admittedly a very rough idea and I'm one of those who needs to dive in and code it and profile it to start resolving things I missed on the drawing board (which I often do) and generate new algorithmic and optimization ideas. Yet I think it should be a good starting point.
If you're doing many of these tests per frame, you might just want to compute an entire distance map for the entire grid by pushing all the nodes to the processing list initially. That would avoid a lot of overlapping/redundant work finding shortest distance to edge from any given point on the grid. Precomputing a distance map for the entire grid in advance can be handy even for cases where the elements in the grid are moving each frame to cut down on the redundant work as it gives you the shortest path from any cell to a target in the minimal number of iterations (the number of iterations being equal to the shortest geodesic distance).
A Little Bit Further On Computing Entire Distance Maps
In response to the comments, assuming you don't just do the pathfinding for one cell/point in your grid per frame (even let's say just a few nodes per frame), I think you'll quickly find the precomputation of the entire distance map for the entire 256^3 grid outperforming alternatives, even if you have to invalidate the distance map every single frame.
It's because the amount of memory you have to read and write to solve this problem, even in the best-case scenarios I can possibly conceive, are quite large. In spite of my proposal to use bitsets, 256^3 is still a lot of cells, and further you need to temporarily associate data to them in parallel storing things like the shortest distance discovered so far in a direction with a BFS or DFS. You'll still read a good chunk of its memory and write to a hefty chunk of parallel memory looking for the nearest edge and keeping track of distances. So it's very redundant work for the hardware, even if it seems less redundant for humans, to throw away all that valuable data you computed and cached in L3, L2, possibly even L1 only to evict it and read and write to a good portion again by doing pathfinding a second time in the same frame.
So actually I'd start as a strategy, unless you're only doing one pathfinding operation per frame, to start with computing the entire distance map for the entire 256^3 grid to trivialize the pathfinding, and make that precomputation your primarily target for profiling and optimization. And only then if you need further performance, you start to break away from it and start computing things more on the fly for each individual pathfinding operation (and with the ability to easily throw away your new solution with, perhaps, a version control branch, since you might easily finding it performing worse). I think that's a more productive path in most cases.

Related

How to deal with draws by repetition in a transposition table?

I'm trying to solve Three Men's Morris. The details of the game don't matter, that that it's a game similar to tic tac toe, but players may be able to force a win from some positions, or be able to force the game to repeat forever by playing the same moves over and over in other positions. So I want to make a function to tell whether a player can force a win, or force a draw by repetition.
I've tried using simple negamax, which works fine but is way too slow to traverse the game tree with unlimited depth. I want to use transposition tables since the number of possible positions is very low (<6000) but that's where my problem comes from. As soon as I add in the transposition table (just a list of all fully searched positions and their values, 0, 1, or -1) the AI starts making weird moves, suddenly saying its a draw in positions where I have a forced win.
I think the problem comes from transposition table entries being saved as draws, since it seemed to work when I limited the depth and only saved forced wins, but I'm not sure how to fix the problem and allow for unlimited depth.
Here's the code in case there's an issue with my implementation:
int evaluate(ThreeMensMorris &board){
//game is won or drawn
if(board.isGameWon()) return -1; //current player lost
if(board.isRepetition()) return 0; //draw by repetition
//check if this position is already in the transposition table
//if so, return its value
uint32_t pos = board.getPosInt();
for(int i = 0; i < transIdx; i++)
if(transList[i] == pos)
return valueList[i];
//negamax
//NOTE: moves are formatted as two numbers, "from" and "to",
//where "to" is -1 to place a piece for the first time
//so this nested for loop goes over all possible moves
int bestValue = -100;
for(int i = 0; i < 9; i++){
for(int j = -1; j < 9; j++){
if(!board.makeMove(i, j)) continue; //illegal move
int value = -1 * evaluate(board, depth+1);
board.unmakeMove(i, j);
if(value > bestValue) bestValue = value;
}
}
//we have a new position complete with a value, push it to the end of the list
transList[transIdx] = pos;
valueList[transIdx] = bestValue;
transIdx++;
return bestValue;
}
I suggest you start looking at transposition tables for chess: https://www.chessprogramming.org/Transposition_Table. You need to give each gamestate an (almost) unique number, e.g. through Zobrist hashing, maybe this is what you do in board.getPosInt()?
A possible fault is that you don't consider who's turn it is? Even if a position is the same on the board, it is not the same if in one position it is player A turn and in the other player B. Are there other things to consider in this game? In chess there are things like en passant possibilities that needs to be considered, and other special cases, to know if the position is actually the same, not just the pieces themselves.
Transposition tables are really complex and super hard to debug unfortunately. I hope you get it to work though!

What is a greedy algorithm for this problem that is minimally optimal + proof?

The details are a bit cringe, fair warning lol:
I want to set up meters on the floor of my building to catch someone; assume my floor is a number line from 0 to length L. The specific type of meter I am designing has a radius of detection that is 4.7 meters in the -x and +x direction (diameter of 9.4 meters of detection). I want to set them up in such a way that if the person I am trying to find steps foot anywhere in the floor, I will know. However, I can't just setup a meter anywhere (it may annoy other residents); therefore, there are only n valid locations that I can setup a meter. Additionally, these meters are expensive and time consuming to make, so I would like to use as few as possible.
For simplicity, you can assume the meter has 0 width, and that each valid location is just a point on the number line aformentioned. What is a greedy algorithm that places as few meters as possible, while being able to detect the entire hallway of length L like I want it to, or, if detecting the entire hallway is not possible, will output false for the set of n locations I have (and, if it isn't able to detect the whole hallway, still uses as few meters as possible while attempting to do so)?
Edit: some clarification on being able to detect the entire hallway or not
Given:
L (hallway length)
a list of N valid positions to place a meter (p_0 ... p_N-1) of radius 4.7
You can determine in O(N) either a valid and minimal ("good") covering of the whole hallway or a proof that no such covering exists given the constraints as follows (pseudo-code):
// total = total length;
// start = current starting position, initially 0
// possible = list of possible meter positions
// placed = list of (optimal) meter placements, initially empty
boolean solve(float total, float start, List<Float> possible, List<Float> placed):
if (total-start <= 0):
return true; // problem solved with no additional meters - woo!
else:
Float next = extractFurthestWithinRange(start, possible, 4.7);
if (next == null):
return false; // no way to cover end of hall: report failure
else:
placed.add(next); // placement decided
return solve(total, next + 4.7, possible, placed);
Where extractFurthestWithinRange(float start, List<Float> candidates, float range) returns null if there are no candidates within range of start, or returns the last position p in candidates such that p <= start + range -- and also removes p, and all candidates c such that p >= c.
The key here is that, by always choosing to place a meter in the next position that a) leaves no gaps and b) is furthest from the previously-placed position we are simultaneously creating a valid covering (= no gaps) and an optimal covering (= no possible valid covering could have used less meters - because our gaps are already as wide as possible). At each iteration, we either completely solve the problem, or take a greedy bite to reduce it to a (guaranteed) smaller problem.
Note that there can be other optimal coverings with different meter positions, but they will use the exact same number of meters as those returned from this pseudo-code. For example, if you adapt the code to start from the end of the hallway instead of from the start, the covering would still be good, but the gaps could be rearranged. Indeed, if you need the lexicographically minimal optimal covering, you should use the adapted algorithm that places meters starting from the end:
// remaining = length (starts at hallway length)
// possible = positions to place meters at, starting by closest to end of hallway
// placed = positions where meters have been placed
boolean solve(float remaining, List<Float> possible, Queue<Float> placed):
if (remaining <= 0):
return true; // problem solved with no additional meters - woo!
else:
// extracts points p up to and including p such that p >= remaining - range
Float next = extractFurthestWithinRange2(remaining, possible, 4.7);
if (next == null):
return false; // no way to cover start of hall: report failure
else:
placed.add(next); // placement decided
return solve(next - 4.7, possible, placed);
To prove that your solution is optimal if it is found, you merely have to prove that it finds the lexicographically last optimal solution.
And you do that by induction on the size of the lexicographically last optimal solution. The case of a zero length floor and no monitor is trivial. Otherwise you demonstrate that you found the first element of the lexicographically last solution. And covering the rest of the line with the remaining elements is your induction step.
Technical note, for this to work you have to be allowed to place monitoring stations outside of the line.

Reduce time taken in Line Sweep for vertical and horizontal line segments

I have used std::set to implement line sweep algorithm for vertical and horizontal lines. But the final range search on the lower bound and uppper bound of 'status' set takes a lot of time. Is there some way to avoid this? I chose std::set because it is based on balanced BST and insertion, deletion and search take logn time. Is there a better data structure to implement this?
// before this I initialize the events set with segments with increasing x co-ordinates. The segment struct has 2 points variable and 1 type variable for identifying vertical segment(1), horizontal segment starting(0) and ending(2).
for(auto iter = events.begin(); iter != events.end(); iter++)
{
segment temp = *iter;
if(temp.type == 0)
status.insert(temp.p1);
else if(temp.type == 2)
status.erase(temp.p2);
else
{
auto lower = status.lower_bound(std::make_pair(temp.p1.x, temp.p1.y));
auto upper = status.upper_bound(std::make_pair(temp.p2.x, temp.p2.y));
// Can the no of elements in the interval be found without this for loop
for(;lower != upper; lower++)
{
count++;
}
}
}
Here event and status are sets of segments struct and points respectively.
typedef std::pair<int, int> point;
struct segment
{
point p1, p2;
int type;
segment(point a, point b, int t)
:p1(a), p2(b), type(t){}
};
std::set<segment, segCompare> events;
...
std::set<point, pointCompare> status;
In order to compute the distance efficiently, the tree would need to maintain size counts for each sub-tree. Since that service is not needed in most cases, it is not too surprising that std::set does not incur its cost for everyone.
I haven't found anything in the C++ standard library that will do this off the shelf. I think you may need to roll your own in this case, or find someone else who has.
If you do batch insertions of the events use a std::vector that is always sorted. There is no difference in asymptomatic runtime, which is O(n log n) for both, for a batch of n insertions.
This lets you do iterator arithmetic among other things.

Even distribution of random points in 2D

I'm trying to do a simple simple 'crowd' model and need distribute random points within a 2D area. This semi-pseudo code is my best attempt, but I can see big issues even before I run it, in that for dense crowds, the chances of a new point being too close could get very high very quickly, making it very inefficient and prone to fail unless the values are fine tuned. Probably issues with signed values too, but I'm leaving that out for simplicity.
int numPoints = 100;
int x[numPoints];
int y[numPoints];
int testX, testY;
tooCloseRadius = 20;
maxPointChecks = 100;
pointCheckCount = 0;
for (int newPoint = 0; newPoint < numPoints; newPoint++ ){
//Keep checking random points until one is found with no other points in close proximity, or maxPointChecks reached.
while (pointCheckCount < maxPointChecks){
tooClose = false;
// Make a new random point and check against all previous points
testX = random(1000);
testY = random(1000);
for ( testPoint = 0; testPoint < newPoint; testPoint++ ){
if ( (isTooClose (x[testPoint] , y[testPoint], textX, testY, tooCloseRadius) ) {
tooClose = true;
break; // (exit for loop)
}
if (tooClose == false){
// Yay found a point with some space!
x[newPoint] = testX;
y[newPoint] = testY;
break; // (exit do loop)
}
//Too close to one of the points, start over.
pointCheckCount++;
}
if (tooClose){
// maxPointChecks reached without finding a point that has some space.
// FAILURE DEPARTMENT
} else {
// SUCCESS
}
}
// Simple Trig to check if a point lies within a circle.
(bool) isTooClose(centerX, centerY, testX, testY, testRadius){
return (testX - centreX)^2 + (testY - centreY)^2) < testRadius ^2
}
After googling the subject, I believe what I've done is called Rejection Sampling (?), and the Adaptive Rejection Sampling could be a better approach, but the math is far too complex.
Are there any elegant methods for achieving this that don't require a degree in statistics?
For the problem you are proposing the best way to generate random samples is to use Poisson Disk Sampling.
https://www.jasondavies.com/poisson-disc
Now if you want to sample random points in a rectangle the simple way. Simply
sample two values per point from 0 to the length of the largest dimension.
if the value representing the smaller dimension is larger than the dimension throw the pair away and try again.
Pseudo code:
while (need more points)
begin
range = max (rect_width, rect_height);
x = uniform_random(0,range);
y = uniform_random(0,range);
if (x > rect_width) or (y > rect_height)
continue;
else
insert point(x,y) into point_list;
end
The reason you sample up to the larger of the two lengths, is to make the uniform selection criteria equivalent when the lengths are different.
For example assume one side is of length K and the other side is of length 10K. And assume the numeric type used has a resolution of 1/1000 of K, then for the shorter side, there are only 1000 possible values, whereas for the longer side there are 10000 possible values to choose from. A probability of 1/1000 is not the same as 1/10000. Simply put the coordinate value for the short side will have a 10x greater probability of occurring than those of the longer side - which means that the sampling is not truly uniform.
Pseudo code for the scenario where you want to ensure that the point generated is not closer than some distance to any already generated point:
while (need more points)
begin
range = max (rect_width, rect_height)
x = uniform_random(0,range);
y = uniform_random(0,range);
if (x > rect_width) or (y > rect_height)
continue;
new_point = point(x,y);
too_close = false;
for (p : all points)
begin
if (distance(p, new_point) < minimum_distance)
begin
too_close = true;
break;
end
end
if (too_close)
continue;
insert point(x,y) into point_list;
end
While Poisson Disk solution is usually fine and dandy, I would like to point an alternative using quasi-random numbers. For quasi-random Sobol sequences there is a statement which says that there is minimum positive distance between points which amounts to 0.5*sqrt(d)/N, where d is dimension of the problem (2 in your case), and N is number of points sampled in hypercube. Paper from the man himself http://www.sciencedirect.com/science/article/pii/S0378475406002382.
Why I thought it should be Python? Sorry, my bad. For C-like languanges best to call GSL, function name is gsl_qrng_sobol. Example to use it at d=2 is linked here

How to solve this by recursion?

I required some help in solving this question by recursion.
Question:
A lazy tourist wants to visit as many interesting locations in a city as possible without going one step further than necessary. Starting from his hotel, located in the north-west corner of city, he intends to take a walk to the south-east corner of the city and then walk back. When walking to the south-east corner, he will only walk east or south, and when walking back to the north-west corner, he will only walk north or west. After studying the city map he realizes that the task is not so simple because some areas are blocked. Therefore he has kindly asked you to write a program to solve his problem.
Given the city map (a 2D grid) where walkable areas are marked by ".", interesting locations are marked by "*", and blocked areas are marked by "#", determine the maximum number of interesting locations he can visit. Locations visited twice are only counted once.
*........
.....**#.
..**...#*
..####*#.
.*.#*.*#.
...#**...
*........
Ans : 7.
My initial thoughts were to solve this problem in two parts.
1) When the tourist starts from top-left corner.
2) When the tourist starts from down-right corner.
For the answer add them both parts, and that would be it.
Here's what the method looks like for the first part.
i and j were initially 0, 0.
path(int i, int j){
if(i == H && j == W){
return (strV[i][j] == '*')?1:0;
}
if(i >H || j >W || i<0 || j<0){
return 0;
}
if(visited[i][j] == 1){
//cout<<"vis";
return 0;
}
if(strV[i][j] == '#' ){
return 0;
}
visited[i][j] =1;
cout<<i<<" "<<j<<"\n";
// as i can go only down and right ,for the first part.
// south and east.
return max(path(i+1, j), path (i, j+1) ) + (strV[i][j] == '*')?1 :0;
}
Similarly I tried calculating for the second part.
As I've maintained the visited array, there is no possibility of revisiting the same point.
Hope that should satisfy the condition as they mention in the question.
I'm not sure if this will solve the question, it's giving incorrect answer.
Here's the question link: http://www.spoj.com/problems/TOURIST/
Thanks for your time.
A few observations will help you solve this problem more easily:
In the return trip the tourist can only move left or up in the grid. This is equivalent to moving right or down in the first trip. So
basically there is no difference between the two trips.
Since both trips are basically same we only need to think of first trip. We can send 2 tourists at the same time starting from (0,0)
cell. So our state will consist of (x1,y1,x2,y2) where (x1,y1) is
position of first tourist and (x2,y2) is position of second tourist in
grid.
At each step either tourist can move right or down, so we have 4 choices for movement(2 choice for each tourist).
If both tourists are on the same cell (x1==x2 and y1==y2) then we can add only 1 to result if that cell is special.
This algorithm has time complexity of O(n^4) which won't probably run in time. We can reduce the complexity to O(n^3). If we know the
position of first tourist is (x1,y1) the x coordinate of second
tourist is x2 then we must have x1+y1=x2+y2 since they both cover same
distance in same amount of time. So y2=x1+y1-x2 and our state depends
only on (x1,y1,x2).
Code:
const int N 100+5
vector<string> board;
int dp[N][N][N]; // initialize with -1
int calc(int x1, int y1, int x2) {
int n=board.size(), m=board[0].size();
int y2=x1+y1-x2;
if(x1>=n || x2>=n || y1>=m || y2>=m) return 0; // out of range
if(board[x1][y1]=='#' || board[x2][y2]=='#') return 0; // path blocked so its an invalid move
if(dp[x1][y1][x2]!=-1) return dp[x1][y1][x2]; // avoid recalculation
int res=0;
if(board[x1][y1]=='*') res++;
if(board[x2][y2]=='*') res++;
if(board[x1][y1]=='*' && x1==x2 && y1==y2) res=1; // both tourist on same spot
int r=calc(x1+1, y1, x2); // first tourist down second tourist right
r=max(r, calc(x1+1, y1, x2+1)); // first tourist down second tourist down
r=max(r, calc(x1, y1+1, x2)); // first tourist right second tourist right
r=max(r, calc(x1, y1+1, x2+1)); // first tourist right second tourist down
res+=r;
dp[x1][y1][x2]=res; // memoize
return res;
}
Your approach has a few flaws. Some that make it give the wrong solution and some that are just lead to an inefficient search.
The flaws that lead to a wrong solution are:
Adding the interesting locations on the best paths each way will not be a valid answer because you haven't discounted the interesting sights that you already visited one way from the path you took on the way back. (This will also just mean you're answer will just be double the one way number as the best path back would be the same path in reverse). You actually need to search your way back from the end of your way forward so you can properly account for the already visited
Terminating the search in a block path with a value of 0. It needs to be highly penalized (at least H+V-2) so just reaching a block and giving up (not reaching the corner) won't be considered a valid path
Not unvisiting the locations when you are backtracking. This will lead to not counting interesting locations that may have been visited in a different path than the one being evaluated
Terminating on a visited location. There is nothing in the problem statement that prohibits crossing the same location twice. It can never happen when considering only one of the directions of travel anyway because you can only travel two directions that don't allow for going back on yourself
The flaw that leads to an inefficient search is not simplifying the search domain before searching for the best path. By creating a graph that only contains the interesting locations as nodes and connections between them when there is a valid (non-blocked) path between them you can decrease your search space considerably (depending on how sparse the interesting locations and abundant the blocks are)
Keeping as close to your attempt as possible my suggestions are:
When visiting a location simply add one so you can unvisit it by removing one before returning your best path (just setting to zero may be a problem if you also do it in the search for the return path)
When you reach the end of the path one way (i == H-1 && j == W-1, because you start at 0,0 not 1,1) do a similar search the other way keeping the visited location info
Change your interesting location increase to (visited[i][j] == 0) && (strV[i][j] == '*') so you don't count interesting locations twice in the same path
Penalise running into a block by returning at least -(H+V-2), but MIN_INT_VALUE or anything in between would also work
Don't give up when you reach a location you already visited
Hope this is helpful
Here's a solution in JavaScript. It recursively tries all valid directions SE and NW, and saves its result if it's greater than the current max result.
function solve(grid) {
var maxVisited = 0;
var rows = grid.length;
var cols = grid[0].length;
// used to make a new copy of the visited set
function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
// used to test a current location returning false or the grid value
function move(r, c) {
if(r < 0 || c < 0 || r >= rows || c >= cols) { return false; }
if(grid[r][c] === "#") { return false; }
return grid[r][c];
}
// recursively solve the problem
function solveRec(r, c, grid, goBack, visited) {
// end trip, check if visited is greater than current max
if(goBack === true && r === 0 && c === 0) {
var count = Object.keys(visited).length;
if(count > maxVisited) { maxVisited = count; }
return;
}
var m = move(r, c);
// invalid move
if(m === false) { return; }
// place of interest, add to visited set
if(m === "*") { visited["" + r + ":" + c] = true; }
// try a move, if at S.E. go back home (turn around)
if(goBack === true || (r === rows-1 && c === cols-1)) {
// north west
solveRec(r-1, c, grid, true, copy(visited));
solveRec(r, c-1, grid, true, copy(visited));
} else {
// south east
solveRec(r+1, c, grid, false, copy(visited));
solveRec(r, c+1, grid, false, copy(visited));
}
}
solveRec(0, 0, grid, false, {});
return maxVisited;
}
console.log(solve([
"*........".split(""),
".....**#.".split(""),
"..**...#*".split(""),
"..####*#.".split(""),
".*.#*.*#.".split(""),
"...#**...".split(""),
"*........".split("")
])); // 7
EDIT Here's the jsfiddle http://jsfiddle.net/reeyws86/ You may want to add logic to avoid corner cases like when the grid is empty or only has 1 square.

Resources