1.编写代码,移除未排序链表中的重复结点。如果不得使用临时缓冲区,该怎么解决?

思路:直接迭代访问整个链表,将每个结点加入散列表。若发现有重复元素,则将该结点从链表中移除,然后继续迭代。使用链表。只需扫描一次即可。时间复杂度是o(N),N是链表结点数目。

import java.util.Hashtable;

class LinkedListNode {
    int data;
    LinkedListNode next;
}

public class DeleteNodes {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
                head.data = 1;
                LinkedListNode node = new LinkedListNode();
                node.data = 1;
                head.next = node;
                LinkedListNode tmp = head;
                while (tmp != null) {
                     System.out.println(tmp.data);
                     tmp = tmp.next;
               }
               tmp = deleteDups(head);
               System.out.println("after delete");
               while (tmp != null) {
                    System.out.println(tmp.data);
                    tmp = tmp.next;
               }

    }
    //使用临时缓冲区
    public static LinkedListNode deleteDups(LinkedListNode n) {
        Hashtable table = new Hashtable();
        LinkedListNode head = n;//保存头结点
        LinkedListNode current = head;
        while (n != null) {
            if (table.containsKey(n.data)) {
                current.next = n.next;
            } else {
                table.put(n.data, true);
                current = n;
            }
            n = n.next;
        }
        return head;
    }
}
View Code

如不借助额外缓冲区,可以用两个指针来迭代:current迭代访问整个链表,runner用于检查后续的结点是否重复。时间复杂度为o(N的平方),空间复杂度为o(1)。

import java.util.Hashtable;

class LinkedListNode {
    int data;
    LinkedListNode next;
}

public class DeleteNodes {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
        head.data = 1;
        LinkedListNode node = new LinkedListNode();
        node.data = 1;
        head.next = node;
        LinkedListNode tmp = head;
        while (tmp != null) {
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
        tmp = deleteDups(head);
        System.out.println("after delete");
        while (tmp != null) {
            System.out.println(tmp.data);
            tmp = tmp.next;
        }

    }

    //不使用临时缓冲区
    public static LinkedListNode deleteDups(LinkedListNode n) {
        if (n == null) {
            return n;
        }
        LinkedListNode head = n;
        LinkedListNode current = head;
        while (current != null) {
            LinkedListNode runner = current;
            while (runner.next != null) {
                if (runner.next.data == current.data) {
                    runner.next = runner.next.next;
                } else {
                    runner = runner.next;
                }
            }
            current = current.next;        
        }
        return head;
    }

}
View Code

1.1删除排序链表中的重复结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点出现一次,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->3->4->5

思路:遍历一次,遇到重复的节点删除(将前一节点的指针指向该节点的下一个节点)即可。

public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode node = head;
        while (node.next != null)  {
            if (node.val == node.next.val) {
                node.next = node.next.next;
            } else {
                node = node.next;
            }
        }
        return head;
    }
}
View Code

1.2删除排序链表中的重复结点II

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:

1.先给原链表添加一个头结点first方便处理;

2.使用3个指针,一个指向前一个节点pre,一个指向当前节点pHead,一个指向下一个节点pHead->next。

3.当当前节点跟后一个节点相等时,不断往后遍历,找到第一个不等于当前节点的节点,然后用 pre 指向它;

当当前节点跟后一个不相等时,将pre 后移指向pHead,pHead后移一位.。

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        ListNode first = new ListNode(Integer.MIN_VALUE);
        ListNode pre;
        first.next = pHead;
        pre = first;
        while(pHead != null && pHead.next != null) {
            if (pHead.val == pHead.next.val) {
                int val = pHead.val;
                while(pHead != null && pHead.val == val) {
                    pHead = pHead.next;
                }
                pre.next = pHead;
            } else {
                pre = pHead;
                pHead = pHead.next;
            }
        }
        return first.next;
    }
}
View Code

2.实现一个算法,找出单向链表中倒数第k个结点。

注意:k定义如下:传入k=1将返回最后一个结点,k=2返回倒数第二个结点,以此类推。

解法1:链表长度已知

若链表长度已知,倒数第k个结点就是第(length - k)个结点,直接迭代访问链表即可,但是过于简单!

解法2:递归

对问题略作调整,只打印倒数第k个结点的值。

