【进阶实战】我的代码实战_进阶相应的算法题

【待完成】

引言

一、思维方法使用

思维应用:从后到前使用 bfs!!!

1.1 题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次能向上下左右移动一步,每移动一步都要耗费1能量。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物 且 机器人只能破坏一次障碍物。

若不能到达右下角就返回 -1;能到达则返回最少的能量。

测试用例输入:
7 3

**.

.**

***

输出:12

1.2 代码实现

#include <algorithm>
#include <cmath>
#include <deque>
#include <iostream>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <unordered_map>
#include <vector>

using namespace std;

#define For(x, y, z) for (ll x = y; x < z; ++x)
typedef long long ll;


/**
 * @Description: 使用bfs 从后往前搜索。
 * @param {int} x
 * @param {int} y
 * @return {*}  返回搜搜中的最短路径/最小能量。
 * @notes: 
 */
int bfs(vector<vector<char>>& matrix, vector<vector<bool>>& visited, int x, int y,int m, int n) {
    // int m = matrix.size(), n = matrix[0].size();
    int dirs[4][2] = { {1,0},{0,1},{-1,0},{0,-1}};
    queue<vector<int>> q;  // 0 1为坐标; 2 为是否破坏过障碍——1为可破坏一次,0为不能破坏。
    int ans=0;  // 最少能量损耗。
    
    q.push({x, y, 1});
    
    while (!q.empty()) {
        int size = q.size();
        while (size--) {
            auto cur = q.front(); q.pop();
            auto i = cur[0];
            auto j = cur[1];
            auto z = cur[2];

            // solve
            if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || (matrix[i][j]=='*' && z==0)) {
                continue; // Don't use return!!!! DFS return, BFS should only contine inside loop!!!
            }
            if(i == 0 && j==0){
                return ans;
            }

            // find closest
            visited[i][j] = true;
            for (const auto& dir : dirs) {
                auto ni = i + dir[0];
                auto nj = j + dir[1];
                if(matrix[i][j] == '*' && z==1){
                    z = 0;
                }
                q.push({ni, nj, z});
            }
        }
        ans++;
    }
    // 能够达到走里面return;达不到走外面 return -1
    return -1;
}


int main(){

    int m,n;
    cin >> m >> n;
    vector<vector<char>> grid(m, vector<char>(n,'.'));
    for(int i = 0;i<m;i++){
        for(int j = 0;j<n;j++){
            char a;
            cin >> a;
            grid[i][j] = a;
            // cin >> grid[i][j];       
        }
    }

    vector<vector<bool>> visited(m, vector<bool>(n, false));
    cout << bfs(grid, visited, m-1, n-1, m ,n) << endl;
    // for(auto c:grid){
    //     for(auto c1:c){
    //         cout << c1 << " ";
    //     }
    //     cout << endl;
    // }
    // system("pause");
    return 0;
}

1.3 不同路径I / II

1.3.1 不同路径I

在这里插入图片描述

在这里插入图片描述

1.3.2 不同路径II

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.4 剑指Offer13 —— 机器人的运动范围

在这里插入图片描述

二、并查集运用

求解最大的联通问题

2.1 题目描述/测试用例

朋友圈中最大的朋友个数:

//输入
7 5
1 2
2 3
3 4
5 6
7 1
// 输出
5

2.2 我的题解

class UnionFind
{
public:
    UnionFind(int n)
    {
        this->cnt = n;
        this->n1 = n;
        // 动态数组内存分配
        this->parent = new int[n];
        this->size = new int[n];
        // this->parent = (int*)malloc(n*sizeof(int));
        // this->size = (int*)malloc(n*sizeof(int));
        // this->parent.resize(n); 也可以 积累!!!
        // this->size.resize(n);

        for (int i = 0; i < n; i++)
        {
            parent[i] = i;
            size[i] = 1;
        }
    }
    // ~UnionFind()
    // {
    //     this->parent.clear();
    //     this->size.clear();
    // }
    /* 判断 p 和 q 是否连通 */
    bool connected(int p, int q)
    {
        int rootP = find(p);
        int rootQ = find(q);
        // cout << rootP << "rootq:" << rootQ << endl;
        return rootP == rootQ;
    }
    /* 返回图中有多少个连通分量 */
    int count()
    {
        return this->cnt;
    }
    // 发现 父节点, 返回父节点
    int find(int x)
    {
        // 经过三次修正 降低路径搜索难度
        while (parent[x] != x)
        {
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }
    // 将p和q连通
    void unionRoot(int p, int q)
    {
        // 比较size,将轻的放在重的上面
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ)
            return; // cuo

        if (size[rootP] > size[rootQ])
        {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ]; // cuo
        }
        else
        {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP]; //cuo
        }
        this->cnt--; // cuo
    }
    int maxCircle()
    { // 返回最大的连通分量
        int max = 1;
        for (int i = 0; i < n1; i++)
        {
            max = size[i] > max ? size[i] : max;
        }
        return max;
    }

