Related
I have a task revolving around a small game, called Lights Out.
Game
The game consists of a board with dimensions 3x3, where each cell can either be 1 or 0, for example:
0 1 0
1 1 0
0 0 0
the game is said to be solved when all cells are 1, so:
1 1 1
1 1 1
1 1 1
and in each turn the user can click any cell which will flip its state and the state of the neighbors to the left, right, above and below (if they exist). So clicking on the cell in the middle of the first example board will yield:
0 0 0
0 0 1
0 1 0
Task
Now I have to find the worst possible initial board for the game and also figure out how many turns it needs to the solved state if played optimal.
Attempt
I tried to write a recursive solver which, given an initial board, finds the optimal sequence of turns to solve the game. And after that I wanted to feed it with all possible initial boards.
However, the recursion runs into a stack overflow. So I probably have to rewrite it in an iterative fashion. How can I do that?
Here is the code, as minimal complete example:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
public class GameTest {
public static void main(String[] args) {
boolean[][] board = {
{false, false, false},
{false, true, false},
{false, false, false}
};
List<GameState> solutionPath = GameSolver.solve(board);
printSolutionPath(solutionPath);
}
private static void printSolutionPath(List<GameState> solutionPath) {
System.out.printf("Solution path uses %d turns%n", solutionPath.get(solutionPath.size() - 1).getTurns());
String turnProgression = solutionPath.stream()
.map(state -> String.format("[%d|%d]", state.getX(), state.getY()))
.collect(Collectors.joining(" -> "));
System.out.println("Turns are: " + turnProgression);
System.out.println("Board progression is:");
for (GameState state : solutionPath) {
System.out.println(state.boardToString());
System.out.println("-----");
}
}
private static class GameSolver {
public static List<GameState> solve(boolean[][] initialBoard) {
GameState state = new GameState(initialBoard);
return solve(state);
}
public static List<GameState> solve(GameState state) {
// Base case
if (state.isSolved()) {
return List.of(state);
}
// Explore all other solutions
List<List<GameState>> solutionPaths = new ArrayList<>();
boolean[][] board = state.getBoard();
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[x].length; y++) {
solutionPaths.add(solve(new GameState(state, x, y)));
}
}
List<GameState> bestSolutionPath = Collections.min(solutionPaths, Comparator.comparingInt(solutionPath -> solutionPath.get(solutionPath.size() - 1).getTurns()));
bestSolutionPath.add(state);
return bestSolutionPath;
}
}
private static class GameState {
private boolean[][] board;
private int turns;
private int x;
private int y;
public GameState(boolean[][] board) {
this.board = board;
turns = 0;
x = -1;
y = -1;
}
public GameState(GameState before, int x, int y) {
board = before.board;
click(x, y);
turns++;
this.x = x;
this.y = y;
}
public boolean isSolved() {
for (boolean[] row : board) {
for (boolean state : row) {
if (!state) {
return false;
}
}
}
return true;
}
public int getTurns() {
return turns;
}
public boolean[][] getBoard() {
return board;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public String boardToString() {
StringBuilder sb = new StringBuilder();
for (int x = 0; x < board.length; x++) {
StringJoiner row = new StringJoiner(" ");
for (int y = 0; y < board[x].length; y++) {
row.add(board[x][y] ? "1" : "0");
}
sb.append(row);
}
return sb.toString();
}
private void click(int centerX, int centerY) {
toggle(centerX, centerY);
toggle(centerX, centerY - 1);
toggle(centerX, centerY + 1);
toggle(centerX - 1, centerY);
toggle(centerX + 1, centerY);
}
private void toggle(int x, int y) {
if (x < 0 || y < 0 || x >= board.length || y >= board[x].length) {
return;
}
board[x][y] = !board[x][y];
}
}
}
Algorithm
If possible, I would also be interested in pure-mathematical arguments that solve or prove this without writing code that solves it by trying out.
The "Lights Out" problem can be simplified by observing that the moves are commutative, i.e. if you flip the plus-shapes centred on a certain set of cells, then it doesn't matter which order you flip them in. So an actual ordered path through a graph is not needed. We can also observe that each move is self-inverse, so no solution requires making the same move more than once, and if a set of moves m is a solution to a position p, then m also produces the position p starting from an empty board.
Here's a short solution in Python based on this observation: I've solved it for the goal of all 0s, i.e. the "lights" are "out", but it is trivial to change it to solve for the goal of all 1s.
The constant list masks represents which cells should be flipped for each of the 9 possible moves.
The bitcount function is used to measure how many moves a solution takes, given a bitmask representing a subset of the 9 possible moves.
The position function computes the board position after a set of moves is made, using the exclusive-or operation to accumulate the results of multiple flips.
The positions dictionary maps each reachable board position to a list of move-sets which produce it starting from an empty board. It turns out that all positions are reachable by exactly one set of moves, but if this is not known in advance then a dictionary of lists gives a more general solution.
The max(..., min(...)) part finds the position maximising the minimum number of moves needed to solve it, as required.
masks = [
int('110100000', 2), int('111010000', 2), int('011001000', 2),
int('100110100', 2), int('010111010', 2), int('001011001', 2),
int('000100110', 2), int('000010111', 2), int('000001011', 2),
]
def bitcount(m):
c = 0
while m:
c += (m & 1)
m >>= 1
return c
def position(m):
r = 0
for i in range(9):
if (1 << i) & m:
r ^= masks[i]
return r
from collections import defaultdict
positions = defaultdict(list)
for m in range(2**9):
p = position(m)
positions[p].append(m)
solution = max(positions, key=lambda p: min(map(bitcount, positions[p])))
print('board:', bin(solution))
print('moves:', ', '.join(map(bin, positions[solution])))
Output:
board: 0b101010101
moves: 0b111111111
That is, the "worst initial position" is an X shape (all four corners plus the centre cell are 1s), and the solution is to perform all 9 moves.
I am proposing an iterative solution to solve this (and related problems) based on graph theory.
Shortest-Path-Problem (SSP)
The problem can be reformulated as shortest-path-problem and, by that, be solved with any standard SPP algorithm, for example Dijkstr's algorithm.
For that, we will interpret all possible game boards as vertices and the action of clicking cells as edges of a graph.
For example
0 1 0
1 1 0
0 0 0
will be a vertex in the graph with 9 outgoing edges in total (one for each cell to click at). So we will for example have an edge
0 1 0 0 0 0
1 1 0 --> 0 0 1
0 0 0 0 1 0
with cost 1. All edge costs will be 1, indicating counting turns.
Given an initial board, like above, we formulate the SPP as the task of finding the shortest path in this graph from the vertex representing the initial board to the vertex representing the solved state
1 1 1
1 1 1
1 1 1
By using standard algorithms for solving SSP we receive the optimal path and its total cost. The path is the sequence of game states and the total cost is the amount of turns needed for that.
*-1 SPP
However, you are not only interested in solving given initial boards but also in finding the worst initial board and its optimal amount of turns.
This can be reformulated as a variant of the SPP family, namely trying to find the longest shortest path to the solved state. This is, among all shortest paths in the graph that end in the solved state, the path that maximizes the total cost.
This can be computed efficiently by a *-1 (many-to-one) SPP. That is, computing all shortest paths from any vertex to a single destination, which will be the solved state. And from those picking the path which has the greatest total cost.
Dijkstra's algorithm can compute that easily by executing the algorithm fully on a reversed graph (all edges reverse their direction) with the solved state as source, until it settled the whole graph (removing its stopping criteria).
Note that in your particular case graph reversal is not needed, as the graph in your game is bidirectional (any turn can be undone by executing it again).
Solution
Applying the above theory yields a pseudo-code looking like
Graph graph = generateGraph(); // all possible game states and turns
int[][] solvedState = [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
List<Path> allShortestPaths = Dijkstra.shortestPathFromSourceToAllNodes(solvedState);
Path longestShortestPath = Collections.max(allPaths);
Some time ago I created a Java library for solving shortest path problems, Maglev. Using that library, the full code is:
import de.zabuza.maglev.external.algorithms.Path;
import de.zabuza.maglev.external.algorithms.ShortestPathComputationBuilder;
import de.zabuza.maglev.external.graph.Graph;
import de.zabuza.maglev.external.graph.simple.SimpleEdge;
import de.zabuza.maglev.external.graph.simple.SimpleGraph;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.StringJoiner;
public class GameTest {
public static void main(String[] args) {
Graph<GameState, SimpleEdge<GameState>> graph = generateGraph();
var algo = new ShortestPathComputationBuilder<>(graph).resetOrdinaryDijkstra()
.build();
GameState solvedState =
new GameState(new boolean[][] { { true, true, true }, { true, true, true }, { true, true, true } });
var pathTree = algo.shortestPathReachable(solvedState);
var longestShortestPath = pathTree.getLeaves()
.stream()
.map(pathTree::getPathTo)
.map(Optional::orElseThrow)
.max(Comparator.comparing(Path::getTotalCost))
.orElseThrow();
System.out.println("The longest shortest path has cost: " + longestShortestPath.getTotalCost());
System.out.println("The states are:");
System.out.println(longestShortestPath.iterator().next().getEdge().getSource());
for (var edgeCost : longestShortestPath) {
System.out.println("------------");
System.out.println(edgeCost.getEdge().getDestination());
}
}
private static Graph<GameState, SimpleEdge<GameState>> generateGraph() {
SimpleGraph<GameState, SimpleEdge<GameState>> graph = new SimpleGraph<>();
generateNodes(graph);
generateEdges(graph);
return graph;
}
private static void generateNodes(Graph<GameState, SimpleEdge<GameState>> graph) {
for (int i = 0; i < 1 << 9; i++) {
String boardString = String.format("%09d", Integer.parseInt(Integer.toBinaryString(i)));
graph.addNode(GameState.of(boardString, 3, 3));
}
}
private static void generateEdges(Graph<GameState, SimpleEdge<GameState>> graph) {
for (GameState source : graph.getNodes()) {
// Click on each field
boolean[][] board = source.getBoard();
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[x].length; y++) {
GameState destination = new GameState(board);
destination.click(x, y);
graph.addEdge(new SimpleEdge<>(source, destination, 1));
}
}
}
}
private static class GameState {
public static GameState of(String boardString, int rows, int columns) {
boolean[][] board = new boolean[rows][columns];
int i = 0;
for (int x = 0; x < rows; x++) {
for (int y = 0; y < columns; y++) {
board[x][y] = boardString.charAt(i) == '1';
i++;
}
}
return new GameState(board);
}
private final boolean[][] board;
private GameState(boolean[][] board) {
this.board = new boolean[board.length][];
for (int x = 0; x < board.length; x++) {
this.board[x] = new boolean[board[x].length];
for (int y = 0; y < board[x].length; y++) {
this.board[x][y] = board[x][y];
}
}
}
public boolean[][] getBoard() {
return board;
}
#Override
public String toString() {
StringJoiner rowJoiner = new StringJoiner("\n");
for (int x = 0; x < board.length; x++) {
StringJoiner row = new StringJoiner(" ");
for (int y = 0; y < board[x].length; y++) {
row.add(board[x][y] ? "1" : "0");
}
rowJoiner.add(row.toString());
}
return rowJoiner.toString();
}
#Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final GameState gameState = (GameState) o;
return Arrays.deepEquals(board, gameState.board);
}
#Override
public int hashCode() {
return Arrays.deepHashCode(board);
}
private void click(int x, int y) {
toggle(x, y);
toggle(x, y - 1);
toggle(x, y + 1);
toggle(x - 1, y);
toggle(x + 1, y);
}
private void toggle(int x, int y) {
if (x < 0 || y < 0 || x >= board.length || y >= board[x].length) {
return;
}
board[x][y] = !board[x][y];
}
}
}
Which yields the following solution to your problem:
The longest shortest path has cost: 9.0
The states are:
1 1 1
1 1 1
1 1 1
------------
1 0 1
0 0 0
1 0 1
------------
1 0 1
1 0 0
0 1 1
------------
1 1 0
1 0 1
0 1 1
------------
1 1 0
1 0 0
0 0 0
------------
1 1 0
1 1 0
1 1 1
------------
0 0 1
1 0 0
1 1 1
------------
1 0 1
0 1 0
0 1 1
------------
0 1 1
1 1 0
0 1 1
------------
0 1 0
1 0 1
0 1 0
So the worst initial game state is
0 1 0
1 0 1
0 1 0
and, if played optimally, it needs 9 turns to solve the game.
Some trivia, the game has 512 states in total (2^9) and 4608 possible moves.
Treat the puzzle as a graph per Zabuzard's answer, then perform breadth-first search starting from the solved node. The last node you reach is among the set having the longest-shortest path to the solution.
If possible, I would also be interested in pure-mathematical arguments that solve or prove this without writing code that solves it by trying out.
I am proposing a solution purely based on linear algebra.
Board as matrix
The game can be interpreted as set of linear equations which can be solved using standard linear equation solving techniques.
For that, a game board is interpreted as matrix .
In total, there are 9 possible actions (one for clicking each cell of the board). We encode which cells have to be flipped per action in 9 corresponding matrices:
where is the action-matrix corresponding to a click on cell in row i and column j.
Actions are commutative and self-inverse
Since entries are in , applying an action to a given board is as simple as adding the corresponding action-matrix to the board-matrix. For example:
This means that applying a set of actions is nothing else than some matrix additions. Matrix additions are commutative. This means that the order in which they are applied does not matter:
Moreover, any action-matrix is self-inverse on addition. Applying it again undoes the action, i.e.
This follows that, for any initial game board, we only have to apply each action at most once and the order in which we do that does not matter.
Linear equation system
This leads to the equation:
With the initial game board matrix L, coefficients which are 1 if the action should be applied, otherwise 0; and 1 being the all-ones matrix indicating that the game is won.
The equation can be simplified by moving L to the other side:
where L* is L but with all cells flipped.
Finally, this equation can be rewritten as standard linear system of equations Ax = b which can then be solved easily:
Since this matrix has maximal rank and a non-zero determinant , the game on a 3x3 board is always solvable and a solution is given by simply solving the linear equation system or by applying Cramer's rule.
Worst initial board
It also follows that the worst initial board is a matrix L that maximizes the coefficients being used, ideally all 9.
Turns out
0 1 0
1 0 1
0 1 0
is such an initial board which needs all 9 coeeficients to be set for a solution. I.e. solving the system
yields exactly one solution, namely for all i, j.
This can also be obtained from the opposite direction by setting all coefficients to 1 and solving for L instead:
which yields
0 1 0
1 0 1
0 1 0
for L again.
I was going through this problem in one of exam paper and found one solution in answer book. I am not able to understand algorithm behind it. Can anyone explain me how this algorithm works?
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
For example, Given the input
[0,1,0,2,1,0,1,3,2,1,2,1]
the return value would be
6
Solution as per answer book is this
public class Solution {
public int trap(int[] height) {
if (height.length <=2 )
return 0;
int h = 0, sum = 0, i = 0, j = height.length - 1;
while(i < j)
{
if ( height[i] < height[j] )
{
h = Math.max(h,height[i]);
sum += h - height[i];
i++;
}
else
{
h = Math.max(h,height[j]);
sum += h - height[j];
j--;
}
}
return sum;
}
}
Thanks
WoDoSc was nice enough to draw a diagram of the elevations and trapped water. The water can only be trapped between two higher elevations.
What I did was run the code and output the results so you can see how the trapped water is calculated. The code starts at both ends of the "mountain" range. Whichever end is lower is moved closer to the center.
In the case where the two ends are the same height, the right end is moved closer to the center. You could move the left end closer to the center instead.
The first column is the height and index of the elevations on the left. The second column is the height and index of the elevations on the right.
The third column is the maximum minimum height. In other words, the maximum height of the left or the right, whichever maximum is smaller. This number is important to determine the local water level.
The fourth column is the sum.
Follow along with the diagram and you can see how the algorithm works.
0,0 1,11 0 0
1,1 1,11 1 0
1,1 2,10 1 0
0,2 2,10 1 1
2,3 2,10 2 1
2,3 1,9 2 2
2,3 2,8 2 2
2,3 3,7 2 2
1,4 3,7 2 3
0,5 3,7 2 5
1,6 3,7 2 6
6
And here's the code. Putting print and println statements in appropriate places can help you understand what the code is doing.
package com.ggl.testing;
public class RainWater implements Runnable {
public static void main(String[] args) {
new RainWater().run();
}
#Override
public void run() {
int[] height = { 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
System.out.println(trap(height));
}
public int trap(int[] height) {
if (height.length <= 2) {
return 0;
}
int h = 0, sum = 0, i = 0, j = height.length - 1;
while (i < j) {
System.out.print(height[i] + "," + i + " " + height[j] + "," + j
+ " ");
if (height[i] < height[j]) {
h = Math.max(h, height[i]);
sum += h - height[i];
i++;
} else {
h = Math.max(h, height[j]);
sum += h - height[j];
j--;
}
System.out.println(h + " " + sum);
}
return sum;
}
}
I know that probably it's not the best way to represent it graphically, but you can imagine the situation as the following figure:
Where the red bars are the terrain (with elevations according to the array of your example), and the blue bars are the water that can be "trapped" into the "valleys" of the terrain.
Simplifying, the algorithm loops all the bar left-to-right (if left is smaller) or right-to-left (if right is smaller), the variable h stores the maximum height found during each step of the loop, because the water can not be higher than the maximum height of the terrains, and to know how much water can be trapped, it sums the differences between the height of the water (maximum height h) and the elevation of the terrain on a specific point, to get the actual quantity of water.
The algorithm works by processing the land from the left (i) and the right (j).
i and j are counters that work towards each other approaching the middle of the land.
h is a variable that tracks the max height found thus far considering the lower side.
The land is processed by letting i and j worked "toward each other." When I read the code, I pictured two imaginary walls squeezing the water toward the middle where the lowest wall moves toward the higher wall. The algorithm continues to sum up the volume of water. It uses h - height[x] because water can only be contained by inside the lowest point between two walls. So essentially it continues to sum up the volume of water from the left and right and subtracts out and water displaced by higher elevation blocks.
Maybe better variable names would have been
leftWall instead of i
rightWall instead of j
waterMaxHeight instead
of h
I think above solution is difficult to understand.I have a simple solution which take o(n) extra space & o(n) time complexity.
Step of algorithm
1.Maintain an array which contain maximum of all element which is right side of current element.
2.maintain a variable max from left side which contain maximum of all element which is left side of current element.
3.find minimum of max from left & max from right which is already present in array.
4.if minimum value is greater than the current value in array than add difference of than in ans & add the difference with current value & update max from left.
import java.util.*;
import java.lang.*;
import java.io.*;
class Solution
{
public static void main (String[] args) throws java.lang.Exception
{
int[] array= {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
int[] arrayofmax=new int[array.length];
int max=0;
arrayofmax[array.length-1]=0;
for(int x=array.length-1;x>0;x--){
if(max<array[x]){
max=array[x];
}
arrayofmax[x-1]=max;
}
int ans=0;
int maxfromleft=0;
for(int i=0;i<array.length-1;i++){
if(maxfromleft<array[i]){
maxfromleft=array[i];
}
int min=maxfromleft>arrayofmax[i+1]?arrayofmax[i+1]:maxfromleft;
if(min>array[i+1]){
ans+=min-array[i+1];
array[i+1]=min;
}
}
System.out.println(ans);
}
}
May be my algorithm is same as above but i think this implementation is easy to understand
Trapping Rain Water problem solved in Java.
class Store
{
static int arr[] = new int[]{0, 1, 0, 2, 2};
// Method for maximum amount of water
static int StoreWater(int n)
{
int max = 0;
int f = 0;
for (int i = 1; i < n; i++)
{
max = Math.max(arr[i], max);
f += Math.max(arr[i], max) - arr[i];
}
return f;
}
public static void main(String[] args)
{
System.out.println("Maximum water that can be accumulated is " +
findWater(arr.length));
}
}
Here is a different and easy approach for water trapping problem. O(1) space and O(N) time complexity.
Logic:
-> Let’s loop from 0 index to the end of the input values.
-> If we find a wall greater than or equal to the previous wall
-> make note of the index of that wall in a var called prev_index
-> keep adding previous wall’s height minus current (ith) wall to the variable water.
-> have a temp variable that also stores the same value as water.
-> Loop till the end, if you dont find any wall greater than or equal to the previous wall, then quit.
-> If the above point is true (i.e, if prev_index < size of input array), then subtract the temp variable from water, and loop from end of the input array to prev_index and find a wall greater than or equal to the previous wall (in this case, the last wall from backwards)
The concept here is if there is a larger wall to the right you can retain water with height equal to the smaller wall on the left.
If there are no larger walls to the right, then start from left. There must be a larger wall to your left now.
You're essentially looping twice, so O(2N), but asymptotically O(N), and of course O(1) space.
JAVA Code Here:
class WaterTrap
{
public static void waterTrappingO1SpaceOnTime(){
int arr[] = {1,2,3,2,1,0}; // answer = 14
int size = arr.length-1;
int prev = arr[0]; //Let first element be stored as previous, we shall loop from index 1
int prev_index = 0; //We need to store previous wall's index
int water = 0;
int temp = 0; //temp will store water until a larger wall is found. If there are no larger walls, we shall delete temp value from water
for(int i=1; i<= size; i++){
if(arr[i] >= prev){ // If current wall is taller then previous wall, make current wall as the previous wall, and its index as previous wall's index for the subsequent loops
prev = arr[i];
prev_index = i;
temp = 0; //because larger or same height wall is found
} else {
water += prev - arr[i]; //Since current wall is shorter then previous, we subtract previous wall height from current wall height and add to water
temp += prev - arr[i]; // Store same value in temp as well, if we dont find larger wall, we will subtract temp from water
}
}
// If the last wall was larger than or equal to the previous wall, then prev_index would be equal to size of the array (last element)
// If we didn't find a wall greater than or equal to the previous wall from the left, then prev_index must be less than index of last element
if(prev_index < size){
water -= temp; //Temp would've stored the water collected from previous largest wall till the end of array if no larger wall was found. So it has excess water. Delete that from 'water' var
prev = arr[size]; // We start from the end of the array, so previous should be assigned to the last element.
for(int i=size; i>= prev_index; i--){ //Loop from end of array up to the 'previous index' which would contain the "largest wall from the left"
if(arr[i] >= prev){ //Right end wall will be definitely smaller than the 'previous index' wall
prev = arr[i];
} else {
water += prev - arr[i];
}
}
}
System.out.println("MAX WATER === " + water);
}
public static void main(String[] args) {
waterTrappingO1SpaceOnTime();
}
}
Algorithm:
1.Create two array left and right of size n. create a variable max_ = INT_MIN.
2.Run one loop from start to end. In each iteration update max_ as max_ = max(max_, arr[i]) and also assign left[i] = max_
3.Update max_ = INT_MIN.
4.Run another loop from end to start. In each iteration update max_ as max_ = max(max_, arr[i]) and also assign right[i] = max_
5.Traverse the array from start to end.
6.The amount of water that will be stored in this column is min(a,b) – array[i],(where a = left[i] and b = right[i]) add this value to total amount of water stored
7.Print the total amount of water stored.
Code:
/*** Theta(n) Time COmplexity ***/
static int trappingRainWater(int ar[],int n)
{
int res=0;
int lmaxArray[]=new int[n];
int rmaxArray[]=new int[n];
lmaxArray[0]=ar[0];
for(int j=1;j<n;j++)
{
lmaxArray[j]=Math.max(lmaxArray[j-1], ar[j]);
}
rmaxArray[n-1]=ar[n-1];
for(int j=n-2;j>=0;j--)
{
rmaxArray[j]=Math.max(rmaxArray[j+1], ar[j]);
}
for(int i=1;i<n-1;i++)
{
res=res+(Math.min(lmaxArray[i], rmaxArray[i])-ar[i]);
}
return res;
}
python code
class Solution:
def trap(self, h: List[int]) -> int:
i=0
j=len(h)-1
ml=-1
mr=-1
left=[]
right=[]
while(i<len(h)):
if ml<h[i]:
ml=h[i]
left.append(ml)
if mr<h[j]:
mr=h[j]
right.insert(0,mr)
i=i+1
j=j-1
s=0
for i in range(len(h)):
s=s+min(left[i],right[i])-h[i]
return s
I have to do a little exercise at my university but I am already stuck for a while. The exercise is about calculating the water capacity of a 2D array, the user has to enter the width (w) and the height (h) of the 2D array, and then all the elements of the array, which represent the height at that location. Really simple example:
10 10 10
10 2 10
10 10 10
The output will then be 8, because that is the maximum water that fits in there. Another example is:
6 4
1 5 1 5 4 3
5 1 5 1 2 4
1 5 1 4 1 5
3 1 3 6 4 1
Output will be 14.
What also important to mention is: The width and height of the array can not be larger than 1000 and the heights of the element cannot be larger than 10^5.
Now I basically have the solution, but it is not fast enough for larger inputs. What I did is the following: I add the heights to a TreeSet and then every time I poll the last one (the highest) and then I go through the array (not looking at the edges) and use DFS and check for every position if the water can stay in there. If the water doesn't go out of the array than calculate the positions that are under water, if it goes out of the array then poll again and do the same.
I also tried looking at the peaks in the array, by going vertically and horizontally. For the example above you get this:
0 5 0 5 4 0
5 0 5 0 0 4
0 5 0 4 0 5
3 1 3 6 4 0
What I did with this was give the peaks a color let say (black) and then for all the white colors take the minimum peak value with DFS again and then take that minimum to calculate the water capacity. But this doesn't work, because for example:
7 7 7 7 7
7 4 4 4 7
7 2 3 1 7
7 4 4 4 7
7 7 7 7 7
Now 3 is a peak, but the water level is 7 everywhere. So this won't work.
But because my solution is not fast enough, I am looking for a more efficient one. This is the part of the code where the magic happens:
while (p.size() != 0 || numberOfNodesVisited!= (w-2)*(h-2)) {
max = p.pollLast();
for (int i=1; i < h-1; i++) {
for (int j=1; j < w-1; j++) {
if (color[i][j] == 0) {
DFSVisit(profile, i, j);
if (!waterIsOut) {
sum+= solveSubProblem(heights, max);
numberOfNodesVisited += heights.size();
for(int x = 0; x < color.length; x++) {
color2[x] = color[x].clone();
}
} else {
for(int x = 0; x < color2.length; x++) {
color[x] = color2[x].clone();
}
waterIsOut = false;
}
heights.clear();
}
}
}
}
Note I am resetting the paths and the colors every time, I think this is the part that has to be improved.
And my DFS: I have three colors 2 (black) it is visited, 1 (gray) if it is an edge and 0 (white) if is not visited and not an edge.
public void DFSVisit(int[][] profile, int i, int j) {
color[i][j] = 2; // black
heights.add(profile[i][j]);
if (!waterIsOut && heights.size() < 500) {
if (color[i+1][j] == 0 && max > profile[i+1][j]) { // up
DFSVisit(profile, i+1, j);
} else if (color[i+1][j] == 1 && max > profile[i+1][j]) {
waterIsOut = true;
}
if (color[i-1][j] == 0 && max > profile[i-1][j]) { // down
DFSVisit(profile, i-1, j);
} else if (color[i-1][j] == 1 && max > profile[i-1][j]) {
waterIsOut = true;
}
if (color[i][j+1] == 0 && max > profile[i][j+1]) { // right
DFSVisit(profile, i, j+1);
} else if (color[i][j+1] == 1 && max > profile[i][j+1]) {
waterIsOut = true;
}
if (color[i][j-1] == 0 && max > profile[i][j-1]) { //left
DFSVisit(profile, i, j-1);
} else if (color[i][j-1] == 1 && max > profile[i][j-1]) {
waterIsOut = true;
}
}
}
UPDATE
#dufresnb referred to talentbuddy.co where the same exercise is given at https://www.talentbuddy.co/challenge/526efd7f4af0110af3836603. However I tested al lot of solutions and a few of them actually make it through my first four test cases, most of them however already fail on the easy ones. Talent buddy did a bad job on making test cases: in fact they only have two. If you want to see the solutions they have just register and enter this code (language C): it is enough to pass their test cases
#include <stdio.h>
void rain(int m, int *heights, int heights_length) {
//What tests do we have here?
if (m==6)
printf("5");
else if (m==3)
printf("4");
//Looks like we need some more tests.
}
UPDATE
#tobias_k solution is a working solution, however just like my solution it is not efficient enough to pass the larger input test cases, does anyone have an idea for an more efficient implementation?
Any ideas and help will be much appreciated.
Here's my take on the problem. The idea is as follows: You repeatedly flood-fill the array using increasing "sea levels". The level a node is first flooded will be the same level that the water would stay pooled over that node when the "flood" retreats.
for each height starting from the lowest to the highest level:
put the outer nodes into a set, called fringe
while there are more nodes in the fringe set, pop a node from the set
if this node was first reached in this iteration and its height is lesser or equal to the current flood height, memorize the current flood height for tha tnode
add all its neighbours that have not yet been flooded and have a height lesser or equal to the current flood height to the fringe
As it stands, this will have compexity O(nmz) for an n x m array with maximum elevation z, but with some optimization we can get it down to O(nm). For this, instead of using just one fringe, and each time working our way from the outside all the way inwards, we use multiple fringe sets, one for each elevation level, and put the nodes that we reach in the fringe corresponding to their own height (or the current fringe, if they are lower). This way, each node in the array is added to and removed from a fringe exactly once. And that's as fast as it possibly gets.
Here's some code. I've done it in Python, but you should be able to transfer this to Java -- just pretend it's executable pseudo-code. You can add a counter to see that the body of the while loop is indeed executed 24 times, and the result, for this example, is 14.
# setup and preparations
a = """1 5 1 5 4 3
5 1 5 1 2 4
1 5 1 4 1 5
3 1 3 6 4 1"""
array = [[int(x) for x in line.strip().split()]
for line in a.strip().splitlines()]
cols, rows = len(array[0]), len(array)
border = set([(i, 0 ) for i in range(rows)] +
[(i, cols-1) for i in range(rows)] +
[(0, i ) for i in range(cols)] +
[(rows-1, i) for i in range(cols)])
lowest = min(array[x][y] for (x, y) in border) # lowest on border
highest = max(map(max, array)) # highest overall
# distribute fringe nodes to separate fringes, one for each height level
import collections
fringes = collections.defaultdict(set) # maps points to sets
for (x, y) in border:
fringes[array[x][y]].add((x, y))
# 2d-array how high the water can stand above each cell
fill_height = [[None for _ in range(cols)] for _ in range(rows)]
# for each consecutive height, flood-fill from current fringe inwards
for height in range(lowest, highest + 1):
while fringes[height]: # while this set is non-empty...
# remove next cell from current fringe and set fill-height
(x, y) = fringes[height].pop()
fill_height[x][y] = height
# put not-yet-flooded neighbors into fringe for their elevation
for x2, y2 in [(x-1, y), (x, y-1), (x+1, y), (x, y+1)]:
if 0 <= x2 < rows and 0 <= y2 < cols and fill_height[x2][y2] is None:
# get fringe for that height, auto-initialize with new set if not present
fringes[max(height, array[x2][y2])].add((x2, y2))
# sum of water level minus ground level for all the cells
volume = sum(fill_height[x][y] - array[x][y] for x in range(cols) for y in range(rows))
print "VOLUME", volume
To read your larger test cases from files, replace the a = """...""" at the top with this:
with open("test") as f:
a = f.read()
The file should contain just the raw array as in your question, without dimension information, separated with spaces and line breaks.
talentbuddy.co has this problem as one of their coding tasks. It's called rain, if you make an account you can view other peoples solutions.
#include <iostream>
#include <vector>
bool check(int* myHeights, int x, int m, bool* checked,int size)
{
checked[x]=true;
if(myHeights[x-1]==myHeights[x] && (x-1)%m!=0 && !checked[x-1])
{
if(!check(myHeights,x-1,m,checked,size))return false;
}
else if((x-1)%m==0 && myHeights[x-1]<=myHeights[x])
{
return false;
}
if(myHeights[x+1]==myHeights[x] && (x+1)%m!=m-1 && !checked[x+1])
{
if(!check(myHeights,x+1,m,checked,size))return false;
}
else if((x+1)%m==m-1 && myHeights[x+1]<=myHeights[x])
{
return false;
}
if(myHeights[x-m]==myHeights[x] && (x-m)>m && !checked[x-m])
{
if(!check(myHeights,x-m,m,checked,size))return false;
}
else if((x-m)<m && myHeights[x-m]<=myHeights[x])
{
return false;
}
if(myHeights[x+m]==myHeights[x] && (x+m)<size-m && !checked[x+m])
{
if(!check(myHeights,x+m,m,checked,size))return false;
}
else if((x+m)>size-m && myHeights[x+m]<=myHeights[x])
{
return false;
}
return true;
}
void rain(int m, const std::vector<int> &heights)
{
int total=0;
int max=1;
if(m<=2 || heights.size()/m<=2)
{
std::cout << total << std::endl;
return;
}
else
{
int myHeights[heights.size()];
for(int x=0;x<heights.size();++x)
{
myHeights[x]=heights[x];
}
bool done=false;
while(!done)
{
done=true;
for(int x=m+1;x<heights.size()-m;++x)
{
if(x<=m || x%m==0 || x%m==m-1)
{
continue;
}
int lower=0;
if(myHeights[x]<myHeights[x-1])++lower;
if(myHeights[x]<myHeights[x+1])++lower;
if(myHeights[x]<myHeights[x-m])++lower;
if(myHeights[x]<myHeights[x+m])++lower;
if(lower==4)
{
++total;
++myHeights[x];
done=false;
}
else if(lower>=2)
{
bool checked[heights.size()];
for(int y=0;y<heights.size();++y)
{
checked[y]=false;
}
if(check(myHeights,x,m,checked,heights.size()))
{
++total;
++myHeights[x];
done=false;
}
}
}
}
}
std::cout << total << std::endl;
return;
}
I have some code that is supposed to find the smallest of the 8 neighboring cells in a 2D array. When this code runs, the smallest is then moved to, and the code run again in a loop. However when it is run the code ends up giving a stack overflow error as it keeps jumping between two points. This seems to be a logical paradox as if Y < X then X !< Y. So it think it is my code at fault, rather than my logic. Here's my code:
private Point findLowestWeight(Point current) {
float lowest = Float.MAX_VALUE;
Point ret = new Point(-1, -1);
LinkedList<Point> pointList = new LinkedList<Point>();
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (!(i == 0 && j == 0)) {
if ((current.x + i >= 0 && current.x + i <= imageX - 2)
&& (current.y + j >= 0 && current.y + j <= imageY - 2)) {
pointList.add(new Point(current.x + i, current.y + j));
}
}
}
}
for (Point p : pointList){
if (map[p.x][p.y] < lowest){
lowest = map[p.x][p.y];
ret = p;
}
}
return ret;
}
You need a stopping case.
find the smallest of the 8 neighboring cells in a 2D array. When this code runs, the smallest is then moved to, and the code run again in a loop
is a fine way to start but says nothing about stopping.
Do you care about the value of the current cell? If so you need to check 9 not 8. If you simply want to move down hill then you need to check where you've been or any flat multi-cell valley will put you into an infinite loop. Consider only moving if moving down.
If you truly don't care where you are then even a single cell valley will put you into an infinite loop as you bounce in and out of it. In which case you'd need some other stopping condition. Consider stopping after imageX * imageY iterations.
Do you move even if the smallest neighbour is greater than the value in the center?
Example:
2 2 2 2
2 0 1 2
2 2 2 2
You start with center cell 0. The smallest neighbour is 1. If you move to 1, the smallest neighbour is 0. You can continue endless.
Probably you should not move, if the smallest neighbour is greater than the current cell.
I am having trouble with part of my homework assignment. Essentially, what we have to do is take a 2D array full of random numbers and sum each row, column, and diagonal. I have figured out the rows and columns, but cannot get the diagonals. Here is my code pertaining to the diagonals:
for (int c = 0 ; c < columnCount; c++)
{
int sum = 0;
for (int i = (7 - c); i >= 0; i--)
{
sum += board[(7 - c) - i][i];
}
return sum;
}
I have gotten some of the diagonals summed, but cannot figure out how to get the rest. For instance:
_ 0 1 2 3 4 5 6 7
0 X X X X X X X X
1 X X X X X X X
2 X X X X X X
3 X X X X X
4 X X X X
5 X X X
6 X X
7 X
As you can see, I have only summed half of the board (it is going from top right to bottom left each time). I will eventually need to sum the diagonals in the opposite direction as well...if anyone has insight on that.
I have already looked around on here and on other websites, but the only thing I can find is how to sum the MAJOR diagonals. I need to sum every single diagonal on an 8X8 board. I have really thought through this but no matter what I try I cannot get the other values without going out of bounds. I appreciate any help!
There are tons of options to do this. Based on the method you are currently using (and trying to be consistent with it), I'd suggest following it up by going from rows 1-7 and completing the missing summations (hint: for remaining items, start at right edge and move down-and-left).
The question that pmkrefeld linked to in the comments contains code to do it (well, hypothetically, I didn't look at it and have no idea if it actually works). The answer there was just given away. It is working code but you will learn more by trying to implement it yourself.
Sometimes it helps to work this things out on paper. Draw your array, then think: How would a human do it? If you can come up with a clear algorithm that way and wrap your head around it, then it will become straightforward to translate it to Java (or whatever language).
For calculating the remaining part of the matrix you can insert two nested for loops below your code. Something like this-
sum = 0;
int t,k;
int result[] = new int[20];
for(c = 1; c <= 7; c++) {
t = c;
for(i = 7; i >= c; i--) {
sum += board[i][t];
t++;
}
result[k] = sum; // result[k] will store each diagonal sum
k++;
sum = 0;
}