zoukankan      html  css  js  c++  java
  • 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;
        }
  • 相关阅读:
    Git 基础
    SharePoint 2013 对象模型操作"网站设置"菜单
    SharePoint 2013 隐藏部分Ribbon菜单
    SharePoint 2013 Designer系列之数据视图筛选
    SharePoint 2013 Designer系列之数据视图
    SharePoint 2013 Designer系列之自定义列表表单
    SharePoint 2013 设置自定义布局页
    SharePoint 2013 "通知我"功能简介
    SharePoint 2013 创建web应用程序报错"This page can’t be displayed"
    SharePoint 禁用本地回环的两个方法
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9646286.html
Copyright © 2011-2022 走看看