private:
    int cnt;     // 连通分量
    int *parent; // 节点父节点索引 —— 方便并查集合并与使用
    int *size;   // 每个节点的重量是多少,方便 减少查找次数; 对应当前节点 所有的节点个数。
    int n1;
};

int main()
{
    int N, K;
    cin >> N >> K;
    UnionFind *unionChick = new UnionFind(N);
    while (K--)
    {
        int p1, p2;
        cin >> p1 >> p2;
        unionChick->unionRoot(p1-1, p2-1);   // 注意这里!!!
    }

    cout << "最大的团队数量为: " << unionChick->maxCircle() << endl;  // 对了。
    system("pause");
    return 0;
}

三、腾讯面试

最近公共祖先的变种问题
原问题以及解答见我的博客:【数据结构&接下来的计划】树剩下的部分+链表 & 计划

变形问题主要是指:

  1. 将对应的最近公共最先变为如果 A和B分别有一个不存在你怎么做?
  2. 对应延伸出 递归遍历的相应前序遍历和后序遍历的问题?

3.1 有一个不存在解决

我的题解

// A B可能有不存在的前提下:
TreeNode * find(TreeNode* root, int A, int B, unordered_map<int, int>& map){

    if(root == nullptr) return nullptr;

    TreeNode *left = find(root->left, A, B);
    TreeNode *right = find(root->right, A, B);
    
	// 如果 A B有一个不存在,必须放在后序
    // 如果保证都存在,则可放在前面 —— 因为前面快!
    if(root->value == A ) {
        map[A]++;
        return root;
    }else if(root->value == B){
        map[B]++;
        return root;
    }
    if(left != nullptr && right != nullptr) return root;
    else if(left == nullptr && right == nullptr) return nullptr;
    else{
        return left == nullptr ? right : left ;
    }
    
}
TreeNode * find(TreeNode* root, int A, int B){
    unordered_map<int, int> map;
    TreeNode *res = find(root, A, B, map);
    if(map.count(A) > 0 && map.count(B)>0){
        return res;
    }else{
        return nullptr;
    }
}

3.2 前后序问题

我的题解

// 保证 A B必存在的前提下:
TreeNode * find(TreeNode* root, int A, int B){

    if(root == nullptr) return nullptr;
	// 如果 A B有一个不存在,必须放在后序
    // 如果保证都存在,则可放在前面 —— 因为前面快!
	if(root->value == A || root->value == B) return root;
	
    TreeNode *left = find(root->left, A, B);
    TreeNode *right = find(root->right, A, B);
    
	
   
    if(left != nullptr && right != nullptr) return root;
    else if(left == nullptr && right == nullptr) return nullptr;
    else{
        return left == nullptr ? right : left ;
    }
    
}

四、字节面试题-动规

4.1 环路0点出发,k步回原点有几种方法

4.1.1 题目描述

一个环上有10个点,编号为0-9,从0点出发,每步可以顺时针到下一个点,也可以逆时针到上一个点,求:经过n步又回到0点有多少种不同的走法?

举例
如果n=1,则从0出发只能到1或者9,不可能回到0,共0种走法
如果n=2,则从0出发有4条路径:0->1->2, 0->1->0, 0->9->8, 0->9->0,其中有两条回到了0点,故一共有2种走法

4.1.2 解析

