Leetcode(剑指offer专项训练)——DP专项(7)

发布时间 2023-04-05 17:25:29作者: 理想国的糕

矩阵中的距离

题目
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
链接

TLS思路题解

暴力DFS的结果是超时?,就是找每个位置的距离它最近的零点的位置

class Solution {
public:
    vector<vector<int>>ans;
    vector<vector<bool>>vis;
    int m,n;
    int inf=99999;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    void dfs(int rx,int ry,int x,int y,vector<vector<int>>& mat){
        vis[x][y]=true;
        if(ans[x][y]!=inf){
            ans[rx][ry]=min(ans[x][y]+abs(rx-x)+abs(ry-y),ans[rx][ry]);
            return;
        }
        if(mat[x][y]==0){
            ans[rx][ry]=min(abs(rx-x)+abs(ry-y),ans[rx][ry]);
            return;
        }else{
            int nx,ny;
            for(int i=0;i<4;i++){
                nx=x+dx[i];
                ny=y+dy[i];
                if(nx>=0&&nx<m&&ny>=0&&ny<n&&!vis[nx][ny]){
                    // cout<<nx<<" : "<<ny<<"|"<<m<<":"<<n<<endl;
                    dfs(rx,ry,nx,ny,mat);
                }
            }
        }
    }
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        //输出每一个元素最近的0的距离
        m=mat.size();
        n=mat[0].size();
        ans=vector<vector<int>>(m,vector<int>(n,inf));
        vis=vector<vector<bool>>(m,vector<bool>(n,false));
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                dfs(i,j,i,j,mat);
                vis.clear();
                vis=vector<vector<bool>>(m,vector<bool>(n,false));
            }
        }
        return ans;
    }
};

题解

BFS

思路:将地图中所有0看作第一层,第二层为紧邻0的那一份1,第三层为紧邻第一层11……以此类推

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        //输出每一个元素最近的0的距离
        int m=mat.size();
        int n=mat[0].size();
        int dx[4]={0,0,1,-1};
        int dy[4]={1,-1,0,0};
        int nx,ny;
        int inf=99999;
        vector<vector<int>>ans(m,vector<int>(n));
        vector<vector<bool>>vis(m,vector<bool>(n,false));
        queue<pair<int,int>>q;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(mat[i][j]==0){
                    vis[i][j]=true;
                    ans[i][j]=0;
                    q.emplace(i,j);
                }
            }
        }
        while(!q.empty()){
            auto[x,y]=q.front();
            q.pop();
            for(int i=0;i<4;i++){
                nx=x+dx[i];
                ny=y+dy[i];
                if(nx>=0&&nx<m&&ny>=0&&ny<n&&!vis[nx][ny]){
                    q.emplace(nx,ny);
                    ans[nx][ny]=ans[x][y]+1;
                    // printf("%d %d, %d %d,[%d,%d]\n",nx,ny,i,j,m,n);
                    vis[nx][ny]=true;
                }
            }
        }
        return ans;
    }
};

DP

思路:其实每次移动都有四种方位选择(1,1)(-1,1)(1,-1)(-1,-1),我们可以得到其中某个方向的状态转移方程:

\[ans[x][y]=0 (if mat[x][y]==0) \]

\[ans[x][y]=1+min(ans[i-1][j],ans[i][j-1]) \]

然后需要对四个方向依次做一遍这种操作即可

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        //输出每一个元素最近的0的距离
        int m=mat.size();
        int n=mat[0].size();
        int inf=99999;
        vector<vector<int>>ans(m,vector<int>(n,inf));  
       //初始化
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(mat[i][j]==0){
                    ans[i][j]=0;
                }
            }
        }
        //右-下
        for(int i=m-1;i>=0;i--){
            for(int j=n-1;j>=0;j--){
                if(j+1<n){
                    ans[i][j]=min(ans[i][j+1]+1,ans[i][j]);
                }
                if(i+1<m){
                    ans[i][j]=min(ans[i+1][j]+1,ans[i][j]);
                }
            }
        }
        //右 上:可以不需要
        for(int i=m-1;i>=0;i--){
            for(int j=0;j<n;j++){
                if(j-1>=0){
                    ans[i][j]=min(ans[i][j-1]+1,ans[i][j]);
                }
                if(i+1<m){
                    ans[i][j]=min(ans[i+1][j]+1,ans[i][j]);
                }
            }
        }
        //左-上
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(j-1>=0){
                    ans[i][j]=min(ans[i][j-1]+1,ans[i][j]);
                }
                if(i-1>=0){
                    ans[i][j]=min(ans[i-1][j]+1,ans[i][j]);
                }
            }
        }
        //左-下:可以不需要
        for(int i=0;i<m;i++){
            for(int j=n-1;j>=0;j--){
                if(j+1<n){
                    ans[i][j]=min(ans[i][j+1]+1,ans[i][j]);
                }
                if(i-1>=0){
                    ans[i][j]=min(ans[i-1][j]+1,ans[i][j]);
                }
            }
        }
        return ans;
    }
};

按照官方说法,常数优化后,其实只需要保留左上和右下两种遍历即可

单词演变

题目:
在字典(单词列表) wordList 中,从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给定两个长度相同但内容不同的单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
链接

BFS

思路:求最短的演变单词数量,本质上是在求最短路径,容易想到dfs,把原始wordList转成链表,可以有效的缩短BFS的遍历时间(具体体现为一个word路过后,可以从链表中摘除);
构造Node结构体,存储每一次遍历到的位置(str)以及单词个数(path),每次继续探索只能改变一个字母,所以需要遍历没有走过的单词链表,找到和当前str字符相差为1的下一个单词位置
完整代码如下:

class Solution {
public:
    int m,n;
    bool near_diff(string &a,string &b){
        int cnt=0;
        for(int i=0;i<n;i++){
            if(a[i]!=b[i]){
                cnt++;
                if(cnt>1){
                    return false;
                }
            }
        }
        if(cnt==1){
            return true;
        }
        return false;
    }
    struct Node{
        string str="";
        Node*next=NULL;
        int path=1;
    };
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        m=wordList.size();
        n=beginWord.size();
        //构建链表
        Node*head,*p,*pre;
        head=new Node;
        pre=head;
        bool flag=false;
        for(int i=0;i<m;i++){
            p=new Node;
            p->str=wordList[i];
            pre->next=p;
            pre=p;
            if(wordList[i]==endWord){
                flag=true;
            }
        }
        if(!flag){
            return 0;
        }
        
        //计算最短路径        
        queue<Node*>q;
        Node*temp=new Node;
        temp->str=beginWord;
        q.emplace(temp);
        while(!q.empty()){
            temp=q.front();
            q.pop();
            //找新的方向
            p=head->next;
            pre=head;
            while(p){
                if(near_diff(temp->str, p->str)){
                    p->path=temp->path+1;
                    //cout<<"temp="<<temp->str<<" p="<<p->str<<" len="<<p->path<<endl;
                    if(p->str==endWord){
                        return p->path;
                    }
                    //hot-dot-dog-cog 
                    q.emplace(p);
                    //删除节点
                    pre->next=p->next;
                    // Node *t=p;
                    p=p->next;
                    // delete(t);
                    continue;
                }
                pre=p;
                p=p->next;
            }
        }
        //["hot","dot","dog","lot","log","cog"]
        //hot->lot->log->log
        return 0;
    }
};