public class NodeLast {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
        head.data = 1;
        LinkedListNode node = new LinkedListNode();
        node.data = 2;
        head.next = node;
        LinkedListNode node1 = new LinkedListNode();
        node1.data = 3;
        node.next = node1;
        nthToLast(head, 2);
    }
    
    //递归解法
    public static int nthToLast(LinkedListNode head, int k) {
        if (head == null) {
            return 0;
        }
        int i = nthToLast(head.next, k) + 1;
        if (i == k) {
            System.out.println(head.data);
        }
        return i;
    }

}
View Code

解法3:迭代:使用双指针p1和p2,并将它们指向链表中相距k个结点的两个结点。【要注意p2移动时i < k - 1】

先将p1和p2指向链表首结点,然后将p2向前移动k个结点。之后,我们以相同的速度移动这两个指针,p2会在移动length-k步后抵达链表尾结点。此时,p1会指向链表第length-k个结点,或者说倒数第k个结点。

public class NodeLast {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
                head.data = 1;
                LinkedListNode node = new LinkedListNode();
                node.data = 2;
                head.next = node;
                LinkedListNode node1 = new LinkedListNode();
                node1.data = 3;
                node.next = node1;
                LinkedListNode node2 = nthToLast(head, 2);
                System.out.println(node2.data);
    }

    //迭代解法
    public static LinkedListNode nthToLast(LinkedListNode head, int k) {
        if (head == null || k <= 0) {
            return null;
        }
        LinkedListNode p1 = head;
        LinkedListNode p2 = head;        
        // Move p2 k nodes into the list.  Keep p1 in the same position.
        for (int i = 0; i < k - 1; i++) { 
            if (p2 == null) {
                return null; // Error: list is too small.
            }
            p2 = p2.next;
        }
        if (p2 == null) { // Another error check.
            return null;
        }
        
        // Move them at the same pace.  When p2 hits the end, 
        // p1 will be at the right element.
        while (p2.next != null) {
            p1 = p1.next;
            p2 = p2.next;
          }
          return p1;
    }

}
View Code

3.实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。

解法:直接将后继结点的数据复制到当前结点,然后删除这个后续结点。

public class DeleteNode {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }
    
    public static boolean deleteNode(LinkedListNode n) {
        if (n == null || n.next == null) {//n.next==null表示待删除的结点为链表的尾结点,这个问题无解。
            return false;
        }
        LinkedListNode next = n.next;
        n.data = next.data;
        n.next = next.next;
        return true;
    }

}
View Code

4.编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。

解法一:直接创建两个链表:一个链表存放小于x的元素;另一个链表存放大于或等于x的元素。迭代访问整个链表,将元素插入before或after链表。一旦抵达链表末端,则表明拆分完成,最后合并两个链表。注意保存当前操作结点的后继结点然后把当前结点的后续结点置为0这一操作。

public class Partition {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
        head.data = 3;
        LinkedListNode node = new LinkedListNode();
        node.data = 2;
        head.next = node;
        LinkedListNode node1 = new LinkedListNode();
        node1.data = 1;
        node.next = node1;
        LinkedListNode tmp = head;
        while(tmp != null){
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
        tmp = partition(head,2);
        System.out.println("after change");
        while(tmp != null){
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
    }
    
    public static LinkedListNode partition(LinkedListNode node, int x) {
        LinkedListNode beforeStart = null;
        LinkedListNode beforeEnd = null;
        LinkedListNode afterStart = null;
        LinkedListNode afterEnd = null;
        while (node != null) {
            LinkedListNode next = node.next;//使用临时结点记下后续结点,以便下一次迭代时使用
            node.next = null;//将当前结点的后继结点置为 0
            if (node.data < x) {
                if (beforeStart == null) {
                    beforeStart = node;
                    beforeEnd = beforeStart;
                } else {
                    beforeEnd.next = node;
                    beforeEnd = node;
                }
            } else {
                if (afterStart == null) {
                    afterStart = node;
                    afterEnd = afterStart;
                } else {
                    afterEnd.next = node;
                    afterEnd = node;
                }
            }
            node = next;
        }
        if (beforeStart == null) {
            return afterStart;
        }
        beforeEnd.next = afterStart;
        return beforeStart;
    }

}
View Code

解法二:与第一种解法略有不同,结点不再追加至before和after链表的末端,而是插入这两个链表的前端。

public class Partition {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LinkedListNode head = new LinkedListNode();
                head.data = 3;
                LinkedListNode node = new LinkedListNode();
                node.data = 2;
                head.next = node;
                LinkedListNode node1 = new LinkedListNode();
                node1.data = 1;
                node.next = node1;
                LinkedListNode tmp = head;
                while(tmp != null){
                     System.out.println(tmp.data);
                     tmp = tmp.next;
                }
                tmp = partition(head,2);
                System.out.println("after change");
                while(tmp != null){
                     System.out.println(tmp.data);
                     tmp = tmp.next;
                }
    }
    
