導入
シミュレーションの問題のほとんどは、脳は理解できるが手では調整できないという感覚を人々に与え、コードを習得する非常に高いです。しかし、ここには落とし穴があります。ほとんどの質問では、シミュレーション方法は適切な選択ではありません。動的に計画したい質問をシミュレーションできますか? 移動ルールのシミュレーションについては、宇宙が再起動するまで一部の質問はコンピューターで計算できません。おそらく、シミュレーションは難易度を考えるのに最も簡単な方法ですが、現在の質問にとって必ずしも最適な選択であるとは限りません。他の解決策もありますので、最も愚かなシミュレーション方法を選択しないでください。しかし、シミュレーション手法から直接出題される問題もあり、これより良い解決策はありません。また、一部の質問は依然として一般的な試験問題です (スパイラル マトリックスなど)。なぜですか? それはコードの習熟度を試すテストだからです。
理論的根拠
実際、シミュレーションの理論的基礎は、より抽象的な能力であるべきです。leetcode の質問はすべて部分的なアルゴリズム実装という特徴があり、質問画面で初めて基本的なアルゴリズム モデルが確認できます。しかしある日、面接をしていると、数十の単語を含む通常のアルゴリズムの質問が、数百の単語を含む小さなエッセイに変わっていることに気づきました。面接ではアルゴリズムモデルを抽象化し、それを適切なアルゴリズムで解くことが求められますが、この時点では抽象化能力が低く、問題を解くことはおろか、質問が明確に読み取れません。シミュレーションの質問における抽象化は、スパイラル マトリックス、ライフ マトリックスなど、leetcode でより考慮されます。シミュレーションの問題にコツはありません。コツはもっと練習することです。コードを見せびらかそうとするのではなく、これがシミュレーションの問題であることを最初から理解できるようにするのが最善です。通常は理解できず、貴重な時間を無駄に浪費することになるからです。
問題解決の経験
- シミュレーションの問題は、思考の難しさという点では単純ですが、コードの実装は難しいのが一般的です。
- アルゴリズムの問題の場合、他の解決策を使用できる場合は、時間計算量が比較的高いため、シミュレーション手法は使用しないでください。
- シミュレーションの質問は、コードをマスターする能力をテストするのに非常に適しています。
- 模擬問題で好成績を収めたいなら、抽象化能力は不可欠です。
- 模擬問題を多く行うことには、賢明な解決策を考えなくても、最初の時点でその問題が模擬問題であるかどうかがわかるという利点があります。そうしないと、無駄に時間を浪費することになります。
アルゴリズムのトピック
43. 文字列の乗算
トピック分析: 配列を使用して乗算を模倣する、詳細な分析は次のとおりです。
num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
例: 123 * 45, 123的第1位 2 和45的第0位 4 乘积 08 存放在结果的第[1, 2]位中
index: 0 1 2 3 4
1 2 3
* 4 5
---------
1 5
1 0
0 5
---------
0 6 1 5
1 2
0 8
0 4
---------
0 5 5 3 5
这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中
コードは以下のように表示されます:
/**
* 模拟
*
**/
class Solution {
public String multiply(String num1, String num2) {
int n1 = num1.length() - 1;
int n2 = num2.length() - 1;
if(n1 < 0 || n2 < 0) return "";
int[] mul = new int[n1 + n2 + 2];
for(int i = n1; i >= 0; --i) {
for(int j = n2; j >= 0; --j) {
int bitmul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
bitmul += mul[i + j + 1]; // 先加低位判断是否有新的进位
mul[i + j] += bitmul / 10;
mul[i + j + 1] = bitmul % 10;
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
// 去掉前导0
while(i < mul.length-1 && mul[i] == 0)
i++;
for(; i < mul.length; ++i)
sb.append(mul[i]);
return sb.toString();
}
}
54. スパイラル・マトリックス
トピック分析: これは古典的なシミュレーションの問題です。特別な解決策はありません。テストは、コードをマスターし、質問の意味を理解し、トラバース プロセス全体をシミュレートする能力です。
コードは以下のように表示されます。
/**
* 模拟
*/
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
if (matrix.length == 0) {
return res;
}
int start_x = 0;
int start_y = 0;
int direction = 0;
int top_border = -1; // 上边界
int right_border = matrix[0].length; // 右边界
int bottom_border = matrix.length; // 下边界
int left_border = -1; // 左边界
// direction 向右:0,向下:1,向左2,向上:3
while (true) {
// 遍历完后返回
if (res.size() == matrix.length * matrix[0].length) {
return res;
}
// 注意,二维数组的行列,和坐标x,y轴,对应关系为 行 = y,列 = x
res.add(matrix[start_y][start_x]);
switch (direction) {
// 向右
case 0:
if (start_x + 1 == right_border) {
// 到底则改变方向,向下,向下移一行,上边界也向下移一行
direction = 1;
start_y += 1;
top_border += 1;
} else {
// 如果没到右边界,则持续往右
start_x += 1;
}
break;
// 向下
case 1:
if (start_y + 1 == bottom_border) {
// 到底则改变方向,向左,向左移一列,右边界也向左移一列
direction = 2;
start_x -= 1;
right_border -= 1;
} else {
// 如果没到下边界,则持续往下
start_y += 1;
}
break;
// 向左
case 2:
if (start_x - 1 == left_border) {
// 到底则改变方向,向上,向上移一行,下边界也向上移一行
direction = 3;
start_y -= 1;
bottom_border -= 1;
} else {
// 如果没到左边界,则持续往左
start_x -= 1;
}
break;
// 向上
case 3:
if (start_y - 1 == top_border) {
// 到底则改变方向,向右,向右移一列,左边界也向右移一列
direction = 0;
start_x += 1;
left_border += 1;
} else {
// 如果没到上边界,则持续往上
start_y -= 1;
}
break;
}
}
}
}
59. スパイラルマトリックスⅡ
トピック分析: 前の質問と同様に、面接の質問は頻繁にテストされます。テストは、コードをマスターして配列全体を回転させる能力です。
コードは以下のように表示されます。
/**
* 模拟
*/
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
// 为零直接返回
if (n == 0) {
return res;
}
int start_row = 0; // 行位置
int start_column = 0; // 列位置
int direction = 0; // 方向,向右:0,向下:1,向左2,向上:3
int top_border = -1; // 上边界
int bottom_border = n; // 下边界
int left_border = -1; // 左边界
int right_border = n; // 右边界
for (int i = 1; i <= n * n; i++) {
res[start_row][start_column] = i;
switch (direction) {
// 向右
case 0:
// 判断是否到右边界
if (start_column + 1 == right_border) {
// 到底则改变方向,向下
direction = 1;
// 位置向下移一行,上边界也向下移一行
start_row += 1;
top_border += 1;
} else {
// 如果没到右边界,则持续往右
start_column += 1;
}
break;
// 向下
case 1:
// 判断是否到下边界
if (start_row + 1 == bottom_border) {
// 到底则改变方向,向左
direction = 2;
// 位置向左移一列,右边界也向左移一列
start_column -= 1;
right_border -= 1;
} else {
// 如果没到下边界,则持续往下
start_row += 1;
}
break;
// 向左
case 2:
// 判断是否到左边界
if (start_column - 1 == left_border) {
// 到底则改变方向,向上
direction = 3;
// 位置向上移一行,下边界也向上移一行
start_row -= 1;
bottom_border -= 1;
} else {
// 如果没到左边界,则持续往左
start_column -= 1;
}
break;
// 向上
case 3:
// 判断是否到上边界
if (start_row - 1 == top_border) {
// 到底则改变方向,向右
direction = 0;
// 位置向右移一列,左边界也向右移一列
start_column += 1;
left_border += 1;
} else {
// 如果没到上边界,则持续往上
start_row -= 1;
}
break;
}
}
return res;
}
}
68. テキストを左右に揃える
トピック分析: 難しいシミュレーションの質問の場合、質問の幹で説明されている貪欲なアルゴリズムに従って、行ごとに、最初に最大でいくつの単語を配置できるかを決定します。これにより、行内のスペースの数を取得できるようになります。行番号内の単語間のスペースの数を決定します。
コードは以下のように表示されます。
/**
* 模拟
*/
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
List<String> ans = new ArrayList<String>();
int right = 0, n = words.length;
while (true) {
int left = right; // 当前行的第一个单词在 words 的位置
int sumLen = 0; // 统计这一行单词长度之和
// 循环确定当前行可以放多少单词,注意单词之间应至少有一个空格
while (right < n && sumLen + words[right].length() + right - left <= maxWidth) {
sumLen += words[right++].length();
}
// 当前行是最后一行:单词左对齐,且单词之间应只有一个空格,在行末填充剩余空格
if (right == n) {
StringBuffer sb = join(words, left, n, " ");
sb.append(blank(maxWidth - sb.length()));
ans.add(sb.toString());
return ans;
}
int numWords = right - left;
int numSpaces = maxWidth - sumLen;
// 当前行只有一个单词:该单词左对齐,在行末填充剩余空格
if (numWords == 1) {
StringBuffer sb = new StringBuffer(words[left]);
sb.append(blank(numSpaces));
ans.add(sb.toString());
continue;
}
// 当前行不只一个单词
int avgSpaces = numSpaces / (numWords - 1);
int extraSpaces = numSpaces % (numWords - 1);
StringBuffer sb = new StringBuffer();
sb.append(join(words, left, left + extraSpaces + 1, blank(avgSpaces + 1))); // 拼接额外加一个空格的单词
sb.append(blank(avgSpaces));
sb.append(join(words, left + extraSpaces + 1, right, blank(avgSpaces))); // 拼接其余单词
ans.add(sb.toString());
}
}
// blank 返回长度为 n 的由空格组成的字符串
public String blank(int n) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; ++i) {
sb.append(' ');
}
return sb.toString();
}
// join 返回用 sep 拼接 [left, right) 范围内的 words 组成的字符串
public StringBuffer join(String[] words, int left, int right, String sep) {
StringBuffer sb = new StringBuffer(words[left]);
for (int i = left + 1; i < right; ++i) {
sb.append(sep);
sb.append(words[i]);
}
return sb;
}
}
289. 人生ゲーム
トピック分析: インプレース アルゴリズムが必要な場合は、配列を使用してより多くの情報を記録します。0 と 1 に加えて、int 型にはより多くの情報を保存できます。-1 は、セルが以前は生きていて、現在は死んでいることを意味します。 2 は、セルが以前は死んでいましたが、現在は生きていることを意味します。
コードは以下のように表示されます。
/**
* 模拟
*
*/
class Solution {
public void gameOfLife(int[][] board) {
int[] neighbors = {0, 1, -1};
int rows = board.length;
int cols = board[0].length;
// 遍历面板每一个格子里的细胞
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
// 对于每一个细胞统计其八个相邻位置里的活细胞数量
int liveNeighbors = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
// 相邻位置的坐标
int r = (row + neighbors[i]);
int c = (col + neighbors[j]);
// 查看相邻的细胞是否是活细胞
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (Math.abs(board[r][c]) == 1)) {
liveNeighbors += 1;
}
}
}
}
// 规则 1 或规则 3
if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) {
// -1 代表这个细胞过去是活的现在死了
board[row][col] = -1;
}
// 规则 4
if (board[row][col] == 0 && liveNeighbors == 3) {
// 2 代表这个细胞过去是死的现在活了
board[row][col] = 2;
}
}
}
// 遍历 board 得到一次更新后的状态
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (board[row][col] > 0) {
board[row][col] = 1;
} else {
board[row][col] = 0;
}
}
}
}
}
ホームページに戻る
Leetcode 500 以上の質問を磨くことについての感想