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

题目:打印两个升序链表的公共部分

《程序员代码面试指南》第12题 P41 难度:士★☆☆☆

本题非常简单,经历了昨天爆炸难的可见的山峰对数量的题的摧残(看到解析足足7页直接放弃了,进阶是级别的题目,原问题士但是感觉也很难),终于有了下面连续几个士的题,而且都是链表(貌似不难的样子)。

本题因为是有序链表,并且给定了两个链表的头指针head1head2,所以只要控制2个指针的移动就行了:

  1. 如果head1的值小于head2的值,则head1向下移动一个;如果大于则是head2移动一个
  2. 如果head1的值与head2的值相等,则输出该值head1head2同时移动一个
  3. head1或者head2为null停止

另外要注意一下,这个公共部分不一定要连续,我一开始受牛客上面案例的影响,第一次提交做错了。并且看书上解析,好像默认就是升序链表。那么降序的话道理是一样的。

此外要注意一下因为Java中没有指针,所以节点(单链表)的实现如下:

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

牛客上题解代码如下(有点多,写的还挺标准的):

/**
 * @描述:打印两个升序链表的公共部分
 * @思路:
 * @复杂度:时间复杂度 O(N) (N为两个链表中较长链表的长度)
 * @链接:https://www.nowcoder.com/practice/8943eea40dbb4185b187d80fd050fee9?tpId=101&tqId=33116&tPage=1&rp=1&ru=/ta/programmer-code-interview-guide&qru=/ta/programmer-code-interview-guide/question-ranking
 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.List;

 class Node {

    private Node next;

    private int value;

    public Node(int value) {
        this.value = value;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public static Node createNodeList(Integer[] values) {
        Node head = new Node((values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(values[i]);
            node.next = newNode;
            node = newNode;
        }
        return head;
    }

    public static Node createNodeList(String[] values) {
        Node head = new Node(Integer.parseInt(values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(Integer.parseInt(values[i]));
            node.next = newNode;
            node = newNode;
        }
        return head;
    }
}

class PrintCommonPart {

    public static void printCommonPart(Node head1, Node head2) {
        StringBuilder builder = new StringBuilder();
        Node node1 = head1;
        Node node2 = head2;
        while (node1 != null && node2 != null) {
            if (node1.getValue() == node2.getValue()) {
                builder.append(node1.getValue()).append(" ");
                node1 = node1.getNext();
                node2 = node2.getNext();
            } else if (node1.getValue() < node2.getValue()) {
                node1 = node1.getNext();
            } else {
                node2 = node2.getNext();
            }
        }
        System.out.print(builder.toString());
    }

}

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(input.readLine());
        String[] strings1 = input.readLine().split(" ");
        Node head1 = Node.createNodeList(strings1);
        int m = Integer.parseInt(input.readLine());
        String[] strings2 = input.readLine().split(" ");
        Node head2 = Node.createNodeList(strings2);
        PrintCommonPart.printCommonPart(head1, head2);

    }

}

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

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

《程序员代码面试指南》第13题 P42 难度:士★☆☆☆

本题也很简单,书上用的解法也很牛逼。

链表为单链表时,思路如下:

  1. 首先判断链表为空或者K值小于1,则直接返回原链表;
  2. 其次,让链表从头开始走到尾每走一步K值减1
  1. 如果K值大于0,说明没有倒数第K个数直接返回head即可;
  2. 如果K值等于0,说明倒数第K个数就是head,直接head=head.next即可;
  3. 如果K值小于0,则此时K的值为K-N。再让其从头开始重新遍历每次K值加1当K值为0时此时的位置就是倒数第K个数的前一个数,即正数第N-K个数(很好理解,前一个数就是总数N减去从最后一个往左到第K个数的总数,即N-K。K-N + N-K = 0,那么此时正好停在第N-K个数)。此时令cur.next=cur.next.next即可

当链表为双链表时,思路完全一样,区别仅仅在于链表的结构,在删除链表时需要将last也进行修改

Java中双链表的节点实现如下:

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

删除头结点时,在head=head.next后还要加上head.last=null

删除中间节点时,过程如下:

DoubleNode newNext = cur.next.next;
cur.next = newNext;
if(newNext != null) {
  newNext.last = cur;
}

即还需要判断要删除的是否为最后一个节点不是的话还需要将其后一个节点的last赋值为其前一个节点

牛客上的题目为单链表(双链表其实按照上面的改一下删除过程即可),题解如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @描述:
 * @思路:
 * @复杂度: 链表长度为N,要删除倒数第K个节点;最重要就是定位到它的前一个节点,即:第N-K个节点;
 * 过程分两步:
 * 1. 先从头到位遍历一遍遍历表,每遍历一个节点将K值减1, 将K值更新为K-N;
 * 2. 再遍历一遍链表,每遍历一个节点将K值加1,直到K值为0停止,这样就将K值更新为0-(K-N)= N-K,此时的节点便是第N-k个节点,即:要删除"倒数第k个节点"的前一个节点。
 * @链接:
 */
class RemoveLastKthNode {


    public static Node removeLastKthNode(Node head, int k) {
        Node cur = head;
        while (cur != null) {
            cur = cur.getNext();
            k--;
        }
        if(k > 0 ) { // 说明不存在倒数第k个节点
            System.out.println("说明不存在倒数第k个节点");
            return head;
        } else if(k == 0) { //说明头节点就是倒数第k个节点
            return head.getNext();
        } else {
            cur = head;
            while (++k != 0 ) {
                cur =  cur.getNext();
            }
            cur.setNext(cur.getNext().getNext());
            return head;
        }
    }

}


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = input.readLine().split(" ");
        String[] s2 = input.readLine().split(" ");
        int n = Integer.parseInt(s1[0]);
        int k = Integer.parseInt(s1[1]);
        Node head = Node.createNodeList(s2);
        head = RemoveLastKthNode.removeLastKthNode(head, k);
        Node.printNodeList(head);
    }

}



class Node {

    private Node next;

    private int value;

    public Node(int value) {
        this.value = value;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }


    public static Node createNodeList(Integer[] values) {
        Node head = new Node((values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(values[i]);
            node.next = newNode;
            node = newNode;
        }
        return head;
    }

    public static Node createNodeList(String[] values) {
        Node head = new Node(Integer.parseInt(values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(Integer.parseInt(values[i]));
            node.next = newNode;
            node = newNode;
        }
        return head;
    }



    public static void printNodeList(Node head) {
        StringBuilder sb = new StringBuilder();
        while (head != null) {
            sb.append(head.getValue()).append(" ");
            head = head.getNext();
        }
        System.out.println(sb.toString());
    }

}

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

题目:删除链表的中间节点

《程序员代码面试指南》第14题 P45 难度:士★☆☆☆

这题比前一题还简单一点,删除节点的过程是完全一样的,就不再阐述了。

本题核心在于找到中间节点a/b处的节点

中间节点就是总节点数/2再向上取整,例如n=5,计算得中间节点为第3个。

a/b处的节点则是n*a/b再向上取整,例如n=5,a/b在区间(1/5,2/5]上,则计算得到要删除第2个节点。

代码中删除节点步骤完全一样,只要再实现上面2种功能即可。

在看了其他大佬的代码后,本题将各个方法抽取出来,分为createList创建链表方法,deleteNode删除节点方法,以及printList打印链表方法。

输入用BufferedReader,输出用StringBuilder。代码更加整洁并且效率得到提升(不过占用内存变大了,不知道是何原因,看来鱼和熊掌不可兼得啊)

(BufferedReader以及InputStreamReader的具体内容参照学长的博客——Java刷题-list,里面讲的还算清楚)