简体   繁体   中英

Maximal value among shortest distances in a matrix

I am trying to solve the following problem and have not been able to develop an algorithm or approach. I have researched for a few hours and tried to map the problem to "Shortest Path" graph/matrix problem or dynamic programming problems but have been unsuccessful in it.

Given a grid with w as width, h as height. Each cell of the grid represents a potential building lot and we will be adding "n" buildings inside this grid. The goal is for the furthest of all lots to be as near as possible to a building. Given an input n, which is the number of buildings to be placed in the lot, determine the building placement to minimize the distance the most distant empty lot is from the building. Movement is restricted to horizontal and vertical ie diagonal movement is not required.

For example, w=4, h=4 and n=3 . An optimal grid placement sets any lot within two unit distance of the building. The answer for this case is 2.

"0" indicates optimal building placement and in this case the maximal value of all shortest distances to the closest building for each cell is "2".

1 0 1 2
2 1 2 1
1 0 1 0
2 1 2 1

The above represents one optimal solution, there could be more like the above array rotated as an example. The above is an optimal solution because out of the 3 buildings (n=3), one building was placed at index (0,1), second was placed at (2,1) and third was placed at (2,3). The surrounding horizontal and vertical distance is shown as 1 and 2 by adding 1 each time we move horizontally and/or vertically. Note again that diagonal movement is not allowed:

1 ← 0 → 1 → 2
    ↓
2 ← 1 → 2 ← 1
    ↑       ↑
1 ← 0 → 1 ← 0
    ↓       ↓
2 ← 1 → 2 ← 1

Other examples:

Example 1)

w=3, h=3, n=2

Two buildings (zeros) have to be optimally placed. One of the optimal plan for this case is:

01
11
10

0 → 1
↓
1   1
    ↑  
1 ← 0

Answer: 1

As an example, the following plan will not be optimal in this case because it has the maximal smallest distance as 2 instead of 1. So, placing 0 greedily at index (1,0) does not work even though the 0 covers three "1" positions in this case instead of two as in above optimal scenario.

1 → 2
↑
0 → 1
↓   ↑   
1 ← 0

Example 2)

w=5, h=1, n=1

One building (zeros) has to be optimally placed. One of the optimal plan:

2 ← 1 ← 0 → 1 → 2

Answer: 2

Example of a non-optimal plan in the above scenario:

3 ← 2 ← 1 ← 0 → 1

The below function should be completed:

int findMinDist(int w, int h, int n)
{

}

Constraints:

1<=w,h
w*h <=28
1<=n<=5
n<=w*h

I haven't been able to write any code because honestly I haven't been able to deduce the solution.

If the two given points are fixed points in a 2d matrix, I can find the distance or shortest distance between the two. But, in this case, I don't know where the two points will be? There can be many optimal solutions and placing combinations of 0 at each place and finding the farthest distance is not possible and will not be feasible. I have tried to place them at positions which yield maximum amount of 1 (like middle or w/2) but that does not seem to work too. Could an existing algorithm be applied to this problem?

As per the given constraint, the matrix size ( w*h ) cannot exceed 28 which is fairly a small number. Also, the maximum possible value for n is 5. From little knowledge of combinatorics, we know that there are 28 C 5 ways of selecting 5 lots from the given grid at the worst case. The figure evaluates to 98280 which is a sufficiently a small space to search over with memoization. Since the maximum value for w*h is 28, we can represent the entire grid in a single integer bit-mask which along with the number of building lot left to be set up will form the states of our DP. To calculate the farthest leftover lot for the end state, we make use of a Breadth First Search (BFS) by initializing the queue with all points where we've set up a building. Sharing the code for the same which runs sufficiently fast enough https://ideone.com/ix1nh8

int W, H, N;

int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, -1, 1};

int calc(int i, int j) {
    if(W <= H)
        return  i + W * j;
    return H * i + j;
}

bool get(int bitmask, int i, int j) {
    return (bitmask&(1<<calc(i,j)));
}

