经典数据结构题目-链表
链表
707. 设计链表
-
解题思路
- 参考官方的单向链表,设置一个成员变量作为虚拟头节点,一个成员变量size保存有效节点数
-
代码
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
// 借助虚拟头节点,可以较为方便地获取到index对应的节点
ListNode cur = head;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
index = Math.max(0, index);
size++;
// 定位到插入位置的前一个元素
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
// 插入元素
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
// 定位到删除位置的前一个
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
// 删除元素
pred.next = pred.next.next;
}
206. 反转链表
解法一
-
思路
- 快慢指针进行遍历,快指针先存下当前节点的next,再将next指向slow的。两指针再一起往前移动
-
代码
public ListNode reverseList(ListNode head) {
ListNode fast = head;
ListNode slow = null;
while(fast != null){
ListNode next = fast.next; // 注意点一
fast.next = slow;
slow = fast;
fast = slow;
}
return last;
}
- 注意点
- 注意点一:操作链表时,修改next指针时,注意是否需要保存下来,防止找不到了
解法二
- 思路
- 使用递归
- 终止条件。只剩下一个节点,节点为空
- 递归参数。入参 ,需要反转的链表头;出参,反转后的头
- 当前层处理。接入到反转后的链表尾,next指针指向空
- 使用递归
- 代码
public ListNode reverseList(ListNode head) {
// 终止条件
if (head == null || head.next == null) return head;
// 递归
ListNode p = reverseList(head.next);
// 当前层处理
head.next.next = head;
head.next = null;
return p;
}
92. 反转链表 II
解法一
-
思路
- 通过一次遍历,定位出反转 开始 和 结束 关键的四个节点
- 遍历的过程,对left和right区间的进行反转
- 最后再根据四个关键节点进行重新拼接
-
代码
public ListNode reverseBetween(ListNode head, int left, int right) {
// 定位出四个关键节点
ListNode leftBefore = null;
ListNode leftNode = null;
ListNode rightAfter = null;
ListNode rightNode = head; // 注意点一
// 定位四个关键节点同时进行反转
ListNode fast = head;
ListNode slow =null;
int index = 1;
while(index <= right){
ListNode next = fast.next;
// 记录下反转开始
if(index == left){
leftBefore = slow;
leftNode = fast;
}
// 开始进行反转
if(index >= left){
fast.next = slow;
}
// 快慢指针移动
slow = fast;
fast = next;
index ++;
}
rightAfter = fast;
rightNode = slow;
// 四个关键节点进行重新拼接
leftNode.next = rightAfter;
if(leftBefore == null){ // 注意点二
return rightNode;
}
leftBefore.next = rightNode;
return head;
}
-
注意点
- 注意点一:因为结果将rightNode作为头返回,当只有一个元素时且要进行反转时,需要将原头节点返回
- 注意点二:left从1开始时,会导致leftBefore为空
解法二
- 思路
- 递归
- 终止条件。当left等于1时,以当前为头的N个元素进行反转即可。可参考反转链表题
- 递归参数。入参,以下一个节点为头的链表,因为少了一个元素,反转的位置都减一。出参,反转后的链表头
- 当前层处理。头的next指针指向新的链表头
- 递归
- 代码
ListNode afterN = null;
public ListNode reverseBetween(ListNode head, int left, int right) {
if(left == 1){
return reverseN(head,right);
}
head.next = reverseBetween(head.next,left-1,right-1);
return head;
}
public ListNode reverseN(ListNode head, int n){
if(n == 1){
afterN = head.next; //注意点一
return head;
}
ListNode newHead = reverseN(head.next, n-1);
head.next.next = head;
head.next = afterN;
return newHead;
}
- 注意点
- 注意点一。反转N个元素后,这N个元素可能是在原本链表的中间,需要记录下来,待整个反转后重新接回
61. 旋转链表
- 思路
- 向右移动k个位置,当节点数 n < k 时,实则移动 k %n 格即可。因此需要计算节点数n进行计算
- 移动k格,实则在 n-k格处断开,n-k+1作为新头,n-k最为新尾,n节点指向老的头节点
- 代码
public ListNode rotateRight(ListNode head, int k) {
if(k == 0 || head == null || head.next == null){ //注意点一
return head;
}
// 统计数量、定位到最后的元素
int n = 1;
ListNode oldTail = head;
while(oldTail.next != null){
oldTail = oldTail.next;
n ++;
}
if(k % n == 0){
return head;
}
// 计算移动到截断处
int move = n - k % n;
ListNode newtail = new ListNode(); // 注意点二
newtail.next = head;
while(move > 0){
newtail = newtail.next;
move --;
}
// 进行截断重新拼接
ListNode newHead = newtail.next;
newtail.next = null;
oldTail.next = head;
return newHead;
}
- 注意点
- 注意点一:以上思路,需考虑边界情况是否适用。头为空、一个节点、k为0等
- 注意点二:移动的格数,是从一个虚拟头节点开始的,因此需要new一个新的ListNode
企鹅号:1272420336,一起学习,一起进步~