穷举vs暴搜vs深搜vs回溯vs剪枝(典型算法思想)—— OJ例题算法解析思路

news2025/4/18 5:48:31

回溯算法的模版

void backtrack(vector<int>& path, vector<int>& choice, ...) 
{
    // 满⾜结束条件
    if (/* 满⾜结束条件 */) 
    {
        // 将路径添加到结果集中
        res.push_back(path);
        return;
    }
    // 遍历所有选择
    for (int i = 0; i < choices.size(); i++) 
    {
        // 做出选择
        path.push_back(choices[i]);
        // 做出当前选择后继续搜索
        backtrack(path, choices);
        // 撤销选择
        path.pop_back();
    }
}

目录

回溯算法的模版

一、46. 全排列 - 力扣(LeetCode)

算法代码:

1. 类的成员变量

2. permute 函数

3. dfs 函数

4. 回溯的核心思想

5. 代码的优化空间

6. 代码的复杂度分析

7. 代码的改进版本

总结

二、78. 子集 - 力扣(LeetCode) 

递归流程:

解法一:算法代码(剪枝->回溯->递归出口) 

1. 类的成员变量

2. subsets 函数

3. dfs 函数

4. 回溯的核心思想

5. 代码的优化空间

6. 代码的复杂度分析

7. 代码的改进版本(避免重复子集)

改进点:

8. 总结

解法二:算法代码(回溯->剪枝->递归出口)

1. 类的成员变量

2. subsets 函数

3. dfs 函数

4. 代码的核心思想

5. 代码的优化空间

6. 代码的复杂度分析

7. 代码的改进版本(避免重复子集)

改进点:

8. 总结


一、46. 全排列 - 力扣(LeetCode)

算法代码:

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    bool check[7];

public:
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums);
        return ret;
    }
    void dfs(vector<int>& nums) {
        if (path.size() == nums.size()) {
            ret.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (!check[i]) {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                // 回溯 -> 恢复现场
                path.pop_back();
                check[i] = false;
            }
        }
    }
};

1. 类的成员变量

  • ret:用于存储所有可能的排列结果,类型为 vector<vector<int>>

  • path:用于存储当前正在构建的排列,类型为 vector<int>

  • check:用于标记某个元素是否已经被使用过,类型为 bool 数组,大小为 7(假设输入数组的长度不超过 7)。

2. permute 函数

  • 这是主函数,接收一个整数数组 nums 作为输入,并返回所有可能的排列。

  • 调用 dfs(nums) 开始深度优先搜索。

  • 最终返回 ret,即所有排列的结果。

3. dfs 函数

  • 这是递归函数,用于生成所有可能的排列。

  • 递归终止条件:如果 path 的大小等于 nums 的大小,说明当前 path 已经是一个完整的排列,将其加入到 ret 中,并返回。

  • 递归过程

    • 遍历 nums 数组中的每一个元素。

    • 如果当前元素没有被使用过(check[i] == false),则将其加入到 path 中,并标记为已使用。

    • 递归调用 dfs,继续生成下一个位置的元素。

    • 回溯:在递归返回后,撤销当前的选择(即从 path 中移除最后一个元素,并将 check[i] 重新标记为未使用),以便尝试其他可能的排列。

4. 回溯的核心思想

  • 回溯是一种通过递归来尝试所有可能的选择,并在每一步撤销选择以回到上一步的算法。

  • 在这段代码中,回溯体现在 path.pop_back() 和 check[i] = false 这两行代码上。它们的作用是撤销当前的选择,以便尝试其他可能的排列。

5. 代码的优化空间

  • check 数组的大小是固定的 7,这意味着如果 nums 的大小超过 7,代码将无法正确处理。可以将 check 数组的大小动态设置为 nums.size()

  • 可以使用 std::swap 来直接在原数组上进行排列,从而减少 path 和 check 的使用,进一步优化空间复杂度。

