单向链表(技巧)

Posted by 令德湖周杰伦 on 01-03,2024

1. 链表合并

1.1 两个链表节点相加后合并

思路:

  1. 遍历2个链表,如果其中一个链表为空,则默认使用0来相加,都为空stop
  2. 节点相加,进位使用一个flag记录
  3. 创建新链表3步法
  4. 最后如果flag为1,也要连接上
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode result = null;
        if(l1 == null && l2 == null){
            return result;
        }
        ListNode cur = result;
        int flag = 0;
        while (l1 != null || l2!=null){
            //如果某个链表为null,创建默认节点进行相加
            l1 = l1 == null ? new ListNode(0) : l1;
            l2 = l2 == null ? new ListNode(0) : l2;

            int count = l1.val + l2.val + flag;
            flag = count / 10;
            count = count % 10;

            ListNode node = new ListNode(count);
            //创建新节点,并连接起来
            if(result == null){
                //1.头节点赋值
                result = node;
            }else{
                //2.遍历指针连接到新节点
                cur.next = node;
            }
            //3.遍历指针指向最新的节点
            cur = node;



            l1 = l1.next;
            l2 = l2.next;
        }
        if(flag == 1){
            cur.next = new ListNode(flag);
        }
        return result;
    }

1.2. 两个有序链表合并

思路:

  1. 遍历2个链表,如果其中一个链表为空,创建默认最大节点,都为空stop
  2. 谁小谁移动
  3. 创建新链表3步法
public ListNode sortedListMerge(ListNode l1, ListNode l2) {
        ListNode result = null;
        if(l1 == null && l2 == null){
            return result;
        }
        ListNode cur = result;
        if(l1!=null || l2!=null){
            //如果某个链表为null,创建默认节点(最大整数)
            l1 = l1 == null ? new ListNode(Integer.MAX_VALUE) : l1;
            l2 = l2 == null ? new ListNode(Integer.MAX_VALUE) : l2;
            int min;
            //谁小谁移动
            if(l1.val <= l2.val){
                min = l1.val;
                //l1移动
                l1 = l1.next;
            }else {
                min = l2.val;
                //l2移动
                l2 = l2.next;
            }
            ListNode node = new ListNode(min);
            //创建新节点,并连接起来
            if(result == null){
                //1.头节点赋值
                result = node;
            }else{
                //2.遍历指针连接到新节点
                cur.next = node;
            }
            //3.遍历指针指向最新的节点
            cur = node;
        }
        return result;
    }

1.3 合并K个有序链表

思路:

  1. 遍历,所有数据建立小顶堆
  2. poll
  3. 创建新链表3步法
public ListNode mergeKLists(ListNode[] lists) {
       if(lists == null || lists.length == 0){
            return null;
        }
        if(lists.length == 1){
            return lists[0];
        }

        PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        });
        //建立小顶堆
        for (int i = 0; i < lists.length; i++) {
            ListNode cur = lists[i];
            while (cur!=null){
                queue.add(cur);
                cur = cur.next;
            }
        }
        //遍历
        ListNode result = null;
        ListNode cur = result;
        while (!queue.isEmpty()){
            ListNode poll = queue.poll();
            ListNode node = new ListNode(poll.val);
            if(result == null){
                result = node;
            }else{
                cur.next = node;
            }
            cur = node;
        }
        return result;
    }

2. 链表回文

2.1 判断链表是否回文

思路:

  1. 快慢指针确定链表中点位置
  2. 从slow的下一个节点开始,逆转链表,逆转3指针兄弟上场
  3. 逆转完的链表头为copy,记得中点的slow指针的next断开,不然成环了
  4. 从链表头尾开始遍历,如果发现不同节点,直接返回false,直到遍历到中点位置
  5. 返回结果前,记得把链表恢复,还是使用逆转链表的方法(见基础篇2.7)
public boolean isPalindrome(ListNode head) {
       if(head == null){
            return false;
        }
        if(head.next == null){
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (slow.next !=null && fast.next != null && fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //此时slow在第(n+1)/2的位置上,中点位置
        //从slow的下一个节点开始,逆转链表,逆转3指针兄弟上场,逆转完的链表头为copy,
        ListNode pre = slow;
        ListNode cur = slow.next;
        ListNode copy = slow;
        while(cur!=null){
            pre = copy;
            copy = cur;
            cur = cur.next;
            copy.next = pre;
        }
        //slow的next断开,不然成环了
        slow.next = null;
        //从链表头尾开始遍历,如果发现不同节点,直接返回false,直到遍历到中点位置
        cur = head;
        pre = copy;
        boolean resultFlag = true;
        while(cur!=null && pre!=null){
            if(cur.val != pre.val){
                return false;
            }
            cur = cur.next;
            pre = pre.next;
        }
        //恢复链表,从链表尾部copy开始,重新逆转链表
        pre = null;
        cur = copy;
        copy = null;
        while(cur!=null){
            pre = copy;
            copy = cur;
            cur = cur.next;
            copy.next = pre;
        }
        return resultFlag;
    }

3. 随机链表的复制

思路:
1.遍历节点,克隆节点放在当前节点的后面,1 1' 2 2' 3 3'
2.复制后隔2个节点这样取,取到1,2,3,设置copy节点的random节点
3.split,将copy节点从链表上分离

public Node copyRandomList(Node head) {
        if(head == null){
            return head;
        }
        Node cur = head;
        Node a = null;
        //遍历节点,克隆节点放在当前节点的后面,1 1' 2 2' 3 3'
        while (cur != null){
            a = cur.next;
            cur.next = new Node(cur.val);
            cur.next.next = a;
            cur = a;
        }
        cur = head;
        Node b  = null;
        //2个2个取,设置copy节点的random节点
        while(cur!=null){
            a = cur.next.next;
            b = cur.next;
            b.random = cur.random!=null ? cur.random.next : null;
            cur = a;
        }
        //split,将copy节点从链表上分离
        Node result = head.next;
        cur = head;
        while(cur!=null){
            a = cur.next.next;
            b = cur.next;
            cur.next = a;
            b.next = a!=null ? a.next : null;
            cur = a;

        }
        return result;
    }

4. Josephu问题

描述:约瑟夫问题,已知n个人(编号1,2....n)坐在圆桌上,从编号k的位置开始报数,报到m时,这个人出列,他的下个人又从1开始报,报到m出列,重复这个动作,直到圆桌上的人全部出列。求出列的顺序。
思路:

  1. 建立一个具有n个节点的单向循环链表
  2. 确定第一个报数的位置
  3. 不断的从链表中删除一个节点,直至链表为空
public static List<Integer> josephus(int n, int k, int m){
        List<Integer> result = new ArrayList<>();
        //建立链表
        ListNode head = null;
        ListNode cur = head;
        for (int i = 1; i <=n  ; i++) {
            ListNode node = new ListNode(i);
            if(head == null){
                head = node;
            }else{
                cur.next = node;
            }
            cur = node;
        }
        //首位连接成单向循环链表
        cur.next = head;

        //确定第一个报数的位置
        cur = head;
        for (int j = 0; j < k-1; j++) {
            head = head.next;
            cur = head;
        }
        //此时,head和cur同时到达第一个报数的位置
        while(cur != null){
            //找到报数m,出列的位置
            for (int i = 0; i < m-1; i++) {
                cur = cur.next;
            }
            result.add(cur.val);
            //出列
            head.next = cur.next;
            cur.next = null;
            cur = head.next;
            head = cur;
        }
        return result;
    }