1,顺序表
逻辑上相邻的两个元素物理位置上也相邻。
可以随机读取,但增删操作复杂。
2,单链表
- 增删快、查询慢
1)定义
结点:
class ListNode{
int val;
ListNode next;
}
2)头插
public void insertAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head;
head = newNode;
}
3)尾插
public void insertAtTail(int val) {
//1个新结点(临时结点)
ListNode newNode = new ListNode(val);
if (head == null) {
// 若链表为空,新节点就是头节点
head = newNode;
} else {
//一个指针cur作为当前遍历结点的指针
ListNode current = head;
// 遍历链表,找到尾节点
while (current.next != null) {
current = current.next;
}
// 将新节点插入到尾节点之后
current.next = newNode;
}
}
4)链表反转
假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
//旧的链表头指针head 头删,cur作为遍历结点,需要断开cur后的指针;断开后为避免丢失,需要一个temp为临时存储
//新的链表newhead 头插,此时newhead->null
ListNode newHead = null, cur = head, temp = null;
while (cur != null) {
//1 遍历下一个,头出
temp = cur.next;
//2 断开旧指针,指向新指针
cur.next = newHead;
//3 头插法 指向新的头
newHead = cur;
//4 准备下一个结点
cur = temp;
}
return newHead;
}
- 从尾到头打印链表
输入一个链表,从尾到头打印链表每个节点的值。
解题思路:由于打印是只读操作,不宜改变原数据格式。可以借助辅助栈,或者使用递归实现(注意链表太长会导致函数调用层级变深,溢出)。
5)删除
总的复杂度:[(n-1)*O(1) +O(n)]/n = O(1)
1>头删
O(1),直接头指针后移
public void deleteAtHead(ListNode head) {
if (head == null) {
// 链表为空,无需删除
return;
}
// 将 head 指针指向下一个节点
head = head.next;
}
2>尾删
O(n),先遍历再删除
public void deleteAtTail(ListNode head) {
if (head == null) {
// 链表为空,无需删除
return;
}
if (head.next == null) {
// 链表只有一个节点,将头指针置为 null
head = null;
return;
}
ListNode prev = null;
ListNode current = head;
// 找到倒数第二个节点
while (current.next != null) {
prev = current;
current = current.next;
}
// 删除尾节点
prev.next = null;
}
6)链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。
思路:定义一快一慢两个指针,快指针走K步,然后慢指针开始走,快指针到尾时,慢指针就找到了倒数第K个节点。
类似题目:
- 找中间节点:
使用两个指针,同时从头结点出发,一个走一步,一个走两步。
当走的快的结点到链表尾部时,走得慢的指针正好在链表中间。
如果链表中结点为奇数个,返回中间结点;如果是偶数,返回中间结点中的任意一个。 - 判断一个单向链表是否形成了环形结构:
使用两个指针,同时从头结点出发,一个走一步,一个走两步。
如果走的快的指针追上了走得慢的指针,那么就是环形链表。
如果走的快的指针走到链表的末尾都没有追上第一个指针,那么就不是环形。
public boolean isPalindrome(ListNode head) {
//快慢指针 first一次走2步 second一次走1步 first如果追上second表示回文
if (head == null || head.next == null) {
return Boolean.FALSE;
}
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
//快追上慢指针 说明有环
if (fast == slow) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
7)链表合并
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:判断两个头结点的值,用较小的作为新链表的头结点。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//2个指针同时遍历
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
//两个表合并后的表头head 当前合并的尾 tail
ListNode head = new ListNode(), tail = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
//尾插法
tail.next = list1;
//断开原来指针
list1 = list1.next;
} else {
tail.next = list2;
list2 = list2.next;
}
tail = tail.next;
}
if (list1 != null) {
tail.next = list1;
} else if (list2 != null) {
tail.next = list2;
}
return head.next;
}
8)其他
###⑦复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:用辅助空间O(n)来存储复制的链表。
1)根据原始链表,对每一个结点N创建它的复制结点N’,并插入到N之后。
2)复制random指针:对原始链表每一个random指针N->S,有N’->S’。
3)拆分为两个链表:奇数位为原始链表,偶数位为复制链表。
代码如下:
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
RandomListNode head = new RandomListNode(pHead.label);
RandomListNode temp = head;
while (pHead.next != null) {
temp.next = new RandomListNode(pHead.next.label);
if (pHead.random != null) {
temp.random = new RandomListNode(pHead.random.label);
}
pHead = pHead.next;
temp = temp.next;
}
return head;
}
###⑧求两个单链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
思路一:两个链表有公共结点,那么第一个公共结点之后的结点都是重合的。如下图:
对于两个链表,如果有公共结点,那么公共结点一定出现在尾部。
从尾部开始比较结点,最后一个相同的结点就是第一个公共结点。
为了实现这种后进先出,使用辅助栈来分别存储这两个单链表。
这种方法虽然可以实现,但是以空间换时间,空间复杂度大。
思路二:先求出链表长度,然后长的链表先走多出的几步,然后两个链表同时向下走去寻找相同的结点。
代码如下:需要遍历两遍,第一遍是为了同步指针。第二遍是保持两个指针同步遍历寻找相同的结点。
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
p1 = (p1 != null ? p1.next : pHead2);
p2 = (p2 != null ? p2.next : pHead1);
}
return p1;
}
###⑨
##3)循环链表
表中最后一个结点的指针指向头结点,只有头结点是固定的。
##4)双向链表
data *prior(直接前驱) *next(直接后继)
###①二叉搜索树和双向链表的转换
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
解题思路:
树的每个结点有2个指针指向它的左右子结点,调整这两个指针。
二叉搜索树的顺序:左子树总数小于父结点,右子树总是大于父结点。
所以调整方案为:原先指向左子树的指针为指向链表中前一个结点的指针,指向右子树的结点为指向链表中后一个结点的指针。这是一个递归的过程。
转换方案:定义一个链表的尾指针,递归处理左右子树,最后返回链表的头节点。
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode Convert(TreeNode pRootOfTree) {
TreeNode lastlist = covertNode(pRootOfTree, null);
TreeNode pHead = lastlist;
while (pHead != null && pHead.left != null) {
pHead = pHead.left;
}
return pHead;
}
public TreeNode covertNode(TreeNode root, TreeNode lastlist) {
if (root == null)
return null;
TreeNode cur = root;
if (cur.left != null) {
lastlist = covertNode(cur.left, lastlist);
}
cur.left = lastlist;
if (lastlist != null) {
lastlist.right = cur;
}
lastlist = cur;
if (cur.right != null) {
lastlist = covertNode(cur.right, lastlist);
}
return lastlist;
}