    //解法二
    public static LinkedListNode partition(LinkedListNode node, int x) {
        LinkedListNode beforeStart = null;
        LinkedListNode afterStart = null;
        while (node != null) {
            LinkedListNode next = node.next;//使用临时结点记下后续结点,以便下一次迭代时使用
            if (node.data < x) {
                //将结点插入before链表的前端
                node.next = beforeStart;
                beforeStart = node;
            } else {
                node.next = afterStart;
                afterStart = node;
            }
            node = next;
        }
        if (beforeStart == null) {
            return afterStart;
        }
        //找到beforeStart链表的末尾,合并两个链表
        LinkedListNode head = beforeStart;
        while (beforeStart.next != null) {
            beforeStart = beforeStart.next;
        }
        beforeStart.next = afterStart;
        return head;
        
    }


}
View Code

5.给定两个用链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。假设这些数位是正向存放的,请再做一遍。

解法:递归地模拟将两个结点的值逐一相加,如有进位则转入下一个结点这一过程。

class ListNode{
    int data;
    ListNode next;
    ListNode(int x){
        data = x;
    }
}

public class AddLists {

    public static void main(String[] args){
        // TODO Auto-generated method stub
        ListNode head = new ListNode(1);
        ListNode node = new ListNode(2);
        head.next = node;
        ListNode node1 = new ListNode(3);
        node.next = node1;
        ListNode tmp = head;
        while(tmp != null){
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
        tmp = sum(head,head);
        while(tmp != null){
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
    }

    public static ListNode sum (ListNode head1, ListNode head2){
        if(head1 == null) return head2;
        if(head2 == null) return head1;
        int n1 = 0;
        int n2 = 0;
        int sum = 0;
        int flag = 0;
        while (head1 != null) {
            n1 += head1.data * Math.pow(10, flag++);
            head1 = head1.next;
        }
        System.out.println("n1 = " + n1);
        flag = 0;
        while (head2 != null){
            n2 += head2.data * Math.pow(10, flag++);
            head2 = head2.next;
        }
        System.out.println("n2 = " + n2);
        sum = n1 + n2;
        System.out.println("sum = " + sum);
        int digit = sum % 10;
        ListNode head = new ListNode(digit);
        ListNode res = head;
        sum /= 10;
        while (sum != 0) {
            digit = sum % 10;
            ListNode n = new ListNode(digit);
            head.next = n;
            head = head.next;
            sum /= 10;
        }
        return res;
    }
}
View Code

进阶解法:

(1)一个链表的结点可能比另一个链表少,可以一开始先比较两个链表的长度并用零填充较短的链表。

(2)在前一个问题中,相加的结果不断追加到链表尾部(也即向前传递)。这就意味着递归调用会传入进位,而且会返回结果(随后追加到链表尾部)。

         进阶问题中的结果要加到首部(也即向后传递),递归调用必须返回结果和进位,通过创建一个PartialSum包裹类来解决这一点。

class ListNode1{
    int data;
    ListNode1 prev;
    ListNode1 next;
    ListNode1(int x, ListNode1 prev, ListNode1 next){
        data = x;
        this.prev = prev;
        this.next = next;
    }
}

class PartialSum {
    public ListNode1 sum = null;
    public int carry = 0;
}

public class AddListsMore {

    /*public static class PartialSum {
        public ListNode1 sum = null;
        public int carry = 0;
    }*/
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ListNode1 head = new ListNode1(1, null, null);
        ListNode1 node = new ListNode1(2, null, null);
        head.next = node;
        ListNode1 node1 = new ListNode1(3, null, null);
        node.next = node1;
        
        ListNode1 head1 = new ListNode1(4, null, null);
        ListNode1 node2 = new ListNode1(5, null, null);
        head1.next = node2;
        ListNode1 node3 = new ListNode1(6, null, null);
        node2.next = node3;
        ListNode1 node4 = new ListNode1(7, null, null);
        node3.next = node4;
        
        ListNode1 tmp = new ListNode1(0, null, null);
        tmp = addLists(head,head1);
        while (tmp != null) {
              System.out.println(tmp.data);
              tmp = tmp.next;
        }
      

    }
    
    public static ListNode1 addLists(ListNode1 l1, ListNode1 l2) {
        int len1 = length(l1);
        int len2 = length(l2);
        //用零填充较短的链表
        if (len1 < len2) {
            l1 = padList(l1, len2 - len1);
        } else {
            l2 = padList(l2, len1 - len2);
        }
        //对两个链表求和
        PartialSum sum = addListsHelper(l1, l2);
        //如有进位,则插入链表首部,否则,直接返回整个链表
        if (sum.carry == 0) {
            return sum.sum;
        } else {
            ListNode1 result = insertBefore(sum.sum, sum.carry);
            return result;
        }
        
        
    }
    //用零填充链表
    private static ListNode1 padList(ListNode1 l, int padding) {
        ListNode1 head = l;
        for (int i = 0; i < padding; i++) {
            ListNode1 n = new ListNode1(0, null, null);
            head.prev = n;
            n.next = head;
            head = n;
        }
        return head;
    }
    //辅助函数,将结点插入链表首部
    private static ListNode1 insertBefore(ListNode1 list, int data) {
        ListNode1 node = new ListNode1(data, null, null);
        if (list != null) {
            list.prev = node;
            node.next = list;
        }
        return node;
    }
    
    private static PartialSum addListsHelper(ListNode1 l1, ListNode1 l2) {
        if (l1 == null && l2 == null) {
            PartialSum sum = new PartialSum();
            return sum;
        }
        //对较小数字递归求和
        PartialSum sum = addListsHelper(l1.next, l2.next);
        //将进位和当前数据相加
        int val = sum.carry + l1.data + l2.data;
        //插入当前数字的求和结果
        ListNode1 full_result = insertBefore(sum.sum, val % 10);
        //返回求和结果和进位值
        sum.sum = full_result;
        sum.carry = val / 10;
        return sum;
    }
    //返回链表长度
    private static int length(ListNode1 l) {
        if (l == null) {
            return 0;
        } else {
            return 1 + length(l.next);
        }
    }

}
View Code

6.给定一个有环链表,实现一个算法返回环路的开头结点。

算法思想:

(1)创建两个指针:FastPointer和SlowPointer

(2)SlowPointer每走一步,FastPointer就走两步

(3)两者碰到一起时,将SlowPointer指向有环链表的头结点,FastPointer保持不变。

(4)以相同速度移动SlowPointer和FastPointer,一次一步,新的碰撞点即为环路的开头结点。