int bfs(int bitmask) {
    int dist[W][H];
    memset(dist, -1, sizeof dist);

    int maxDist = 0;
    queue<pair<int,int>> Q;

    for(int i = 0; i < W; i++)
        for(int j = 0; j < H; j++)
            if(get(bitmask, i, j)) {
                dist[i][j] = 0;
                Q.push({i, j});
            }
    assert(Q.size() == N);

    while(!Q.empty()) {
        int x = Q.front().first;
        int y = Q.front().second;
        maxDist = max(maxDist, dist[x][y]);
        Q.pop();

        for(int d = 0; d < 4; d++) {
            int newx = x + dx[d];
            int newy = y + dy[d];

            if(newx >= W || newy >= H || newx < 0 || newy < 0)
                continue;
            if(dist[newx][newy] == -1) {
                dist[newx][newy] = dist[x][y] + 1;
                Q.push({newx, newy});
            }
        }
    }
    return maxDist;
}

map<pair<int,int>, int> dp;

int solve(int bitmask, int left) {
    if(left == 0) {
        return bfs(bitmask);
    }
    if(dp.find({bitmask, left}) != dp.end()) {
        return dp[{bitmask, left}];
    }
    int minDistance = INT_MAX;
    for(int i = 0; i < W; i++)
        for(int j = 0; j < H; j++)
            if(!get(bitmask, i, j)) {
                int val = solve((bitmask|(1<<calc(i, j))), left-1);
                minDistance = min(minDistance, val);
            }
    return dp[{bitmask, left}] = minDistance;
}

Java Solution without bitmask and need for memoization by passing the positions to the recursive calls

class MaximumShortestDist
{
    static int[] dx = new int[]{1, -1, 0, 0};
    static int[] dy = new int[]{0, 0, -1, 1};

    public static void main(String[] args) {
        System.out.println(findMinDist(14,2,5));
    }

    static int findMinDist(int w, int h, int n)
    {

        int[][] grid = new int[w][h];
        for(int i=0;i<w;i++)
            Arrays.fill(grid[i],-1);
        return solve(n,w,h,0,0,grid);
    }

    static int bfs(int W, int H, int[][] grid) {

        int[][] dist = new int[W][H];
        for(int i=0;i<W;i++)
            for(int j=0;j<H;j++)
                dist[i][j] = grid[i][j];

        int maxDist = 0;
        Queue<Location> Q = new LinkedList<>();
        for(int i = 0; i < W; i++)
            for(int j = 0; j < H; j++)
                if(dist[i][j] == 0){
                    Q.add(new Location(i,j));
                }

        while(!Q.isEmpty()) {
            int x = Q.peek().first;
            int y = Q.peek().second;
            maxDist = Math.max(maxDist, dist[x][y]);
            Q.poll();

            for(int d = 0; d < 4; d++) {
                int newx = x + dx[d];
                int newy = y + dy[d];

                if(newx >= W || newy >= H || newx < 0 || newy < 0)
                    continue;
                if(dist[newx][newy] == -1) {
                    dist[newx][newy] = dist[x][y] + 1;
                    Q.add(new Location(newx, newy));
                }
            }
        }
        return maxDist;
    }

    static int solve(int left, int W, int H, int row, int col,int[][] grid) {
        if(left == 0) {
            return bfs(W,H,grid);
        }
        int r = row,c=col;
        if(col >= H) {
            r += col/H;
            c = col%H;
        }
        int minDistance = Integer.MAX_VALUE;
        for(int i=r;i<W;i++){
            for(int j=c;j<H;j++) {
                //Mark Building locations in the recursive call.
                grid[i][j] = 0;
                int val = solve(left-1, W, H,i,j+1,grid);
                minDistance = Math.min(minDistance, val);
                // Remove the building
                grid[i][j] = -1;
            }
        }
        return minDistance;
    }
}


class Location {
    int first;
    int second;
    Location(int x, int y) {
        first = x;
        second = y;
    }
}

Look at this simple python program.

  1. It finds all possible permutations of where the offices may lie
  2. Then it finds the maximum distance to a cell from the closest office [use BFS]
  3. Take the minimum distance of all such maximums
def findMinDistance(w, h, n):
    def maxDist(w,h,p):
        arr = [[-1]*h for i in range(w)]
        q = collections.deque()
        for x in p:
            r,c = x//h, x%h
            arr[r][c] = 0
            q.append((r,c,0))

        maxd = -1
        while len(q):
            x,y,d = q.popleft()
            maxd = max(maxd, d)
            if 0 <= x+1 < w and arr[x+1][y] == -1:
                arr[x+1][y] = d+1
                q.append((x+1,y,d+1))
            if 0 <= x-1 < w and arr[x-1][y] == -1:
                arr[x-1][y] = d+1
                q.append((x-1,y,d+1))
            if 0 <= y+1 < h and arr[x][y+1] == -1:
                arr[x][y+1] = d+1
                q.append((x,y+1,d+1))
            if 0 <= y-1 < h and arr[x][y-1] == -1:
                arr[x][y-1] = d+1
                q.append((x,y-1,d+1))

        return maxd

    ans = 100000
    pos = [i for i in range(w*h)]
    for p in list(itertools.combinations(pos, n)):
        ans = min(ans, maxDist(w,h,p))
    return ans

