链表,应该是在面试时被提及最频繁的数据结构,链表的结构很简单,由指针把若干个结点连接成连续结构,链表的创建,插入,删除等操作都只需要20行左右的代码就能实现,其代码量非常适合用来面试。此外,链表是一种动态的数据结构,其操作需要对指针进行操作,因此需要较好的编程功底才能写出完整的操作链表的代码,而且其非常灵活,可以用链表来设计具有挑战性的代码。
面试题5 从尾到头打印链表
输入一个链表,从尾到头打印链表每个节点的值。
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
//由于需要的是从尾到头的链表结构,即最先遍历的最后输出,那么一个很自然的想法应该是栈
//同时,递归的本质即为栈,所以可以使用递归的思想。
//但是采用递归内存超出,而且当数据足够大的时候会出现栈溢出。
这里采用栈的方式实现
public void printListFromTailToHead(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode node = head;
while (node != null) {
stack.push(node);
node = node.next;
}
while (!stack.isEmpty()) {
node = stack.pop();
System.out.println(node.value);
}
}
面试题 13 在O(1) 的时间内删除结点
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)的时间内删除该结点
思路:在单向链表中删除结点,最常规的做法无非就是遍历链表,找到要删除的结点,但是该方法的时间复杂度是O(n),不符合题目的要求。那么我们换一种思路,,我们可以很方便的删除该结点的下一个结点,如果我们把下一个结点的内容给复制到要删除的结点上,并且删除下一个结点,那是不是就相当于把当前需要删除的结点删除了?
但是还有一个问题就是,如果要删除的结点位于链表的尾部,那么他没有下一个结点,怎么办?这种情况下,我们仍然需要遍历到该结点的前序结点,并完成删除操作。
public void deleteNode(ListNode head, ListNode toBeDelNode){
if( head ==null || toBeDelNode ==null){
return ;
}
//不是尾结点
if( toBeDelNode.next !=null){
ListNode next = toBeDelNode.next;
toBeDelNode.value = next.value;
toBeDelNode.next = next.next;
next = null ;
}
//此时对应的情况是只有一个结点,此时需要把头结点置空
else if(head == toBeDelNode){
toBeDelNode = null;
head =null ;
}else{
//删除尾结点
ListNode node = head;
while( node.next != toBeDelNode){
node = node.next;
}
node.next = null ;
toBeDelNode =null ;
}
}
面试题15 ,链表中的倒数第k个结点
链表的尾结点为倒数第一个结点,输出倒数第k个结点。
这题的常规思路就是遍历链表两次,但是还有更好的解法,为了实现找到倒数第k个结点,我们需要定义两个指针,第一个指针从链表的头指针开始先走k-1 步,之后两个指针一起走,当第一个指针到达尾结点的时候,此时第二个指针恰好指向倒数第k个结点
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null || k<=0){
return null;
}
//两个指针,一个快一个慢
ListNode quick=head;
ListNode slow=head;
for(int i=1;i<k;i++){
//先移动k-1步,可能会出现k大于链表长度的情况
if(quick!=null){
quick=quick.next;
}
else{
return null;
}
}
while(quick.next!=null){
quick=quick.next;
slow=slow.next;
}
return slow;
}
其实,这种有两个指针的思想不单单适用于解决这种问题,还可以解决其他问题,比如要判断链表是否有环,也可以使用两个指针,一个一次走两步,一个一次走一步,当慢的追上快的之后,就证明有环。如果快指针走到末尾都没有追上,则证明无环。此外,还可以用来求链表中结点,也是两个指针,
一个一次走两步,一个一次走一步。当快指针走到末尾的时候,慢指针所指向的就是中间的结点。
面试题16 . 反转链表
输入一个链表,反转链表后,反转链表,并返回反转链表的头结点。
这道题只要把逻辑处理好即可
public ListNode ReverseList(ListNode head) {
//如果链表为空,或者链表中的元素只有一个,返回head即可
if(head==null || head.next==null){
return head;
}
ListNode pre=null; //前一个结点
ListNode nextNode=null; //后一个结点
while(head!=null){
nextNode = head.next; //后一个结点赋值
head.next = pre; //反转
pre=head; //前一个结点指向当前结点
head=nextNode; <span style="font-family: arial, STHeiti, 'Microsoft YaHei', 宋体;">//移动当前的结点</span>
}
return pre;
}
链表的题目大多不太复杂,但是有的题目考察思维能力,比如在O(1)时间内删除结点,如果没有想到使用后面的结点的值覆盖前面的值,再删除后面的结点,那么就很难想到怎么做。所以,一定要扩宽自己的思维