阅读视图

发现新文章,点击刷新页面。

每日一题-区间乘法查询后的异或 I🟡

给你一个长度为 n 的整数数组 nums 和一个大小为 q 的二维整数数组 queries,其中 queries[i] = [li, ri, ki, vi]

对于每个查询,按以下步骤执行操作:

  • 设定 idx = li
  • idx <= ri 时:
    • 更新:nums[idx] = (nums[idx] * vi) % (109 + 7)
    • idx += ki

在处理完所有查询后,返回数组 nums 中所有元素的 按位异或 结果。

 

示例 1:

输入: nums = [1,1,1], queries = [[0,2,1,4]]

输出: 4

解释:

  • 唯一的查询 [0, 2, 1, 4] 将下标 0 到下标 2 的每个元素乘以 4。
  • 数组从 [1, 1, 1] 变为 [4, 4, 4]
  • 所有元素的异或为 4 ^ 4 ^ 4 = 4

示例 2:

输入: nums = [2,3,1,5,4], queries = [[1,4,2,3],[0,2,1,2]]

输出: 31

解释:

  • 第一个查询 [1, 4, 2, 3] 将下标 1 和 3 的元素乘以 3,数组变为 [2, 9, 1, 15, 4]
  • 第二个查询 [0, 2, 1, 2] 将下标 0、1 和 2 的元素乘以 2,数组变为 [4, 18, 2, 15, 4]
  • 所有元素的异或为 4 ^ 18 ^ 2 ^ 15 ^ 4 = 31

 

提示:

  • 1 <= n == nums.length <= 103
  • 1 <= nums[i] <= 109
  • 1 <= q == queries.length <= 103
  • queries[i] = [li, ri, ki, vi]
  • 0 <= li <= ri < n
  • 1 <= ki <= n
  • 1 <= vi <= 105

模拟

解法:模拟

数据范围较小,模拟即可。复杂度 $\mathcal{O}(nq)$。

参考代码(c++)

class Solution {
public:
    int xorAfterQueries(vector<int>& nums, vector<vector<int>>& queries) {
        int n = nums.size();
        long long A[n];
        for (int i = 0; i < n; i++) A[i] = nums[i];

        const int MOD = 1e9 + 7;
        for (auto &qry : queries) for (int i = qry[0]; i <= qry[1]; i += qry[2]) A[i] = A[i] * qry[3] % MOD;
        
        long long ans = 0;
        for (int i = 0; i < n; i++) ans ^= A[i];
        return ans;
    }
};

每日一题-模拟行走机器人 II🟡

给你一个在 XY 平面上的 width x height 的网格图,左下角 的格子为 (0, 0) ,右上角 的格子为 (width - 1, height - 1) 。网格图中相邻格子为四个基本方向之一("North""East""South" 和 "West")。一个机器人 初始 在格子 (0, 0) ,方向为 "East" 。

机器人可以根据指令移动指定的 步数 。每一步,它可以执行以下操作。

  1. 沿着当前方向尝试 往前一步 。
  2. 如果机器人下一步将到达的格子 超出了边界 ,机器人会 逆时针 转 90 度,然后再尝试往前一步。

如果机器人完成了指令要求的移动步数,它将停止移动并等待下一个指令。

请你实现 Robot 类:

  • Robot(int width, int height) 初始化一个 width x height 的网格图,机器人初始在 (0, 0) ,方向朝 "East" 。
  • void step(int num) 给机器人下达前进 num 步的指令。
  • int[] getPos() 返回机器人当前所处的格子位置,用一个长度为 2 的数组 [x, y] 表示。
  • String getDir() 返回当前机器人的朝向,为 "North" ,"East" ,"South" 或者 "West" 。

 

示例 1:

example-1

输入:
["Robot", "step", "step", "getPos", "getDir", "step", "step", "step", "getPos", "getDir"]
[[6, 3], [2], [2], [], [], [2], [1], [4], [], []]
输出:
[null, null, null, [4, 0], "East", null, null, null, [1, 2], "West"]

解释:
Robot robot = new Robot(6, 3); // 初始化网格图,机器人在 (0, 0) ,朝东。
robot.step(2);  // 机器人朝东移动 2 步,到达 (2, 0) ,并朝东。
robot.step(2);  // 机器人朝东移动 2 步,到达 (4, 0) ,并朝东。
robot.getPos(); // 返回 [4, 0]
robot.getDir(); // 返回 "East"
robot.step(2);  // 朝东移动 1 步到达 (5, 0) ,并朝东。
                // 下一步继续往东移动将出界,所以逆时针转变方向朝北。
                // 然后,往北移动 1 步到达 (5, 1) ,并朝北。
robot.step(1);  // 朝北移动 1 步到达 (5, 2) ,并朝  (不是朝西)。
robot.step(4);  // 下一步继续往北移动将出界,所以逆时针转变方向朝西。
                // 然后,移动 4 步到 (1, 2) ,并朝西。
robot.getPos(); // 返回 [1, 2]
robot.getDir(); // 返回 "West"

 

提示:

  • 2 <= width, height <= 100
  • 1 <= num <= 105
  • stepgetPos 和 getDir 总共 调用次数不超过 104 次。

【宫水三叶】简单模拟题

模拟

根据题目给定的移动规则可知,机器人总是在外圈移动(共上下左右四条),而移动方向分为四类:

image.png

当行走步数为 $mod = 2 * (w - 1) + 2 * (h - 1)$ 的整数倍时,会回到起始位置,因此我们可以通过维护一个变量 loc 来记录行走的总步数,并且每次将 locmod 进行取模来得到有效步数。

在回答 getPosgetDir 询问时,根据当前 loc 进行分情况讨论(见注释)。

另外还有一个小细节:根据题意,如果当前处于 $(0, 0)$ 位置,并且没有移动过,方向为 East,若移动过,方向则为 South,这可以通过一个变量 moved 来进行特判处理。

代码:

###Java

class Robot {
    String[] ss = new String[]{"East", "North", "West", "South"};
    int w, h, loc; // loc: 有效(取模后)移动步数
    boolean moved; // 记录是否经过移动,用于特判 (0,0) 的方向
    public Robot(int width, int height) {
        w = width; h = height;
    }
    public void step(int num) {
        moved = true;
        loc += num;
        loc %= 2 * (w - 1) + 2 * (h - 1);
    }
    public int[] getPos() {
        int[] info = move();
        return new int[]{info[0], info[1]};
    }
    public String getDir() {
        int[] info = move();
        int x = info[0], y = info[1], dir = info[2];
        // 特殊处理当前在 (0,0) 的情况,当未移动过方向为 East,移动过方向为 South
        if (x == 0 && y == 0) return moved ? ss[3] : ss[0];
        return ss[dir];
    }
    int[] move() {
        if (loc <= w - 1) {
            // 当移动步数范围在 [0,w-1] 时,所在位置为外圈的下方,方向为 East
            return new int[]{loc, 0, 0};
        } else if (loc <= (w - 1) + (h - 1)) {
            // 当移动步数范围在 [w,(w-1)+(h-1)] 时,所在位置为外圈的右方,方向为 North
            return new int[]{w - 1, loc - (w - 1), 1};
        } else if (loc <= 2 * (w - 1) + (h - 1)) {
            // 当移动步数范围在 [(w-1)+(h-1)+1,2*(w-1)+(h-1)] 时,所在位置为外圈的上方,方向为 West
            return new int[]{(w - 1) - (loc - ((w - 1) + (h - 1))), h - 1, 2};
        } else {
            // 当移动步数范围在 [2*(w-1)+(h-1)+1,2*(w-1)+2*(h-1)] 时,所在位置为外圈的左方,方向为 South
            return new int[]{0, (h - 1) - (loc - (2 * (w - 1) + (h - 1))), 3};
        }
    }
}
  • 时间复杂度:$O(1)$
  • 空间复杂度:$O(1)$

最后

如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。

所有题解已经加入 刷题指南,欢迎 star 哦 ~