6. 代码的复杂度分析

  • 时间复杂度:O(n!),其中 n 是 nums 的大小。因为全排列的数量是 n!。

  • 空间复杂度:O(n!),用于存储所有排列的结果。递归栈的深度为 n,因此递归的空间复杂度为 O(n)。

7. 代码的改进版本

class Solution {
    vector<vector<int>> ret;

public:
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int start) {
        if (start == nums.size()) {
            ret.push_back(nums);
            return;
        }
        for (int i = start; i < nums.size(); i++) {
            swap(nums[start], nums[i]);
            dfs(nums, start + 1);
            swap(nums[start], nums[i]); // 回溯
        }
    }
};

在这个改进版本中,我们直接在原数组上进行排列,减少了 path 和 check 的使用,从而优化了空间复杂度。

总结

这段代码通过深度优先搜索和回溯的思想,实现了全排列的生成。代码的核心在于递归和回溯的处理,通过撤销选择来尝试所有可能的排列。

二、78. 子集 - 力扣(LeetCode) 

递归流程:

解法一:算法代码(剪枝->回溯->递归出口) 

// 解法⼀:
class Solution {
    vector<vector<int>> ret;
    vector<int> path;

public:
    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int>& nums, int pos) {
        if (pos == nums.size()) {
            ret.push_back(path);
            return;
        }
        // 选
        path.push_back(nums[pos]);
        dfs(nums, pos + 1);
        path.pop_back(); // 恢复现场
        // 不选
        dfs(nums, pos + 1);
    }
};

1. 类的成员变量

  • ret:用于存储所有子集的结果,类型为 vector<vector<int>>

  • path:用于存储当前正在构建的子集,类型为 vector<int>


2. subsets 函数

  • 这是主函数,接收一个整数数组 nums 作为输入,并返回所有可能的子集。

  • 调用 dfs(nums, 0) 开始深度优先搜索,0 表示从数组的第一个元素开始处理。

  • 最终返回 ret,即所有子集的结果。


3. dfs 函数

  • 这是递归函数,用于生成所有可能的子集。

  • 递归终止条件:如果 pos 等于 nums 的大小,说明已经处理完所有元素,此时 path 中存储的就是一个子集,将其加入到 ret 中,并返回。

  • 递归过程

    1. 选择当前元素

      • 将 nums[pos] 加入到 path 中。

      • 递归调用 dfs(nums, pos + 1),继续处理下一个元素。

      • 在递归返回后,撤销选择(即从 path 中移除最后一个元素),以便尝试不选择当前元素的情况。

    2. 不选择当前元素

      • 直接递归调用 dfs(nums, pos + 1),跳过当前元素,继续处理下一个元素。


4. 回溯的核心思想

  • 回溯是一种通过递归来尝试所有可能的选择,并在每一步撤销选择以回到上一步的算法。

  • 在这段代码中,回溯体现在 path.pop_back() 这一行代码上。它的作用是撤销当前的选择,以便尝试不选择当前元素的情况。


5. 代码的优化空间

  • 如果输入数组 nums 中包含重复元素,这段代码会生成重复的子集。可以通过排序和剪枝来避免重复子集的生成。

  • 可以将 path 改为引用传递,减少拷贝的开销。


6. 代码的复杂度分析

  • 时间复杂度:O(2^n),其中 n 是 nums 的大小。因为每个元素有两种选择(选或不选),总共有 2^n 个子集。

  • 空间复杂度:O(n),递归栈的深度为 n。结果存储空间不计入空间复杂度。


7. 代码的改进版本(避免重复子集)

如果输入数组 nums 中包含重复元素,可以通过排序和剪枝来避免生成重复的子集。改进后的代码如下:

class Solution {
    vector<vector<int>> ret;
    vector<int> path;

public:
    vector<vector<int>> subsets(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 排序,便于剪枝
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos) {
        ret.push_back(path); // 每次递归都加入当前子集
        for (int i = pos; i < nums.size(); i++) {
            if (i > pos && nums[i] == nums[i - 1]) continue; // 剪枝,避免重复
            path.push_back(nums[i]);
            dfs(nums, i + 1);
            path.pop_back(); // 回溯
        }
    }
};
改进点:
  1. 排序:先对 nums 排序,使得相同的元素相邻。

  2. 剪枝:在递归过程中,如果当前元素和前一个元素相同,并且不是第一次遇到该元素,则跳过,避免重复子集。

  3. 提前加入子集:在每次递归开始时,直接将当前 path 加入到 ret 中,这样可以避免在递归终止时才加入子集。


8. 总结

        这段代码通过深度优先搜索和回溯的思想,实现了求解数组的所有子集。代码的核心在于对每个元素的选择和不选择两种情况的分支处理,并通过回溯撤销选择以尝试其他可能性。如果输入数组包含重复元素,可以通过排序和剪枝来优化,避免生成重复子集。

解法二:算法代码(回溯->剪枝->递归出口)

// 解法⼆:
class Solution {
    vector<vector<int>> ret;
    vector<int> path;

public:
    vector<vector<int>> subsets(vector<int>& nums) {

        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int>& nums, int pos) {
        ret.push_back(path);
        for (int i = pos; i < nums.size(); i++) {
            path.push_back(nums[i]);
            dfs(nums, i + 1);
            path.pop_back(); // 恢复现场
        }
    }
};

1. 类的成员变量

  • ret:用于存储所有子集的结果,类型为 vector<vector<int>>

  • path:用于存储当前正在构建的子集,类型为 vector<int>


2. subsets 函数

  • 这是主函数,接收一个整数数组 nums 作为输入,并返回所有可能的子集。

  • 调用 dfs(nums, 0) 开始深度优先搜索,0 表示从数组的第一个元素开始处理。

  • 最终返回 ret,即所有子集的结果。


3. dfs 函数

  • 这是递归函数,用于生成所有可能的子集。

  • 递归过程

    1. 将当前子集加入结果

      • 在每次递归调用开始时,直接将当前 path 加入到 ret 中。这是因为 path 在每一层递归中都表示一个有效的子集。

    2. 遍历数组元素

      • 从当前位置 pos 开始遍历 nums 数组。

      • 将当前元素 nums[i] 加入到 path 中,表示选择该元素。

      • 递归调用 dfs(nums, i + 1),继续处理下一个元素。

      • 在递归返回后,撤销选择(即从 path 中移除最后一个元素),以便尝试其他可能的子集。


4. 代码的核心思想

  • 子集的生成

    • 子集的生成可以看作是对每个元素的选择或不选择。

    • 通过递归和回溯,代码枚举了所有可能的选择组合。

  • 提前加入子集

    • 在每次递归调用开始时,直接将当前 path 加入到 ret 中。这是因为 path 在每一层递归中都表示一个有效的子集,无需等到递归终止才加入。


5. 代码的优化空间

  • 如果输入数组 nums 中包含重复元素,这段代码会生成重复的子集。可以通过排序和剪枝来避免重复子集的生成。

  • 可以将 path 改为引用传递,减少拷贝的开销。


6. 代码的复杂度分析

  • 时间复杂度:O(2^n),其中 n 是 nums 的大小。因为每个元素有两种选择(选或不选),总共有 2^n 个子集。

  • 空间复杂度:O(n),递归栈的深度为 n。结果存储空间不计入空间复杂度。


7. 代码的改进版本(避免重复子集)

如果输入数组 nums 中包含重复元素,可以通过排序和剪枝来避免生成重复的子集。改进后的代码如下:

class Solution {
    vector<vector<int>> ret;
    vector<int> path;

public:
    vector<vector<int>> subsets(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 排序,便于剪枝
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos) {
        ret.push_back(path); // 将当前子集加入结果
        for (int i = pos; i < nums.size(); i++) {
            if (i > pos && nums[i] == nums[i - 1]) continue; // 剪枝,避免重复
            path.push_back(nums[i]);
            dfs(nums, i + 1);
            path.pop_back(); // 回溯
        }
    }
};
改进点:
  1. 排序:先对 nums 排序,使得相同的元素相邻。

  2. 剪枝:在递归过程中,如果当前元素和前一个元素相同,并且不是第一次遇到该元素,则跳过,避免重复子集。