    public static LinkedListNode FindBeginning(LinkedListNode head) {
        LinkedListNode slow = head;
        LinkedListNode fast = head;
        //找出碰撞处
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                break;
            }
        }
        //错误检查,没有碰撞处,也即没有环路
        if (fast == null || fast.next == null) {
            return null;
        }
        //将slow指向首部,fast不动,两者以相同的速度移动,必定会在环路起始处再次碰撞在一起
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
View Code

7.编写一个函数,检查链表是否为回文。可以将回文定义为0->1->2->1->0。

思路:只需将链表前半部分反转,可以利用栈将前半部分结点入栈来实现。根据链表长度已知与否,入栈有两种方式。

若链表长度已知,可以用标准for迭代访问前半部分结点,将每个结点入栈。要小心处理长度为奇数的情况。

若链表长度未知,可以用快慢runner方法迭代访问链表。在迭代循环的每一步,将慢速runner的数据入栈。在快速runner抵达链表尾部时,慢速runner刚好位于链表中间位置。

这时,栈里就存放了链表前半部分的所有结点,不过顺序是相反的。接下来,迭代访问链表余下结点。每次迭代时,比较当前结点和栈顶元素,若完成迭代时比较结果完全相同,则该链表是回文序列。

    public static boolean isPalindrome(LinkedListNode head) {
        LinkedListNode fast = head;
        LinkedListNode slow = head;
        Stack<Integer> stack = new Stack<Integer>();
        //将链表前半部分元素入栈
        while (fast != null && fast.next != null) {
            stack.push(slow.data);
            slow = slow.next;
            fast = fast.next.next;
        }
        //链表有奇数个元素,跳过中间元素
        if (fast.next != null) {
            slow = slow.next;
        }
        while (slow != null) {
            int top = stack.pop().intValue();
            if (top != slow.data) {
                return false;
            }
            slow = slow.next;
        }
        return true;
    }
View Code