1 总结
1.1 方向数组的二维表示方法
- 定义方向数组(一般设定的方向比较灵活,比如(-1,0), (1,0)可以表示向上和向下移动也可以相反,看题目需要改成最易懂的样子即可):可以使用二维数组
dirs
来表示四个方向(上、右、下、左),例如:
这里,int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
{-1, 0}
表示向上移动,{0, 1}
表示向右移动,{1, 0}
表示向下移动,{0, -1}
表示向左移动。
1.2 方向数组的一维表示方法
- 一维数组表示:另一种方法是使用一维数组结合模运算来表示方向。例如,可以定义一个一维数组
dx
和dy
,分别表示 x 轴和 y 轴的变化:
这里的数组同样表示上、右、下、左四个方向。int[] dx = {-1, 0, 1, 0}; int[] dy = {0, 1, 0, -1};
1.3 旋转的处理
-
初始化方向:假设机器人最初面向北方,可以用一个变量
d
来表示当前方向,初始设为1
(表示向右)。 -
右旋转 90 度:向右旋转意味着
d
的值增加 1。可以使用模运算来确保d
的值不超过方向数组的长度:d = (d + 1) % 4;
-
左旋转 90 度:向左旋转意味着
d
的值减少 1。需要处理负数的情况,以确保d
仍然是有效的索引:d = (d + 3) % 4; // 或 d = (d - 1 + 4) % 4;
-
旋转 180 度:旋转 180 度意味着
d
的值增加或减少 2:d = (d + 2) % 4;
1.4 应用示例
-
结合示例:您可以提供一个简单的例子来说明如何使用这些方向数组和旋转操作来控制机器人在二维网格上的移动。
-
图解辅助:辅以图表来说明方向的改变,会使读者更容易理解。
这篇文章不仅对理解相关的编程题目有帮助,而且也能够加深对方向控制和数组操作的理解。
2 岛屿系列问题
利用1.1和1.2中的行走表示法就可以解决
3 旋转系列问题
3.1 第一题
3.1.1 LC1041. 困于环中的机器人
class Solution {
public boolean isRobotBounded(String instructions) {
int[][] direc = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int direcIndex = 0;
int x = 0, y = 0;
int n = instructions.length();
for (int idx = 0; idx < n; idx++) {
char instruction = instructions.charAt(idx);
if (instruction == 'G') {
x += direc[direcIndex][0];
y += direc[direcIndex][1];
} else if (instruction == 'L') {
direcIndex += 3;
direcIndex %= 4;
} else {
direcIndex++;
direcIndex %= 4;
}
}
return direcIndex != 0 || (x == 0 && y == 0);
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/robot-bounded-in-circle/solutions/2217873/kun-yu-huan-zhong-de-ji-qi-ren-by-leetco-kjya/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.1.2 改编题:LC1041. 困于环中的机器人: 原题中的一条字符串指令是原子花执行完之后看是否有环,但是现在认为"每一个instruction指令不再原子化执行,而是每一个instruction中的字符原子化执行", 请问在无限执行instruction中,是否会碰到环?
class Solution {
public boolean isRobotBounded(String instructions) {
HashMap<Integer,Set<Integer>>mp=new HashMap<>();
mp.put(0,new HashSet<Integer>());
mp.get(0).add(0);
int x=0,y=0,d=1;
int[][]dirs=new int[][]{{-1,0},{0,1},{1,0},{0,-1}};
int n=instructions.length()*4;
StringBuilder sb=new StringBuilder();
for(int i=0;i<4;i++){
sb.append(instructions);
}
String ni=sb.toString();
for(int i=0;i<n;i++){
char c=ni.charAt(i);
if(c=='L'){
d=(d+3)%4;
}else if(c=='R'){
d=(d+1)%4;
}else{
x+=dirs[d][0];
y+=dirs[d][1];
if(mp.containsKey(x)){
if(mp.get(x).contains(y)){
System.out.println("i:"+i+", x:"+x+", y:"+y);
return true;
}else{
mp.get(x).add(y);
}
}else{
mp.put(x,new HashSet<Integer>());
mp.get(x).add(y);
}
}
}
return false;
}
}
3.2 LC874. 模拟行走机器人
3.2.1 原题答案
1 代码
public int robotSim(int[] commands, int[][] obstacles) {
int[][]dirs=new int[][]{{-1,0},{0,1},{1,0},{0,-1}};
int x=0,y=0,d=1;
HashSet<Integer>set=new HashSet<>();
for(int i=0;i<obstacles.length;i++){
set.add(obstacles[i][0]*60002+obstacles[i][1]);
}
int ans=0;
for(int i=0;i<commands.length;i++){
if(commands[i]<0){
if(commands[i]==-2){
d=(d+3)%4;
}else if(commands[i]==-1){
d=(d+1)%4;
}
}else{
for(int j=0;j<commands[i];j++){
if(set.contains((x+dirs[d][0])*60002+y+dirs[d][1]))break;
x+=dirs[d][0];
y+=dirs[d][1];
ans=Math.max(ans,x*x+y*y);
}
}
}
return ans;
}
2 Q1: “set.add(obstacles[i][0]*60002+obstacles[i][1]);中, 60001 怎么来的?10001就不行“
obstacle的坐标范围是正负3w。如果10001, (1, 0)和(0, 10001)就会撞到一起,但是如果乘以一个比6w严格大的数,那么每一个二维坐标被一个一维度的id标识时能保证唯一性
3 Q1: 机器人如何以最快的速度知道自己能走的最大步子, 而不是走一步看一步?
3.2.2 在3.2.1中规定机器人一次性走的步子最大是9, 那么可以把所有的障碍点都存到set中,然后枚举每一步,查看是否撞倒了障碍点,这样复杂度还是不高,假如现在机器人每一次走的最大步子是1000,如何让机器人以最小的复杂度知道自己可以向前走多少步呢?
1 思路一:可以用两个二维set存储所有的坐标,一个存储所有横轴上的障碍物,另一个存储纵轴,遍历当前方向上的哈希表,判断是否有在[from, to]的中间值,有则直接截断
class Solution {
private:
// 1右 -1左
vector<pair<int, int>> MOVE = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
unordered_map<int, set<int>> row;
unordered_map<int, set<int>> col;
for (auto&& arr : obstacles) {
col[arr[0]].insert(arr[1]);
row[arr[1]].insert(arr[0]);
}
int ans = 0;
// 起始方向是北
int pos = 0;
int x = 0, y = 0;
for (int num : commands) {
// 向右
if (num == -1) {
pos = (pos + 1) % 4;
}
// 向左
else if (num == -2) {
pos = (pos - 1 + 4) % 4;
}
// move
else {
auto [movX, movY] = MOVE[pos];
// set的参数分分清除
// y轴方向移动
if (movY) {
y = is_between(col[x], y, y + num * movY, movY);
}
// x轴方向移动
else {
x = is_between(row[y], x, x + num * movX, movX);
}
}
ans = max(ans, x * x + y * y);
}
return ans;
}
int is_between(set<int>& st, int from, int to, int flag = 1) {
// lower 和 upper 的使用注意
// 正向
if (flag > 0) {
auto it = st.upper_bound(from);
if (it == st.end()) {
return to;
} else if (*it > to) {
return to;
} else {
return *it - 1;
}
}
// 逆向
else {
if (st.size() == 0) {
return to;
}
auto it = st.lower_bound(from);
it--;
if (it == st.end()) {
return to;
} else if (*it > from) {
return to;
} else if (*it < to) {
return to;
} else {
return *it + 1;
}
}
}
};
2 思路二:(通过了100%的case)
可以用两个二维treeset存储所有的坐标,一个存储所有横轴上的有序障碍物,另一个存储纵轴,利用二分查找前方距离自己最近的障碍物,看看自己当前可以最多向前移动多少步。
二分查找时需要注意floor,ceil,lower和higher这几个函数的区别:
在使用 Java 的 TreeMap
和 TreeSet
进行二分查找时,确实需要注意 floor
, ceil
, lower
和 higher
这几个函数的区别。它们都是用来在有序集合中查找特定元素的,但行为略有不同:
对于 TreeMap
:
- floorKey(K key): 返回小于或等于给定键的最大键。
- ceilingKey(K key): 返回大于或等于给定键的最小键。
- lowerKey(K key): 返回严格小于给定键的最大键。
- higherKey(K key): 返回严格大于给定键的最小键。
对于 TreeSet
:
- floor(E e): 返回小于或等于给定元素的最大元素。
- ceiling(E e): 返回大于或等于给定元素的最小元素。
- lower(E e): 返回严格小于给定元素的最大元素。
- higher(E e): 返回严格大于给定元素的最小元素。
注意事项:
-
等于情况的处理:
floor
和ceiling
方法包含等于的情况,而lower
和higher
方法则不包括等于的情况。 -
返回值的类型:在
TreeMap
中,floorKey
,ceilingKey
,lowerKey
, 和higherKey
返回键的值;而在TreeSet
中,floor
,ceiling
,lower
, 和higher
返回元素本身。 -
使用场景:根据您的查找需求(是否包括等于的情况,查找的是键还是元素),选择合适的方法。
在进行二分查找或者需要快速找到最接近的元素时,合理利用这些方法可以大大提高效率和代码的可读性。
代码:
import java.util.*;
class Solution {
public int robotSim(int[] commands, int[][] obstacles) {
// 横轴和纵轴上的有序障碍物集合
TreeMap<Integer, TreeSet<Integer>> rowMap = new TreeMap<>();
TreeMap<Integer, TreeSet<Integer>> colMap = new TreeMap<>();
// 初始化障碍物集合
for (int[] obstacle : obstacles) {
int x = obstacle[0], y = obstacle[1];
rowMap.putIfAbsent(x, new TreeSet<>());
colMap.putIfAbsent(y, new TreeSet<>());
rowMap.get(x).add(y);
colMap.get(y).add(x);
}
int x = 0, y = 0, direction = 0; // 初始坐标和方向(北)
int[][] moves = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 北、东、南、西
int maxDistance = 0;
for (int cmd : commands) {
if (cmd == -1) { // 右转
direction = (direction + 1) % 4;
} else if (cmd == -2) { // 左转
direction = (direction + 3) % 4;
} else { // 移动
int dx = moves[direction][0];
int dy = moves[direction][1];
if (dx != 0) {
x = getNextPosition(colMap, x, y, dx, cmd);
} else {
y = getNextPosition(rowMap, y, x, dy, cmd);
}
maxDistance = Math.max(maxDistance, x * x + y * y);
}
}
return maxDistance;
}
private int getNextPosition(TreeMap<Integer, TreeSet<Integer>> map, int pos, int fixedPos, int delta, int steps) {
TreeSet<Integer> set = map.getOrDefault(fixedPos, new TreeSet<>());
if (delta > 0) {
Integer higher = set.higher(pos);
if (higher != null) {
return Math.min(pos + steps, higher - 1);
}
} else {
Integer lower = set.lower(pos);
if (lower != null) {
return Math.max(pos - steps, lower + 1);
}
}
return pos + steps * delta;
}
}