6

I need to find the largest triangle of ones in a matrix of zeros and ones using dynamic programming. So if this is my matrix:

 1 0 0 1 1
 0 1 1 1 1
 0 1 1 1 0
 1 1 1 1 1
 1 1 0 0 1

Then there are two largest triangles with the right corner at [2,2] and [4,4]. I only have to look for the right isosceles triangles (which have the angles 90◦, 45◦, 45◦) and I also need to look at only one orientation, because all the others are symmetric. So basically I need a function which takes the matrix and returns a triangle, with triangle being an object. I don't need complete code pseudocode is fine too me.

First I thought of just using the square algorithm here: Dynamic programming - Largest square block, and when you have found the largest square, then the largest triangle must be in there. But I can easy find counter examples where this doesn't work. After that I tried to look at the upper cell and counting this with dynamic programming, but I am not sure what to do next... So my count will look like this with the matrix from above:

1 0 0 1 1
0 1 1 2 2
0 2 2 3 0
1 3 3 4 1
2 4 0 0 2

I think have to use this in some way.

UPDATE:

I think I am pretty close now, when you walkthrough the matrix n*m and and make count[i][j] = 1+ min(count[i-1][j], count[i][j-1]), so look at the left and upper cell. We get this:

1 0 0 1 1
0 1 1 2 2
0 1 2 3 0
1 2 3 4 1
1 2 0 0 1

This looks pretty good to me, you can the see where the right corner is of the [4,4] solution. Can anyone think of any counter examples? I only I have to return one solution, so returning this solution is fine.

UPDATE 2: I have found a counterexample, let position [4,4] be 0, we then get the following matrix:

 1 0 0 1 1
 0 1 1 1 1
 0 1 1 1 0
 1 1 1 0 1
 1 1 0 0 1

Count will look like this after walking through the matrix:

1 0 0 1 1
0 1 1 2 2
0 1 2 3 0
1 2 3 0 1
1 2 0 0 1

Now it will return the triangle with right corner [3,4] (third row fourth column), which is incorrect it should find [2,2]. So I thought maybe just going from the top left (what we have done so far) and from the right bottom and take the max from that. So count with right bottom will look like this (look at the cell below and to the right):

1 0 0 2 1
0 4 3 2 1
0 3 2 1 0
2 2 1 1 1
1 1 0 0 1

Now we do find the solution of [2,2]. So I think using these methods will give me the solution, can anyone think of a better solution or a counter example for this one?

UPDATE 3: kraskevich made me realize that we have to use this algorithm four times. From the top left, top right, bottom left, bottom right and then just take the maximum, because then you have taken all the possibilities. Anyone has a better way to do this? Is this algorithm then correct? (So just four times the same algorithm, only an other start point in the matrix)

Also for the people who don't really understand what I am doing (I might go a little fast) take a look again at this: Dynamic programming - Largest square block the approach is very similar and it is very well explained there what there is done.

Community
  • 1
  • 1
Chantal
  • 111
  • 2
  • 10
  • First I thought of just using the square algorithm here: http://stackoverflow.com/questions/1726632/dynamic-programming-largest-square-block, and when you have found the largest square, then the largest triangle must be in there right? But I can easy find counter examples where this doesn't work. After that I tried to look at the upper cell and counting this with dynamic programming, but I am not sure what to do next... – Chantal Feb 27 '15 at 09:34
  • How large can this matrix be? – kraskevich Feb 27 '15 at 10:14
  • @Chantal I have added the proof of correctness and optimality to my answer. – kraskevich Feb 27 '15 at 11:15
  • @kraskevich, thx I think this is the best solution. I will start writing my code. – Chantal Feb 27 '15 at 11:21

3 Answers3

3

Yes, your idea is almost correct. Let's formalize it.

Claim:

f(i, j) is the size of the largest triangle with the right bottom corner at the (i, j) position and it is computed correctly.

Proof:

Let's use induction.

The base case: for all cells in the first row and or in the first column the size of a triangle is either one or zero(depending on the value in this cell).

Induction step:

Let's assume that f(i - 1, j) and f(i, j - 1) have been computed correctly.

  1. f(i - 1, j) >= f(i, j) - 1 and f(i, j - 1) >= f(i, j) - 1.It is the case because any sub-triangle of a triangle with 1s is a triangle with 1s. It implies that f(i, j) <= f(i - 1, j) + 1 and f(i, j) <= f(i, j - 1) + 1, or, put it another way, f(i, j) <= min(f(i - 1, j) + 1, f(i, j - 1) + 1) = min(f(i - 1, j), f(i, j - 1)) + 1. Thus, f(i, j) <= min(f(i - 1, j), f(i, j - 1)) + 1 holds true.

  2. Let's assume that min(f(i - 1, j), f(i, j - 1)) = k. Then there is triangle of size k in the (i - 1, j) cell and another triangle of size k in the (i, j - 1) cell. Together with the (i, j) cell, these two triangles form a triangle of size k + 1. Thus, f(i, j) >= min(f(i - 1, j), f(i, j - 1)) + 1.

I have just shown that f(i, j) >= min(f(i - 1, j), f(i, j - 1)) + 1 and f(i, j) <= min(f(i - 1, j), f(i, j - 1)) + 1. It implies that f(i, j) = min(f(i - 1, j), f(i, j - 1)) + 1. Thus f(i, j) is computed correctly, too.

Note that we have taken into account only such triangles that have their right angle in the right-bottom position. To take into account all possible triangles, we can simply run this algorithm for all 4 possible rotations of the given matrix.

Let's prove that running this algorithm in 4 directions is sufficient:

There are only four possible positions of the right angle of an arbitrary right isosceles triangle in a matrix: bottom-left, bottom-right, top-left and top-right. Running this algorithm in a fixed direction finds the largest triangle for this direction(I have proven it above). Thus, running this algorithm in all possible direction is sufficient to find the largest triangle in the matrix.

This algorithm is optimal in terms of time complexity because it has an O(n * m) time complexity, and just reading the input already requires O(n * m) time(we cannot find the answer without seeing all elements of the matrix in a general case).

kraskevich
  • 18,368
  • 4
  • 33
  • 45
  • You are right, I just updated my description where I found a counter example, but your last sentence made me realize I have to this algorithm indeed 4 times, from top left, top right, bottom right, bottom left. And then take the max from that. Stupid of me to think that just top left and bottom right will do the trick. I will update it again. – Chantal Feb 27 '15 at 11:01
  • Nice answer, but you should mention (especially in your point 2) that this holds *provided that the (i, j) cell is 1*. f(i, j) = 0 otherwise! :) – j_random_hacker Mar 01 '15 at 00:44
0

Disclaimer:

I did not merely restate what the OP is doing, in fact, the way we are updating the count is different.

I agreed with @IVlad that I should elaborate more on why the algorithm stated in my solution works, so I have edited my answer to include the key idea (the observation part) of the approach I wrote.

At the time I posted my solution, I am completely unaware of the OP's approach (see the revision history for evidence, the third revision was made by me), so actually I did not even attempt to restate what the OP was doing. I was just hoping to give a formalised description of an approach for the problem.

Originally I have been trying to avoid posting formal mathematical proof of correctness (since Stackoverflow is mainly for coding), and I believe from the way I wrote the solution, it should be obvious that the correctness can be derived directly from induction. Nonetheless, I have included the proof for completeness in my latest update as request by the OP.

========================================================================

Solution

You may use a similar approach as in the post you referenced. For simplicity, we only consider finding triangles with right angle at the top-left corner in our discussion. It should be straight forward to generalise our approach to the other cases.

We define a matrix D of the same size as the input matrix, and we initialise all it's entry to 0. We want to use D[i][j] to denote the size of the largest triangle (with right angle at the top-left corner) cornered at (i, j).

Our approach bases on the following observation:

For a triangle of size n at (i, j), it is necessary that we have triangles of size (at least) n - 1 at both (i + 1, j) and (i, j + 1).

We can make use of this fact to compute D using the bottom-up approach. We will compute D iteratively by many passes, where after the n-th pass we will have found all the triangles of size n. We will update D according to the following rules:

  1. In the first pass, we set D[i][j] = 1 if the input matrix has 1 at (i, j).

  2. In the n-th pass, for each entry in D with value n - 1 (say this entry is at (i, j), we check whether both D[i + 1][j] and D[i][j+1] equal to n - 1, if so, we set D[i][j] = n.

  3. We terminate our algorithm when we encounter a pass where no entry of D get updated.

Remark: The n-th pass actually finds all the location of triangles of size n, and we will be using this info to update the next pass, and this is the key idea in dynamic programming - we have found the solution of smaller sub-problems, and we will use it to help solving larger size problems.

The size of the largest triangle (and it's location) can then be found by considering the largest entry of D.

Proof of correctness

This follows trivially by mathematical induction.

Base case: If there is a triangle of size 1 located at (i, j), then we must have D[i,j] = 1 by step (1) of our algorithm.

Inductive step: Suppose we have found all triangles of size n - 1. For any triangle of size n, it follows from the observation that the triangles on it's right (i.e. cornered at (i + 1, j)) and bottom (i.e. cornered at (i, j + 1)) must be of size n - 1. By our induction hypothesis, we have already found these triangles, hence by step (2) of our algorithm we will have D[i, j] = n.

Community
  • 1
  • 1
chiwangc
  • 3,566
  • 16
  • 26
  • 32
  • 2
    This is exactly what the OP is doing. He's asking if there are any counterexamples. Can you explain **why** this works? – IVlad Feb 27 '15 at 10:19
  • IVlad is right, this is what I basically already had, and I have found a counterexample, set [4,4] at 0, so not available, then there is only one solution which is the triangle with right corner [2,2] which it won't find then. I will update my description to mention this. – Chantal Feb 27 '15 at 10:36
  • As far as I can tell, the way we are updating the count matrix is a bit different. I will update my solution with a mathematical proof of correctness of my algorithm when I get home (I am typing on my mobile right now) – chiwangc Feb 27 '15 at 10:47
  • @IVlad and Chantal: I have included the proof of correctness for reference – chiwangc Feb 27 '15 at 12:17
  • The third revision you added a link, in the fourth revision i updated it with a solution and then you came with basically the same solution, only with a different way of count and not yet mentioning that we should use all the cases (top left, top right, bottom left, bottom right) Still your effort is appreciated:) I think my way of count (from top left) is more natural to understand for people who are new to this though. – Chantal Feb 27 '15 at 14:07
  • No offence or anything, I just wanted to point out that I wouldn't have noticed your update when I am focusing on typing out an answer of considerable length. Moreover, I did mention that I only handle the case when the right angle is at the top-left corner in my original post, and it is straight forward to generalise the result to other cases (just modify step 2 slightly without even rotating the input matrix). – chiwangc Feb 27 '15 at 14:24
  • Sorry you were right, I looked it through, anyway thanks for the help. – Chantal Feb 27 '15 at 15:28
0

This solution is very smart and elegant. I love it!

However, you can do a slight optimization, because the following assertion is not totaly true:

For a triangle of size n at (i, j), it is necessary that we have triangles of size (at least) n - 1 at both (i + 1, j) and (i, j + 1).

This is not a necessary condition. Here is another (and less restrictive) one:

For a triangle of size n at (i,j) it is egnough that we have a triangle of size (at least) n - 1 in (i-1,j) and a segment of size (at least) n - 1 at the left of (i,j).

This result is less reading in the matrix because it is easy to use an accumulator to find the size of the segment. This results in a reduction of reading of about 33%.

Here is a visual example of the algorithm: http://jsfiddle.net/swvk32e8/1/

function solve() {
    var row, col;
    var i = 0;
    var n;
    var here,above;
    for (row = 1 ; row < SIZE ; row++) {
        n = grid[0 + SIZE * row];
        for (col = 1 ; col < SIZE ; col++) {
            here = grid[col + SIZE * row];
            if (here > 0) {
                if (n > 0) {
                    above = grid[col + SIZE * (row - 1)];
                    grid[col + SIZE * row] = 1 + Math.min(n, above);
                }
                n++;
            } else {
                n = 0;
            }
        }
    }
}
Tolokoban
  • 2,297
  • 14
  • 17