リンクリスト
1、リンクリスト
リンクリストはノードに接続されたデータ構造であり、一般的なノード構造には、データを格納するデータフィールドと、実行のために次のノードを格納するポインタフィールドが含まれます。二重リンクリストは、ポインタフィールドを先行ノードpreと後続ノードnextに絞り込みます。循環リンクリストは、テールノードをヘッドノードに接続します。
したがって、リンクリストの分類には、実装に応じて、単一リンクリスト、二重リンクリスト、および循環リンクリストが含まれます。
リンクリストの設計原則は、追加と削除が速く、クエリが遅いということです。リンクリストの知識については、以下を参照してください。
Javaのリンクリスト:
Javaでのリンクリストの実装は次のとおりです。
2.ボタンの質問タイプを強制します
2.1、203リンクリスト要素を削除する
リンクリストのヘッドノード
head
と整数val
を指定して、をNode.val == val
満足、新しいヘッドノードを返します。输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
リンク:https ://leetcode-cn.com/problems/remove-linked-list-elements/
- 再帰的書き込み(ヘッドノードを設定する必要はありません)
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) return null;
//移动到尾部,返回值在压栈的时候表示下一结点,弹栈时表示前一结点
head.next = removeElements(head.next,val);//传的是啥返回的就是啥
//弹栈时head作为当前结点,匹配则返回前一节点;跳过该节点就相当于删除了。
if(head.val == val) return head.next;
//否则,返回前一节点继续
else{
return head;}
}
}
一般的な方法は、削除するノードの前のノードを、削除するノードの後のノードにポイントすることです。
削除するノードがヘッドノードの場合は操作できません。したがって、ソリューションを統合するために仮想ヘッドノードが追加されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(-1); //新建一个节点做头节点
dummy.next = head;
ListNode cur = dummy;
while(cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next; //否则移动到下一个节点
}
}
//为什么返回的头结点是这个而非cur,因为cur一直在移动
//为什么是dummy.next而非dummy,因为dummy是个虚拟的头节点,要返回自己的
return dummy.next;
}
}
2.2、206逆リンクリスト
単一リンクリストのヘッドノードを指定して、リンクリスト
head
を逆にして、逆リンクリストを返します。1-> 2-> 3-> 4-> null
null <-1 <-2 <-3 <-4
反復法:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null; //前一个结点
ListNode next = null; //下一个结点(临时存储)
ListNode curr = head; //当前结点
while(curr != null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
再帰的方法:再帰的ユニットの実行は、ヘッドノードを除く残りのノードを反転することです。
class Solution{
publiC ListNode reverseList(ListNode head){
//递归的停止条件
//我们通常使用head.next判断就可以了,head为null是为了防止链表本身为null的情况
if(head == null || head.next == null)return head;
//压栈,直到移动到最后一个结点
ListNode prev = reverseList(head.next);
head.next.next = head; //弹栈的过程就是把当前结点指向前一个结点的过程
head.next = null; //把前一结点指向null
return prev;
}
}
ステートメント分析:1-> 2-> 3-> 4-> null
reverseList(3.next)= reverseList(4)、ここで4.nextはnullなので、スタックからポップアウトします。返されるprevは4です。この時点で、headは3です。
スタックをポップした後に実行します:3.next.next = 3; 3.next=null。すなわち4->3->null
次のステップ:3-> 2-null; 2->1->null。スタックを3回押すだけで、スタックを4回プレイしました。
テストデモ:
public class ReverseListTest { static int ya = 0; static int tan = 0; public static void main(String[] args) { ListNode head = new ListNode(1); ListNode node2 = new ListNode(2); ListNode node3 = new ListNode(3); ListNode node4 = new ListNode(4); head.next = node2; node2.next = node3; node3.next = node4; node4.next = null; reverseList(head); } public static ListNode reverseList(ListNode head){ ya++; System.out.println("压栈第"+ya+"次"); if(head == null || head.next == null)return head; //压栈,直到移动到最后一个结点 ListNode prev = reverseList(head.next); tan++; System.out.println("弹栈第"+tan+"次"); head.next.next = head; //弹栈的过程就是把当前结点指向前一个结点的过程 head.next = null; //把前一结点指向null return prev; } } class ListNode{ int val; ListNode next; ListNode(int x){ val = x; } }
压栈第1次 压栈第2次 压栈第3次 压栈第4次 弹栈第1次 弹栈第2次 弹栈第3次
2.3、24リンクリスト内のノードをペアで交換します
リンクリストを指定して、隣接するノードを2つずつスワップし、スワップされたリンクリストを返します。
ノード内の値を変更するだけでなく、実際にノードスワップを行う必要があります。
输入:head = [1,2,3,4] 输出:[2,1,4,3]
アイデア:
反復法:更新はプロセスシミュレーションです。
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);//虚拟头结点
dummy.next = head; //加入结点
ListNode temp = dummy;
while(temp.next !=null && temp.next.next !=null){
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
temp.next = node2; //temp复位node2
node1.next = node2.next; //node1指向node3
node2.next = node1; //node2指向node1
temp = node1; //前进一步
}
return dummy.next;
}
}
再帰的な方法:より簡単で、毎回前の方法を指すだけです
class Solution {
public ListNode swapPairs(ListNode head) {
// 1,结束条件
if(head == null || head.next == null) return head;
// 2,移动
ListNode next = head.next;
// 注意要间隔一个
head.next = swapPairs(next.next);
// 弹栈执行
next.next = head;
return next;
}
}
2.4、19リンクリストの最後のN番目のノードを削除します
リンクリストを指定したら、リンクリストの最後からn番目のノードを削除し、リンクリストのヘッドノードを返します。
入力:ヘッド= [1,2,3,4,5]、n = 2
出力:[1,2,3,5]入力:ヘッド= [1]、n = 1
出力:[]リンク:https ://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
アイデア:この問題は、ダブルポインターの古典的なアプリケーションです。まず、高速ポインターを最初にn移動させ、次に低速ポインターを同時に移動し始めます。高速ポインターが終了すると、低速ポインターは前のポインターに移動します。指定した位置の位置で、直接削除できます。(ここでの高速ポインターの本質は、リンクリストの長さを見つけることと同じです)
移動停止状態:(双方向)
fIndex !=null
:高速ポインタは最後のNULLに移動します。このとき、同期を維持するために、低速ポインタは事前に仮想ノードのソースに移動する必要があります。仮想ノードを使用すると、ヘッドノードでの処理の問題が統合されます。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 双指针
// 因为无法直接获取链表长度,从头往后遍历
ListNode dummy = new ListNode(0,head);
ListNode fIndex = head;
ListNode sIndex = dummy;
if(head == null || head.next == null) return null;
// 先移动n个长度,
for(int i = 0;i<n;i++){
fIndex = fIndex.next;
}
while(fIndex!=null){
fIndex = fIndex.next;
sIndex = sIndex.next;
}
sIndex.next = sIndex.next.next;
return dummy.next;
}
}
fIndex.next != null
、このとき、高速ポインタは1つ少なく移動し、最後の位置に移動します。その後、低速ポインタはヘッドポインタから開始して、同期を維持できます。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 双指针
// 因为无法直接获取链表长度,从头往后遍历
ListNode fIndex = head;
ListNode sIndex = head;
// 先做判空处理
if(head == null || head.next == null) return null;
// 先移动n个长度,
for(int i = 0;i<n;i++){
fIndex = fIndex.next;
}
// 注意:这里就需要考虑删除节点为头结点的情况
// 即删除倒数第n个,移动了n个
if(fIndex == null) return head.next;
while(fIndex.next!=null){
fIndex = fIndex.next;
sIndex = sIndex.next;
}
sIndex.next = sIndex.next.next;
return head;
}
}
(実際、公式のソリューションでも最も独創的なアイデアが得られました。つまり、最初にリンクリストの長さを計算してから、最初からトラバースします。ソリューションを参照してください)
2.5、インタビューの質問02.07。リンクリストの交差点
2つの単一リンクリストのヘッドノードheadAとheadBを前提として、2つの単一リンクリストが交差する開始ノードを見つけて返します。2つのリンクリストが交差しない場合はnullを返します。
この図は、2つのリンクリストがノードc1で交差していることを示しています。
リンク:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci
ここでの共通部分は、ノードではなく、最初のノードの文字列であることに注意してください。したがって、2つのリンクリストの最後のノードは等しくなければなりません。したがって、2つのリンクリストは右揃えである必要があります。右揃えの直後にトラバースできます。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 此种方法的缺点是要考虑到所有输出为null的情况
if (headA == null || headB == null) return null;
int sa=0,sb = 0;
// 算出各自的长度
ListNode tailA = headA,tailB = headB;
while(tailA != null){
tailA = tailA.next;
sa++;
}
while(tailB != null){
tailB = tailB.next;
sb++;
}
//最后一个不一样直接退出
if(tailA!=tailB) return null;
int diff = Math.abs(sa-sb);
if(sa>sb){
//A比B长,A先移动diff位
while(diff-- > 0) headA=headA.next;
}
if(sa<sb){
while(diff-- > 0) headB=headB.next;
}
// 此时就可以直接遍历处理了
while(headA != headB){
headA = headA.next;
headB = headB.next;
}
return headA;
}
2.6、循環リンクリスト||
リンクリストを指定して、リンクリストがリングに入り始める最初のノードを返します。リンクリストにサイクルがない場合に戻ります
null
。输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
リンク:https ://leetcode-cn.com/problems/linked-list-cycle-ii/submissions/
方法1、高速および低速ポインター:
なぜ会わなければならないのですか?ヘッドノードから同時に開始し、高速ポインターが毎回2ステップかかる場合、低速ポインターは毎回1ステップかかります。リングがある場合は、高速ポインターと低速ポインターがリング内で出会う必要があります。 。スローに追いついた後、この時点でヘッドノードとスローが同時に歩き始め、それらが出会うとリングポイントの位置になります。高速ポインタによる微分式は、低速ポインタの2倍の速度で移動します。
public class Solution {
public ListNode detectCycle(ListNode head) {
//同时从头结点出发
ListNode fast = head;
ListNode slow = head;
while(fast!=null){
slow = slow.next;
if(fast.next!=null){
fast = fast.next.next;//每次走两步
}else{
return null;
}
if(fast == slow){
//此时prev指针走slow多走的就可以走到环点
ListNode prev = head;
while(prev != slow){
prev = prev.next;
slow = slow.next;
}
return prev;
}
}
return null;
}
}
HashSetの非反復機能を使用することもできます。
public class Solution {
public ListNode detectCycle(ListNode head) {
// 利用hashset的不重复特性
HashSet<ListNode> hashSet = new HashSet<ListNode>();
ListNode curr = head;
while(curr!=null){
if(!hashSet.contains(curr)){
hashSet.add(curr);
curr = curr.next;
}else{
return curr;
}
}
return null;
}
}
3.まとめ
再帰
繰り返す
ダブルポインタ