分类讨论,O(1) 时间复杂度(Python/Java/C++/C/Go/JS/Rust)

本文把 $\textit{width}$ 简称为 $w$,把 $\textit{height}$ 简称为 $h$。

根据题意,机器人只能在网格图的最外圈中移动,移动一整圈需要 $2(w+h-2)$ 步。

lc2069.png

设当前移动的总步数模 $2(w+h-2)$ 的结果为 $s$。分类讨论:

  • 如果 $s < w$,机器人往右走了 $s$ 步,位于 $(s,0)$,面朝东。
  • 如果 $w\le s < w+h-1$,机器人先往右走 $w-1$ 步,再往北走 $s-(w-1)$ 步,位于 $(w-1,s-w+1)$,面朝北。
  • 如果 $w+h-1\le s < 2w+h-2$,机器人先往右走 $w-1$ 步,再往北走 $h-1$ 步,到达右上角 $(w-1,h-1)$,再往西走 $s-(w-1)-(h-1)$ 步,位于 $(w-1-(s-(w-1)-(h-1)),h-1) = (2w + h - s - 3,h-1)$,面朝西。
  • 否则,机器人先往右走 $w-1$ 步,再往北走 $h-1$ 步,再往西走 $w-1$ 步,到达左上角 $(0,h-1)$,再往南走 $s-2(w-1)-(h-1)$ 步,位于 $(0,h-1-(s-2(w-1)-(h-1))) = (0, 2(w+h)-s-4)$,面朝南。

注意:总步数为 $0$ 时,机器人面朝东,但总步数为 $2(w+h-2)$ 的正整数倍时,机器人面朝南。需要特判总步数为 $0$ 的特殊情况吗?不需要,当总步数大于 $0$ 时,我们可以把取模后的范围从 $[0, 2(w+h-2)-1]$ 调整到 $[1, 2(w+h-2)]$,从而使原先模为 $0$ 的总步数变成 $2(w+h-2)$,落入面朝南的分支中,这样就可以避免特判了。

class Robot:
    def __init__(self, width: int, height: int) -> None:
        self.w = width
        self.h = height
        self.s = 0

    def step(self, num: int) -> None:
        # 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
        # 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s == 0 时的方向
        self.s = (self.s + num - 1) % ((self.w + self.h - 2) * 2) + 1

    def _getState(self) -> Tuple[int, int, str]:
        w, h, s = self.w, self.h, self.s
        if s < w:
            return s, 0, "East"
        if s < w + h - 1:
            return w - 1, s - w + 1, "North"
        if s < w * 2 + h - 2:
            return w * 2 + h - s - 3, h - 1, "West"
        return 0, (w + h) * 2 - s - 4, "South"

    def getPos(self) -> List[int]:
        x, y, _ = self._getState()
        return [x, y]

    def getDir(self) -> str:
        return self._getState()[2]
class Robot {
    private int w, h, s;

    public Robot(int width, int height) {
        w = width;
        h = height;
        s = 0;
    }

    public void step(int num) {
        // 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
        // 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s == 0 时的方向
        s = (s + num - 1) % ((w + h - 2) * 2) + 1;
    }

    public int[] getPos() {
        Object[] t = getState();
        return new int[]{(int) t[0], (int) t[1]};
    }

    public String getDir() {
        Object[] t = getState();
        return (String) t[2];
    }

    private Object[] getState() {
        if (s < w) {
            return new Object[]{s, 0, "East"};
        } else if (s < w + h - 1) {
            return new Object[]{w - 1, s - w + 1, "North"};
        } else if (s < w * 2 + h - 2) {
            return new Object[]{w * 2 + h - s - 3, h - 1, "West"};
        } else {
            return new Object[]{0, (w + h) * 2 - s - 4, "South"};
        }
    }
}
class Robot {
    int w;
    int h;
    int s = 0;

    tuple<int, int, string> getState() {
        if (s < w) {
            return {s, 0, "East"};
        } else if (s < w + h - 1) {
            return {w - 1, s - w + 1, "North"};
        } else if (s < w * 2 + h - 2) {
            return {w * 2 + h - s - 3, h - 1, "West"};
        } else {
            return {0, (w + h) * 2 - s - 4, "South"};
        }
    }

public:
    Robot(int width, int height) : w(width), h(height) {}

    void step(int num) {
        // 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
        // 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s == 0 时的方向
        s = (s + num - 1) % ((w + h - 2) * 2) + 1;
    }

    vector<int> getPos() {
        auto [x, y, _] = getState();
        return {x, y};
    }

    string getDir() {
        return get<2>(getState());
    }
};
typedef struct {
    int w;
    int h;
    int s;
} Robot;

Robot* robotCreate(int width, int height) {
    Robot* r = malloc(sizeof(Robot));
    r->w = width;
    r->h = height;
    r->s = 0;
    return r;
}

void robotStep(Robot* r, int num) {
    // 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
    // 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s == 0 时的方向
    r->s = (r->s + num - 1) % ((r->w + r->h - 2) * 2) + 1;
}

int* robotGetPos(Robot* r, int* returnSize) {
    int w = r->w, h = r->h, s = r->s;
    int x, y;
    if (s < w) {
        x = s;
        y = 0;
    } else if (s < w + h - 1) {
        x = w - 1;
        y = s - w + 1;
    } else if (s < w * 2 + h - 2) {
        x = w * 2 + h - s - 3;
        y = h - 1;
    } else {
        x = 0;
        y = (w + h) * 2 - s - 4;
    }

    int* ans = malloc(2 * sizeof(int));
    *returnSize = 2;
    ans[0] = x;
    ans[1] = y;
    return ans;
}

char* robotGetDir(Robot* r) {
    int w = r->w, h = r->h, s = r->s;
    if (s < w) {
        return "East";
    } else if (s < w + h - 1) {
        return "North";
    } else if (s < w * 2 + h - 2) {
        return "West";
    } else {
        return "South";
    }
}

void robotFree(Robot* r) {
    free(r);
}
type Robot struct {
w, h, step int
}

func Constructor(width, height int) Robot {
return Robot{width, height, 0}
}

func (r *Robot) Step(num int) {
// 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
// 把 step 取模调整到 [1, (w+h-2)*2],这样不需要特判 step == 0 时的方向
r.step = (r.step+num-1)%((r.w+r.h-2)*2) + 1
}

func (r *Robot) getState() (x, y int, dir string) {
w, h, step := r.w, r.h, r.step
switch {
case step < w:
return step, 0, "East"
case step < w+h-1:
return w - 1, step - w + 1, "North"
case step < w*2+h-2:
return w*2 + h - step - 3, h - 1, "West"
default:
return 0, (w+h)*2 - step - 4, "South"
}
}

func (r *Robot) GetPos() []int {
x, y, _ := r.getState()
return []int{x, y}
}

func (r *Robot) GetDir() string {
_, _, d := r.getState()
return d
}
var Robot = function(width, height) {
    this.w = width;
    this.h = height;
    this.s = 0;
};

Robot.prototype.step = function(num) {
    // 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
    // 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s === 0 时的方向
    this.s = (this.s + num - 1) % ((this.w + this.h - 2) * 2) + 1;
};

Robot.prototype.getState = function() {
    const w = this.w, h = this.h, s = this.s;
    if (s < w) {
        return [s, 0, "East"];
    } else if (s < w + h - 1) {
        return [w - 1, s - w + 1, "North"];
    } else if (s < w * 2 + h - 2) {
        return [w * 2 + h - s - 3, h - 1, "West"];
    } else {
        return [0, (w + h) * 2 - s - 4, "South"];
    }
};

Robot.prototype.getPos = function() {
    const [x, y, _] = this.getState();
    return [x, y];
};

Robot.prototype.getDir = function() {
    return this.getState()[2];
};
struct Robot {
    w: i32,
    h: i32,
    s: i32,
}

impl Robot {
    fn new(width: i32, height: i32) -> Self {
        Self { w: width, h: height, s: 0 }
    }

