目录
目录
一、单链表
1.定义一个单链表:
public class LinkList<E> {
//单链表长度
private int size = 0;
//链表的第一个结点
private Node<E> firstNode;
public LinkList() {}
//头插
public void addFirst(E e) {}
//尾插
public void addLast(E e) {}
//按索引位置插入
public void add(int index, E e) {}
//判断指定数据是否存在链表中
public boolean isExist(E e) {
return true;
}
//移除指定元素
public boolean remove(Object o) {
return true;
}
//删除指定位置结点
public void remove(int index) {}
//删除第一个结点
public E removeFirst() {}
//删除最后一个结点
public E removeLast() {}
//获取指定位置元素
public E get(int index) {}
//获取单链表长度
public int size() {
return size;
}
//定义链表结点
private static class Node<E> {
private E data;
private Node<E> nextNode;
Node(E data, Node<E> nextNode) {
this.data = data;
this.nextNode = nextNode;
}
}
}
头插 尾插:都新增个节点newNode,
头插 newNode.next = firstNode;
尾插 lastNode.next = newNode;
2.头插法建立单链表
//头插
public void addFirst(E e) {
Node newNode = new Node(e,null);
if(firstNode == null) {
firstNode = newNode;
}else {
newNode.nextNode = firstNode;
}
size++;
}
3.尾插法建立单链表
//尾插
public void addLast(E e) {
Node newNode = new Node(e,null);
if(firstNode == null) {
firstNode = newNode;
}
// 遍历到最后一个节点
lastNode.nextNode = newNode;
size++;
}
4.特点:
- 在单链表上插入、删除一个结点,必须知道其前驱结点
- 单链表不具有按序号随机访问的特点,因此插入位置的查找只能从头指针开始一个一个顺序进行
5.链表分类:
- 单链表
- 循环单链表
- 静态链表:用一维数组存储,next 域存放的是后继结点在数组中的下标
- 双向链表:LinkedList
例0:单链表反转
方法一:递归法。核心思想:后一个的next 指向 前一个,前一个的next指向null
head.next.next = head;
head.next = null;
// 定义链表节点
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public class LinkedListReversal {
// 反转链表的方法(递归)
public ListNode reverseList(ListNode head) {
// 递归终止条件:如果链表为空或只有一个节点,直接返回头节点
if (head == null || head.next == null) {
return head;
}
// 递归地反转head.next之后的链表,并返回反转后的头节点(原链表的第二个节点成为新的头节点)
ListNode newHead = reverseList(head.next);
// 将当前节点(原链表的头节点)的next指针指向前一个节点(即newHead),完成反转
head.next.next = head;
// 避免当前节点(原头节点)成为新链表的尾节点时形成环,将其next指针置为null
head.next = null;
// 返回新的头节点
return newHead;
}
// 辅助方法:打印链表
public void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.val + " -> ");
current = current.next;
}
System.out.println("null");
}
public static void main(String[] args) {
// 创建链表 1 -> 2 -> 3 -> 4 -> 5
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
// 实例化LinkedListReversal对象
LinkedListReversal reverser = new LinkedListReversal();
// 反转链表
ListNode reversedHead = reverser.reverseList(head);
// 打印反转后的链表
reverser.printList(reversedHead);
// 输出应为:5 -> 4 -> 3 -> 2 -> 1 -> null
}
}
方法二:三指针法
prev、curr、nextTemp 三个指针代表三个节点位置。curr 作为 循环主要的指针
// 反转链表的方法(非递归)
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode prev = head;
ListNode curr = head.next;
while (curr != null) {
ListNode nextTemp = curr.next; // 保存 当前节点的下一个节点
curr.next = prev; // 反转 当前节点的指向
prev = curr; // 前一个节点 前进到当前节点
curr = nextTemp; // 当前节点 前进到原来的下一个节点
}
// 当循环结束时,curr为null,prev为新的头节点
return prev;
}
例1:判断链表 是否有环
方法一:采用快慢指针,一个走的快些一个走的慢些, 如果最终相遇了就说明有环,时间复杂度 O(n),空间复杂度 O(1)
/*
* 判断链表是否有环
*/
public boolean isLoop(Node first) {
Node slow = first;
Node fast = first.nextNode;
while(fast != null) {
//两引用指向同一结点,说明有环
if(slow == fast)
return true;
//slow走慢点,fast走快点
slow = slow.nextNode;
fast = fast.nextNode.nextNode;
}
return false;
}
方法二:把每个节点放入hash表中,边找边放,先找后放,如果不为null,则已存在该结点,即有环
例1.1:如果链表有环,寻找环入口位置
public Node isLoop(Node first) {
/***************************************/
//从头取结点,放入一个链表
LinkList link = new LinkList();
Node temp = first;
//如果该结点已在链表中存在,则该节点就是入口节点
while(temp.nextNode != null) {
if(link.isExist(temp)) {
return temp;
}else {
link.addFirst(temp);
temp = temp.nextNode;
}
}
return null;
/****************************************/
}
例2:从尾到头打印出 链表每个结点的值
方法一:使用递归,先递归打印后继结点,再打印当前结点。时间复杂度 O(lb n),空间复杂度 O(n)
public ArrayList<Integer> printListFromTailToHead(Node first) {
ArrayList<Integer> list = new ArrayList<>();
Node node = first;
if (node!= null) {
list .addAll(printListFromTailToHead(node.nextNode));
list .add(node.data);
}
return list;
}
方法二:使用头插法,可以得到一个逆序的链表(翻转链表)。时间复杂度 O(n),空间复杂度 O(n)
public ArrayList<Integer> printListFromTailToHead(Node first) {
// 头插法构建逆序链表
Node head = new Node();
Node node = first;
while (node != null) {
Node temp = node.next;
node.next = head.next;
head.next = node;
node = temp;
}
// 构建 ArrayList
ArrayList<Integer> list = new ArrayList<>();
node = head.next;
while (node != null) {
list.add(node.data);
node = node.next;
}
return list;
}
方法三:使用栈,栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,则出栈的顺序即为逆序
public ArrayList<Integer> printListFromTailToHead(Node first) {
Stack<Integer> stack = new Stack<>();
Node node = first;
while (node != null) {
stack.push(node.data);
node = node.next;
}
ArrayList<Integer> list = new ArrayList<>();
while (!stack.isEmpty())
list.add(stack.pop());
return list;
}
例3:链表求和,实现两数相加
public Node addTwoNumbers(Node l1, Node l2) {
Stack<Integer> stack1 = buildStack(l1);
Stack<Integer> stack2 = buildStack(l2);
Node head = new LinkList.Node();
int carry = 0;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
int x = stack1.isEmpty() ? 0 : stack1.pop();
int y = stack2.isEmpty() ? 0 : stack2.pop();
int sum = x + y + carry;
Node node = new Node(sum % 10, null);
node.nextNode = head.nextNode;
head.nextNode = node;
carry = sum / 10;
}
return head.nextNode;
}
private Stack<Integer> buildStack(Node node) {
Stack<Integer> stack = new Stack<>();
while (node != null) {
stack.push(node.data);
node = node.nextNode;
}
return stack;
}
例4:归并两个有序的链表
public Node mergeTwoLists(Node l1, Node l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.data < l2.data) {
l1.nextNode = mergeTwoLists(l1.nextNode, l2);
return l1;
} else {
l2.nextNode = mergeTwoLists(l1, l2.nextNode);
return l2;
}
}
例5:从有序链表中删除重复结点
public Node deleteDuplicates(Node head) {
if (head == null || head.nextNode == null) return head;
head.nextNode = deleteDuplicates(head.nextNode);
return head.data == head.nextNode.data ? head.nextNode : head;
}
例6:删除链表的倒数第 n 个结点
public Node removeNthFromEnd(Node head, int n) {
Node fast = head;
while (n-- > 0) {
fast = fast.nextNode;
}
if (fast == null) return head.nextNode;
Node slow = head;
while (fast.nextNode != null) {
fast = fast.nextNode;
slow = slow.nextNode;
}
slow.nextNode = slow.nextNode.nextNode;
return head;
}
例7:每k个长度的链表翻转,最后不够k个的不翻转
import java.util.Scanner;
public class Main {
static Node first;
static Node temp;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
int k = sc.nextInt();
int len = s.length();
for(int i = 0; i < len; i++) {
addLast(s.charAt(i));
}
temp = first;
for(int j = 0; j < len/k; j++) {
for(int i = 0; i < k; i++) {
if(i == 0) {
reverse(temp);
}
temp = temp.next;
}
}
}
public static void addLast(char ch) {
Node newNode = new Node(ch, null);
if(first == null) {
first = newNode;
temp = first;
}else {
temp.next = newNode;
}
temp = newNode;
}
public static void reverse(Node node) {
}
}
class Node{
char data;
Node next;
Node(char data, Node next){
this.data = data;
this.next = next;
}
}