The Java code for this without the bitwise operations.

import javafx.util.Pair;
import java.util.*;

class Office_N {
    // W for width, H for height, N for no of offices to build
    int W, H, N;

    // dx and dy value together gives (x,y)
    // which helps to move in 4 adjacent cells
    // Right (1,0)
    // Left (-1,0)
    // Down (0,1)
    // Up (0,-1)
    int[] dx = {1, -1, 0, 0};
    int[] dy = {0, 0, 1, -1};
    Map<String, Integer> dp = new HashMap<>();

    int[][] grid;

    // Constructor will set the values and clear the hashmap.
    public Office_N(int w, int h, int n) {
        W = w;
        H = h;
        N = n;
        dp.clear();
        grid = new int[W][H];

        for (int[] r : grid) {
            Arrays.fill(r, 0);
        }
    }

    // We convert the 2D array of W*H into 1D array in Row Order (if square matrix or Width is less),
    // or Column Wise (if Height is less)
    // BitMask holds the bits 0 empty spots, and 1 for where offices are present
    // Left means how many offices are still left to be built
    public int solve(int[][] grid, int left) {

        // If no more offices are left to be built, get the maximum distance for this scenario
        if (left == 0) {
            return bfs(grid);
        }

        StringBuilder k = new StringBuilder();

        for (int i = 0; i < W; i++) {
            for (int j = 0; j < H; j++) {
                if (grid[i][j] == 1) {
                    k.append(i + ":" + j + "::");
                }
            }
        }

        k.append("#" + left);
        // if the current scenario along with offices left are already processed, return the result
        String key = k.toString();
        if (dp.containsKey(key)) {
            return dp.get(key);
        }

        int[][] gridtemp = new int[W][H];
        for (int i = 0; i < W; i++) {
            for (int j = 0; j < H; j++) {
                gridtemp[i][j] = grid[i][j];
            }
        }

        //  We are trying every possible scenario to build offices in the given grid
        int minDist = Integer.MAX_VALUE;
        for (int i = 0; i < W; i++) {
            for (int j = 0; j < H; j++) {
                // If no office present in (i,j)th location, put one office there and check the minimum distance for that scenario
                if (gridtemp[i][j] == 0) {
                    gridtemp[i][j] = 1;
                    int val = solve(gridtemp, left - 1);
                    minDist = Math.min(minDist, val);
                    gridtemp[i][j] = 0;
                }
            }
        }

        // Store the min distance possible for the current scenario
        dp.put(key, minDist);
        return minDist;
    }

    // This function gives the maximum distance from all the empty spots to the offices for a given case of scenario
    private int bfs(int[][] grid) {
        // get a distance matrix with initial values as -1
        int[][] dist = new int[W][H];
        for (int[] row : dist)
            Arrays.fill(row, -1);

        int maxDist = 0;
        // Queue for processing the cells in Bredth-First-Search order.
        Queue<Pair<Integer, Integer>> Q = new LinkedList<>();

        // if office is present at (i,j)th location, the distance is 0, and put the (i,j) pair in Queue
        for (int i = 0; i < W; i++) {
            for (int j = 0; j < H; j++) {
                if (grid[i][j] == 1) {
                    dist[i][j] = 0;
                    Q.add(new Pair<>(i, j));
                }
            }
        }


        while (!Q.isEmpty()) {
            Pair<Integer, Integer> kv = Q.poll();
            int x = kv.getKey();
            int y = kv.getValue();

            // Get maximum distance for (i,j)th location
            maxDist = Math.max(maxDist, dist[x][y]);

            // Process all adjacent cells
            for (int d = 0; d < dx.length; d++) {
                int xNew = x + dx[d];
                int yNew = y + dy[d];

                // if the adjacent cell is within grid boundary, and is not yet processed,
                // set the max dist of he adjacent cell 1 more than the (i,j)th cell
                // add the adjacent cell to queue
                if (xNew >= 0 && xNew < W && yNew >= 0 && yNew < H && dist[xNew][yNew] == -1) {
                    dist[xNew][yNew] = dist[x][y] + 1;
                    Q.add(new Pair<>(xNew, yNew));
                }
            }
        }

        return maxDist;
    }