8. 总结

        这段代码通过深度优先搜索和回溯的思想,实现了求解数组的所有子集。与解法一相比,解法二的代码更加简洁,直接通过循环和递归来生成所有子集。如果输入数组包含重复元素,可以通过排序和剪枝来优化,避免生成重复子集。代码的核心思想是对每个元素的选择和不选择进行枚举,并通过回溯撤销选择以尝试其他可能性。


重点:

递归的本质

        递归是一种通过函数调用自身来解决问题的编程技巧。在递归过程中,问题的规模会逐渐减小,直到达到一个终止条件。递归的核心思想是分治,即将一个大问题分解为若干个小问题,然后分别解决这些小问题。

在子集问题中,递归的作用是对每个元素做出决策(选或不选),从而生成所有可能的子集。


为什么解法一不需要 for 循环?

在解法一中,递归的逻辑是对每个元素做出“选”或“不选”的决策。具体来说:

  1. 对于当前元素 nums[pos],有两种选择:

    • 选择它:将其加入 path,然后递归处理下一个元素(pos + 1)。

    • 不选择它:直接递归处理下一个元素(pos + 1)。

  2. 递归的终止条件是 pos == nums.size(),表示已经处理完所有元素。

这种递归逻辑已经隐含了对所有元素的遍历,因此不需要显式的 for 循环。


为什么解法二需要 for 循环?

在解法二中,递归的逻辑是显式地遍历数组中的元素,依次生成子集。具体来说:

  1. for 循环从 pos 开始遍历数组 nums,表示从当前位置开始选择元素。

  2. 对于每个元素 nums[i],将其加入 path,然后递归处理下一个元素(i + 1)。

  3. 在递归返回后,通过 path.pop_back() 回溯,恢复现场,尝试下一个元素。

这种递归逻辑通过 for 循环显式地遍历元素,确保每个元素都有机会被选中,并且避免生成重复的子集。


递归和 for 循环的关系

  • 递归的本质是遍历:递归确实可以遍历所有元素,但遍历的方式可以是隐式的(如解法一)或显式的(如解法二)。

  • 是否需要 for 循环:取决于递归的逻辑设计。如果递归的逻辑已经隐含了对所有元素的遍历(如解法一),则不需要 for 循环;如果需要显式地遍历元素(如解法二),则需要 for 循环。


两种解法的对比

特性解法一(无 for 循环)解法二(有 for 循环)
递归逻辑对每个元素做出“选”或“不选”的决策显式遍历元素,生成子集
是否需要 for 循环
代码结构更简洁更直观
时间复杂度O(2^n)O(2^n)

为什么解法二需要 for 循环?

解法二的递归逻辑是通过 for 循环显式地遍历元素,确保每个元素都有机会被选中,并且避免生成重复的子集。具体来说:

  1. 显式遍历元素for 循环从 pos 开始遍历数组 nums,表示从当前位置开始选择元素。

  2. 避免重复子集:通过 for 循环从 pos 开始遍历,可以避免生成重复的子集。例如,如果已经选择了 nums[1],那么后续的子集只能从 nums[2] 开始选择,而不能回头选择 nums[0]

  3. 生成所有子集:通过 for 循环和递归的结合,确保所有可能的子集都被生成。


总结

  • 递归确实可以遍历所有元素,但遍历的方式可以是隐式的(如解法一)或显式的(如解法二)。

  • 是否需要 for 循环取决于递归的逻辑设计。如果递归的逻辑已经隐含了对所有元素的遍历,则不需要 for 循环;如果需要显式地遍历元素,则需要 for 循环。

  • 解法一和解法二都是正确的,只是它们的递归逻辑和实现方式不同。解法一更简洁,解法二更直观。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2307621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java项目】基于Spring Boot的校园博客系统