    fn step(&mut self, num: i32) {
        // 由于机器人只能走外圈,那么走 (w+h-2)*2 步后会回到起点
        // 把 s 取模调整到 [1, (w+h-2)*2],这样不需要特判 s == 0 时的方向
        self.s = (self.s + num - 1) % ((self.w + self.h - 2) * 2) + 1;
    }

    fn get_state(&self) -> (i32, i32, String) {
        let w = self.w;
        let h = self.h;
        let s = self.s;
        if s < w {
            (s, 0, "East".to_string())
        } else if s < w + h - 1 {
            (w - 1, s - w + 1, "North".to_string())
        } else if s < w * 2 + h - 2 {
            (w * 2 + h - s - 3, h - 1, "West".to_string())
        } else {
            (0, (w + h) * 2 - s - 4, "South".to_string())
        }
    }

    fn get_pos(&self) -> Vec<i32> {
        let (x, y, _) = self.get_state();
        vec![x, y]
    }

    fn get_dir(&self) -> String {
        let (_, _, d) = self.get_state();
        d
    }
}

复杂度分析

  • 时间复杂度:均为 $\mathcal{O}(1)$。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

5911. 模拟行走机器人 II【模拟详解】

分析

  • 题目:
  • 思路:
    • 直接按照题意模拟一遍。
    • 我们发现机器人只会绕着网格图的外圈走,因此可以先将多余的圈数去掉,直接取余。
    • 140/142测试点过不去,有两类错误
      • 第一种,没有考虑到余数为0的时候,方向可能没有转过来,还是之前的方向。比如饶了一圈回到(0,0),最开始是向右,但是此时正确答案应该向下。
      • 第二种,考虑了余数为0,但是直接对方向回退。在有的数据上,并不能直接回退方向,这个方向是固定的,可以写死。

QQ截图20211114002552.png

代码

class Robot {
public:
    int w, h;
    int x, y, d;
    string dir[4] = {"East", "North", "West", "South"};
    int dx[4]={1, 0, -1, 0};
    int dy[4]={0, 1, 0, -1};
    Robot(int width, int height) {
        w = width;
        h = height;
        x = 0, y = 0, d = 0;
    }
    
    void move(int num) {
        // 外一圈的步数,这里不等于周长,而是长宽减一后的周长,最好手动模拟走一下。
        // 也可以这样写 int cc = 2*(w-1 + h-1);
        int cc = 2*w + 2*h - 4;
        num %= cc;
        // 140/142没考虑到的测试点 num == 0
        if(!num && !x && !y) d = 3;
        while(num--) {
            int nx = dx[d] + x, ny = dy[d] + y;
            if(nx < 0 || nx >= w || ny < 0 || ny >= h) {
                // 如果越界了,就逆时针转90度,换到下一个方向继续走
                d++, d %= 4;
                nx = dx[d] + x, ny = dy[d] + y;
            }
            x = nx, y = ny;
        }
    }
    
    vector<int> getPos() {
        return  {x, y};
    }
    
    string getDir() {
        return dir[d];
    }
};

/**
 * Your Robot object will be instantiated and called as such:
 * Robot* obj = new Robot(width, height);
 * obj->move(num);
 * vector<int> param_2 = obj->getPos();
 * string param_3 = obj->getDir();
 */

每日一题-模拟行走机器人🟡

机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands

  • -2 :向左转 90
  • -1 :向右转 90
  • 1 <= x <= 9 :向前移动 x 个单位长度

在网格上有一些格子被视为障碍物 obstacles 。第 i 个障碍物位于网格点  obstacles[i] = (xi, yi)

机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,并继续执行下一个命令。

返回机器人距离原点的 最大欧式距离平方 。(即,如果距离为 5 ,则返回 25

 

注意:

  • 北方表示 +Y 方向。
  • 东方表示 +X 方向。
  • 南方表示 -Y 方向。
  • 西方表示 -X 方向。
  • 原点 [0,0] 可能会有障碍物。

 

示例 1:

输入:commands = [4,-1,3], obstacles = []
输出:25
解释:
机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 3 个单位,到达 (3, 4)
距离原点最远的是 (3, 4) ,距离为 32 + 42 = 25

示例 2:

输入:commands = [4,-1,4,-2,4], obstacles = [[2,4]]
输出:65
解释:机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 1 个单位,然后被位于 (2, 4) 的障碍物阻挡,机器人停在 (1, 4)
4. 左转
5. 向北走 4 个单位,到达 (1, 8)
距离原点最远的是 (1, 8) ,距离为 12 + 82 = 65

示例 3:

输入:commands = [6,-1,-1,6], obstacles = []
输出:36
解释:机器人开始位于 (0, 0):
1. 向北移动 6 个单位,到达 (0, 6).
2. 右转
3. 右转
4. 向南移动 6 个单位,到达 (0, 0).
机器人距离原点最远的点是 (0, 6),其距离的平方是 62 = 36 个单位。

提示:

  • 1 <= commands.length <= 104
  • commands[i] 的值可以取 -2-1 或者是范围 [1, 9] 内的一个整数。
  • 0 <= obstacles.length <= 104
  • -3 * 104 <= xi, yi <= 3 * 104
  • 答案保证小于 231

利用向量数组简化代码(Python/Java/C++/Go)

总体思路:模拟机器人行走的过程。一步一步走,如果下一步是障碍物,则停止移动,继续执行下一个命令。

怎么表示机器人移动的方向?

我们可以用一个向量数组

$$
\textit{dirs} = [(0, 1), (1, 0), (0, -1), (-1, 0)]
$$

分别表示顺时针的上右下左(北东南西)四个方向。

用一个下标 $k$ 表示当前机器人的方向为 $\textit{dirs}[k]$,初始 $k=0$,表示初始方向为上。

  • 右转:也就是顺时针转 $90^\circ$,把 $k$ 增加一。如果 $k=4$,则绕回到 $\textit{dirs}$ 数组的最左边,即 $k$ 更新为 $0$。我们可以把 $k$ 统一更新为 $(k+1)\bmod 4$,这样可以兼容 $k=3$ 加一后变成 $0$ 的情况。
  • 左转:也就是逆时针转 $90^\circ$,把 $k$ 减少一。如果 $k=0$,则绕回到 $\textit{dirs}$ 数组的最右边,即 $k$ 更新为 $3$。我们可以把 $k$ 统一更新为 $(k+3)\bmod 4$,这是因为 $\textit{dirs}$ 是个循环数组,一个元素的左边相邻元素,相当于往右数 $3$ 个元素。比如 $\textit{dirs}$ 中的 $(1,0)$ 往右数 $3$ 个元素,就是 $(0,1)$。

设 $c = \textit{commands}[i]$。当 $c>0$ 时,机器人要往 $\textit{dirs}[k]$ 方向移动 $c$ 个单位长度。一步一步移动,如果发现下一步是障碍物,则停止移动,继续执行下一个命令。

为了快速判断某个坐标是否为障碍物(是否在 $\textit{obstacles}$ 数组中),我们可以把 $\textit{obstacles}$ 转成哈希集合,判断坐标是否在哈希集合中。

:可能起点也有障碍物,这相当于机器人站在障碍物上,是可以继续移动的。

写法一

###py

DIRS = (0, 1), (1, 0), (0, -1), (-1, 0)  # 上右下左(顺时针)

class Solution:
    def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
        obstacle_set = set(map(tuple, obstacles))
        ans = x = y = k = 0
        for c in commands:
            if c == -1:  # 右转
                k = (k + 1) % 4
            elif c == -2:  # 左转
                k = (k + 3) % 4
            else:  # 直行
                while c > 0 and (x + DIRS[k][0], y + DIRS[k][1]) not in obstacle_set:
                    x += DIRS[k][0]
                    y += DIRS[k][1]
                    c -= 1
                ans = max(ans, x * x + y * y)
        return ans

###java

class Solution {
    private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 上右下左(顺时针)

    public int robotSim(int[] commands, int[][] obstacles) {
        HashSet<Integer> obstacleSet = new HashSet<>(obstacles.length, 1); // 预分配空间
        final int OFFSET = (int) 3e4;
        for (int[] p : obstacles) {
            // p 是两个 16 位整数,合并成一个 32 位整数
            obstacleSet.add((p[0] + OFFSET) << 16 | (p[1] + OFFSET));
        }

        int x = 0;
        int y = 0;
        int k = 0;
        int ans = 0;
        for (int c : commands) {
            if (c == -1) { // 右转
                k = (k + 1) % 4;
            } else if (c == -2) { // 左转
                k = (k + 3) % 4;
            } else { // 直行
                while (c-- > 0) {
                    int nx = x + DIRS[k][0];
                    int ny = y + DIRS[k][1];
                    if (obstacleSet.contains((nx + OFFSET) << 16 | (ny + OFFSET))) {
                        break;
                    }
                    x = nx;
                    y = ny;
                }
                ans = Math.max(ans, x * x + y * y);
            }
        }
        return ans;
    }
}

###cpp

class Solution {
    static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 上右下左(顺时针)

public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        unordered_set<int> obstacle_set;
        obstacle_set.reserve(obstacles.size()); // 预分配空间
        constexpr int OFFSET = 3e4;
        for (auto& p : obstacles) {
            // p 是两个 16 位整数,合并成一个 32 位整数
            obstacle_set.insert((p[0] + OFFSET) << 16 | (p[1] + OFFSET));
        }

        int ans = 0, x = 0, y = 0, k = 0;
        for (int c : commands) {
            if (c == -1) { // 右转
                k = (k + 1) % 4;
            } else if (c == -2) { // 左转
                k = (k + 3) % 4;
            } else { // 直行
                while (c--) {
                    int nx = x + DIRS[k][0];
                    int ny = y + DIRS[k][1];
                    if (obstacle_set.contains((nx + OFFSET) << 16 | (ny + OFFSET))) {
                        break;
                    }
                    x = nx;
                    y = ny;
                }
                ans = max(ans, x * x + y * y);
            }
        }
        return ans;
    }
};

