概要
配列フリッピング、文字列フリッピング、リンク リスト フリッピング、バイナリ ツリー フリッピングなど、フリッピング アルゴリズムには非常に多くのトピック タイプがあります。一部のトピックはフリッピングと呼ばれていませんが、数のセットを逆順に出力するなど、同様の種類のものです。
分析する
フリッピングアルゴリズムの実装は何ですか?
方法 1: スタック
データ構造には、逆順スタックを実現するための自然な補助ツールがあります。スタックの先入れ後出し機能により、大多数の反転問題を処理できます。以下に例を示します:
リンクされたリストを端から端まで出力します
private static void printReverseSingleNode(SingleNode head){
Stack<Integer> stack = new Stack<>();
while (head != null){
stack.push(head.data);
head = head.next;
}
while (!stack.empty()){
System.out.print(stack.pop());
}
}
ここでは、連結リスト ノードの値のみをスタックに格納し、連結リスト ノードは格納しないことに注意してください。これは実装が最も簡単でもあります。スタックを使用して連結リストを反転すると、最後に新しい連結リストが作成されるため、損失が利益を上回り、スペースの複雑さが高くなり、実装がより面倒になります。
方法 2: 再帰
再帰の学習に関して、私は以前から常に誤解を持っていました。つまり、再帰実行の詳細をすべて理解したいのですが、詳細を掘り下げると混乱することがわかりました(不確実性に似ています)。量子力学の原理..) したがって、再帰に遭遇する限り、層ごとの呼び出し関係を考えずに再帰式に抽象化し、人間の脳を使用してそれぞれを分解しようとはしません。再帰のステップ。二分木での再帰アルゴリズムの使用は非常に一般的です. 二分木の例を次に示します:
二分木の反転
/**
* 递归版本,其实就是左右交换
* 由于树中每一个节点都需要被访问,因此时间复杂度就是O(n),其中n 是节点个数
* 本方法使用了递归,在最坏情况下栈内存需要存放O(h)个方法调用,其中h是树的高度,由于h属于O(n),可得空间复杂度是O(n)
*/
private static TreeNode reverseTree1(TreeNode root){
if (root == null){
return null;
}
TreeNode temp = root.leftChild;
root.leftChild = reverseTree1(root.rightChild);
root.rightChild = reverseTree1(temp);
return root;
}
方法 3: 反復
ここでは、ダブルポインタの方法が使用されます。場合によっては、インタビュアーが再帰を使用してアルゴリズムを実装することを許可しない場合があります (たとえば、バイナリ ツリーの前、中、後ろのトラバーサル、再帰を使用して記述するには単純すぎる)、または深く理解していない場合があります。再帰のアイデア (まだ深く掘り下げる必要があります。再帰は非常に重要です) を使用すると、現時点では、反復法を使用して問題を処理することは、前の 2 つの方法に比べてそれほど複雑ではありません。例:
文字列を逆にする
/**
* 反转方法,使用双指针法
* @param s
*/
private static void reverseString(char[] s) {
int left = 0;
int right = s.length -1;
char temp;
int len = s.length;
for (int i = 0; i < len; i++) {
if (left < right){
temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
1 つは配列の先頭を指し、もう 1 つは配列の末尾を指す 2 つのポインターを使用して (このアイデアは二分探索でも使用されます)、その後、両方の当事者が呼び出しを開始します。N/2 と呼ばれる。時間計算量は O(n) で、空間計算量は O(1) です.
もちろん、この double ポインタは少し単純に見えます.もう少し複雑なものを見てみましょう.
単一リンクリストを反転し、最初にコードを見てください:
*/
public static SingleNode reverseSingleNode2(SingleNode head){
SingleNode current = head;
SingleNode pre = null;
SingleNode next;
while (current != null) {
// 取出 next
next = current.next;
// 将上一个赋值给 next
current.next = pre;
// 更改 上一个到当前位置
pre = current;
// 当前位置往下移动
current = next;
}
return pre;
}
一重リンクのリストをひっくり返すのは、文字列よりも大きく見えるものをひっくり返すよりも複雑です. 私も段階的にデバッグし、慎重に考えた後にそれを理解しました (私は少し愚かなので、愚かな方法を使用しました) . 理解するために、次の 3 番目のポインターを置くことができます一時的なものとして理解すると、リンクされたリストが後方に移動するのに便利です。一度に 1 つのノードを取得し、それを前のレコード ノードの先頭に追加します。重要なのは、current.next = pre; および pre = current; を理解することです。
実は、スタックの使い方も一種の反復です。二分木の反転を実現するための補助ツールとしてキューを使用する例を次に示します。
/**
* 非递归版本,即使用迭代法
* 创建一个队列来存储所有的左孩子和右孩子还没有被交换过的节点,开始的时候仅根节点在队列中,只要队列不为空,
* 就一直从队列中取出节点,然后交换这个节点的左右孩子节点,接着再把孩子节点放入队列中,对于其中的空节点不用加到队列中,
* 因为最后队列一定为空,这个时候所有的孩子节点已经被交换过了,所以最后再返回根节点即可。
*/
private static TreeNode reverseTree2(TreeNode root){
if (root == null){
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode current = queue.poll();
TreeNode tmp = current.leftChild;
current.leftChild = current.rightChild;
current.rightChild = tmp;
if (current.leftChild != null) queue.add(current.leftChild);
if (current.rightChild != null) queue.add(current.rightChild);
}
return root;
}
反復アルゴリズムはフリッピングを実装します, 大きく分けて 2 つのステップ. 非線形データ構造のデータ (バイナリ ツリーなど) の場合は、データのフリッピングを完了するための補助ツールが必要です。線形構造データの場合は、2 つのポインターを直接使用するだけです。