【Java项目】基于Spring Boot的校园博客系统 技术简介&#xff1a;采用Java技术、Spring Boot框架、MySQL数据库等实现。 系统简介&#xff1a;校园博客系统是一个典型的管理系统&#xff0c;主要功能包括管理员&#xff1a;首页、个人中心、博主管理、文章分类管理、文章信息…

计算机毕业设计SpringBoot+Vue.js图书进销存管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

算法-数据结构(图)-迪杰斯特拉最短逻辑算法( Dijkstra)

迪杰斯特拉算法&#xff08;Dijkstras Algorithm&#xff09; 是一种用于计算单源最短路径的经典算法&#xff0c;由荷兰计算机科学家 艾兹赫尔迪杰斯特拉&#xff08;Edsger W. Dijkstra&#xff09; 于1956年提出。它的主要目标是找到从图中的某个源节点到所有其他节点的最短…

C语言【进阶篇】之指针——涵盖基础、数组与高级概念

目录 &#x1f680;前言&#x1f914;指针是什么&#x1f31f;指针基础&#x1f4af;内存与地址&#x1f4af;指针变量&#x1f4af; 指针类型&#x1f4af;const 修饰指针&#x1f4af;指针运算&#x1f4af;野指针和 assert 断言 &#x1f4bb;数组与指针&#x1f4af;数组名…

关于命令行下的 git( git add、git commit、git push)

文章目录 关于 gitgit 的概念git 操作&#xff08;git add、git commit、git push 三板斧&#xff09;安装 git新建仓库及配置git clone.gitignoregit addgit commitgit push其他 git 指令git pull&#xff08;把远端的东西拉到本地进行同步&#xff09;其他指令 关于 git git…

DaoCloud 亮相 2025 GDC丨开源赋能 AI 更多可能

2025 年 2 月 21 日至 23 日&#xff0c;上海徐汇西岸&#xff0c;2025 全球开发者先锋大会以 “模塑全球&#xff0c;无限可能” 的主题&#xff0c;围绕云计算、机器人、元宇宙等多元领域&#xff0c;探讨前沿技术创新、应用场景拓展和产业生态赋能&#xff0c;各类专业论坛、…

极速探索 HarmonyOS NEXT:开启国产操作系统开发的新篇章

极速探索 HarmonyOS NEXT&#xff1a;开启国产操作系统开发的新篇章 一、引言二、HarmonyOS NEXT 是什么&#xff1f;背景核心特性 三、HarmonyOS NEXT 的发展历程从 LiteOS 到 HarmonyOS 的逐步演进HarmonyOS NEXT 5.0 的发布 四、HarmonyOS NEXT 对科技的影响技术突破开发者生…

火狐浏览器多开指南:独立窗口独立IP教程

无论是跨境电商从业者需要管理多个店铺账号&#xff0c;还是海外社交媒体营销人员要运营多个社交平台账号&#xff0c;亦或是从事多账号广告投放的人员&#xff0c;都面临着一个共同的挑战 —— 如何高效管理多个账号&#xff0c;并确保每个账号的独立性。 在这种情况下&#…

内容中台是什么?内容管理平台解析

内容中台的核心价值 现代企业数字化转型进程中&#xff0c;内容中台作为中枢系统&#xff0c;通过构建统一化的内容管理平台实现数据资产的高效整合与智能调度。其核心价值体现在打破传统信息孤岛&#xff0c;将分散于CRM、ERP等系统的文档、知识库、产品资料进行标准化归集&a…

1.2 Kaggle大白话:Eedi竞赛Transformer框架解决方案02-GPT_4o生成训练集缺失数据

目录 0. 本栏目竞赛汇总表1. 本文主旨2. AI工程架构3. 数据预处理模块3.1 配置数据路径和处理参数3.2 配置API参数3.3 配置输出路径 4. AI并行处理模块4.1 定义LLM客户端类4.2 定义数据处理函数4.3 定义JSON保存函数4.4 定义数据分片函数4.5 定义分片处理函数4.5 定义文件名排序…

sql server笔记

