単連結リストアルゴリズムの質問タイプの概要

目次

1. 基本的な考え方    

2. コードは単一リンクリストを実現します (追加、削除、変更、チェックを含む)。

3. 単一リンクリストの古典的な質問タイプ

3.1 逆単一リンクリスト

3.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;
    }
}

おすすめ

転載: blog.csdn.net/Mike_honor/article/details/126058818