目次
2. コードは単一リンクリストを実現します (追加、削除、変更、チェックを含む)。
3.3 ランダムなポインタノードを含むリンクリストをコピーする
1. 基本的な考え方
リンク リストは連鎖ストレージ データ構造であり、任意のアドレスを持つストレージ ユニットのグループを使用してデータ構造を線形リストに格納します。シングル リンク リスト、ダブル リンク リストなど、さまざまな種類のリンク リストがあります。シングル リンク リストの設計思想は最も基本的なもので、関連するアルゴリズムの実装はダブル リンク リストよりも少し複雑になる場合があります。そのため、この記事では単一のリンク リストを例として説明します。単結合リスト内のデータはノードによって表され、各ノードの構成は要素 (データ要素のイメージ) + ポインタ (次の要素の格納場所を示す) です。要素はデータ要素の格納単位です。ノードのアドレス データ、つまり単一リンク リストの各ノードには、次の図に示すように、データ フィールドと次のフィールドが含まれます。
単一リンクリストの論理構造の概略図は次のとおりです。
理解を容易にするために、編集者は単一リンク リストについて次の点を要約します。
①リンクリストはノードの形式で保存されます。
②シングルリンクリストの各ノードにはデータフィールド(データを格納する)とネクストフィールド(次のノードを指す)が含まれ、ダブルリンクリストにはプレフィールド(前のノードを指す)も含まれます。
③各ノードは必ずしも順番に格納されているわけではありません。つまり、次のノードの位置がノードの次のアドレスであるとは限りません。つまり、リンクリスト内のデータ要素の論理は整然としており、データ要素は物理ストレージユニット内に故障中
④ リンクリストの先頭ノードがnullか、先頭ノードがnullでない コード要件に応じて選択してください
単一リンクリストのノード構造:
class Node<V>{
V data;
Node next;
}
二重リンク リストのノード構造には、単一リンク リストよりもノード タイプの pre 変数が 1 つ多くあります。
class Node<V>{
V data;
Node next;
Node pre;
}
2. コードは単一リンクリストを実現します (追加、削除、変更、チェックを含む)。
例を使用して、コードで単純な単一リンクリストを記述し、追加、削除、変更、およびクエリの機能を実現する方法を説明します。
//测试代码
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode newHero2 = new HeroNode(2, "卢哥", "火麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//不考虑排序的加入方式
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.showSingleLinkedList();
System.out.println("==========================================");
singleLinkedList.reverseList(singleLinkedList.head);
singleLinkedList.showSingleLinkedList();
//考虑排序的加入方式
/*singleLinkedList.addByOrder(hero3);
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.showSingleLinkedList();*/
//修改单链表
/*System.out.println("==========================================");
System.out.println("修改后的单链表");
singleLinkedList.update(newHero2);
singleLinkedList.showSingleLinkedList();*/
//删除单链表
/*System.out.println("==========================================");
System.out.println("删除后的单链表");
singleLinkedList.del(2);
singleLinkedList.showSingleLinkedList();*/
}
}
//单链表
class SingleLinkedList {
public SingleLinkedList() {
}
HeroNode head = new HeroNode(0, "", "");
//无顺序添加
public void add(HeroNode heroNode) {//英雄节点直接添加在单链表的最后,不考虑排序问题
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
//有顺序添加
public void addByOrder(HeroNode heroNode) {//考虑排序问题,讲添加的英雄节点加在指定位置
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {//表示temp已经在链表的最后位置,直接在该temp位置插入
break;
}
if (temp.next.no > heroNode.no) {//表示位置(中间位置)已找到,就在temp处插入
break;
}
if (temp.next.no == heroNode.no) {//表示添加的英雄节点的编号已经在单链表中出现
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("编号存在,添加失败");
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点信息,根据节点编号no修改
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("单链表为空");
return;//这条语句的作用是直接结束,不执行下面的程序
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else {
System.out.printf("编号为%d的节点没有找到,不能修改", newHeroNode.no);
}
}
//删除节点
public void del(int no) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.printf("没有找到编号为%d的节点", no);
}
}
//显示单链表
public void showSingleLinkedList() {
if (head.next == null) {
System.out.println("单链表是空的");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
//创建一个类HeroNode,用来代表单链表中的各个节点
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
//为了显示英雄信息,可以不用再写一个show()方法,直接重写toString()方法即可
//重写之后,直接println一个HeroNode变量时,就会按照重写的格式输出,而不是按照默认格式
//不重写toString()方法那么系统有自己的默认输出,具体见课本359页
@Override
public String toString() {
return "HeroNode{ no=" + no + ", name='" + name + '\'' + ", " +
"nickName='" + nickName + '\'' + '}';
}
}
3. 単一リンクリストの古典的な質問タイプ
3.1 逆単一リンクリスト
アイデア:
① まず、新しいヘッド ノード オブジェクト newHead を作成します。
② 2 つの補助変数 cur と temp を定義します。これらは単一リンク リストをトラバースするために使用されます。cur は現在のノードを指すために使用され、temp は現在のノードの次のノードを記録するために使用されます。
③ 現在のノードに移動し、ノードを取り出して新しいヘッド ノードの後ろに置きます (cur.next=newHead.next; newHead.next=cur は、取り出したノードを前後に接続し、ルートを後ろに接続する必要があることを意味します)ノードを最初に接続する必要があります。ワイヤーを一緒に接続してから、前のワイヤーを接続します。順序は変更できません)
④元のリンクリストのhead.nextをnewHead.nextに接続します。
コードは次のように実装されます。
//将单链表反转
public void reverseList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode temp = null;
HeroNode newHead = new HeroNode(0, "", "");
while (cur != null) {
temp = cur.next;
cur.next = newHead.next;
newHead.next = cur;
cur = temp;
}
head.next = newHead.next;
}
3.2 単結合リストが回文構造かどうかを判断する
この質問タイプは次の 3 つの方法で行うことができます。
方法 1 (筆記テストに適しています。筆記テストの時間計算量は低く、この方法は空間計算量が高くなりますが、コードは最も簡単です): スタック スペースを開きます (スタックの特性: 先入れ、後入れ)。外);
方法 2: 高速ポインターと低速ポインターを使用する (開始点が異なります。高速ポインターと低速ポインターの戦略は自分で検討する必要があります) + スタック。
方法 3 (インタビューに適しています。3 つの方法の時間計算量は似ていますが、方法 3 の空間計算量は最も低い o(1) であり、変数の数は限られています): 高速ポインターと低速ポインターのみを使用し、他のデータ構造を借用しないでください。
import java.util.Stack;
public class Question1 {
//这里的head都是value有值的节点
//方法1(适合笔试,笔试时间复杂度低就行,这个方法空间复杂度高,但是代码最容易):开辟一块栈空间(栈的特点:先进后出)
public static boolean isPalindrome1(Node head) {
Stack<Node> stack = new Stack<>();
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (head != null) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
//方法2:使用快慢指针(起点不同,快慢指针的策略需要自己思考)+栈
public static boolean isPalindrome2(Node head) {
if (head == null || head.next == null) {
return true;
}
Node right = head.next;
Node cur = head;
while (cur.next != null && cur.next.next != null) {
right = right.next;
cur = cur.next.next;
}
Stack<Node> stack = new Stack<>();
while (right != null) {
stack.push(right);
right = right.next;
}
while (!stack.isEmpty()) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
//方法3(适合面试,3种方法的时间复杂度都差不多,但方法3空间复杂度最低o(1),只有有限几个变量):只使用快慢指针,不借用其他数据结构
public static boolean isPalindrome3(Node head) {
if (head == null || head.next == null) {
return true;
}
//n1慢指针,一次一步,n2快指针,一次两步,等走完之后,n1指的位置正好是链表中点位置
Node n1 = head;
Node n2 = head;
while (n2.next != null && n2.next.next != null) {
n1 = n1.next;
n2 = n2.next.next;
}
//将n1之后的节点逆序,n1.next指向null
n2 = n1.next;
n1.next = null;
Node n3 = null;
while (n2 != null) {
n3 = n2.next;
n2.next = n1;
n1 = n2;
n2 = n3;
}
//进行比对
n3 = n1;
n2 = head;
boolean res = true;
while (n1 != null && n2 != null) {
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next;
n2 = n2.next;
}
//将后半段链表逆序回来,恢复原样
n1 = n3.next;
n3.next = null;
while (n1 != null) {
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
//测试代码
public static void main(String[] args) {
Node data1 = new Node(6);
Node data2 = new Node(8);
Node data3 = new Node(9);
Node data4 = new Node(28);
Node data5 = new Node(9);
Node data6 = new Node(8);
Node data7 = new Node(6);
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(data1);
singleLinkedList.add(data2);
singleLinkedList.add(data3);
singleLinkedList.add(data4);
singleLinkedList.add(data5);
singleLinkedList.add(data6);
singleLinkedList.add(data7);
singleLinkedList.showSingleLinkedList();
System.out.println(isPalindrome3(data1));
}
}
class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
public Node() {
}
//为了显示英雄信息,可以不用再写一个show()方法,直接重写toString()方法即可
//重写之后,直接println一个HeroNode变量时,就会按照重写的格式输出,而不是按照默认格式
//不重写toString()方法那么系统有自己的默认输出,具体见课本359页
@Override
public String toString() {
return "Node{ value=" + value + '}';
}
}
//作用:创建单链表(多个节点连接起来)
class SingleLinkedList {
public SingleLinkedList() {
}
//这里的head是空节点
Node head = new Node();
//无顺序添加
public void add(Node node) {//英雄节点直接添加在单链表的最后,不考虑排序问题
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
//显示单链表
public void showSingleLinkedList() {
if (head.next == null) {
System.out.println("单链表是空的");
return;
}
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
3.3 ランダムなポインタノードを含むリンクリストをコピーする
特殊节点定义
class Node{
int value;
Node next;
Node rand;//rand指针可能指向链表中任意一个节点,也可能指向null
}
方法① (筆記試験に適しています): ハッシュ テーブルを作成します。ハッシュ テーブルはキーと値のペア構造になっているため、元のリンク リストがキーとして使用され、コピーされたリンク リストが値の位置に配置されます。
方法② (他のデータ構造を借用せず、インタビューに適していますが、コードが複雑です): 最初のノードをコピーして、最初のノードのすぐ後ろ、2 番目のノードの前に配置すると、単結合リストは 2n になります。次に、next と rand をそれぞれ決定し、最後に元のリンク リストとコピーされたリンク リストを分離します。
方法①のコード実装は以下のとおりです。
import java.util.HashMap;
public class Question3 {
public static void main(String[] args) {
//测试代码自己写
//先创建一个由随机节点组成的单链表,往单链表中加节点
//然后在调用copyLinkedList1函数
}
//使用哈希表复制单链表,哈希表中存放键值对(java中叫Entry)
public static RandomNode copyLinkedList1(RandomNode head){
HashMap<RandomNode,RandomNode> map=new HashMap<>();
RandomNode cur=head;
while (cur!=null){
map.put(cur,new RandomNode(cur.value));
cur=cur.next;
}
cur=head;
while (cur!=null){
//cur 老节点
//map.get(cur) 新节点,也就是复制的节点
map.get(cur).next=map.get(cur.next);
map.get(cur).rand=map.get(cur.rand);
cur=cur.next;
}
return map.get(head);
}
}
class RandomNode{
int value;
RandomNode next;
RandomNode rand;
public RandomNode() {
}
public RandomNode(int value) {
this.value = value;
}
}