###go

type pair struct{ x, y int }
var dirs = [...]pair{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 上右下左(顺时针)

func robotSim(commands []int, obstacles [][]int) (ans int) {
isObstacle := make(map[pair]bool, len(obstacles)) // 预分配空间
for _, p := range obstacles {
isObstacle[pair{p[0], p[1]}] = true
}

x, y, k := 0, 0, 0
for _, c := range commands {
if c == -1 { // 右转
k = (k + 1) % 4
} else if c == -2 { // 左转
k = (k + 3) % 4
} else { // 直行
for ; c > 0 && !isObstacle[pair{x + dirs[k].x, y + dirs[k].y}]; c-- {
x += dirs[k].x
y += dirs[k].y
}
ans = max(ans, x*x+y*y)
}
}
return
}

写法二

设 $c = \textit{commands}[i]$。

  • 右转时 $c=-1$,我们把 $k$ 增加了 $2c+3 = 1$。
  • 左转时 $c=-2$,我们把 $k$ 增加了 $2c+3 = -1$。

因此,这两种情况可以进一步统一成,把 $k$ 更新成 $(k + 2c + 3)\bmod 4$。但是,当 $k=0$ 且 $2c+3=-1$ 时,$k + 2c + 3=-1$ 是负数。对于模 $4$ 运算,多增加 $4$ 不影响结果,所以可以把 $2c+3$ 改成 $2c+7$,也就把 $k$ 更新成

$$
(k + 2c + 7)\bmod 4
$$

###py

DIRS = (0, 1), (1, 0), (0, -1), (-1, 0)  # 上右下左(顺时针)

class Solution:
    def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
        obstacle_set = set(map(tuple, obstacles))
        ans = x = y = k = 0
        for c in commands:
            if c < 0:
                k = (k + c * 2 + 7) % 4  # c=-2 左转,c=-1 右转
                continue
            while c > 0 and (x + DIRS[k][0], y + DIRS[k][1]) not in obstacle_set:
                x += DIRS[k][0]
                y += DIRS[k][1]
                c -= 1
            ans = max(ans, x * x + y * y)
        return ans

###java

class Solution {
    private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 上右下左(顺时针)

    public int robotSim(int[] commands, int[][] obstacles) {
        HashSet<Integer> obstacleSet = new HashSet<>(obstacles.length, 1); // 预分配空间
        final int OFFSET = (int) 3e4;
        for (int[] p : obstacles) {
            // p 是两个 16 位整数,合并成一个 32 位整数
            obstacleSet.add((p[0] + OFFSET) << 16 | (p[1] + OFFSET));
        }

        int x = 0;
        int y = 0;
        int k = 0;
        int ans = 0;
        for (int c : commands) {
            if (c < 0) {
                k = (k + c * 2 + 7) % 4; // c=-2 左转,c=-1 右转
                continue;
            }
            while (c-- > 0) {
                int nx = x + DIRS[k][0];
                int ny = y + DIRS[k][1];
                if (obstacleSet.contains((nx + OFFSET) << 16 | (ny + OFFSET))) {
                    break;
                }
                x = nx;
                y = ny;
            }
            ans = Math.max(ans, x * x + y * y);
        }
        return ans;
    }
}

###cpp

class Solution {
    static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 上右下左(顺时针)

public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        unordered_set<int> obstacle_set;
        obstacle_set.reserve(obstacles.size()); // 预分配空间
        constexpr int OFFSET = 3e4;
        for (auto& p : obstacles) {
            // p 是两个 16 位整数,合并成一个 32 位整数
            obstacle_set.insert((p[0] + OFFSET) << 16 | (p[1] + OFFSET));
        }

        int ans = 0, x = 0, y = 0, k = 0;
        for (int c : commands) {
            if (c < 0) {
                k = (k + c * 2 + 7) % 4; // c=-2 左转,c=-1 右转
                continue;
            }
            while (c--) {
                int nx = x + DIRS[k][0];
                int ny = y + DIRS[k][1];
                if (obstacle_set.contains((nx + OFFSET) << 16 | (ny + OFFSET))) {
                    break;
                }
                x = nx;
                y = ny;
            }
            ans = max(ans, x * x + y * y);
        }
        return ans;
    }
};

###go

