打印两个有序链表的公共部分
《程序员代码面试指南》第12题 P41 难度:士★☆☆☆
本题非常简单,经历了昨天爆炸难的可见的山峰对数量的题的摧残(看到解析足足7页直接放弃了,进阶是将级别的题目,原问题士但是感觉也很难),终于有了下面连续几个士的题,而且都是链表(貌似不难的样子)。
本题因为是有序链表,并且给定了两个链表的头指针head1和head2,所以只要控制2个指针的移动就行了:
- 如果head1的值小于head2的值,则head1向下移动一个;如果大于则是head2移动一个
- 如果head1的值与head2的值相等,则输出该值,head1与head2同时移动一个
- 当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个节点
《程序员代码面试指南》第13题 P42 难度:士★☆☆☆
本题也很简单,书上用的解法也很牛逼。
链表为单链表时,思路如下:
- 首先判断链表为空或者K值小于1,则直接返回原链表;
- 其次,让链表从头开始走到尾,每走一步,K值减1;
- 如果K值大于0,说明没有倒数第K个数,直接返回head即可;
- 如果K值等于0,说明倒数第K个数就是head,直接head=head.next即可;
- 如果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,里面讲的还算清楚)