创建数据库 use master gocreate database stuuuuu//删除数据库if db_id ($$$) is not nullDrop database [$$$] go//新建表USE [studyTest] GOSET ANSI_NULLS ON GOSET QUOTED_IDENTIFIER ON GOCREATE TABLE [dbo].[Table_1]([id] [int] NULL,[name] [varchar](10) NULL ) ON…

uni小程序wx.switchTab有时候跳转错误tab问题,解决办法

在一个子页面里面使用uni.switchTab或者wx.switchTab跳转到tab菜单的时候&#xff0c;先发送了一个请求&#xff0c;然后执行跳转到tab菜单&#xff0c;但是这个时候&#xff0c;出错了........也是非常的奇怪&#xff0c;不加请求就没问题......但是业务逻辑就是要先执行某个请…

【第十节】C++设计模式(结构型模式)-Flyweight( 享元)模式

目录 一、问题背景 二、模式选择 三、代码实现 四、总结讨论 一、问题背景 享元模式&#xff08;Flyweight Pattern&#xff09;在对象存储优化中的应用 在面向对象系统的设计与实现中&#xff0c;创建对象是最常见的操作之一。然而&#xff0c;如果一个应用程序使用了过多…

AORO M6北斗短报文终端:将“太空黑科技”转化为安全保障

在卫星导航领域&#xff0c;北斗系统作为我国自主研发的全球卫星导航系统&#xff0c;正以其独特的短报文通信功能引发全球范围内的广泛关注。这一突破性技术不仅使北斗系统在全球四大导航系统中独树一帜&#xff0c;具备了双向通信能力&#xff0c;更通过遨游通讯推出的AORO M…

深度生成模型(二)——基本概念与数学建模

上一篇笔记中提到了端到端模型底层核心采用了深度生成模型&#xff0c;先简单梳理一下 生成式人工智能&#xff08;Artificial Intelligence Generated Content&#xff0c;AIGC&#xff09;经历了从早期基于概率模型和规则系统的方法到现代深度生成模型的跨越式发展 深度神经…

Mac本地部署Deep Seek R1

Mac本地部署Deep Seek R1 1.安装本地部署大型语言模型的工具 ollama 官网&#xff1a;https://ollama.com/ 2.下载Deepseek R1模型 网址&#xff1a;https://ollama.com/library/deepseek-r1 根据电脑配置&#xff0c;选择模型。 我的电脑&#xff1a;Mac M3 24G内存。 这…

项目——仿RabbitMQ实现消息队列

1.项目介绍 曾经在学习Linux的过程中&#xff0c;我们学习过阻塞队列 (BlockingQueue) 。 当时我们说阻塞队列最大的用途, 就是用来实现生产者消费者模型。 生产者消费者模型是后端开发的常用编程方式&#xff0c; 它存在诸多好处&#xff1a; 解耦合支持并发支持忙闲不均削峰…

【nextjs官方demo】Chapter 6连接数据库报错

问题&#xff1a;跟着demo创建完成postgres数据库&#xff0c;并修改了env文件&#xff0c;需要访问/seed去初始化数据的时候&#xff1a; 报错信息如下&#xff0c;看信息就是bcrypt模块有问题&#xff1a; 排除了你的环境问题后&#xff0c;就看下面这句话&#xff1a; 它的…

Nginx的反向代理(超详细)

正向代理与反向代理概念 1.概念&#xff1a; 反向代理服务器位于用户与目标服务器之间&#xff0c;但对用户而言&#xff0c;反向代理服务器就相当于目标服务器&#xff0c;即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时&#xff0c;用户不需要知道目标服务…

Plantsimulation中机器人怎么通过阻塞角度设置旋转135°

创建一个这样的简单模型。 检查PickAndPlace的角度表。源位于180的角位置&#xff0c;而物料终结位于90的角位置。“返回默认位置”选项未被勾选。源每分钟生成一个零件。启动模拟时&#xff0c;Plant Simulation会选择两个位置之间的最短路径。示例中的机器人无法绕135的角位…