type pair struct{ x, y int }
var dirs = [...]pair{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 上右下左(顺时针)

func robotSim(commands []int, obstacles [][]int) (ans int) {
isObstacle := make(map[pair]bool, len(obstacles)) // 预分配空间
for _, p := range obstacles {
isObstacle[pair{p[0], p[1]}] = true
}

x, y, k := 0, 0, 0
for _, c := range commands {
if c < 0 {
k = (k + c*2 + 7) % 4 // c=-2 左转,c=-1 右转
continue
}
for ; c > 0 && !isObstacle[pair{x + dirs[k].x, y + dirs[k].y}]; c-- {
x += dirs[k].x
y += dirs[k].y
}
ans = max(ans, x*x+y*y)
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nU + m)$,其中 $n$ 是 $\textit{commands}$ 的长度,$m$ 是 $\textit{obstacles}$ 的长度,$U=\max(\textit{commands})\le 9$。
  • 空间复杂度:$\mathcal{O}(m)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

[Python3/Java/C++/Go/TypeScript] 一题一解:哈希表 + 模拟

方法一:哈希表 + 模拟

我们定义一个长度为 $5$ 的方向数组 $dirs=[0, 1, 0, -1, 0]$,数组中的相邻两个元素表示一个方向。即 $(dirs[0], dirs[1])$ 表示向北,而 $(dirs[1], dirs[2])$ 表示向东,以此类推。

我们使用一个哈希表 $s$ 来存储所有障碍物的坐标,这样可以在 $O(1)$ 的时间内判断下一步是否会遇到障碍物。

另外,使用两个变量 $x$ 和 $y$ 来表示机器人当前所在的坐标,初始时 $x = y = 0$。变量 $k$ 表示机器人当前的方向,答案变量 $ans$ 表示机器人距离原点的最大欧式距离的平方。

接下来,我们遍历数组 $commands$ 中的每个元素 $c$:

  • 如果 $c = -2$,表示机器人向左转 $90$ 度,即 $k = (k + 3) \bmod 4$;
  • 如果 $c = -1$,表示机器人向右转 $90$ 度,即 $k = (k + 1) \bmod 4$;
  • 否则,表示机器人向前移动 $c$ 个单位长度。我们将机器人当前的方向 $k$ 与方向数组 $dirs$ 结合,即可得到机器人在 $x$ 轴和 $y$ 轴上的增量。我们将 $c$ 个单位长度的增量分别累加到 $x$ 和 $y$ 上,并判断每次移动后的新坐标 $(nx, ny)$ 是否在障碍物的坐标中,如果不在,则更新答案 $ans$,否则停止模拟,进行下一条指令的模拟。

最后返回答案 $ans$ 即可。

class Solution:
    def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
        dirs = (0, 1, 0, -1, 0)
        s = {(x, y) for x, y in obstacles}
        ans = k = 0
        x = y = 0
        for c in commands:
            if c == -2:
                k = (k + 3) % 4
            elif c == -1:
                k = (k + 1) % 4
            else:
                for _ in range(c):
                    nx, ny = x + dirs[k], y + dirs[k + 1]
                    if (nx, ny) in s:
                        break
                    x, y = nx, ny
                    ans = max(ans, x * x + y * y)
        return ans
class Solution {
    public int robotSim(int[] commands, int[][] obstacles) {
        int[] dirs = {0, 1, 0, -1, 0};
        Set<Integer> s = new HashSet<>(obstacles.length);
        for (var e : obstacles) {
            s.add(f(e[0], e[1]));
        }
        int ans = 0, k = 0;
        int x = 0, y = 0;
        for (int c : commands) {
            if (c == -2) {
                k = (k + 3) % 4;
            } else if (c == -1) {
                k = (k + 1) % 4;
            } else {
                while (c-- > 0) {
                    int nx = x + dirs[k], ny = y + dirs[k + 1];
                    if (s.contains(f(nx, ny))) {
                        break;
                    }
                    x = nx;
                    y = ny;
                    ans = Math.max(ans, x * x + y * y);
                }
            }
        }
        return ans;
    }

    private int f(int x, int y) {
        return x * 60010 + y;
    }
}
class Solution {
public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        int dirs[5] = {0, 1, 0, -1, 0};
        auto f = [](int x, int y) {
            return x * 60010 + y;
        };
        unordered_set<int> s;
        for (auto& e : obstacles) {
            s.insert(f(e[0], e[1]));
        }
        int ans = 0, k = 0;
        int x = 0, y = 0;
        for (int c : commands) {
            if (c == -2) {
                k = (k + 3) % 4;
            } else if (c == -1) {
                k = (k + 1) % 4;
            } else {
                while (c--) {
                    int nx = x + dirs[k], ny = y + dirs[k + 1];
                    if (s.count(f(nx, ny))) {
                        break;
                    }
                    x = nx;
                    y = ny;
                    ans = max(ans, x * x + y * y);
                }
            }
        }
        return ans;
    }
};
func robotSim(commands []int, obstacles [][]int) (ans int) {
dirs := [5]int{0, 1, 0, -1, 0}
type pair struct{ x, y int }
s := map[pair]bool{}
for _, e := range obstacles {
s[pair{e[0], e[1]}] = true
}
var x, y, k int
for _, c := range commands {
if c == -2 {
k = (k + 3) % 4
} else if c == -1 {
k = (k + 1) % 4
} else {
for ; c > 0 && !s[pair{x + dirs[k], y + dirs[k+1]}]; c-- {
x += dirs[k]
y += dirs[k+1]
ans = max(ans, x*x+y*y)
}
}
}
return
}

func max(a, b int) int {
if a > b {
return a
}
return b
}
function robotSim(commands: number[], obstacles: number[][]): number {
    const dirs = [0, 1, 0, -1, 0];
    const s: Set<number> = new Set();
    const f = (x: number, y: number) => x * 60010 + y;
    for (const [x, y] of obstacles) {
        s.add(f(x, y));
    }
    let [ans, x, y, k] = [0, 0, 0, 0];
    for (let c of commands) {
        if (c === -2) {
            k = (k + 3) % 4;
        } else if (c === -1) {
            k = (k + 1) % 4;
        } else {
            while (c-- > 0) {
                const [nx, ny] = [x + dirs[k], y + dirs[k + 1]];
                if (s.has(f(nx, ny))) {
                    break;
                }
                [x, y] = [nx, ny];
                ans = Math.max(ans, x * x + y * y);
            }
        }
    }
    return ans;
}

时间复杂度 $O(C \times n + m)$,空间复杂度 $O(m)$。其中 $C$ 表示每次可以移动的最大步数,而 $n$ 和 $m$ 分别表示数组 $commands$ 和数组 $obstacles$ 的长度。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

图解【模拟行走机器人】

先把题目意思搞明白

解释题目中示例 2 的意思

示例2

输入: commands = [4,-1,4,-2,4], obstacles = [[2,4]]
输出: 65
解释: 机器人在左转走到 (1, 8) 之前将被困在 (1, 4) 处

输入:commands 和 obstacles,其中 obstacles = [[2,4]] 的意思是坐标点(2,4)代表障碍物的坐标
输出:机器人所经过的每个坐标点(x,y)到原点的欧式距离的平方的最大值
欧式距离: $\sqrt {x^2+y^2}$
欧式距离的平方: ${x^2+y^2}$

模拟机器人行走图解.png

如上图所示:
机器人初始位置为坐标点(0,0),初始方向为向北

  1. 读取第一个指令为4,沿着当前方向“北”,向前走4个单位,停在坐标点(0,4)
  2. 读取第二个指令-1,该指令表示“向右转90度”,那么机器人就由原来的“北”右转90度之后方向变为“东”
  3. 读取第三个指令4,沿着当前方向“东”,向前走4个单位,但是发现坐标点(2,4)是一个障碍物,不能跨越障碍物,
    只能停留在障碍物前面一个单位,即坐标点(1,4)
  4. 读取第四个指令-2,该指令表示“向左转90度”,那么机器人就由原来的“东”左转90度之后方向变为“北”
  5. 读取第五个指令4,沿着当前方向“北”,向前走4个单位,停在坐标点(1,8)

65怎么得来的? 机器人所经过的这些点中,坐标点(1,8)计算出的欧式距离的平方最大,为 $1^2+8^2=65$

解题思路

参考官方题解

总体思想:模拟机器人行走过程,计算每一步坐标点到原点的欧式距离的平方,与保存的最大值比较,实时更新最大值
具体的

1.分解机器人行走

k步,就是朝着一个方向走k1
怎么朝着某个方向走出一步

  • 方向向北,机器人坐标点向上走一步
  • 方向向东,机器人坐标点向右走一步
  • 方向向南,机器人坐标点向下走一步
  • 方向向西,机器人坐标点向上左一步
int direx[] = {0,1,0,-1};
int direy[] = {1,0,-1,0};
direx[],direy[] 要竖着对齐看
    - 向北,坐标轴上x不动,y+1, 即(0,1)
    - 向东,坐标轴上x+1,y不动, 即(1,0)
    - 向南,坐标轴上x不动,y-1, 即(0,-1)
    - 向西,坐标轴上x-1,y不动, 即(-1,0)

( direx[i], direy[i] ),加上当前坐标后为 (curx,cury) + ( direx[i], direy[i] )

2.机器人如何调整方向

direx[]direy[] 的下标 i 代表了当前机器人的方向

  • i=0,向北
  • i=1,向东
  • i=2,向南
  • i=3,向西

当读取到调整方向的指令时,如

  • "-1":“向右转90度”,只要当前方向curdire + 1就可以得到右转方向
  • "-2":“向左转90度”,只要当前方向curdire + 3 就可以得到左转方向 (curdire + 3) % 4
    因为不管curdire当前是哪个方向,左转都在其左边,在direx数组的定义中顺势针数3个就是其左边,所以就是加3

3.怎么判断是否遇到了障碍物

障碍物有多个,所以需要有一个障碍物坐标点集合
机器人每试图走一个位置,就用此位置与障碍物集合列表里的坐标进行比较,看是否刚好是障碍物坐标点

  • 不是,则“真正走到这个点”,更新机器人坐标点(curx,cury)
  • 是障碍物,那么不走下一步,停留在当前,执行下一条命令

代码实现

参考官方题解,可以提交通过,注意注释

#include<utility>//pair的头文件
#include<set>//set的头文件
class Solution {
public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        int direx[] = {0,1,0,-1};
        int direy[] = {1,0,-1,0};
        int curx=0,cury=0;
        int curdire = 0;
        int comLen = commands.size();
        int ans = 0;
        set<pair<int, int>> obstacleSet;
        for(int i=0;i<obstacles.size();i++)
            obstacleSet.insert(make_pair(obstacles[i][0], obstacles[i][1]));

        for(int i=0;i<comLen;i++){
            if(commands[i] == -1){  // -1:向右转 90 度
                curdire = (curdire + 1) % 4;
            }else if(commands[i] == -2){  // -2:向左转 90 度
                 curdire = (curdire + 3) % 4;
            }else{  //  1 <= x <= 9:向前移动 x 个单位长度
                for(int j=0;j<commands[i];j++){
                    //试图走出一步,并判断是否遇到了障碍物,
                    int nx = curx + direx[curdire];
                    int ny = cury + direy[curdire];
                    //当前坐标不是障碍点,计算并与存储的最大欧式距离的平方做比较
                    if (obstacleSet.find(make_pair(nx, ny)) == obstacleSet.end()) {
                        curx = nx;
                        cury = ny;
                        ans = max(ans, curx*curx + cury*cury);
                    }else{
                        //是障碍点,被挡住了,停留,只能等待下一个指令,那可以跳出当前指令了
                        break;
                    }
                }
            }
        }
        return ans;
    }
};

