一:总述
面试时链表解题的方法论:
- 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度;
- 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法。
对于笔试,追求以最快的速度解决问题;对于面试,则追求问题的最优解。
重要技巧:
- 额外数据结构记录(哈希表,链表,集合等);
- 快慢指针。
二:删除链表中重复的节点
1.题目
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
2.题解
2.1 思路分析
2.2 具体代码
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null) {
return pHead;
}
ListNode head = new ListNode(-1);
head.next = pHead;
ListNode prev = head;
ListNode last = prev.next;
while (last != null) {
//1.找重复节点的开始
while (last.next != null && last.val != last.next.val) {
prev = prev.next;
last = last.next;
}
//2.找到了重复的开始
while (last.next != null && last.val == last.next.val) {
last = last.next;
}
//3.走到这里,一共三种情况
//1. last.next != null 并且 (prev, last] 限定了一段重复范围,此时进行去重,prev.next = last.next
//2. last.next == null 并且 (prev, last] 限定了一段重复范围,此时进行去重,prev.next = last.next
//3. last.next == null 并且 prev.next == last,这说明,从本次循环开始,都不相同,就不需要进行去重,这个是特殊情况
if (prev.next != last) { //找到了一段范围,可以去重
prev.next = last.next;
}
last = last.next;//走这一步,就是为了保证恢复的和最开始一致
}
return head.next;
}
}
三:判断一个链表是否为回文结构
1.题目
给定一个链表,请判断该链表是否为回文结构。
2.题解
第一种方法,也就是最简单方法,可以借助栈或者顺序表。遍历单链表,将单链表的每一个节点存储到栈或者顺序表中。如果存储到栈,就再次遍历单链表,并将节点依次出栈,判断是否相同;如果存储到顺序表,就分别从首尾两端遍历顺序表,判断每两个节点是否相同。具体代码如下:
import java.util.*;
public class Solution {
public boolean isPail (ListNode head) {
ArrayList<Integer> nums = new ArrayList();
//将链表元素取出一次放入数组
while (head != null) {
nums.add(head.val);
head = head.next;
}
//双指针指向首尾
int left = 0;
int right = nums.size() - 1;
//分别从首尾遍历,代表正序和逆序
while (left <= right) {
int x = nums.get(left);
int y = nums.get(right);
//如果不一致就是不为回文
if (x != y)
return false;
left++;
right--;
}
return true;
}
}
第二种方法,快慢指针法。当快指针走到单链表末尾时,慢指针走到了单链表的中间位置。此时,将慢指针后面的节点依次入栈。然后,从单链表表头开始,依次与栈中弹出的元素进行比对,直到栈为空。相比于方法一,这种方法可以节省一半的空间。代码如下:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
public boolean isPail (ListNode head) {
// write code here
if (head == null || head.next == null) {
return true;
}
ListNode right = head.next;
ListNode cur = head;
while (cur.next != null && cur.next.next != null) {
right = right.next;
cur = cur.next.next;
}
Stack<ListNode> stack = new Stack<ListNode>();
while (right != null) {
stack.push(right);
right = right.next;
}
while (!stack.isEmpty()) {
if (head.val != stack.pop().val) {
return false;
}
head = head.next;
}
return true;
}
}
第三种方法:
思路不算难,当然代码量就比较大了,但这种方法确实没有开始额外的空间,只用几个变量就完成了题目。代码如下:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
public boolean isPail (ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode n1 = head;
ListNode n2 = head;
while (n2.next != null && n2.next.next != null) { // find mid node
n1 = n1.next; // n1 -> mid
n2 = n2.next.next; // n2 -> end
}
n2 = n1.next; // n2 -> right part first node
n1.next = null; // mid.next -> null
ListNode n3 = null;
while (n2 != null) { // right part convert
n3 = n2.next; // n3 -> save next node
n2.next = n1; // next of right node convert
n1 = n2; // n1 move
n2 = n3; // n2 move
}
n3 = n1; // n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while (n1 != null && n2 != null) { // check palindrome
if (n1.val != n2.val) {
res = false;
break;
}
n1 = n1.next; // left to mid
n2 = n2.next; // right to mid
}
n1 = n3.next;
n3.next = null;
while (n1 != null) { // recover list
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
}
要想比较容易滴看懂这部分代码,要求对于单链表的基本操作烂熟于心,比如反转一个单链表。
四:划分单链表
1. 题目
给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的
节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点.
【进阶】在实现原问题功能的基础上增加如下的要求
【要求】调整后所有小于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有等于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有大于pivot的节点之间的相对顺序和调整前一样
【要求】时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
2. 题解
最简单的一种方法,把每一个链表放到数组里,在数组里排好序,然后再串起来就行了。
package LinkedList_Basic_Class04;
public class Code05_SmallerEqualBigger {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node listPartition1(Node head, int pivot) {
if (head == null) {
return head;
}
Node cur = head;
int i = 0;
while (cur != null) {
i++;
cur = cur.next;
}
Node[] nodeArr = new Node[i];
i = 0;
cur = head;
for (i = 0; i != nodeArr.length; i++) {
nodeArr[i] = cur;
cur = cur.next;
}
arrPartition(nodeArr, pivot);
for (i = 1; i != nodeArr.length; i++) {
nodeArr[i - 1].next = nodeArr[i];
}
nodeArr[i - 1].next = null;
return nodeArr[0];
}
public static void arrPartition(Node[] nodeArr, int pivot) {
int small = -1;
int big = nodeArr.length;
int index = 0;
while (index != big) {
if (nodeArr[index].value < pivot) {
swap(nodeArr, ++small, index++);
} else if (nodeArr[index].value == pivot) {
index++;
} else {
swap(nodeArr, --big, index);
}
}
}
public static void swap(Node[] nodeArr, int a, int b) {
Node tmp = nodeArr[a];
nodeArr[a] = nodeArr[b];
nodeArr[b] = tmp;
}
public static void printLinkedList(Node node) {
System.out.print("Linked List: ");
while (node != null) {
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head1 = new Node(7);
head1.next = new Node(9);
head1.next.next = new Node(1);
head1.next.next.next = new Node(8);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(2);
head1.next.next.next.next.next.next = new Node(5);
printLinkedList(head1);
head1 = listPartition1(head1, 5);
printLinkedList(head1);
}
}
方法二:使用六个变量,从头开始遍历整个链表。需要六个变量,小于部分的头和尾,等于部分的头和尾,大于部分的头和尾,一开始所有变量都为空,然后依次将不同区间的节点串起来,最后再整体串起来。以pivot=5为例。
在进行最后连接时,需要进行充分的考虑。也许整个链表都没有小于5的节点,或者整个链表全是大于5的节点,所以一定要注意考虑全面连接点的情况。
package LinkedList_Basic_Class04;
public class Code05_SmallerEqualBigger {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node listPartition2(Node head, int pivot) {
Node sH = null; // small head
Node sT = null; // small tail
Node eH = null; // equal head
Node eT = null; // equal tail
Node bH = null; // big head
Node bT = null; // big tail
Node next = null; // save next node
// every node distributed to three lists
while (head != null) {
next = head.next;
head.next = null;
if (head.value < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head;
bT = head;
}
}
head = next;
}
// small and equal reconnect
if (sT != null) {
sT.next = eH;
eT = eT == null ? sT : eT;
}
// all reconnect
if (eT != null) {
eT.next = bH;
}
return sH != null ? sH : eH != null ? eH : bH;
}
public static void printLinkedList(Node node) {
System.out.print("Linked List: ");
while (node != null) {
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head1 = new Node(7);
head1.next = new Node(9);
head1.next.next = new Node(1);
head1.next.next.next = new Node(8);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(2);
head1.next.next.next.next.next.next = new Node(5);
printLinkedList(head1);
head1 = listPartition2(head1, 5);
printLinkedList(head1);
}
}
最后的连接点处的判断,浓缩为了两个if语句:
五 : 链表指定区间内反转
5.1 题目
5.2 题解
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
ListNode pHead = new ListNode(-1);
pHead.next = head;
ListNode pre = pHead;
for(int i=0; i< m-1;i++) {
pre = pre.next;
}
ListNode cur = pre.next;
ListNode curNext = new ListNode(-1);
for(int i=0; i< n-m; i++) {
curNext = cur.next;
cur.next = curNext.next;
curNext.next = pre.next;
pre.next = curNext;
}
return pHead.next;
}
}
这道题目使用了两种思想 , 一种是普遍使用于链表类题目的 , 即设置虚拟头结点 ; 另外一种则是抽书法 .
按照这种"抽书法"的思想 , 我们试着对下面链表的指定区间进行反转 .
本文到此结束 !