本次题目全部选自剑指offer,主要是为了锻炼复习一下链表部分的基础知识。(全都java实现)
(1)剑指offer 5:从尾到头打印链表
题目大致为: 输入一个链表的头结点,从未到头反过来打印每个结点的值。
思路:题目的要求是进行从尾到头输出,而链表的查找只能是顺序查找,栈的结构满足这样的条件:先进后出。同样,也可以使用递归的方式求解。
code:
/**
* @author Administrator
*
*/
public class ListNode {
public int value;
public ListNode next;
public ListNode(int value) {
super();
this.value = value;
}
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
import java.util.Stack;
/*
* 从尾到头打印链表:输入一个链表的头结点,从尾到头反过来打印每个结点的值。
* 思路:题目的要求是进行从尾到头输出,而链表的查找只能是顺序查找,栈的结构满足这样的条件:先进后出。同样,也可以使用递归的方式求解。
*/
public class PrintLianBiao {
public static void main(String[] args) {
ListNode head=new ListNode(0);
ListNode node_one=new ListNode(1);
ListNode node_two=new ListNode(2);
ListNode node_three=new ListNode(3);
ListNode node_four=new ListNode(4);
head.setNext(node_one);
node_one.setNext(node_two);
node_two.setNext(node_three);
node_three.setNext(node_four);
node_four.setNext(null);
System.out.println("第一种方式:递归实现");
printListReverse_1(head);
System.out.println();
System.out.println("第二种方式:非递归实现");
printListReverse_2(head);
}
/*
* 用递归实现
*/
public static void printListReverse_1(ListNode head){
if(head!=null){
if(head.next!=null){
printListReverse_1(head.getNext());
}
System.out.print(head.getValue()+",");
}
}
/*
*非递归实现
*/
public static void printListReverse_2(ListNode head){
Stack<Integer> s=new Stack<Integer>();
ListNode p=head;
//进栈
while(p!=null){
s.push(p.getValue());
p=p.getNext();
}
//出栈
while(!s.isEmpty()){
System.out.print(s.pop()+",");
}
System.out.println();
}
}
(2)剑指offer 13:在O(1)时间删除链表结点
题目大致为: 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
思路:
想要在O(1)时间内删除链表的指定结点,要遍历的话得O(n),则肯定不能遍历。若是要删除的结点不是尾结点,那么可以将后面的那个值复制到该指针处,并将后面指针所指空间删除,用复制+删除后面的实现删除,时间复杂度为O(1)。对于尾结点,需要遍历,那么时间复杂度是O(n),但是总的时间复杂度为[(n-1)*O(1)+O(n)]/n,结果是O(1)。
codepublic class ListNode {
public int value;
public ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
/*
* 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
* 思路:
想要在O(1)时间内删除链表的指定结点,要遍历的话得O(n),则肯定不能遍历。
若是要删除的结点不是尾结点,那么可以将后面的那个值复制到该指针处,并将后面指针所指空间删除,用复制+删除后面的实现删除,时间复杂度为O(1)。
对于尾结点,需要遍历,那么时间复杂度是O(n),但是总的时间复杂度为[(n-1)*O(1)+O(n)]/n,结果是O(1)。
*/
public class DeleteNodeinList {
public static void main(String[] args) {
//构建链表
ListNode head=new ListNode(1);
ListNode node_2=new ListNode(2);
ListNode node_3=new ListNode(3);
ListNode node_4=new ListNode(4);
ListNode node_5=new ListNode(5);
ListNode node_6=new ListNode(6);
ListNode node_7=new ListNode(7);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
//输出原始链表
System.out.println("原始链表为:");
printList(head);
System.out.println("----------------------");
//删除结点3
deleteNode(head, node_3);
printList(head);
System.out.println("----------------------");
//删除头结点
deleteNode(head,head);
printList(head);
System.out.println("----------------------");
//删除尾结点
deleteNode(head,node_7);
printList(head);
System.out.println("----------------------");
}
/*
* 打印链表
*/
public static void printList(ListNode head){
ListNode current=head;
while(current!=null){
System.out.print(current.getValue()+",");
current=current.getNext();
}
System.out.println();
}
/*
* 删除结点
*/
public static void deleteNode(ListNode head,ListNode tobeDeleted){
if(head==null||tobeDeleted==null){
return;
}
//找到要删除结点的下一个结点
if(tobeDeleted.getNext()!=null){
ListNode p=tobeDeleted.getNext();//p为tobeDeleted的下一个结点
tobeDeleted.setValue(p.getValue());
//删除p结点
tobeDeleted.setNext(p.getNext());
}else if(head==tobeDeleted){
//如果头结点就是需要删除的结点
head=null;
}else{
//删除尾结点
ListNode currentNode=head;
while(currentNode.getNext()!=tobeDeleted){
currentNode=currentNode.getNext();
}
currentNode.setNext(null);
}
}
}
(3)剑指offer 15:链表中倒数第k个结点
题目大致为:在一个链表中,查找倒数的第k个数。
思路:使用双指针的方式,前一个指针先走k步(中间隔k-1个结点),后一个指针才开始走,直到第一个指针走到尾,后一个指针指向的就是要找的倒数第k个数。值得注意的是:1、k是否超过链表长度且k必须为正整数;2、链表是否为空。
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
public class KthNodefromend {
public static void main(String[] args) {
//构建链表
ListNode head=new ListNode(1);
ListNode node_2=new ListNode(2);
ListNode node_3=new ListNode(3);
ListNode node_4=new ListNode(4);
ListNode node_5=new ListNode(5);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(null);
print(head);
//查找第k个
ListNode p=findKthtotail(head, 3);
System.out.println(p.getValue());
}
public static ListNode findKthtotail(ListNode head,int k){
//首先判断链表是否存在,K是否大于0
if(head==null||k<0){
return null;
}
ListNode prePoint=head;//第一个指针
ListNode postPoint=head;//第二个指针
for(int i=0;i<k-1;i++){
if(prePoint.getNext()!=null){
prePoint=prePoint.getNext();
}else{
return null;
}
}
while(prePoint.getNext()!=null){
prePoint=prePoint.getNext();
postPoint=postPoint.getNext();
}
return postPoint;
}
public static void print(ListNode head){
ListNode current=head;
while(current!=null){
System.out.print(current.getValue()+",");
current=current.getNext();
}
System.out.println();
}
}
(4)剑指offer 16:反转链表
题目大致为: 对于一个链表,反转该链表并返回头结点。
思路:
主要是指针的操作,但是要注意不能断链。这里可以使用非递归的方式求解。
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
/*
* 反转链表
* 题目大致意思为:对于一个链表反转该链表并返回头结点
* 思路:主要是指针的操作,但是要注意不能断链。这里可以使用非递归的方式求解。
*/
public class ReverseListy {
public static void main(String[] args) {
//构建链表
ListNode head=new ListNode(1);
ListNode node_one=new ListNode(2);
ListNode node_two=new ListNode(3);
ListNode node_three=new ListNode(4);
ListNode node_four=new ListNode(5);
head.setNext(node_one);
node_one.setNext(node_two);
node_two.setNext(node_three);
node_three.setNext(node_four);
node_four.setNext(null);
//打印
//print(head);
//打印反转链表
ListNode reservedhead=reverseList(head);
ListNode tmp=reservedhead;
while(tmp!=null){
System.out.println(tmp.getValue()+",");
tmp=tmp.getNext();
}
}
//非递归实现
public static ListNode reverseList(ListNode head){
ListNode reservedhead=null;
ListNode pNode=head;
ListNode pPrev=null;
while(pNode!=null){
ListNode pnext=pNode.getNext();
if(pnext==null){
reservedhead=pNode;
}
pNode.setNext(pPrev);
pPrev=pNode;
pNode=pnext;
}
return reservedhead;
}
public static void print(ListNode head){
ListNode current=head;
while(current!=null){
System.out.print(current.getValue()+",");
current=current.getNext();
}
System.out.println();
}
}
(5)剑指offer 17:合并两个排序的链表
题目大致为:输入两个递增排序的链表,合并这两个链表并使得新链表中的结点仍然按照递增排序的。
思路:
主要是链表中值的比较,取较小的结点插入到新的链表中。
public class ListNode {
private int value;
private ListNode next;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
}
public class UnionTwoList {
/*
* 合并两个排序的链表
* 题目大致意思为:输入两个递增排序的链表,合并这两个链表并使得新链表中的节点仍然按照递增排序
* 思路:其实主要是链表中值得比较,取较小的结点插入到新的链表当中
*/
public static void main(String[] args) {
//构建链表1
ListNode head1=new ListNode(1);
ListNode node1_2=new ListNode(3);
ListNode node1_3=new ListNode(5);
ListNode node1_4=new ListNode(7);
head1.setNext(node1_2);
node1_2.setNext(node1_3);
node1_3.setNext(node1_4);
node1_4.setNext(null);
//构建链表2
ListNode head2=new ListNode(2);
ListNode node2_2=new ListNode(4);
ListNode node2_3=new ListNode(6);
ListNode node2_4=new ListNode(8);
head2.setNext(node2_2);
node2_2.setNext(node2_3);
node2_3.setNext(node2_4);
node2_4.setNext(null);
//打印两个链表
System.out.println("链表1:");
printList(head1);
System.out.println("------------------------");
System.out.println("链表2:");
printList(head2);
System.out.println("------------------------");
System.out.println("合并后的链表:");
ListNode head=mergeList(head1,head2);
printList(head);
System.out.println("------------------------");
}
public static ListNode mergeList(ListNode head1,ListNode head2){
ListNode head=null;//合并后的头指针
//如果有一个链表为空链表,则合并后直接是另一个链表
if(head1==null){
head=head2;
}
if(head2==null){
head=head1;
}
//两个都不为空的时候
if(head1!=null&&head2!=null){
//node_1和node_2是用于遍历
ListNode node_1=head1;
ListNode node_2=head2;
if(node_1.getValue()<node_2.getValue()){
head=node_1;
head.setNext(mergeList(node_1.getNext(), head2));
}else{
head=node_2;
head.setNext(mergeList(head1, node_2.getNext()));
}
}
return head;
}
public static void printList(ListNode head){
ListNode current=head;
while(current!=null){
System.out.print(current.getValue()+",");
current=current.getNext();
}
System.out.println();
}
}
(6)剑指offer 37:两个链表的第一个公共结点
题目大致为:输入两个链表,找出它们的第一个公共结点。
思路:
第一个公共结点开始往后都是公共结点,所以在末尾向前遍历,就可以找到第一个公共结点。利用上面的思想,可以先计算两个链表的长度,计算两个链表的长度差,然后先遍历较长的链表,等到剩余长度相等时开始同时遍历,这样就能较快地找到相同的结点,时间复杂度为O(m+n),其中m,n分别为两个链表的长度。
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
* 两个链表的第一个公共结点
* 输入两个链表,找到他们的第一个公共点
*/
public class FirstCommonNodeinList {
public static void main(String[] args) {
ListNode head1=new ListNode(1);
ListNode node_2=new ListNode(2);
ListNode node_3=new ListNode(3);
ListNode head2=new ListNode(4);
ListNode node_5=new ListNode(5);
ListNode node_6=new ListNode(6);
ListNode node_7=new ListNode(7);
head1.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
head2.setNext(node_5);
node_5.setNext(node_6);
ListNode result=findFirst(head1, head2);
System.out.println("第一个公共结点是:"+result.getValue());
}
public static ListNode findFirst(ListNode head1,ListNode head2){
ListNode p1=head1;
ListNode p2=head2;
int list_1_len=0;
int list_2_len=0;
//分别计算两个链表的长度
while(p1!=null){
list_1_len++;
p1=p1.getNext();
}
while(p2!=null){
list_2_len++;
p2=p2.getNext();
}
//长度差
int nlength=list_1_len-list_2_len;
ListNode plong=head1;
ListNode pshort=head2;
if(list_1_len<list_2_len){
plong=head2;
pshort=head1;
nlength=list_2_len-list_1_len;
}
//长的先走nlength步
for(int i=0;i<nlength;i++){
plong=plong.getNext();
}
//此时长度相等,一起向前走,并判断它们的值是否相等
while(plong!=null && pshort!=null && plong!=pshort){
plong=plong.getNext();
pshort=pshort.getNext();
}
return plong;
}
}
(7)剑指offer 56:链表中环的入口结点
题目大致为: 一个链表中包含环,如何找出环的入口结点?
思路:
对于上图中的链表,首先得判断是否有环存在,在环存在的情况下求出环中结点的个数,最后类似求链表的倒数第K个结点求出入口结点。这样的过程可以使用快慢指针的方式求解。
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
public class EntryNodeListLoop {
public static void main(String[] args) {
ListNode head = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode node_4 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_3);
// 找到环的入口结点
System.out.println("环的入口结点为:" + getEntry(head).getValue());
}
/*
* 求出环内结点的个数
*/
public static int numOfCircle(ListNode head) {
// 链表是否存在
if (head == null) {
return -1;// -1表示不存在环
}
// 建立快慢指针
ListNode fastNode = head;
ListNode slowNode = head;
int num = 0;// 环中结点的个数
while (fastNode != null && slowNode != null) {
if (fastNode.getNext() != null && fastNode.getNext().getNext() != null) {
fastNode = fastNode.getNext().getNext();
} else {
return -1;// -1表示不存在环
}
slowNode = slowNode.getNext();
// 相遇表示存在环
if (slowNode == fastNode) {
break;// 调出循环
}
}
// 计算环中结点的个数
num++;
slowNode = slowNode.getNext();
while (slowNode != fastNode) {
slowNode = slowNode.getNext();
num++;
}
return num;
}
/*
* 得到入口的结点
*/
public static ListNode getEntry(ListNode head) {
if (head == null) {
return null;
}
ListNode fastnode = head;
ListNode slownode = head;
for (int i = 0; i < numOfCircle(head); i++) {
fastnode = fastnode.getNext();
}
while (fastnode != slownode) {
slownode = slownode.getNext();
fastnode = fastnode.getNext();
}
return fastnode;
}
}
(8)剑指offer 57:删除链表中重复的结点
题目大致为:在一个排序的链表中,如何删除重复的结点?
思路:
原始的链表:
删除重复结点后的链表
链表中结点的删除,最关键的就是不能断链。在删除的过程中,被删除结点的前一个结点的指针必须保存,这样才不会断链,所以必须存在一个指针preNode。
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public ListNode() {
super();
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
/*
* 删除链表中重复的结点
* 注意:链表中结点的删除,最关键的就是不能断链。在删除的过程中,被删除结点的前一个结点的指针必须保存,这样才不会断链,所以必须存在一个指针preNode。
* 思路:定义四个结点,前结点prenode,当前结点node,下一个结点nextnode,删除结点delnode,在遇到删除结点时候要保证prenode连接上nextnode,防止断裂情况。
*/
public class DeletethesameNode {
public static void main(String[] args) {
ListNode head=new ListNode(1);
ListNode node_2=new ListNode(2);
ListNode node_3=new ListNode(3);
ListNode node_4=new ListNode(3);
ListNode node_5=new ListNode(4);
ListNode node_6=new ListNode(4);
ListNode node_7=new ListNode(5);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_7);
print(head);
ListNode result=deleteDuplication(head);
print(result);
}
public static ListNode deleteDuplication(ListNode head){
//链表为null
if(head==null){
return null;
}
//只有一个结点
if(head.getNext()==null){
return head;
}
//临时的头结点
ListNode root=new ListNode();
root.setNext(head);
//记录前驱结点
ListNode prev=root;
//记录当前处理的结点
ListNode node=head;
while(node!=null&& node.getNext()!=null){
//有重复结点,与node值相同的结点都需要删除
if(node.getValue()==node.getNext().getValue()){
//找到下一个不同值得点,注意其有可能也是重复结点
while(node.getNext()!=null&&node.getValue()==node.getNext().getValue()){
node=node.getNext();
}
//指向下一个结点,pre.next也有可能是重复结点
//所以prev需要移动到下一个结点
prev.setNext(node.getNext());
}else{
//相邻两个值不同,说明node不可删除,需要保留
prev.setNext(node);
prev=prev.getNext();
}
node=node.getNext();
}
return root.getNext();
}
public static void print(ListNode head){
while(head!=null){
System.out.print(head.getValue()+"->");
head=head.getNext();
}
System.out.println("null");
}
}
(9)剑指offer 26:复杂链表的复制
题目描述:复制一个复杂链表,在复杂链表中,每个结点除了有一个next指针指向下一个结点外,还有一个sbiling指向链表中的任意结点或者null。下图是一个复杂链表的示例,Null的指针没有画出。
解题思路:
1.很直观的解法就是分成两步:
1).复制原始链表上的每一个结点,并用next指针连起来。
2).复制sbiling指针。
但是复制sbiling指针时需要比较高的复杂度。
以上图为例,如果我们要复制B对应B’的的sbiling指针,那就是要找到E’,想要找到E’只能根据
B到E要走的步数= B’到E’要走的步数
然而又如D.sbiling = B,指向的结点在它的前面,而链表并没有指向前一个元素的指针,所以,每次都只能根据从链表头结点到目标的结点的步数来找到sbiling应该指向的元素。
这种方法显然效率太低,时间复杂度达到了O(n*n)。
2.书中提到了利用哈希表存储(N, N’)的配对信息的方法
这是一个在时间上很高效的方法,在查找上,利用哈希表的高效性。但是缺点在于要用额外的空间。
3.更为高效的一种不利用辅助空间的方法
这个方法的巧妙之处在于利用链表结点本身记录sbiling指针的位置。
分成三个步骤
1).根据原始链表的每个结点N创建对应的N’,并把N’连在N的后面。
如下图:
2)看到上图我们就应该知道这个算法的巧妙之处了,B’.sbiling就记录在B.sbiling.next,这一步就是通过这个方法设置sbiling指针了。
3).将两个链表断开。
public class ListNode {
private int value;
private ListNode next;
private ListNode sbiling;
public ListNode(int value, ListNode next, ListNode sbiling) {
super();
this.value = value;
this.next = next;
this.sbiling = sbiling;
}
public ListNode(int value, ListNode next) {
super();
this.value = value;
this.next = next;
}
public ListNode(int value) {
super();
this.value = value;
}
public ListNode() {
super();
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
public ListNode getSbiling() {
return sbiling;
}
public void setSbiling(ListNode sbiling) {
this.sbiling = sbiling;
}
@Override
public String toString(){
StringBuilder sb=new StringBuilder();
sb.append("value="+value);
sb.append(",next="+(next==null?"null":next.value));
sb.append(",sbiling"+(sbiling==null?"null":sbiling.value));
return sb.toString();
}
}
public static void main(String[] args) {
ListNode head =new ListNode(1);
ListNode node2=new ListNode(2);
ListNode node3=new ListNode(3);
ListNode node4=new ListNode(4);
ListNode node5=new ListNode(5);
head.setNext(node2);
head.setSbiling(node3);
node2.setNext(node3);
node2.setSbiling(node5);
node3.setNext(node4);
node4.setNext(node5);
node4.setSbiling(node2);
/*while(head!=null){
System.out.print(head+"->");
head=head.getNext();
}
*/
//System.out.println("---------------");
ListNode copyhead=copy(head);
while(copyhead!=null){
System.out.print(copyhead+"->");
copyhead=copyhead.getNext();
}
}
public static ListNode copy(ListNode head){
copyList(head);
setsbiling(head);
return disconnect(head);
}
//1.根据原始链表的每个结点N创建对应的N’,并把N’连在N的后面。
public static void copyList(ListNode head){
ListNode node=head;
while(node!=null){
ListNode copynode=new ListNode(node.getValue());
copynode.setNext(node.getNext());
copynode.setSbiling(null);
node.setNext(copynode);
node=copynode.getNext();
}
}
//2.设置sbiling指针
public static void setsbiling(ListNode head){
ListNode node=head;
while(node!=null){
ListNode copynode=node.getNext();
if(node.getSbiling()!=null){
copynode.setSbiling(node.getSbiling());
}
node=copynode.getNext();
}
}
//3.断开两个链表
public static ListNode disconnect(ListNode head){
ListNode node=head;
ListNode copyhead=null;
ListNode copynode=null;
if(node!=null){
copyhead=node.getNext();
node.setNext(copyhead.getNext());
node=copyhead.getNext();
copynode=copyhead;
}
while(node!=null){
ListNode temp=node.getNext();
node.setNext(temp.getNext());
node=node.getNext();
copynode.setNext(temp);
copynode=temp;
}
return copyhead;
}
}