注:
set 和 unordered_set 底层分别是用红黑树和哈希表实现的。
unordered_set 不能用来保存 pair<int, int>,但是 set 可以。
因为 unordered_set 是基于哈希的,而 C++ 并没有给 pair 事先写好哈希方法。
set 是基于比较的树结构,所以 pair 里的数据结构只要都支持比较就能储存。

每日一题-机器人能否返回原点🟢

在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束

移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。

如果机器人在完成所有动作后返回原点,则返回 true。否则,返回 false

注意:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右移动一次,“L” 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。

 

示例 1:

输入: moves = "UD"
输出: true
解释:机器人向上移动一次,然后向下移动一次。所有动作都具有相同的幅度,因此它最终回到它开始的原点。因此,我们返回 true。

示例 2:

输入: moves = "LL"
输出: false
解释:机器人向左移动两次。它最终位于原点的左侧,距原点有两次 “移动” 的距离。我们返回 false,因为它在移动结束时没有返回原点。

 

提示:

  • 1 <= moves.length <= 2 * 104
  • moves 只包含字符 'U''D''L' 和 'R'

简单题,简单做(Python/Java/C++/C/Go/JS/Rust)

机器人的横坐标,等于向右移动的次数,减去向左移动的次数。如果 $\texttt{R}$ 的个数等于 $\texttt{L}$ 的个数,那么最终横坐标为 $0$。

机器人的纵坐标,等于向上移动的次数,减去向下移动的次数。如果 $\texttt{U}$ 的个数等于 $\texttt{D}$ 的个数,那么最终纵坐标为 $0$。

这两个条件同时成立,才能回到原点。

###py

class Solution:
    def judgeCircle(self, moves: str) -> bool:
        return moves.count('R') == moves.count('L') and \
               moves.count('U') == moves.count('D')

###py

class Solution:
    def judgeCircle(self, moves: str) -> bool:
        cnt = Counter(moves)
        return cnt['R'] == cnt['L'] and cnt['U'] == cnt['D']

###java

class Solution {
    public boolean judgeCircle(String moves) {
        int x = 0;
        int y = 0;
        for (char move : moves.toCharArray()) {
            if (move == 'R') {
                x++;
            } else if (move == 'L') {
                x--;
            } else if (move == 'U') {
                y++;
            } else {
                y--;
            }
        }
        return x == 0 && y == 0;
    }
}

###cpp

class Solution {
public:
    bool judgeCircle(string moves) {
        return ranges::count(moves, 'R') == ranges::count(moves, 'L') &&
               ranges::count(moves, 'U') == ranges::count(moves, 'D');
    }
};

###c

bool judgeCircle(char* moves) {
    int x = 0, y = 0;
    for (int i = 0; moves[i]; i++) {
        char move = moves[i];
        if (move == 'R') {
            x++;
        } else if (move == 'L') {
            x--;
        } else if (move == 'U') {
            y++;
        } else {
            y--;
        }
    }
    return x == 0 && y == 0;
}

###go

func judgeCircle(moves string) bool {
    return strings.Count(moves, "R") == strings.Count(moves, "L") &&
           strings.Count(moves, "U") == strings.Count(moves, "D")
}

###js

var judgeCircle = function(moves) {
    const cnt = _.countBy(moves);
    return cnt['R'] === cnt['L'] && cnt['U'] === cnt['D'];
};

###rust

