リンクリストの質問は、人のコードの内部スキルの深さをテストするための最良の方法です。最初にリンクリストを学習した後、私はたくさんの質問を書きましたが、しばらくして、リンクリストを開いて奇妙に感じました。前のクラスで先生が言った「リンクリストの質問を5回読まなければ、リンクリストを学んだと言ってはいけない」という文章を思い出しました。そこで、以前のリンクリストの質問を見始めました。繰り返しますが、lettcodeの上の古典的なインタビューの質問、次のコードはすべて静かな方法で書かれています。
要約すると、リンクリストの古典的なインタビューの質問には主に次のものが含まれます。
2.
単一リンクリストを逆にします。 OJリンク
5.
2つの順序付きリンクリストを新しい順序付きリンクリストに結合して、戻ります。新しいリンクリストは、指定された2つのリンクリストのすべてのノードを接続することによって構成されます。OJリンク
7.
ソートされたリンクリストに重複ノードがあります。リンクリスト内の重複ノードを削除してください。重複ノードは保持されず、リンクリストのヘッドポインタが返されます。OJリンク
8.
リンクリストの回文構造。OJリンク
9.
2つのリンクリストを入力し、それらの最初の共通ノードを見つけます。OJリンク
10.
リンクリストが与えられたら、リンクリストにループがあるかどうかを判断します。OJリンク
12.
その他。間違いなく他のもっと難しいリンクリストの質問があります、あなたはそれらを磨くためにNiukeとLeetcodeで質問を見つけることができます。
0:カスタム実装リンクリスト
リンクリストをカスタマイズする方法には、頭の挿入、尾の挿入、任意の位置への挿入、初めて表示されるキーの削除、キーワードキーが含まれているかどうかの確認などがあります。エラーが発生しやすい場所は、図に示すように、任意に挿入する場合は、最初に裏側を結び、次に表側を結ぶことを忘れないでください。
キーが初めて表示されるノードを削除します。次の図に示すように、最初にトラバースして前のノードcurを見つけてキーを削除し、次にステートメントcur.next = nodeを実行して削除操作を完了します。
public class Node {
public int data;
public Node next;
public Node(int data){
this.data=data;
}
}
public class MyLinkedList {
private Node head;
//头插法
public void addFirst(int data){
//空表的时候
Node node=new Node(data);
if (this.head != null) {
node.next = head;
}
head=node;
}
public void display(){
Node cur=this.head;
while(cur!=null){
System.out.println(cur.data+" ");
cur=cur.next;
}
}
//尾插法
public void addLast(int data){
Node node=new Node(data);
if(head==null){
this.head=node;
}else{
Node cur=this.head;
while(cur.next!=null){//cur
cur=cur.next;
}
cur.next=node;
}
}
//任意插
public void addIndex(int index,int data){
//先要判断下标是否合法
if(index<0||index>getLength()){
System.out.println("下标不合法");
return;
}
if(index==0){
addFirst(data);
return;
}
if(index==getLength()){
addLast(data);
return;
}
//现在在中间进行插入
Node node=new Node(data);
//找到要插入下标的前一个结点
Node cur=searchPrev(index-1);
node.next=cur.next;
cur.next=node;
}
//找到要插入位置index的前一个位置index-1的位置节点
public Node searchPrev(int index){
Node cur=this.head;
int count=0;
while(count<index){
cur=cur.next;
}
return cur;
}
public int getLength(){
int count=0;
Node cur=this.head;
while(cur!=null){
count++;
cur=cur.next;
}
return count;
}
//删除第一次出现的key
public void remove(int key){
if(this.head==null){
return;
}
Node cur=searchPrevNode(key);
//要注意cur是否没有找到。进行判断一下
if(cur==null){
System.out.println("没有找到你要删除的下标");
return;
}
cur.next=cur.next.next;
}
//查找要删除元素的前驱结点
public Node searchPrevNode(int key){
Node cur=this.head;
while(cur!=null){
if(cur.next.data==key){
return cur;
}
cur=cur.next;
}
//没有找到要删的结点
return null;
}
//查找关键字key是否包含在单链表中
public boolean contains(int key){
Node cur=this.head;
while(cur!=null){
if(cur.data==key){
return true;
}
cur=cur.next;
}
return false;
}
1.
指定された値キーに等しいリンクリスト
内のすべてのノードを削除します。たとえば、図に示すように、key = 33のノードを削除するには次のようにします。
アイデア:1。ヘッドノードを指すようにprevを定義し、削除するノードを指すようにcurを定義します。2。curが指す
値がキーと等しいかどうかを判断します。等しい場合:prev.nextを実行します。 = cur.next、これはcurが削除されたことを意味します現在ポイントされているノード、次にcurは下にトラバースし続けます;
3.現在参照されているcurの値がkeyの値と等しくない場合は、prev = curとcurを続行しますcur = nullまで下にトラバースします
。4。cur= nullの場合、キーと等しい値のノードがまだある場合は、それがヘッドノードである必要があります。判定の開始時にヘッドノードが判定されないため、ヘッド次にノードを判断する必要があります。値が等しい場合は削除してください。
値がキーと等しいかどうかを判断します。等しい場合:prev.nextを実行します。 = cur.next、これはcurが削除されたことを意味します現在ポイントされているノード、次にcurは下にトラバースし続けます;
3.現在参照されているcurの値がkeyの値と等しくない場合は、prev = curとcurを続行しますcur = nullまで下にトラバースします
。4。cur= nullの場合、キーと等しい値のノードがまだある場合は、それがヘッドノードである必要があります。判定の開始時にヘッドノードが判定されないため、ヘッド次にノードを判断する必要があります。値が等しい場合は削除してください。
コードは次のように表示されます。
public void removeAllKey(int key){
//定义一个prev指向要删除的节点的前一个
//定义一个cur指向要删除的结点
Node prev=this.head;
Node cur=this.head.next;
while(cur!=null){
if(cur.data==key){
prev.next=cur.next;
cur=cur.next;
}else{
prev=cur;
cur=cur.next;
}
}
//现在如果有还有和key相等的值,那么一定在头节点
if(this.head.data==key){
this.head=this.head.next;
}
}
2.
単一リンクリストを逆にします。要件:1回トラバースし、リンクリストを逆にします。
アイデア:1。図に示すように、プリカーサーノードprevを定義し、curを定義します。
2.最初にprevとcurの間のポインティング関係を変更し、cur.next = prevとし、prevがcurによってポイントされた位置をポイントすると、curは次のノードまでトラバースし続けます。
3.最初にprev = nullが定義されている場合、リンクリストが逆になると、最後のノードの次のフィールドがnullを指します(図のノード123の次のフィールドはnullであるため、試す必要はありません)。最後のノードを見つけるには次のフィールドはnullに設定されます)。
リンクリストの質問をする前に、自分の考えを明確に考えてください。自分でコードを描いて書く必要があります。やり方がわからない場合は、自分でもっと絵を描いてください。さあ、間違いなくできると信じてください。
public Node reverseList(){
Node newHead=null;
Node prev=null;
Node cur=this.head;
while(cur!=null){
Node curNext=cur.next;
if(curNext==null){
newHead=cur;
}
cur.next=prev;
prev=cur;
cur=curNext;
}
return newHead;
}
3.ヘッドノード
headを
持つ空でない単一リンクリストが与えられた
場合、リンクリストの中央ノードを返します。中間ノードが2つある場合は、2番目の中間ノードに戻ります。要件:リンクリストを1回トラバースします。
アイデア:高速と低速の2つの参照を定義して、高速のものは一度に2ステップ、低速のものは一度に1ステップずつ実行します。ループ条件は高速です。= null && fast.next!= null。これは、高速参照が高速になり、この順序を入れ替えることができないためです。fast!= nullであるがfast.next = nullの場合、nullポインター例外が発生するためです。
public Node middleNode(){
Node fast=this.head;
Node slow=this.head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
4.
リンクリストを入力し、リンクリストの下
から
k番目の
ノードを出力し
ます。
アイデア:2つの参照を定義します。1つは高速でもう1つは低速で、最初に高速でk-1ステップを実行し、次に2つを同期して実行します。下からk番目のノードでは、正当性判断のためにfastにステップk-1を実行するように要求すると、最適化を実行できます。つまり、ステップk-1を実行するように要求されたときに、高速かどうかを判断します。 nextは空です。空の場合は、高速でダウンを続ける必要はありません。ループからジャンプするだけです。(例:下から8番目のノードを探しますが、リンクリストの全長はわずか5であるため、fast.next =を実行すると、k-1 = 8-1 = 7ステップを高速化する必要はありません。 = null、直接nullを返します)。
public Node findKthToTail(int k){
if(k<0||head==null){
return null;
}
//定义两个引用,fast先走k-1步,让偶让fast和slow同时开始走
Node fast=this.head;
Node slow=this.head;
while(k-1>0){
if(fast.next!=null) {
fast = fast.next;
k--;
}else{
System.out.println("k过大");
return null;
}
}
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
5.
2つの順序付きリンクリストを新しい順序付きリンクリストに結合して、戻ります。新しいリンクリストは、指定された2つのリンクリストのすべてのノードを接続することによって構成されます。
アイデア:2つのリンクリストをトラバースするには、偽のヘッドノードを定義し、2つのリンクリストのノードを直列に接続します。
public Node mergeTwoLists(Node headA,Node headB){
Node newHead=new Node(-1);
Node tmp=newHead;
//当前两个链表都有数据
while(headA!=null&&headB!=null){
if(headA.val<headB.val){
tmp.next=headA;
tmp=tmp.next;
headA=headA.next;
}else{
tmp.next=headB;
tmp=tmp.next;
headB=headB.next;
}
}
if(headA!=null){
tmp.next=headA;
}
if(headB!=null){
tmp.next=headB;
}
return newHead.next;
}
6.
書き込みにコード
二つの部分にリンクされたリストを分割する所定の値に基づいて
X
、およびすべての
ノードを未満
、Xが
より大きいまたは等しいノードの前に配置されている
X
。
アイデア:図に示すように、セグメンテーションを実行します。セグメントを挿入するときは、間隔が空であるかどうかを判断する必要があります。最後に、2つの間隔をマージするときに、2つの間隔に要素がないかどうかを考慮する必要があります。 ;;
public Node partition(int x){
Node bs=null;
Node be=null;
Node as=null;
Node ae=null;
Node cur=this.head;
while(cur!=null){
if(cur.data<x){
//第一次插入
if(bs==null){
bs=cur;
be=bs;
}else{
be.next=cur;
be=cur;
}
}else{
if(as==null){
as=cur;
ae=as;
}else{
ae.next=cur;
ae=cur;
}
}
cur=cur.next;
}
if(bs==null){
return as;
}
be.next=as;
if(as!=null){
ae.next=null;
}
return as;
}
7.
ソートされたリンクリストに重複ノードがあります。リンクリスト内の重複ノードを削除してください。重複ノードは保持されず、リンクリストのヘッドポインタが返されます。
アイデア:リンクリストをトラバースするには、重要なポイントは順序付けられたリンクリストにあるため、最初からトラバースし、同じ次の値を見つけてスキップします。互いに接続された複数の同一の値に遭遇した場合は、while()cycleを使用します。
public Node deleteDuplication(){
Node newHead=new Node(-1);
Node tmp=newHead;
Node cur=this.head;
while(cur!=null){
if(cur.next!=null&&cur.data==cur.next.data){
//相等的时候
while(cur.next!=null&&cur.data==cur.next.data){
cur=cur.next;
}
cur=cur.next;
}else{
tmp.next=cur;
tmp=cur;
cur=cur.next;
}
}
tmp.next=null;//进行这一步的作用就是要保证不出现死循环
return newHead.next;
}
8.
リンクリストの回文構造。
アイデア:1。最初に中間ノードを見つけます。
2.中央のノードを見つけたら、中央から反転を開始します。
3. 2つの参照が頭と遅い、1つは前から始まり、後ろに戻り、もう1つは後ろから始まり、前に進んで、データが同じであるかどうかを判断します。
public boolean chkPalindrome(){
//第一步:先找到中间节点
Node fast=this.head;
Node slow=this.head;
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//第二步:对链表进行翻转
Node cur=slow.next;
while(cur!=null){
Node curNext=cur.next;
cur.next=slow;
slow=cur;
cur=curNext;
}
//第三步:从两边开始向中间遍历,判断值是否相等
while(this.head!=slow){//还没有相遇的时候
if(this.head.data!=slow.data){
return false;
}
//专门为偶数设计的
if(this.head.next==slow){
return true;
}
this.head=this.head.next;
slow=slow.next;
}
return true;
}
9.
2つのリンクリストを入力し、それらの最初の共通ノードを見つけます。
アイデア:1。2つのリンクリストの長さを別々に見つけます。
2.最長のリンクリストに、2つのリンクリストの違いを説明させます。
3. 2つの参照(pl、長いリンクリストの先頭を指す、ps、短いリンクリストの先頭を指す)を同時に実行します。それらが一致すると、2つのリンクリストが交差します。
public Node getIntersectionNode(Node headA,Node headB){
if(headA==null||headB==null){
return null;
}
int lenA=0;
int lenB=0;
Node pl=headA;
Node ps=headB;
while(ps!=null){
lenB++;
ps=ps.next;
}
while(pl!=null){
lenA++;
pl=pl.next;
}
pl=headA;
ps=headB;
int len=lenA-lenB;
if(len<0){
ps=headA;
pl=headB;
len=lenB-lenA;
}
while(len>0){
len--;
pl=pl.next;
}
while(pl!=null&&ps!=null&&pl!=ps){
pl=pl.next;
ps=ps.next;
}
if(pl==ps&&pl!=null){
return pl;
}
return null;
}
10.
リンクリストが与えられたら、リンクリストにループがあるかどうかを判断します。
アイデア:1。2つの参照を定義します。1つは高速用、もう1つは低速用です。これにより、高速は一度に2つのステップを実行し、低速は一度に1つのステップを実行します。
2. Fastは、Slowに遭遇したかどうかにかかわらず、2つのステップごとに判断する必要があります。
public boolean hasCycle(){
//首先定义两个引用,让一个走一步,一个走两步
Node fast=this.head;
Node slow=this.head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
11.
リンクリストを指定して、リンクリストがリングに入り始める最初のノードを返します。リンクリストにリングがない場合は、
nullが返され
ます。
アイデア:1。ループがあるかどうかを解決するというアイデアと同様に、2つの参照を定義することです。1つは高速でもう1つは低速で、高速は一度に2つのステップを実行し、低速は一度に1つのステップを実行します。 nullまたはfast.next = nullがループから外れています。
2.ループから飛び出した後、終了の理由を判別します。fast== nullまたはfast.next == nullの場合、リンクリストにリングがないことを意味します。
3.ループが終了した場合、ループの最初のノードを解決する必要があります。写真が示すように:
public Node detectCycle(){
Node fast=this.head;
Node slow=this.head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
break;
}
}
if(fast==null||fast.next==null){
return null;
}
slow=this.head;
while(slow!=fast){
fast=fast.next;
slow=slow.next;
}
return slow;
}