    public static void main(String[] args) {
        Office_N ofc = new Office_N(4, 4, 3);
        int res = ofc.solve(ofc.grid, ofc.N);
        System.out.println(res);
    }
}

I use math method to achieve the solution.

The shortest distance comes from the optimal occupied subarea in the grid. It means that the max value of each subarea's difference is 1 . Take w=4, h=4 and n=3 as example. In this grid, there are 16 nodes inside. For each occupied area of building, we can calculate by w*h/n = 16/3 = 5.33... . It means that the max number of nodes in the area is the ceiling number A = Math.ceil(w*h/n) = 6 .

From above calculation, there is only one node in each subarea , so that we could put this building at the center in assumption. Then we could calculate that the longest distance from the building to the edge would be Math.sqrt(A) = 2.45 . The ceiling number is 3 . Since it is zero-based, we could minus 1 to get 2 .

Special Case - width or height is 1 :

We could use the other math calculation which is much simpler than above to solve.

Here is my C++ version of solution. I have tested all the test cases in Build Offices Google Doc and it works. The challenge seems very terrifying; however, it would change to a very simple problem if you found description of w*h <= 28 in problem. Therefore, I just need to use depth-first search to list all the cases of 0 and breadth-first search to find the shortest path visiting the whole map, Done:) You can find more details in comments, Good luck.

#include <bits/stdc++.h>
using namespace std;

int w, h, n;
vector<int> dirx = {0, 0, 1, -1};
vector<int> diry = {1, -1, 0, 0};
int maxdistance = INT_MAX;

// help convert (x, y) to decimal-based position
int calc(int x, int y) {
    return (w * x) + y;
}

// check whether out of bound and visited before in bfs
bool check_valid(int x, int y, unordered_set<int> visited) {
    if(x < 0 || x >= h)
        return false;
    if(y < 0 || y >= w)
        return false;
    if(visited.count(calc(x, y)))
        return false;
    return true;
}

// level-ordered bfs and each time find the longest path on map
int bfs(vector<vector<int>> map,
        vector<pair<int, int>> start_point) {
    int local_max = 0;
    queue<pair<int, int>> q;
    unordered_set<int> visited;

    for(auto s : start_point) {
        q.push(s);
        visited.insert(calc(s.first, s.second));
    }

    int dist = 0;
    while(!q.empty()) {
        int sz = q.size();
        dist++;
        for(int i = 0; i < sz; i++) {
            pair<int, int> head = q.front();
            q.pop();
            for(int i = 0; i < 4; i++) {
                pair<int, int> neighbor = make_pair(head.first + dirx[i], head.second + diry[i]);
                if(check_valid(neighbor.first, neighbor.second, visited)) {
                    map[neighbor.first][neighbor.second] = dist;
                    visited.insert(calc(neighbor.first, neighbor.second));
                    q.push(neighbor);
                }
            }
        }
    }

    // after visiting the whole map
    // find the maxdistance
    for(int i = 0; i < h; i++) {
        for(int j = 0; j < w; j++) {
            if(map[i][j] > local_max)
                local_max = map[i][j];
        }
    }

    return local_max;
}

// list all the cases for starting point: 0
// in fact, not all the cases
// I considered optimal start point would show up in each distance of (w*h/n)
void dfs(vector<vector<int>> map,
        int d,
        int n_,
        int n, 
        vector<pair<int, int>> start_point) {
    if(n_ == n) {
        // cout << "Map: " << '\n';
        /**
        for(int i = 0; i < map.size(); i++) {
            for(int j = 0; j < map[i].size(); j++) {
                cout << map[i][j] << ' ';
            }
            cout << '\n';
        }
        cout << "distance?: ";**/
        if(bfs(map, start_point) < maxdistance) {
            maxdistance = bfs(map, start_point);
            cout << maxdistance << '\n';
        }
        return;
    }

    for(int i = 0; i < d; ++i) {
        int pos = n_ * d + i;
        map[pos / w][pos % w] = 0;
        start_point[n_].first = pos / w;
        start_point[n_].second = pos % w;
        dfs(map, d, n_ + 1, n, start_point);
        map[pos / w][pos % w] = INT_MAX;
        start_point[n_].first = -1;
        start_point[n_].second = -1;
    }
}