impl Solution {
    pub fn judge_circle(moves: String) -> bool {
        moves.matches('R').count() == moves.matches('L').count() &&
        moves.matches('U').count() == moves.matches('D').count()
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{moves}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

三行代码搞定!还能有人比我短?

模拟

最直白的思路非模拟莫属。
设机器人的坐标为 (x, y)。
显然初始时,机器人在原点,即 x = 0, y = 0。
然后遍历整个字符串 moves,根据具体的方向更新 x 或 y。
最后判断 x, y 是否均为 0,即是否又回到了原点。

###cpp

class Solution {
public:
    bool judgeCircle(string moves) {
        int x = 0, y = 0;
        for (auto move: moves) {
            switch (move) {
                case 'U': y--; break;
                case 'D': y++; break;
                case 'L': x--; break;
                case 'R': x++; break;
            }
        }
        return x == 0 && y == 0;
    }
};

统计字符数量

让我们先把问题简化为一维问题,即机器人只在 X 轴上移动。
假设机器人朝着正方向移动了 R 次,朝着负方向移动了 L 次。
无论这 L+R 次如何排列,最后的机器人在 X 轴上的坐标必为 R - L。即当 R == L 时,机器人才能回到 0 处。

同理,在二维平面上,分别向上下左右四个方向移动了,U,D,L,R 次,当且仅当 L == R 且 U == D 时,机器人才能回到原点。

那么问题,就变成了统计 moves 里各个字符出现的次数。三行搞定 ~

###cpp

class Solution {
 public:
  bool judgeCircle(const string &moves) {
    std::unordered_map<char, int> cnt;
    std::for_each(moves.begin(), moves.end(), [&cnt](char c) { cnt[c]++; });
    return cnt['U'] == cnt['D'] && cnt['L'] == cnt['R'];
  }
};

如果感觉有点意思,那就关注一下【我的公众号】吧~

image.png


看了评论区老铁们的花式短代码,感觉熟练掌握 STL 属实能提高生产力。
推荐一本 《C++ 标准库》,关注公众号,回复 "CPP标准库(第二版)" 即可获取下载方式。

网格图中机器人回家的最小代价

方法一:贪心

提示 $1$

如果在某一条路径中,相邻的两步分别为横向(左/右)和纵向(上/下)移动,那么交换这两步前后,路径的总代价不变。

提示 $1$ 解释

由于路径的其它部分不会改变,对应部分的代价也不会改变,因此我们只需要考虑交换的两步。不妨假设在这两步的过程中,机器人从 $(r, c)$ 移动到了 $(r + 1, c + 1)$。

考虑交换前后两种不同的移动方式(用 $\rightarrow$ 表示沿着某个方向一直移动,下同):

  • $(r, c) \rightarrow (r + 1, c) \rightarrow (r + 1, c + 1)$:第一步移动到 $r + 1$ 行,代价为 $\textit{rowCost}[r + 1]$;第二步移动到 $c + 1$ 列,代价为 $\textit{colCost}[c + 1]$。总代价为 $\textit{rowCost}[r + 1] + \textit{colCost}[c + 1]$。

  • $(r, c) \rightarrow (r, c + 1) \rightarrow (r + 1, c + 1)$:第一步移动到 $c + 1$ 列,代价为 $\textit{colCost}[c + 1]$;第二步移动到 $r + 1$ 行,代价为 $\textit{rowCost}[r + 1]$。总代价为 $\textit{colCost}[c + 1] + \textit{rowCost}[r + 1]$。

可以发现,这两种方式代价相同。因此,路径的总代价也不会改变。

提示 $2$

如果某一条路径中包含相反操作(即同时含有向左和向右的操作,或同时含有向上和向下的操作),那么这条路径的代价一定不优于将这些操作成对抵消后的路径。

除此之外,任意不包含任何相反操作的路径对应的总代价一定最小。

提示 $2$ 解释

我们首先考虑前半部分。

不失一般性地,首先考虑从 $(r, c)$ 到 $(r + x, c) (x \ge 0)$ 的两种路径。一种路径为 $(r, c) \rightarrow (r + x, c)$,另一种路径为 $(r, c) \rightarrow (r, c + 1) \rightarrow (r + x, c + 1) \rightarrow (r + x, c)$。计算可得,后者相对于前者多出了 $\textit{colCost}[c] + \textit{colCost}[c + 1] \ge 0$ 的总代价,亦即前者一定更优。

而对于一般的存在相反方向操作的路径,其中必定包含上述的路径片段;而将路径片段中的相反操作抵消后,新的路径在总代价上一定不高于原路径。因此,我们可以递归地抵消这些相反操作,直至路径不包含任何相反操作,同时在每次操作时,总代价一定不会增加。

综上可知,对于任意包含相反操作的路径,一定存在一个不包含相反操作的路径,后者的总代价小于等于前者。因此,最小总代价对应的路径一定是不包含相反操作的路径。

而对于所有的这些不包含任何相反操作的路径,这些路径一定是由一些(数量可能为 $0$)单方向的横向操作和一些(数量可能为 $0$)单方向的纵向操作组成。根据 提示 $1$,我们可以任意交换这些操作,且总代价不变。因此,任意不包含任何相反操作的路径对应的总代价一定最小。

思路与算法

根据 提示 $2$,我们只需要构造任意一条从起点到家的不包含相反操作的路径,该路径对应的总代价即为最小总代价。

为了方便计算,我们先让机器人向上或向下移动至家所在行,再让机器人向左或向右移动至家所在的格子,并在这过程中计算总代价。

而对于如何确定移动的方向,我们行间的上下移动为例:我们比较机器人所在行号 $r_1$ 与家所在行号 $r_2$,如果 $r_1 < r_2$,则我们需要向下移动;如果 $r_1 > r_2$,则我们需要向上移动;如果 $r_1 = r_2$,则我们无需移动。

最终,我们返回该总代价作为答案。

代码

###C++

class Solution {
public:
    int minCost(vector<int>& startPos, vector<int>& homePos, vector<int>& rowCosts, vector<int>& colCosts) {
        int r1 = startPos[0], c1 = startPos[1];
        int r2 = homePos[0], c2 = homePos[1];
        int res = 0;   // 总代价
        // 移动至家所在行,判断行间移动方向并计算对应代价
        if (r2 >= r1){
            res += accumulate(rowCosts.begin() + r1 + 1, rowCosts.begin() + r2 + 1, 0);
        }
        else{
            res += accumulate(rowCosts.begin() + r2, rowCosts.begin() + r1, 0);
        }
        // 移动至家所在位置,判断列间移动方向并计算对应代价
        if (c2 >= c1){
            res += accumulate(colCosts.begin() + c1 + 1, colCosts.begin() + c2 + 1, 0);
        }
        else{
            res += accumulate(colCosts.begin() + c2, colCosts.begin() + c1, 0);
        }
        return res;
    }
};

###Python

class Solution:
    def minCost(self, startPos: List[int], homePos: List[int], rowCosts: List[int], colCosts: List[int]) -> int:
        r1, c1 = startPos[0], startPos[1]
        r2, c2 = homePos[0], homePos[1]
        res = 0   # 总代价
        # 移动至家所在行,判断行间移动方向并计算对应代价
        if r2 >= r1:
            for i in range(r1 + 1, r2 + 1):
                res += rowCosts[i]
        else:
            for i in range(r2, r1):
                res += rowCosts[i]
        # 移动至家所在位置,判断列间移动方向并计算对应代价
        if c2 >= c1:
            for i in range(c1 + 1, c2 + 1):
                res += colCosts[i]
        else:
            for i in range(c2, c1):
                res += colCosts[i]
        return res

###Java

class Solution {
    public int minCost(int[] startPos, int[] homePos, int[] rowCosts, int[] colCosts) {
        int r1 = startPos[0], c1 = startPos[1];
        int r2 = homePos[0], c2 = homePos[1];
        int res = 0;   // 总代价
        
        // 移动至家所在行,判断行间移动方向并计算对应代价
        if (r2 >= r1) {
            for (int i = r1 + 1; i <= r2; i++) {
                res += rowCosts[i];
            }
        } else {
            for (int i = r2; i < r1; i++) {
                res += rowCosts[i];
            }
        }
        
        // 移动至家所在位置,判断列间移动方向并计算对应代价
        if (c2 >= c1) {
            for (int i = c1 + 1; i <= c2; i++) {
                res += colCosts[i];
            }
        } else {
            for (int i = c2; i < c1; i++) {
                res += colCosts[i];
            }
        }
        
        return res;
    }
}

###C#

public class Solution {
    public int MinCost(int[] startPos, int[] homePos, int[] rowCosts, int[] colCosts) {
        int r1 = startPos[0], c1 = startPos[1];
        int r2 = homePos[0], c2 = homePos[1];
        int res = 0;   // 总代价
        
        // 移动至家所在行,判断行间移动方向并计算对应代价
        if (r2 >= r1) {
            for (int i = r1 + 1; i <= r2; i++) {
                res += rowCosts[i];
            }
        } else {
            for (int i = r2; i < r1; i++) {
                res += rowCosts[i];
            }
        }
        
        // 移动至家所在位置,判断列间移动方向并计算对应代价
        if (c2 >= c1) {
            for (int i = c1 + 1; i <= c2; i++) {
                res += colCosts[i];
            }
        } else {
            for (int i = c2; i < c1; i++) {
                res += colCosts[i];
            }
        }
        
        return res;
    }
}

###Go

func minCost(startPos []int, homePos []int, rowCosts []int, colCosts []int) int {
    r1, c1 := startPos[0], startPos[1]
    r2, c2 := homePos[0], homePos[1]
    res := 0 // 总代价
    
    // 移动至家所在行,判断行间移动方向并计算对应代价
    if r2 >= r1 {
        for i := r1 + 1; i <= r2; i++ {
            res += rowCosts[i]
        }
    } else {
        for i := r2; i < r1; i++ {
            res += rowCosts[i]
        }
    }
    
    // 移动至家所在位置,判断列间移动方向并计算对应代价
    if c2 >= c1 {
        for i := c1 + 1; i <= c2; i++ {
            res += colCosts[i]
        }
    } else {
        for i := c2; i < c1; i++ {
            res += colCosts[i]
        }
    }
    
    return res
}

###C

int minCost(int* startPos, int startPosSize, int* homePos, int homePosSize, 
            int* rowCosts, int rowCostsSize, int* colCosts, int colCostsSize) {
    int r1 = startPos[0], c1 = startPos[1];
    int r2 = homePos[0], c2 = homePos[1];
    int res = 0;   // 总代价
    
    // 移动至家所在行,判断行间移动方向并计算对应代价
    if (r2 >= r1) {
        for (int i = r1 + 1; i <= r2; i++) {
            res += rowCosts[i];
        }
    } else {
        for (int i = r2; i < r1; i++) {
            res += rowCosts[i];
        }
    }
    
    // 移动至家所在位置,判断列间移动方向并计算对应代价
    if (c2 >= c1) {
        for (int i = c1 + 1; i <= c2; i++) {
            res += colCosts[i];
        }
    } else {
        for (int i = c2; i < c1; i++) {
            res += colCosts[i];
        }
    }
    
    return res;
}

###JavaScript

var minCost = function(startPos, homePos, rowCosts, colCosts) {
    const r1 = startPos[0], c1 = startPos[1];
    const r2 = homePos[0], c2 = homePos[1];
    let res = 0;   // 总代价
    
    // 移动至家所在行,判断行间移动方向并计算对应代价
    if (r2 >= r1) {
        for (let i = r1 + 1; i <= r2; i++) {
            res += rowCosts[i];
        }
    } else {
        for (let i = r2; i < r1; i++) {
            res += rowCosts[i];
        }
    }
    
    // 移动至家所在位置,判断列间移动方向并计算对应代价
    if (c2 >= c1) {
        for (let i = c1 + 1; i <= c2; i++) {
            res += colCosts[i];
        }
    } else {
        for (let i = c2; i < c1; i++) {
            res += colCosts[i];
        }
    }
    
    return res;
};

###TypeScript

function minCost(startPos: number[], homePos: number[], rowCosts: number[], colCosts: number[]): number {
    const r1 = startPos[0], c1 = startPos[1];
    const r2 = homePos[0], c2 = homePos[1];
    let res = 0;   // 总代价
    
    // 移动至家所在行,判断行间移动方向并计算对应代价
    if (r2 >= r1) {
        for (let i = r1 + 1; i <= r2; i++) {
            res += rowCosts[i];
        }
    } else {
        for (let i = r2; i < r1; i++) {
            res += rowCosts[i];
        }
    }
    
    // 移动至家所在位置,判断列间移动方向并计算对应代价
    if (c2 >= c1) {
        for (let i = c1 + 1; i <= c2; i++) {
            res += colCosts[i];
        }
    } else {
        for (let i = c2; i < c1; i++) {
            res += colCosts[i];
        }
    }
    
    return res;
}

###Rust

impl Solution {
    pub fn min_cost(start_pos: Vec<i32>, home_pos: Vec<i32>, row_costs: Vec<i32>, col_costs: Vec<i32>) -> i32 {
        let r1 = start_pos[0] as usize;
        let c1 = start_pos[1] as usize;
        let r2 = home_pos[0] as usize;
        let c2 = home_pos[1] as usize;
        let mut res = 0;   // 总代价
        
        // 移动至家所在行,判断行间移动方向并计算对应代价
        if r2 >= r1 {
            for i in (r1 + 1)..=r2 {
                res += row_costs[i];
            }
        } else {
            for i in r2..r1 {
                res += row_costs[i];
            }
        }
        
        // 移动至家所在位置,判断列间移动方向并计算对应代价
        if c2 >= c1 {
            for i in (c1 + 1)..=c2 {
                res += col_costs[i];
            }
        } else {
            for i in c2..c1 {
                res += col_costs[i];
            }
        }
        
        res
    }
}

复杂度分析

  • 时间复杂度:$O(m + n)$,其中 $m$ 为网格图的行数,$n$ 为网格图的列数。即为计算最小代价的时间复杂度。

  • 空间复杂度:$O(1)$。

每日一题-网格图中机器人回家的最小代价🟡

给你一个 m x n 的网格图,其中 (0, 0) 是最左上角的格子,(m - 1, n - 1) 是最右下角的格子。给你一个整数数组 startPos ,startPos = [startrow, startcol] 表示 初始 有一个 机器人 在格子 (startrow, startcol) 处。同时给你一个整数数组 homePos ,homePos = [homerow, homecol] 表示机器人的  在格子 (homerow, homecol) 处。

机器人需要回家。每一步它可以往四个方向移动:,同时机器人不能移出边界。每一步移动都有一定代价。再给你两个下标从 0 开始的额整数数组:长度为 m 的数组 rowCosts  和长度为 n 的数组 colCosts 。

  • 如果机器人往  或者往  移动到第 r  的格子,那么代价为 rowCosts[r] 。
  • 如果机器人往  或者往  移动到第 c  的格子,那么代价为 colCosts[c] 。

请你返回机器人回家需要的 最小总代价 。

 

示例 1:

输入:startPos = [1, 0], homePos = [2, 3], rowCosts = [5, 4, 3], colCosts = [8, 2, 6, 7]
输出:18
解释:一个最优路径为:
从 (1, 0) 开始
-> 往下走到 (2, 0) 。代价为 rowCosts[2] = 3 。
-> 往右走到 (2, 1) 。代价为 colCosts[1] = 2 。
-> 往右走到 (2, 2) 。代价为 colCosts[2] = 6 。
-> 往右走到 (2, 3) 。代价为 colCosts[3] = 7 。
总代价为 3 + 2 + 6 + 7 = 18

示例 2:

输入:startPos = [0, 0], homePos = [0, 0], rowCosts = [5], colCosts = [26]
输出:0
解释:机器人已经在家了,所以不需要移动。总代价为 0 。

 

提示:

  • m == rowCosts.length
  • n == colCosts.length
  • 1 <= m, n <= 105
  • 0 <= rowCosts[r], colCosts[c] <= 104
  • startPos.length == 2
  • homePos.length == 2
  • 0 <= startrow, homerow < m
  • 0 <= startcol, homecol < n

中等题的简单解法(emo了)

一看到题目第一感觉是用dp,但是我dp一点都没看过呀,觉得一定做不出来,遂准备放弃。突然灵光一闪,好像只要讨论四个方向的情况回家就行(以startPos为原点)😂
这里是以homePos坐标减去startPos坐标得到:

  • 第一象限:row<0, col >0;
  • 第二象限:row<0, col <0;
  • 第三象限:row>0, col <0;
  • 第四象限:row>0, col >0;
/**
 * @param {number[]} startPos
 * @param {number[]} homePos
 * @param {number[]} rowCosts
 * @param {number[]} colCosts
 * @return {number}
 */
var minCost = function(startPos, homePos, rowCosts, colCosts) {
    let res = 0;
    if (startPos[0] === homePos[0] && startPos[1] === homePos[1]) {
        return 0;
    }
    
    let row = homePos[0] - startPos[0]; // 行差
    let col = homePos[1] - startPos[1]; // 列差
    if (row >= 0 && col >= 0) {
        for (let i = 0; i < row; i++) {
            res += rowCosts[startPos[0] + i + 1];
        }
        for (let j = 0; j < col; j++) {
            res += colCosts[startPos[1] + j + 1];
        }
    } else if (row >= 0 && col < 0) {
        for (let i = 0; i < row; i++) {
            res += rowCosts[startPos[0] + i + 1];
        }
        for (let j = 0; j < Math.abs(col); j++) {
            res += colCosts[startPos[1] - j -1];
        }
    } else if (row < 0 && col >= 0) {
        for (let i = 0; i < Math.abs(row); i++) {
            res += rowCosts[startPos[0] - i - 1];
        }
        for (let j = 0; j < col; j++) {
            res += colCosts[startPos[1] + j + 1];
        }
    } else {
        for (let i = 0; i < Math.abs(row); i++) {
            res += rowCosts[startPos[0] - i - 1];
        }
        for (let j = 0; j < Math.abs(col); j++) {
            res += colCosts[startPos[1] - j -1];
        }
    }
    
    return res;
};
❌