단일 연결 목록 알고리즘 질문 유형 요약

목차

1. 기본 개념    

2. 코드는 단일 연결 목록(추가, 삭제, 수정 및 확인 포함)을 구현합니다.

3. 단일 연결 목록의 고전적인 질문 유형

3.1 역방향 단일 연결 리스트

3.2 단일 연결 리스트가 회문 구조인지 확인

3.3 임의의 포인터 노드가 있는 연결 리스트 복사

1. 기본 개념    

        연결된 목록은 선형 목록에 데이터 구조를 저장하기 위해 임의의 주소가 있는 저장 단위 그룹을 사용하는 체인형 저장 데이터 구조입니다. 연결 리스트에는 단일 연결 리스트, 이중 연결 리스트 등 다양한 종류가 있습니다. 단일 연결 리스트의 설계 아이디어가 가장 기본이며 관련 알고리즘의 구현은 이중 연결 리스트보다 조금 더 복잡할 수 있습니다. 연결리스트이므로 이 글에서는 단일 연결리스트를 예로 들어 설명하겠습니다. 단일 연결 리스트의 데이터는 노드로 표시되며 각 노드의 구성은 요소(데이터 요소의 이미지) + 포인터(다음 요소의 저장 위치를 ​​나타냄)이며 요소는 저장 단위입니다. 노드의 주소 데이터, 즉 단일 연결 리스트의 각 노드는 아래 그림과 같이 데이터 필드와 다음 필드를 포함합니다.

         단일 연결 리스트의 논리적 구조를 도식적으로 표현하면 다음과 같습니다.

         이해의 편의를 위해 편집기는 단일 연결 목록에 대해 다음 사항을 요약합니다.

①링크드 리스트는 노드 형태로 저장됩니다.

② 단일 연결 리스트의 각 노드에는 데이터 필드(데이터 저장)와 다음 필드(다음 노드를 가리킴)가 포함되며, 이중 연결 리스트에도 사전 필드(이전 노드를 가리킴)가 포함됩니다.

③각 노드는 반드시 순차적으로 저장될 필요는 없습니다. 즉, 다음 노드의 위치가 반드시 노드의 다음 주소일 필요는 없습니다. 즉, 연결 리스트의 데이터 요소의 논리는 질서 있고 데이터 요소는 다음과 같습니다. 물리적 저장 장치에 있습니다. 고장났습니다.

④ 연결리스트의 헤드노드가 null이고 헤드노드가 null이 아닌 경우 코드 요구사항에 따라 선택

단일 연결 리스트의 노드 구조:

class Node<V>{
    V data;
    Node next;
}

이중 연결 목록의 노드 구조에는 단일 연결 목록보다 노드 유형의 사전 변수가 하나 더 있습니다.

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를 생성합니다.

②단일 연결 리스트를 순회하는 데 사용되는 두 개의 보조 변수 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 단일 연결 리스트가 회문 구조인지 확인

        이 질문 유형은 세 가지 방법으로 수행할 수 있습니다.

방법 1(필기 테스트에 적합하고 필기 테스트의 시간 복잡도가 낮으며 이 방법은 공간 복잡도가 높지만 코드가 가장 쉽습니다): 스택 공간을 엽니다(스택의 특성: 먼저 들어오고 마지막으로 밖으로);

방법 2: 빠른 포인터와 느린 포인터를 사용합니다(시작점이 다르므로 빠른 포인터와 느린 포인터의 전략은 스스로 고려해야 함) + 스택;

방법 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
}

방법 ① (필기 테스트에 적합): 해시 테이블을 생성합니다. 해시 테이블은 키-값 쌍 구조이므로 원래 연결 목록을 키로 사용하고 복사된 연결 목록을 값 위치에 배치합니다.

방법 ② (인터뷰에 적합, 다른 자료 구조를 차용하지 않고, 코드가 복잡함): 첫 번째 노드를 복사하여 첫 번째 노드 바로 뒤에 배치하고, 두 번째 노드 앞에 차례로 단일 연결 리스트가 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