int main() {
    cin >> w >> h >> n;
    int d = (w*h)/n;   // build one office in each d distance
    vector<vector<int>> map(h, vector<int>(w, INT_MAX));
    vector<pair<int, int>> start_point(n, make_pair(-1, -1));
    dfs(map, d, 0, n, start_point);
    cout << "Max distance of one of optimal solution is: " << maxdistance << '\n';

    return 0;
}

I tried solving this question using python. The core of the answer lies in my step function, which gets all possible position of the N buildings in a W x H grid and gives the result as a list. Each position in the list is the location at W*i + H. That's the positions in a 2x2 are treated as 0, 1, 2, 3.



# generator function to give each building position 
# in a W x H grid
def step(W, H, N):
    dim = W * H
    slots = [n for n in range(N)]
    slots_temp = list(slots)
    persist = list(slots)
    last = [dim - n for n in slots]
    last = last[::-1]
    while slots != [0] * N:
        yield slots
        for i in range(len(slots)-1,-1,-1):
            slots[i]+=1
            if slots[i] >= last[i] :
                slots[i] = 0
            else:
                while i < len(slots)-1:
                    slots[i+1] = slots[i] + 1
                    i+=1
                break

# converts a ixj to a step
# assumes W <= H
def get_step(i, j, W , H):
    return (i * W) + j

# does bfs from each building position
# and gets the maximum distance 
def bfs(step,W,H):
    dist = [[-1]*H for i in range(W)]
    queue = []
    dx = [1,-1,0,0]
    dy = [0,0,1,-1]
    for i in range(W):
        for j in range(H):
            step_val = get_step(i, j, W, H)
            if step_val in step:
                dist[i][j] = 0
                queue.append((i,j))
    max_val = 0
    while len(queue) != 0:
        i,j = queue.pop(0)
        max_val = max(max_val, dist[i][j])
        for _dx,_dy in zip(dx,dy):
            new_i,new_j = i + _dx, j + _dy
            if new_i < 0 or new_i >= W or new_j <0 or new_j >= H:
                continue
            if dist[new_i][new_j] == -1:
                dist[new_i][new_j] = dist[i][j] + 1
                queue.append((new_i,new_j))
    return max_val


# calls each posible position of the building
# and computes the minimum distance of all
def main(W, H, N ): 
    min_val = float('inf')
    if W > H:
        W, H = H, W
    s = step(W, H, N)
    for slot in s:
        b = bfs(slot, W, H)
        min_val = min(min_val, b)
    return min_val



main(4, 4, 2)

The Ruby code for this

class MaximumShortestDist
  def findMinDist(w, h, n)
  grid = Array.new(w){Array.new(h)}
  for i in 0...w do
      grid[i].fill(-1)
  end

  solve(n,w,h,0,0,grid);
end

def bfs(ww, hh, grid)
        dx = [1, -1, 0, 0];
dy = [0, 0, -1, 1];
    dist = Array.new(ww){Array.new(hh)};

    for i in 0...ww do
        for j in 0...hh do
            dist[i][j] = grid[i][j];
          end
        end

    maxDist = 0;

    qu = Array.new

    for i in 0...ww do
      for j in 0...hh do
          if dist[i][j] == 0
             qu.push(Location.new(i,j));
          end
      end
    end

    while !qu.empty?
        x = qu.first.first;
        y = qu.first.second;
        maxDist = [maxDist, dist[x][y]].max;
        qu.shift;

        for d in 0...4  do
            newx = x + dx[d];
            newy = y + dy[d];

            next if newx >= ww || newy >= hh || newx < 0 || newy < 0


            if dist[newx][newy] == -1
                dist[newx][newy] = dist[x][y] + 1;
                qu.push(Location.new(newx, newy));
            end
        end
    end

    return maxDist;
end

def solve(left, ww, hh, row,  col, grid)
    return bfs(ww,hh,grid) if left == 0

    r = row
    c=col

    if col >= hh
        r += col/hh;
        c = col%hh;
    end

    minDistance = 999;
    for i in r...ww do
        for j in c...hh  do
            grid[i][j] = 0;
            val = solve(left-1, ww, hh,i,j+1,grid);
            minDistance = [minDistance, val].min;
            grid[i][j] = -1;
        end
    end
    return minDistance;
  end
end


class Location
   attr_reader :first, :second
  def initialize(x, y)
    @first = x;
    @second = y;
  end
end

puts MaximumShortestDist.new.findMinDist(2,3,2)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM