• OptimalSolution(3)--链表问题(1)简单


      单链表Node节点类

    public class Node {
    
        public int val;
        public Node next;
    
        public Node(int val) {
            this.val = val;
        }
    }

      双链表DoubleNode类

    public class DoubleNode {
    
        public int val;
        public DoubleNode last;
        public DoubleNode next;
        
        public DoubleNode(int val) {
            this.val = val;
        }
    }

      

      一、打印两个有序链表的公共部分

      问题:给定两个有序链表head1和head2,打印两个链表的公共部分

      解答:(1)如果head1的值小于head2,则head1向下移动。(2)如果head2的值小于head1的值,则head2向下移动。(3)如果head1的值等于head2的值,则打印这个值,然后head1和head2都往下移动。(4)head1或head2有任意一个移动到null,则整个过程停止

        public void printCommonPart(Node head1, Node head2) {
            while (head1 != null && head2 != null) {
                if (head1.val < head2.val) {
                    head1 = head1.next;
                } else if (head1.val > head2.val) {
                    head2 = head2.next;
                } else {
                    System.out.print(head1.val + " ");
                    head1 = head1.next;
                    head2 = head2.next;
                }
            }
            System.out.println();
        }
    打印两个有序链表的公共部分

      

      二、在单链表和双链表中删除倒数第K个节点

      题目1:删除单链表中倒数第K个节点

      在删除问题中,如果想要删除某一个节点,就必须定位到要删除节点的前一个节点。

      解答:

      (1)如果链表为空或者K值小于1,则直接返回即可。否则让链表从头开始走到尾,每移动一步,就让K值减1,链表走到结尾时,判断K值的大小

      (2)如果K值大于0(1→2→3,K=4,K:3 2 1),说明链表没有倒数第K个节点,直接返回即可。

      (3)如果K值等于0(1→2→3,K=3,K:2 1 0),说明倒数第K个节点就是头节点,直接删除头节点,返回head.next即可

      (4)如果K值小于0(1→2→3,K=2,K:1 0 -1),此时,重新从头节点开始走,每移动一步,就让K的值加1,当K等于0时,移动停止,此时的节点就是要删除节点的前一个节点。(假设链表长度为N,那么倒数第K个节点的前一个节点就是第N-K个节点,第一次遍历后,K变为K-N,第二次遍历后,遍历到第N-K个节点时K为0)

        public Node removeLastKthNode(Node head, int lastKth) {
            if (head == null || lastKth < 1) {
                return head;
            }
            Node cur = head;
            while (cur != null) {
                lastKth--;
                cur = cur.next;
            }
            if (lastKth == 0) {
                head = head.next;
            }
            if (lastKth < 0) {
                cur = head;
                while (++lastKth != 0) {
                    cur = cur.next;
                }
                cur.next = cur.next.next;
            }
            return head;
        }
    删除单链表中倒数第K个节点

      题目2:删除双链表中倒数第K个节点(和单链表同理,只是注意last指针的重连即可)

        public DoubleNode removeLastKthNode(DoubleNode head, int lastKth) {
            if (head == null || lastKth < 1) {
                return head;
            }
            DoubleNode cur = head;
            while (cur != null) {
                lastKth--;
                cur = cur.next;
            }
            if (lastKth == 0) {
                head = head.next;
                head.last = null;
            }
            if (lastKth < 0) {
                cur = head;
                while (++lastKth != 0) {
                    cur = cur.next;
                }
                DoubleNode newNext = cur.next.next;
                cur.next = newNext;
                if (newNext != null) {
                    newNext.last = cur;
                }
            }
            return head;
        }
    删除双链表中倒数第K个节点

      

      三、删除链表的中间节点和a/b处的节点

      题目1:删除链表的中间节点,例如1→2删除节点1,1→2→3删除节点2,1→2→3→4删除节点2,1→2→3→4→5删除节点3

      解答:如果链表为空或长度为1,则直接返回,如果链表长度为2,删除头节点即可,则3-2,4-2,5-3,6-3...,也就是长度每增加2,要删除的节点就后移一个节点。

        public Node removeMidNode(Node head) {
            if (head == null || head.next == null) {
                return head;
            }
            if (head.next.next == null) {
                return head.next;
            }
            Node pre = head;
            Node cur = head.next.next;
            while (cur.next != null && cur.next.next != null) {
                pre = pre.next;
                cur = cur.next.next;
            }
            pre.next = pre.next.next;
            return head;
        }
    删除链表的中间节点

      题目2:删除位于a/b处节点,链表1→2→3→4→5,假设a/b=r,则r==0不删除任何节点,r在(0,1/5]上删除节点1,r在(1/5,2/5]删除节点2,如果r大于1,不删除任何节点

      解答:根据链表长度n,以及a和b先计算出n*(a/b)的值,然后r向上取整就是要删除的节点的位置。

        public Node removeByRatio(Node head, int a, int b) {
            if (a < 1 || a > b) {
                return head;
            }
            int n = 0;
            Node cur = head;
            while (cur != null) {
                n++;
                cur = cur.next;
            }
            n = (int)Math.ceil((double) (a * n) / (double)b);
            if (n == 1) {
                head = head.next;
            }
            if (n > 1) {
                cur = head;
                while (--n != 1) {
                    cur = cur.next;
                }
                cur.next = cur.next.next;
            }
            return head;
        }
    删除单链表中a/b处的节点

      四、反转单向和双向链表

      题目1:反转单向链表

        public Node reverseList(Node head) {
            Node pre  = null;
            Node next = null;
            while (head != null) {
                next = head.next;
                head.next = pre;
                pre = head;
                head = next;
            }
            return pre;
        }
    反转单链表

      题目2:反转双向链表

        public DoubleNode reverseList(DoubleNode head) {
            DoubleNode pre  = null;
            DoubleNode next = null;
            while (head != null) {
                next = head.next;
                head.next = pre;
                head.last = next;
                pre = head;
                head = next;
            }
            return pre;
        }
    反转双向链表

      五、反转部分单向链表

      题目:给定一个单链表和两个整数from和to,把第from个到第to个节点这一部分进行反转。例如1→2→3→4→5→null,from=2,to=4,则1→4→3→2→5→null

      解答:

      (1)如果1<=from<=to<=N不满足,返回原来的头节点

      (2)找到第from-1个节点fPre和第to+1个节点tPos,把反转的部分先反转,然后正确连接fPre和tPos

      (3)如果fPre为null,说明反转部分是包含头节点的,返回每反转以前反转部分的最后一个节点作为新的头节点,如果fPre不为null,返回旧的头节点。

        public Node reversePart(Node head, int from, int to) {
            int len = 0;
            Node node1 = head;
            Node fPre = null;
            Node tPos = null;
            while (node1 != null) {
                len++;
                fPre = len == from - 1 ? node1 : fPre;
                tPos = len == to +   1 ? node1 : tPos;
                node1 = node1.next;
            }
            if (from > to || from < 1 || to > len) {
                return head;
            }
            node1 = fPre == null ? head : fPre.next;
            Node node2 = node1.next;
            node1.next = tPos;
            Node next = null;
            while (node2 != tPos) {
                next = node2.next;
                node2.next = node1;
                node1 = node2;
                node2 = next;
            }
            if (fPre != null) {
                fPre.next = node1;
                return head;
            }
            return node1;
        }
    反转部分单向链表

      六、两个单链表生成相加链表

      问题:9→3→7代表整数937,6→3代表整数63,将两个链表相加得到937+63=1000,返回1→0→0→0

      思路:如果先分别算出各自链表所代表的整数,那么链表的长度很长,可以表示一个很大的数,转换成int时可能会异常

      解法1:利用栈结构

      (1)将两个链表从左到右遍历,并压入各自的栈中。因此s1:9 3 7,s2:6 3,

      (2)将s1和s2同步弹出,然后相加链表即可,同时还需要关注是否有进位  

      (3)当s1和s2都为空时,如果进位信息为1,还要生成一个节点值为1的新结点

       注意:两个整数相加与进位问题。1. n1 = s1.isEmpty() ? 0 : s1.pop();2.n = n1 + n2 + ca;3.node = new Node(n % 10);4.ca = n / 10;

       注意:从后往前生成链表的过程。 pre = node; node = new Node(n % 10); node.next = pre;

        public Node addLists1(Node head1, Node head2) {
            Stack<Integer> s1 = new Stack<>();
            Stack<Integer> s2 = new Stack<>();
            while (head1 != null) {
                s1.push(head1.val);
                head1 = head1.next;
            }
            while (head2 != null) {
                s2.push(head2.val);
                head2 = head2.next;
            }
            int ca = 0;
            int n1 = 0, n2 = 0;
            int n = 0;
            Node node = null;
            Node pre  = null;
            while (!s1.isEmpty() || !s2.isEmpty()) {
                n1 = s1.isEmpty() ? 0 : s1.pop();
                n2 = s2.isEmpty() ? 0 : s2.pop();
                n = n1 + n2 + ca;
                pre = node;
                node = new Node(n % 10);
                node.next = pre;
                ca = n / 10;
            }
            if (ca == 1) {
                pre = node;
                node = new Node(1);
                node.next = pre;
            }
            return node;
        }

      解法2:利用链表的逆序

      (1)将两个链表逆序,依次得到从低位到高位的数字

      (2)遍历两个逆序后的链表,过程和解法1类似

      需要注意:1.1 = c1 != null ? c1.val : 0; 2.c1 = c1 != null ? c1.next : null; 3.完成后还需要reverseList(head1); reverseList(head2);将原来的链表返回原状。

        public Node addList2(Node head1, Node head2) {
            head1 = reverseList(head1);
            head2 = reverseList(head2);
            int ca = 0, n1 = 0, n2 = 0, n = 0;
            Node c1 = head1;
            Node c2 = head2;
            Node node = null;
            Node pre  = null;
            while (c1 != null || c2 != null) {
                n1 = c1 != null ? c1.val : 0;
                n2 = c2 != null ? c2.val : 0;
                n = n1 + n2 + ca;
                pre = node;
                node = new Node(n%10);
                node.next = pre;
                ca = n / 10;
                c1 = c1 != null ? c1.next : null;
                c2 = c2 != null ? c2.next : null;
            }
            if (ca == 1) {
                pre = node;
                node = new Node(1);
                node.next = pre;
            }
            reverseList(head1);
            reverseList(head2);
            return node;
        }
    利用链表的逆序

      七、删除无序单链表中值重复出现的节点

      问题:给定一个无序单链表,删除其中值重复出现的节点,例如1→2→3→3→4→4→2→1→1→null,返回1→2→3→4→null。

      解法1:利用哈希表,但是时间复杂度为O(N),空间复杂度为O(N)

      最近一个没有被删除的节点为pre,当前节点cur的值如果出现过,就令pre.next=cur.next;如果没出现过,就将cur的值加入到哈希表,同时令pre=cur。

        public void removeRep1(Node head) {
            if (head == null) {
                return;
            }
            HashSet<Integer> set = new HashSet<>();
            Node pre = head;
            Node cur = head.next;
            set.add(head.val);
            while (cur != null) {
                if (set.contains(cur.val)) {
                    pre.next = cur.next;
                } else {
                    set.add(cur.val);
                    pre = cur;
                }
                cur = cur.next;
            }
        }
    删除无序单链表中重复节点

      解法2:利用选择排序的过程,时间复杂度为O(N2),空间复杂度为O(1)(直接选择排序时间复杂度为O(N2))

      例如1→2→3→3→4→4→2→1→1→null,(1)cur值为1,检查后面所有值为1的节点全部删除(2)cur值为2,检查后面值为2的节点全部删除...依次类推

        public void removeRep2(Node head) {
            Node cur = head;
            Node pre = null;
            Node next = null;
            while(cur != null) {
                pre = cur;
                next = cur.next;
                while (next != null) {
                    if (cur.val == next.val) {
                        pre.next = next.next;
                    } else {
                        pre = next;
                    }
                    next = next.next;
                }
                cur = cur.next;
            }
    直接选择排序思想删除重复节点

      八、在单链表删除指定值的节点

      题目:给定一个单链表和一个整数num,删除值为num的全部节点。例如:1→2→3→4→null,num=3,返回:1→2→4→null

      方法1:O(N)+O(N)利用栈或者其他容器收集值不等于num的节点,收集完成后重新连接即可。最后返回栈底节点为新的头结点

        public Node removeValue1(Node head, int num) {
            Stack<Node> stack = new Stack<>();
            while(head != null) {
                if (head.val != num) {
                    stack.push(head);
                }
                head = head.next;
            }
            while (!stack.isEmpty()) {
                stack.peek().next = head;    // head一开始为null
                head = stack.pop();
            }
            return head;
        }
    利用栈删除单链表中指定值

      方法2:O(N)+O(1)不用任何容器直接调整。找到第一个不为num的节点作为新的头结点newHead,继续往后遍历,如果cur的值等于num,就将cur删除,即令最近一个值不等于num的pre.next=cur.next,如果不等于,就令pre=cur

        public Node removeValue2(Node head, int num) {
            while (head != null) {
                if (head.val != num) {
                    break;
                }
                head = head.next;
            }
            Node pre = head;
            Node cur = head;
            while(cur != null) {
                if (cur.val == num) {
                    pre.next = cur.next;
                } else {
                    pre = cur;
                }
                cur = cur.next;
            }
            return head;
        }
    直接调整法删除单链表中指定值

      

      九、单链表的选择排序

      题目:给定一个无序单链表,实现单链表的选择排序,要求空间复杂度为O(1)

      选择排序就是:从未排序的部分中找到最小值,然后放在排好序部分的尾部,逐渐将未排序的部分缩小,最后全部变成排好序的部分。

      解法:O(N2)+O(1)

      (1)找到最小值节点,将其设置成新的头节点newHead

      (2)每次在未排序的部分中找到最小值的节点,然后把这个节点从未排序的链表中删除,同时连接删除节点的前后节点

      (3)把删除的节点连接在排好序部分的链表尾部

        public Node getSmallestPreNode(Node head) {
            Node smallPre = null;
            Node small = head;
            Node pre = head;
            Node cur = head.next;
            while (cur != null) {
                if (cur.val < small.val) {
                    smallPre = pre;
                    small = cur;
                }
                pre = cur;
                cur = cur.next;
            }
            return smallPre;// 返回以head为头结点的单链表中最小值节点的前一个节点
        }
        
        public Node selectionSort(Node head) {
            Node tail = null;        // 已排序部分尾部
            Node cur  = head;        // 未排序部分头部
            Node smallPre = null;    // 最小节点的前一个节点
            Node small = null;        // 值最小的节点
            while (cur != null) {
                small = cur;
                smallPre = getSmallestPreNode(cur);
                if (smallPre != null) {
                    // small为未排序部分的最小值
                    small = smallPre.next;
                    // 将最小值节点从未排序的部分删除
                    smallPre.next = small.next;
                }
                // smallPre为null时,说明当前节点cur已经是最小值,因此在原链表中删除头节点
                cur = cur == small ? cur.next : cur;
                if (tail == null) {
                    head = small;
                } else {
                    tail.next = small;
                }
                tail = small;
            }
            return head;
        }

      十、一种怪异的节点删除方式

      问题:给定一个链表的节点node,但不给定整个整个链表的头节点,如何在链表中删除node?并分析出现的问题

      思路:例如1→2→3→null,如果要删除节点2,那么在仅给定节点2的情况下,将节点2的值变成3,然后删除结点3即可。(在链表中删除节点的原则,必须定位到待删节点的上一个节点)

      可能出现的问题:

      (1)无法删除最后一个节点。因为如果要删除节点3,由于根本没有下一个节点来代替节点3被删除,那么只有让节点2的next指向null这一种办法,但是又根本找不到节点2,所以就无法正确删除节点3。

      (2)这种删除方式在本质上根本不是删除了node节点,而是把node节点的值改变,然后删除node的下一个节点。在实际工程中,一个节点可能代表很复杂的结构,节点的复制会相当复杂,或者可能改变节点值这个操作都是被禁止的;或者工程上一节点代表提供服务的一个服务器,外界对每个节点都有很多依赖,例如,在删除节点2时,实际上影响了节点3对外提供的服务。

      注意:异常的抛出,RuntimeException异常类不需要导包。

        public void removeNodeWired(Node node) {
            if (node == null) {
                return;
            }
            Node next = node.next;
            if (next == null) {
                throw new RuntimeException("can not remove last node.");
            }
            node.val = next.val;
            node.next = next.next;
        }

      十一、向有序的环形单链表中插入新节点

      问题:一个环形单链表从头节点head开始不降序,同时由最后的节点指向头节点。给定一个环形单链表的头节点和一个整数num,生成节点值为num的新节点,并插入到这个环形链表中,保证调整后的链表依然有序。

      解法:

      (1)生成节点值为num的新节点,记为node

      (2)如果链表为空,直接返回node

      (3)令pre=head,cur=head.next,令pre和cur同步移动,如果pre的节点值小于或等于num,并且cur的节点值大于或等于num,说明node应该在pre和cur节点之间插入

      (4)如果pre和cur转了一圈都没有发现插入位置,说明node应该插入到头节点的前面,此时是因为链表中每个节点都大于num或者都小于num。如果num比每个节点值都大,返回原来的头节点即可;如果num比每个节点的值都小,应该把node作为链表新的头节点返回。

        public Node insertNum(Node head, int num) {
            Node node = new Node(num);
            if (head == null) {
                node.next = node;
                return node;
            }
            Node pre = head;
            Node cur = head.next;
            while (cur != head) {
                if (pre.val <= num && cur.val >= num) {
                    break;
                }
                pre = cur;
                cur = cur.next;
            }
            pre.next = node;
            node.next = cur;
            return head.val < num ? head : node;
        }
    向有序的环形单链表中插入节点

      

      十二、合并两个有序的单链表

      题目:给定两个有序单链表head1和head2,返回合并后的链表。例如:0→2→3→7→null,1→3→5→7→9→null,返回0→1→2→3→3→5→7→7→9→null

      解法:(O(M+N) + O(1))

      (1)如果两个链表有一个为空,返回非空链表头节点

      (2)head1和head2中小的节点就是新链表的头节点

      (3)每次比较遍历到的值,根据大小关系作出调整,同时用一个变量pre表示上次比较时值较小的节点

    链表1:1→5→6→null, 链表2:2→3→7→null
    cur1=1,cur2=2,pre=null,cur1小于cur2,不作调整,pre=cur1
    cur1=5,cur2=2,pre=1,cur2小于cur1,pre.next指向cur2,cur2.next指向cur1,pre=2,此时1→2→5→6→null,3→7→null,
    cur1=5,cur2=3,pre=2,cur2小于cur1,pre.next指向cur2,cur2.next指向cur1,pre=3,此时1→2→3→5→6→null,7→null
    cur1=5,cur2=7,pre=3,cur1小于cur2,不作调整,pre=5
    cur1=6,cur2=7,pre=5,cur1小于cur2,不作调整,pre=6,
    此时cur1=null,pre为链表1的最后一个节点,把pre.next指向cur2

      (4)如果链表1先走完,此时cur1=null,pre为链表1的最后一个节点,那么就把pre的next指针指向链表2的当前节点,即cur2;如果链表2先走完,说明链表2的所有节点已经插入到链表1中,此时返回新的头节点即可。

      注意:pre这个变量在这道题里面所起到的作用。 pre.next = cur1 == null ? cur2 : cur1;

        public Node merge(Node head1, Node head2) {
            if (head1 == null || head2 == null) {
                return head1 != null ? head1 : head2;
            }
            Node head = head1.val < head2.val ? head1 : head2;
            Node cur1 = head == head1 ? head1 : head2;
            Node cur2 = head == head1 ? head2 : head1;
            Node pre = null;
            Node next = null;
            while (cur1 != null && cur2 != null) {
                if (cur1.val <= cur2.val) {
                    pre = cur1;
                    cur1 = cur1.next;
                } else {
                    next = cur2.next;
                    pre.next = cur2;
                    cur2.next = cur1;
                    pre = cur2;
                    cur2 = next;
                }
            }
            pre.next = cur1 == null ? cur2 : cur1;
            return head;
        }

      十三、按照左右半区的方式重新组合单链表

      问题:给定一个长度为N的单链表,如果N为偶数,那么前N/2个节点为左半区,后N/2个节点为右半区;如果N为奇数,那么前N/2个节点算作左半区,后N/2+1个节点为右半区,将单链表调整成L1→R1→L2→R2..的形式。例如:1→2→3→4→null调整为1→3→2→4→null,1→2→3→4→5→null调整为1→3→2→4→5→null

      解法:O(N)+O(1)

      (1)如果链表为空或长度为1,则直接返回

      (2)遍历一遍找到左半区的最后一个节点,记为mid,这里涉及到之前做过的题,求中间节点的方法:即从长度为2开始,长度每增加2,mid就向后移动一个节点。例如:12mid为1,123mid为1,1234mid为2,12345mid为2,123456mid为3...

      (3)将左右半区分离成2个链表即mid.next=null,头结点分别为原来的head记为left,和mid.next记为right。

      注意:1.mergeLR方法是将right链表的每一个节点依次插入到left的合适位置中。

         2.求链表中间节点mid的方法: Node mid = head; Node right = head.next; while (right.next != null && right.next.next != null) { mid = mid.next; right = right.next.next; }

        public void relocate(Node head) {
            if (head == null || head.next == null) {
                return;
            }
            Node mid = head;
            Node right = head.next;
            while (right.next != null && right.next.next != null) {
                mid = mid.next;
                right = right.next.next;
            }
            right = mid.next;
            mid.next = null;
            mergeLR(head, right);
        }
        
        public void mergeLR(Node left, Node right) {
            Node next = null;
            while (left.next != null) {
                next = right.next;
                right.next = left.next;
                left.next = right;
                left = right.next;
                right = next;
            }
            left.next = right;
        }
  • 相关阅读:
    javascript中数据属性的一些小结
    求教BFC的一些疑惑
    snowinmay.net学习运用
    4月份总结(二)
    瀑布流布局浅析(转)
    JS函数的参数(arguments)的使用(形参和实参)
    网页布局什么时候用图片背景,什么时候用img标签?
    chrome,ff浏览器默认行高
    盒模型中的内容讨论
    Cow Pedigrees(△)
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9646286.html
Copyright © 2020-2023  润新知