找出最长的胖子

题目:

有一个Boolean组成的矩阵,大小是N*M,代表路面,能不能在上面行走,假设胖子的形状都是正方形,也就是s*s的矩阵,其中s代表胖子的边长,现在想寻找从矩阵最左上角能走到矩阵最右下角的最大腰围的胖子。而胖子只能往右边和往下面两个方向移动,且每次只能移动一步。要求每次胖子所占用的地面都是可以在上面行走的,也就是都是值为true的元素。

分析:

举个栗子,有以下的矩阵,(其中绿色代表的是true,红色代表的是false)


此图中,最大的胖子的s可以达到2,按照如下图中的路线行走,(蓝色虚线代表最大的胖子,黄线代表走的路线)


所以应该返回2。

如果是一个4*5的矩阵,并且里面都是true,那么应该返回4。

如果最小为1边长的胖子也不能从左上角移动到右下角,那么返回0。

怎么思考这样的问题,路径的遍历可以用DFS,那么又如何找到最大的胖子的边长呢?

一种比较暴力的方法就是遍历从s = 1到min(N, M),然后对s的边长进行DFS看是否能顺利完成路径,如果成功则记录,如果失败则停止,返回最后一个成功的s就是最大的边长。

但是这样太费时了。

我的想法是:通过把问题分解成两个子问题,通过递归去解决。

怎么分解?题目中只能往右或者往下走。

举个栗子,

如果往右走,它的最大的胖子的大小被两个因素限制:第0列最大的连续true的数量mmax,以及子问题除去第0列剩下的矩阵的最大胖子rs

往右走的最大胖子right_s = min(mmax, rs)

如果往左走也是类似:

它的最大的胖子的大小被两个因素限制:第0行最大的连续true的数量nmax,以及子问题除去第0列剩下的矩阵的最大胖子bs

往右走的最大胖子bottom_s = min(nmax, bs)

而最终的胖子的大小是s = max(right_s, bottom_s),即从往下走和往右走中选出最大值。

让问题解函数为getMax(A, x, y, n, m),其中A表示矩阵,x,y表示胖子刚开始待的位置,n,m表示矩阵大小。

那么问题分解的过程如下图:


这是非常典型的分解问题+递归解决。

而终结的条件就是当胖子的右下角到达了n和m的边界的时候,进行判断是否胖子当前所占的地方是否全为true,因为胖子来的路中左边和右边我们都通过最左列和最上行的true最大的数量判断过,不过胖子最后占的位置我们还没有判断,如果都为true,则当前胖子的长度s就是答案,否则s还要小。

代码如下:

#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
int getMax(vector<vector<bool>>&A, int x, int y, int n, int m){
 int nmax = 0, mmax = 0;
 if (x >= n || y >= m)return 0;//终结问题的部分,已到了边界
 while (nmax + x< n && A[y][nmax + x] == true)//找出最多的列的T
  nmax++;
 while (mmax + y< m && A[mmax + y][x] == true)//找出最多的行的T
  mmax++;
 bool flag = false;
 if (nmax == 0 && mmax == 0)//终结问题的部分
  return 0;
 if (nmax == mmax && nmax + x == n && mmax + y == m){//终结问题的部分,到了边界,判断是不是都是true
  flag = true;
  for (int i = x; i < n; ++i){
   for (int j = y; j < m; ++j){
    if (A[j][i] == false){
     flag = false;
     break;
    }
   }
  }
 }
 if (flag == true){
  return nmax;
 }
 int ret;
 if (nmax == 0)
  ret = min(mmax, getMax(A, x, y + 1, n, m));
 else if (mmax == 0)
  ret = min(nmax, getMax(A, x + 1, y, n, m));
 else
  ret = max(min(mmax, getMax(A, x + 1, y, n, m)), min(nmax, getMax(A, x, y + 1, n, m)));//子问题1和子问题2
 return ret;
}
int solution(vector<vector<bool>> &A){
 int m = A.size();//找出行数
 int n = A[0].size();//找出列数
 int ret = getMax(A, 0, 0, n, m);//进入递归
 return ret;
}
int main(){
 vector<vector<bool>>VV;
 //VV = { { true, true, true, false }, { true, true, true, false }, { true, true, true, false }, { true, true, true, true }, { false, true, true, true } };//答案是2
 //VV = { { true } };//答案是1
 //VV = { { true, true, false, false }, { true, false, false, false }, { false, true, false, true },  { false, true, true, true } };//答案是0
 //VV = { { true,true,true }, { true,true,true }, { true,true,true } };//答案是3
 VV = { { true, true, true, true }, { true, true, true, true }, { false, true, true, true } };//答案是2
 cout << solution(VV) << endl;
 return 0;
}

——Apie陈小旭




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值