动态规划:
状态: 步数变化、当前点变化。
目标: dp[n,0]
dp数组 dp二维数组——从0出发,k步到达j点的方法有多少种。
j步达到i点的问题,转化为j-1步从相邻的两个节点到达目标节点的方法数之和。

状态方程 d(k, j) = d(k-1, j-1) + d(k-1, j+1);
由于可能发生越界,故转换为
d(k, j) = d(k-1, (j-1+n)%n) + d(k-1, (j+1)%n);

4.1.3 解法

public class Solution {
    //n为环数,k为步数
    public int GetSteps(int n,int k){
        if(n==0) return 1;
        //如果只有2个环,则偶数有1个方法,奇数不能到达
        if(n==2){
            if(k%2==0) return 1;
            else return 0;
        }
        int[][] dp = new int[k+1][n];
        //0步到达0点有1种方法
        dp[0][0]=1;
        //0步到达任意点有0种方法,可java自赋0值,可省略
        //for(int i=0;i<n;i++){
        //    dp[0][i]=0
        //}
        for(int j=1;j<=k;j++){
            for(int i=0;i<n;i++){
                //j步达到i点的问题,转化为j-1步从相邻的两个节点到达目标节点的方法数之和。
                //需要保证在0~n-1范围,故需要防止越界进行处理
                dp[j][i]=dp[j-1][(i-1+n)%n]+dp[j-1][(i+1)%n];
            }
        }
        //这里的0对应的是回到0点,达到任意点可以通过将0改为目标点即可
        return dp[k][0];
    }

    public static void main(String[] args) {
        System.out.println(new Solution().GetSteps(10,6));
    }
}

我的题解C++

class Solution{
public:
    /**
     * @Description: 有n个节点,问:需要从0出发走 k步  有多少种方案。
     * @param {int} n
     * @param {int} k
     * @return {*}
     * @notes: 关键动规——思考清楚 状态/目标(  )=》dp =》 状态转移关系
     * 状态: 当前点变化/或者说0点出发到目标点的方案几种、 步数变化。目标: dp[n,0]
dp数组 dp二维数组——从0出发,k步到达j点的方法有多少种。
j步达到i点的问题,转化为j-1步从相邻的两个节点到达目标节点的方法数之和。

状态方程 d(k, j) = d(k-1, j-1) + d(k-1, j+1);
由于可能发生越界,故转换为
d(k, j) = d(k-1, (j-1+n)%n) + d(k-1, (j+1)%n);
     */
    int GetSteps(int n,int k){
        if(n==0) return 1;
        //如果只有2个结点,则偶数有1个方法,奇数不能到达
        if(n==2){
            if(k%2==0) return 1;
            else return 0;
        }
        vector<vector<int>> dp(k+1, vector<int>(n, 0));
        //0步到达0点有1种方法
            // 此为关键 —— 标志从0 开始出发

        // base —— 第一行,因为都要用上一行
        dp[0][0]=1;
        //0步到达任意点有0种方法,上面已赋值
        //for(int i=0;i<n;i++){
        //    dp[0][i]=0
        //}
        for(int i=1;i<=k;i++){
            for(int j=0;j<n;j++){
                //i步达到i点的问题,转化为i-1步从相邻的两个节点到达目标节点的方法数之和。
                //需要保证在0~n-1范围,故需要防止越界进行处理
                dp[i][j]=dp[i-1][(j-1+n)%n]+dp[i-1][(j+1)%n];
            }
        }
        //这里的0对应的是回到0点,达到任意点可以通过将0改为目标点即可
        return dp[k][0];
    }
    
};

int main(){
    Solution s1;
    cout << s1.GetSteps(10, 1) << endl;
    cout << s1.GetSteps(10, 2) << endl;
    cout << s1.GetSteps(10, 3) << endl;
    cout << s1.GetSteps(10, 4) << endl;
    cout << s1.GetSteps(10, 5) << endl;
    cout << s1.GetSteps(10, 6) << endl;

    system("pause");
    return 0;
}

4.1.4 扩展

如果问到到达任意点的方法数,则将结果的dp[k][0]改为对应的目标值即可,比如达到5号的方法数,则return dp[k][5]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值