From b4b9c22c3cd1667d0bb70244b123f470536bcddf Mon Sep 17 00:00:00 2001 From: yanglbme Date: Fri, 28 Jul 2023 15:30:12 +0800 Subject: [PATCH] feat: add solutions to lc problem: No.0353 No.0353.Design Snake Game --- .../0353.Design Snake Game/README.md | 315 +++++++++++++++++- .../0353.Design Snake Game/README_EN.md | 297 +++++++++++++++++ .../0353.Design Snake Game/Solution.cpp | 64 ++++ .../0353.Design Snake Game/Solution.go | 55 +++ .../0353.Design Snake Game/Solution.java | 59 ++++ .../0353.Design Snake Game/Solution.py | 43 +++ .../0353.Design Snake Game/Solution.ts | 63 ++++ 7 files changed, 895 insertions(+), 1 deletion(-) create mode 100644 solution/0300-0399/0353.Design Snake Game/Solution.cpp create mode 100644 solution/0300-0399/0353.Design Snake Game/Solution.go create mode 100644 solution/0300-0399/0353.Design Snake Game/Solution.java create mode 100644 solution/0300-0399/0353.Design Snake Game/Solution.py create mode 100644 solution/0300-0399/0353.Design Snake Game/Solution.ts diff --git a/solution/0300-0399/0353.Design Snake Game/README.md b/solution/0300-0399/0353.Design Snake Game/README.md index 086b767aa14b1..6141fbce8ea5d 100644 --- a/solution/0300-0399/0353.Design Snake Game/README.md +++ b/solution/0300-0399/0353.Design Snake Game/README.md @@ -65,6 +65,22 @@ snakeGame.move("U"); // 返回 -1 ,蛇与边界相撞,游戏结束 +**方法一:双端队列模拟** + +我们可以使用双端队列来模拟蛇的移动。 + +定义一个双端队列 $q$,其中保存蛇的身体坐标,队头为蛇头,队尾为蛇尾。同时使用一个集合 $vis$ 来保存蛇的身体坐标,用于快速判断蛇头是否与蛇身相撞。 + +定义一个变量 $score$ 来保存蛇的得分,初始值为 $0$;定义一个变量 $idx$ 来保存当前食物的索引,初始值为 $0$。 + +每次移动时,首先判断蛇头是否与边界相撞,如果相撞则游戏结束,返回 $-1$;否则,判断蛇头是否与食物重合,如果重合则蛇的得分加 $1$,同时食物索引 $idx$ 加 $1$;否则,蛇的身体长度不变,需要将蛇尾从队尾弹出,并从集合 $vis$ 中删除对应的坐标。 + +然后,判断蛇头是否与蛇身相撞,如果相撞则游戏结束,返回 $-1$;否则,将蛇头的坐标加入集合 $vis$ 中,并从队头加入蛇头的坐标。 + +最后,返回蛇的得分 $score$。 + +时间复杂度 $O(k)$,空间复杂度 $O(k)$,其中 $k$ 为移动的次数。 + ### **Python3** @@ -72,7 +88,49 @@ snakeGame.move("U"); // 返回 -1 ,蛇与边界相撞,游戏结束 ```python - +class SnakeGame: + def __init__(self, width: int, height: int, food: List[List[int]]): + self.m = height + self.n = width + self.food = food + self.score = 0 + self.idx = 0 + self.q = deque([(0, 0)]) + self.vis = {(0, 0)} + + def move(self, direction: str) -> int: + i, j = self.q[0] + x, y = i, j + match direction: + case "U": + x -= 1 + case "D": + x += 1 + case "L": + y -= 1 + case "R": + y += 1 + if x < 0 or x >= self.m or y < 0 or y >= self.n: + return -1 + if ( + self.idx < len(self.food) + and x == self.food[self.idx][0] + and y == self.food[self.idx][1] + ): + self.score += 1 + self.idx += 1 + else: + self.vis.remove(self.q.pop()) + if (x, y) in self.vis: + return -1 + self.q.appendleft((x, y)) + self.vis.add((x, y)) + return self.score + + +# Your SnakeGame object will be instantiated and called as such: +# obj = SnakeGame(width, height, food) +# param_1 = obj.move(direction) ``` ### **Java** @@ -80,7 +138,262 @@ snakeGame.move("U"); // 返回 -1 ,蛇与边界相撞,游戏结束 ```java +class SnakeGame { + private int m; + private int n; + private int[][] food; + private int score; + private int idx; + private Deque q = new ArrayDeque<>(); + private Set vis = new HashSet<>(); + + public SnakeGame(int width, int height, int[][] food) { + m = height; + n = width; + this.food = food; + q.offer(0); + vis.add(0); + } + + public int move(String direction) { + int p = q.peekFirst(); + int i = p / n, j = p % n; + int x = i, y = j; + if ("U".equals(direction)) { + --x; + } else if ("D".equals(direction)) { + ++x; + } else if ("L".equals(direction)) { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.length && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int t = q.pollLast(); + vis.remove(t); + } + int cur = f(x, y); + if (vis.contains(cur)) { + return -1; + } + q.offerFirst(cur); + vis.add(cur); + return score; + } + + private int f(int i, int j) { + return i * n + j; + } +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame obj = new SnakeGame(width, height, food); + * int param_1 = obj.move(direction); + */ +``` + +### **C++** + +```cpp +class SnakeGame { +public: + SnakeGame(int width, int height, vector>& food) { + m = height; + n = width; + this->food = food; + score = 0; + idx = 0; + q.push_back(0); + vis.insert(0); + } + + int move(string direction) { + int p = q.front(); + int i = p / n, j = p % n; + int x = i, y = j; + if (direction == "U") { + --x; + } else if (direction == "D") { + ++x; + } else if (direction == "L") { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.size() && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int tail = q.back(); + q.pop_back(); + vis.erase(tail); + } + int cur = f(x, y); + if (vis.count(cur)) { + return -1; + } + q.push_front(cur); + vis.insert(cur); + return score; + } + +private: + int m; + int n; + vector> food; + int score; + int idx; + deque q; + unordered_set vis; + + int f(int i, int j) { + return i * n + j; + } +}; + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame* obj = new SnakeGame(width, height, food); + * int param_1 = obj->move(direction); + */ +``` + +### **Go** + +```go +type SnakeGame struct { + m int + n int + food [][]int + score int + idx int + q []int + vis map[int]bool +} + +func Constructor(width int, height int, food [][]int) SnakeGame { + return SnakeGame{height, width, food, 0, 0, []int{0}, map[int]bool{}} +} + +func (this *SnakeGame) Move(direction string) int { + f := func(i, j int) int { + return i*this.n + j + } + p := this.q[0] + i, j := p/this.n, p%this.n + x, y := i, j + if direction == "U" { + x-- + } else if direction == "D" { + x++ + } else if direction == "L" { + y-- + } else { + y++ + } + if x < 0 || x >= this.m || y < 0 || y >= this.n { + return -1 + } + if this.idx < len(this.food) && x == this.food[this.idx][0] && y == this.food[this.idx][1] { + this.score++ + this.idx++ + } else { + t := this.q[len(this.q)-1] + this.q = this.q[:len(this.q)-1] + this.vis[t] = false + } + cur := f(x, y) + if this.vis[cur] { + return -1 + } + this.q = append([]int{cur}, this.q...) + this.vis[cur] = true + return this.score +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * obj := Constructor(width, height, food); + * param_1 := obj.Move(direction); + */ +``` +### **TypeScript** + +```ts +class SnakeGame { + private m: number; + private n: number; + private food: number[][]; + private score: number; + private idx: number; + private q: number[]; + private vis: Set; + + constructor(width: number, height: number, food: number[][]) { + this.m = height; + this.n = width; + this.food = food; + this.score = 0; + this.idx = 0; + this.q = [0]; + this.vis = new Set([0]); + } + + move(direction: string): number { + const p = this.q[0]; + const i = Math.floor(p / this.n); + const j = p % this.n; + let x = i; + let y = j; + if (direction === 'U') { + --x; + } else if (direction === 'D') { + ++x; + } else if (direction === 'L') { + --y; + } else { + ++y; + } + if (x < 0 || x >= this.m || y < 0 || y >= this.n) { + return -1; + } + if ( + this.idx < this.food.length && + x === this.food[this.idx][0] && + y === this.food[this.idx][1] + ) { + ++this.score; + ++this.idx; + } else { + const t = this.q.pop()!; + this.vis.delete(t); + } + const cur = x * this.n + y; + if (this.vis.has(cur)) { + return -1; + } + this.q.unshift(cur); + this.vis.add(cur); + return this.score; + } +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * var obj = new SnakeGame(width, height, food) + * var param_1 = obj.move(direction) + */ ``` ### **...** diff --git a/solution/0300-0399/0353.Design Snake Game/README_EN.md b/solution/0300-0399/0353.Design Snake Game/README_EN.md index f700c69dfd251..7c275030870b5 100644 --- a/solution/0300-0399/0353.Design Snake Game/README_EN.md +++ b/solution/0300-0399/0353.Design Snake Game/README_EN.md @@ -65,13 +65,310 @@ snakeGame.move("U"); // return -1, game over because snake collides wi ### **Python3** ```python +class SnakeGame: + def __init__(self, width: int, height: int, food: List[List[int]]): + self.m = height + self.n = width + self.food = food + self.score = 0 + self.idx = 0 + self.q = deque([(0, 0)]) + self.vis = {(0, 0)} + def move(self, direction: str) -> int: + i, j = self.q[0] + x, y = i, j + match direction: + case "U": + x -= 1 + case "D": + x += 1 + case "L": + y -= 1 + case "R": + y += 1 + if x < 0 or x >= self.m or y < 0 or y >= self.n: + return -1 + if ( + self.idx < len(self.food) + and x == self.food[self.idx][0] + and y == self.food[self.idx][1] + ): + self.score += 1 + self.idx += 1 + else: + self.vis.remove(self.q.pop()) + if (x, y) in self.vis: + return -1 + self.q.appendleft((x, y)) + self.vis.add((x, y)) + return self.score + + +# Your SnakeGame object will be instantiated and called as such: +# obj = SnakeGame(width, height, food) +# param_1 = obj.move(direction) ``` ### **Java** ```java +class SnakeGame { + private int m; + private int n; + private int[][] food; + private int score; + private int idx; + private Deque q = new ArrayDeque<>(); + private Set vis = new HashSet<>(); + + public SnakeGame(int width, int height, int[][] food) { + m = height; + n = width; + this.food = food; + q.offer(0); + vis.add(0); + } + + public int move(String direction) { + int p = q.peekFirst(); + int i = p / n, j = p % n; + int x = i, y = j; + if ("U".equals(direction)) { + --x; + } else if ("D".equals(direction)) { + ++x; + } else if ("L".equals(direction)) { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.length && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int t = q.pollLast(); + vis.remove(t); + } + int cur = f(x, y); + if (vis.contains(cur)) { + return -1; + } + q.offerFirst(cur); + vis.add(cur); + return score; + } + + private int f(int i, int j) { + return i * n + j; + } +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame obj = new SnakeGame(width, height, food); + * int param_1 = obj.move(direction); + */ +``` + +### **C++** + +```cpp +class SnakeGame { +public: + SnakeGame(int width, int height, vector>& food) { + m = height; + n = width; + this->food = food; + score = 0; + idx = 0; + q.push_back(0); + vis.insert(0); + } + + int move(string direction) { + int p = q.front(); + int i = p / n, j = p % n; + int x = i, y = j; + if (direction == "U") { + --x; + } else if (direction == "D") { + ++x; + } else if (direction == "L") { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.size() && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int tail = q.back(); + q.pop_back(); + vis.erase(tail); + } + int cur = f(x, y); + if (vis.count(cur)) { + return -1; + } + q.push_front(cur); + vis.insert(cur); + return score; + } + +private: + int m; + int n; + vector> food; + int score; + int idx; + deque q; + unordered_set vis; + + int f(int i, int j) { + return i * n + j; + } +}; + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame* obj = new SnakeGame(width, height, food); + * int param_1 = obj->move(direction); + */ +``` + +### **Go** + +```go +type SnakeGame struct { + m int + n int + food [][]int + score int + idx int + q []int + vis map[int]bool +} + +func Constructor(width int, height int, food [][]int) SnakeGame { + return SnakeGame{height, width, food, 0, 0, []int{0}, map[int]bool{}} +} + +func (this *SnakeGame) Move(direction string) int { + f := func(i, j int) int { + return i*this.n + j + } + p := this.q[0] + i, j := p/this.n, p%this.n + x, y := i, j + if direction == "U" { + x-- + } else if direction == "D" { + x++ + } else if direction == "L" { + y-- + } else { + y++ + } + if x < 0 || x >= this.m || y < 0 || y >= this.n { + return -1 + } + if this.idx < len(this.food) && x == this.food[this.idx][0] && y == this.food[this.idx][1] { + this.score++ + this.idx++ + } else { + t := this.q[len(this.q)-1] + this.q = this.q[:len(this.q)-1] + this.vis[t] = false + } + cur := f(x, y) + if this.vis[cur] { + return -1 + } + this.q = append([]int{cur}, this.q...) + this.vis[cur] = true + return this.score +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * obj := Constructor(width, height, food); + * param_1 := obj.Move(direction); + */ +``` + +### **TypeScript** + +```ts +class SnakeGame { + private m: number; + private n: number; + private food: number[][]; + private score: number; + private idx: number; + private q: number[]; + private vis: Set; + + constructor(width: number, height: number, food: number[][]) { + this.m = height; + this.n = width; + this.food = food; + this.score = 0; + this.idx = 0; + this.q = [0]; + this.vis = new Set([0]); + } + + move(direction: string): number { + const p = this.q[0]; + const i = Math.floor(p / this.n); + const j = p % this.n; + let x = i; + let y = j; + if (direction === 'U') { + --x; + } else if (direction === 'D') { + ++x; + } else if (direction === 'L') { + --y; + } else { + ++y; + } + if (x < 0 || x >= this.m || y < 0 || y >= this.n) { + return -1; + } + if ( + this.idx < this.food.length && + x === this.food[this.idx][0] && + y === this.food[this.idx][1] + ) { + ++this.score; + ++this.idx; + } else { + const t = this.q.pop()!; + this.vis.delete(t); + } + const cur = x * this.n + y; + if (this.vis.has(cur)) { + return -1; + } + this.q.unshift(cur); + this.vis.add(cur); + return this.score; + } +} +/** + * Your SnakeGame object will be instantiated and called as such: + * var obj = new SnakeGame(width, height, food) + * var param_1 = obj.move(direction) + */ ``` ### **...** diff --git a/solution/0300-0399/0353.Design Snake Game/Solution.cpp b/solution/0300-0399/0353.Design Snake Game/Solution.cpp new file mode 100644 index 0000000000000..bbf69e0f742fa --- /dev/null +++ b/solution/0300-0399/0353.Design Snake Game/Solution.cpp @@ -0,0 +1,64 @@ +class SnakeGame { +public: + SnakeGame(int width, int height, vector>& food) { + m = height; + n = width; + this->food = food; + score = 0; + idx = 0; + q.push_back(0); + vis.insert(0); + } + + int move(string direction) { + int p = q.front(); + int i = p / n, j = p % n; + int x = i, y = j; + if (direction == "U") { + --x; + } else if (direction == "D") { + ++x; + } else if (direction == "L") { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.size() && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int tail = q.back(); + q.pop_back(); + vis.erase(tail); + } + int cur = f(x, y); + if (vis.count(cur)) { + return -1; + } + q.push_front(cur); + vis.insert(cur); + return score; + } + +private: + int m; + int n; + vector> food; + int score; + int idx; + deque q; + unordered_set vis; + + int f(int i, int j) { + return i * n + j; + } +}; + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame* obj = new SnakeGame(width, height, food); + * int param_1 = obj->move(direction); + */ \ No newline at end of file diff --git a/solution/0300-0399/0353.Design Snake Game/Solution.go b/solution/0300-0399/0353.Design Snake Game/Solution.go new file mode 100644 index 0000000000000..e7f794d1c8dac --- /dev/null +++ b/solution/0300-0399/0353.Design Snake Game/Solution.go @@ -0,0 +1,55 @@ +type SnakeGame struct { + m int + n int + food [][]int + score int + idx int + q []int + vis map[int]bool +} + +func Constructor(width int, height int, food [][]int) SnakeGame { + return SnakeGame{height, width, food, 0, 0, []int{0}, map[int]bool{}} +} + +func (this *SnakeGame) Move(direction string) int { + f := func(i, j int) int { + return i*this.n + j + } + p := this.q[0] + i, j := p/this.n, p%this.n + x, y := i, j + if direction == "U" { + x-- + } else if direction == "D" { + x++ + } else if direction == "L" { + y-- + } else { + y++ + } + if x < 0 || x >= this.m || y < 0 || y >= this.n { + return -1 + } + if this.idx < len(this.food) && x == this.food[this.idx][0] && y == this.food[this.idx][1] { + this.score++ + this.idx++ + } else { + t := this.q[len(this.q)-1] + this.q = this.q[:len(this.q)-1] + this.vis[t] = false + } + cur := f(x, y) + if this.vis[cur] { + return -1 + } + this.q = append([]int{cur}, this.q...) + this.vis[cur] = true + return this.score +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * obj := Constructor(width, height, food); + * param_1 := obj.Move(direction); + */ \ No newline at end of file diff --git a/solution/0300-0399/0353.Design Snake Game/Solution.java b/solution/0300-0399/0353.Design Snake Game/Solution.java new file mode 100644 index 0000000000000..3c246368e177e --- /dev/null +++ b/solution/0300-0399/0353.Design Snake Game/Solution.java @@ -0,0 +1,59 @@ +class SnakeGame { + private int m; + private int n; + private int[][] food; + private int score; + private int idx; + private Deque q = new ArrayDeque<>(); + private Set vis = new HashSet<>(); + + public SnakeGame(int width, int height, int[][] food) { + m = height; + n = width; + this.food = food; + q.offer(0); + vis.add(0); + } + + public int move(String direction) { + int p = q.peekFirst(); + int i = p / n, j = p % n; + int x = i, y = j; + if ("U".equals(direction)) { + --x; + } else if ("D".equals(direction)) { + ++x; + } else if ("L".equals(direction)) { + --y; + } else { + ++y; + } + if (x < 0 || x >= m || y < 0 || y >= n) { + return -1; + } + if (idx < food.length && x == food[idx][0] && y == food[idx][1]) { + ++score; + ++idx; + } else { + int t = q.pollLast(); + vis.remove(t); + } + int cur = f(x, y); + if (vis.contains(cur)) { + return -1; + } + q.offerFirst(cur); + vis.add(cur); + return score; + } + + private int f(int i, int j) { + return i * n + j; + } +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame obj = new SnakeGame(width, height, food); + * int param_1 = obj.move(direction); + */ \ No newline at end of file diff --git a/solution/0300-0399/0353.Design Snake Game/Solution.py b/solution/0300-0399/0353.Design Snake Game/Solution.py new file mode 100644 index 0000000000000..340120e0163a6 --- /dev/null +++ b/solution/0300-0399/0353.Design Snake Game/Solution.py @@ -0,0 +1,43 @@ +class SnakeGame: + def __init__(self, width: int, height: int, food: List[List[int]]): + self.m = height + self.n = width + self.food = food + self.score = 0 + self.idx = 0 + self.q = deque([(0, 0)]) + self.vis = {(0, 0)} + + def move(self, direction: str) -> int: + i, j = self.q[0] + x, y = i, j + match direction: + case "U": + x -= 1 + case "D": + x += 1 + case "L": + y -= 1 + case "R": + y += 1 + if x < 0 or x >= self.m or y < 0 or y >= self.n: + return -1 + if ( + self.idx < len(self.food) + and x == self.food[self.idx][0] + and y == self.food[self.idx][1] + ): + self.score += 1 + self.idx += 1 + else: + self.vis.remove(self.q.pop()) + if (x, y) in self.vis: + return -1 + self.q.appendleft((x, y)) + self.vis.add((x, y)) + return self.score + + +# Your SnakeGame object will be instantiated and called as such: +# obj = SnakeGame(width, height, food) +# param_1 = obj.move(direction) diff --git a/solution/0300-0399/0353.Design Snake Game/Solution.ts b/solution/0300-0399/0353.Design Snake Game/Solution.ts new file mode 100644 index 0000000000000..81c6f132c77f8 --- /dev/null +++ b/solution/0300-0399/0353.Design Snake Game/Solution.ts @@ -0,0 +1,63 @@ +class SnakeGame { + private m: number; + private n: number; + private food: number[][]; + private score: number; + private idx: number; + private q: number[]; + private vis: Set; + + constructor(width: number, height: number, food: number[][]) { + this.m = height; + this.n = width; + this.food = food; + this.score = 0; + this.idx = 0; + this.q = [0]; + this.vis = new Set([0]); + } + + move(direction: string): number { + const p = this.q[0]; + const i = Math.floor(p / this.n); + const j = p % this.n; + let x = i; + let y = j; + if (direction === 'U') { + --x; + } else if (direction === 'D') { + ++x; + } else if (direction === 'L') { + --y; + } else { + ++y; + } + if (x < 0 || x >= this.m || y < 0 || y >= this.n) { + return -1; + } + if ( + this.idx < this.food.length && + x === this.food[this.idx][0] && + y === this.food[this.idx][1] + ) { + ++this.score; + ++this.idx; + } else { + const t = this.q.pop()!; + this.vis.delete(t); + } + const cur = x * this.n + y; + if (this.vis.has(cur)) { + return -1; + } + this.q.unshift(cur); + this.vis.add(cur); + return this.score; + } +} + +/** + * Your SnakeGame object will be instantiated and called as such: + * var obj = new SnakeGame(width, height, food) + * var param_1 = obj.move(direction) + */