算法-代码随想录-链表
移除链表元素
迭代法
注意:
- head可能为null,也可能是要删除的val;因此要设置一个虚拟头结点指向头结点,再通过迭代一个一个删除。
- 删除方式为:循环,判断下一节点是否是目标节点,是则指向目标节点的下一节点(这里不要移动指针,因为删除后的下一节点仍然有可能是目标节点)。不是则将指针后移。
代码:
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode remo = new ListNode(0);
remo.next = head;
ListNode tmp = remo;
while(tmp.next != null){
if(tmp.next.val == val){
tmp.next = tmp.next.next;
}else{
tmp = tmp.next;
}
}
// 这里不能直接返回head结点
return remo.next;
}
}
设计链表
单链表
- 自定义链表节点
- 设计链表中,包含链表大小及虚拟头结点
MyLinkedList()
初始化链表,设置初始值void addAtIndex(int index, int val)
此处index与其他添加方法不同,index可以等于size,这种情况就是插入尾节点。
public class design {
//测试主方法
public static void main(String[] args) {
MyLinkedList obj = new MyLinkedList();
obj.addAtHead(7);
obj.addAtHead(2);
obj.addAtHead(1);
obj.addAtIndex(3, 0);
obj.deleteAtIndex(2);
obj.addAtHead(6);
obj.addAtTail(4);
int a = obj.get(4);
System.out.println(a);//4
obj.addAtHead(4);
obj.addAtIndex(5, 0);
obj.addAtHead(6);
}
}
//自定义节点类
class LinkedNode{
int val;
LinkedNode next;
LinkedNode(){}
LinkedNode(int val){
this.val = val;
}
}
//设计链表
class MyLinkedList{
int size;//链表大小
LinkedNode head;//虚拟头结点
//初始化链表
public MyLinkedList(){
size = 0;
head = new LinkedNode(0);
}
//获取指定下标元素
public int get(int index){
if(index < 0 || index > size - 1){
return -1;
}
LinkedNode aHead = head;
for(int i = 0; i <= index; i++){
aHead = aHead.next;
}
return aHead.val;
}
//在头部插入节点
public void addAtHead(int val){
LinkedNode node = new LinkedNode(val);
LinkedNode aHead = head;
node.next = aHead.next;
aHead.next = node;
size++;
}
//在尾部插入节点
public void addAtTail(int val) {
LinkedNode aHead = head;
LinkedNode node = new LinkedNode(val);
while(aHead.next != null){
aHead = aHead.next;
}
aHead.next = node;
size++;
}
//在指定索引插入元素
public void addAtIndex(int index, int val) {
//index可以等于size
if(index < 0 || index > size){
return;
}
int i = 0;
LinkedNode aHead = head;
LinkedNode node = new LinkedNode(val);
while(i < index){
aHead = aHead.next;
i++;
}
node.next = aHead.next;
aHead.next = node;
size++;
}
//删除索引处元素
public void deleteAtIndex(int index) {
if(index < 0 || index > size - 1){
return;
}
int i = 0;
LinkedNode aHead = head;
while(i < index){
i++;
aHead = aHead.next;
}
aHead.next = aHead.next.next;
size--;
}
}
双链表
- 节点包含next、prev双向指针
- 创建链表时,创建虚拟头结点和虚拟尾节点
class LinkedNode{
int val;
LinkedNode next, prev;
LinkedNode(){}
LinkedNode(int val){this.val = val;}
}
class MyLinkedList{
int size;
LinkedNode head, tail;
MyLinkedList(){
//初始化
size = 0;
head = new LinkedNode(0);
tail = new LinkedNode(0);
head.next = tail;
tail.prev = head;
}
public int get(int index){
if (index < 0 || index >= size){
return -1;
}
LinkedNode aHead = head;
LinkedNode aTail = tail;
if(index < size / 2){
for (int i = 0; i <= index; i++) {
aHead = aHead.next;
}
return aHead.val;
} else{//4, 3
for (int i = size; i > index; i--) {
aTail = aTail.prev;
}
return aTail.val;
}
}
public void addAtIndex(int index, int val){
if(index > size){
return;
}
LinkedNode node = new LinkedNode(val);
LinkedNode aHead = head;
//找到前驱
for (int i = 0; i < index; i++) {
aHead = aHead.next;
}
node.next = aHead.next;
aHead.next.prev = node;
node.prev = aHead;
aHead.next = node;
size++;
}
public void addAtHead(int val) {
//等价于在第0个元素前添加
addAtIndex(0,val);
}
public void addAtTail(int val) {
//等价于在最后一个元素(null)前添加
addAtIndex(size,val);
}
public void deleteAtIndex(int index) {
//判断索引是否有效
if(index<0 || index>=size){
return;
}
//删除操作
size--;
LinkedNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
pre.next.next.prev = pre;
pre.next = pre.next.next;
}
}
反转链表
双指针法
cur指针指向head结点,pre指针指向null。将cur.next 指向null。再将两个指针向右平移。
import java.util.Scanner;
public class reverse {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
ListNode head = new ListNode();
//fprint(head);
ListNode aHead = head;
String str = scan.nextLine();
String[] s = str.split(" ");
for (int i = 0; i < s.length; i++) {
int num = Integer.valueOf(s[i]);
ListNode newNode = new ListNode(num);
aHead.next = newNode;
aHead = aHead.next;
}
fprint(head.next);
head = reverseList(head);
fprint(head);
}
//打印链表方法
static void fprint(ListNode head){
while(head != null){
System.out.print(head.val + " ");
head = head.next;
}
}
//反转链表***
static ListNode reverseList(ListNode head) {
ListNode tmp;//临时记录cur.next
ListNode cur = head.next;//双指针-前指针
ListNode pre = null;//双指针-后指针
while(cur != null){
tmp = cur.next;
cur.next = pre;
//先后顺序不能变
pre = cur;
cur = tmp;
}
return pre;
}
}
//节点定义
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
递归法
思路与双指针法类似,写法不同。
static ListNode reverseList(ListNode head) {
return reverse(null, head);
}
static ListNode reverse(ListNode pre, ListNode cur){
if (cur == null) return pre;
ListNode tmp = cur.next;
cur.next = pre;
return reverse(cur, tmp);
}
两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
思路:
设置虚拟头结点,交换后两个节点,再将虚拟头节点向后移动两位继续做交换。
代码:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode cur = new ListNode();
cur.next = head;
ListNode aHead = cur;
while(aHead.next != null && aHead.next.next != null){
//临时记录
ListNode tmp = aHead.next;
ListNode tmp1 = aHead.next.next.next;
aHead.next = aHead.next.next;//步骤一
aHead.next.next = tmp;//步骤二
aHead.next.next.next = tmp1;//步骤三
aHead = aHead.next.next;
}
ListNode result = cur.next;
return result;
}
}
删除链表倒数第n个节点
思路:
- 虚拟头结点,方便删除
- 双结点,设置fast和slow两个结点,要找到倒数第n个数,即与最后一个结点的后一位空结点相差n个距离。那么在最开始,先让fast移动n个位置,使得fast与slow相差n个结点,再让fast与slow一起移动,当fast移动到空结点,slow就是需要删除的结点。
- 移动n+1个单位,是为了让slow指向目标结点的前一结点,方便后续删除
代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode val = new ListNode(0);
val.next = head;
ListNode fast = val;
ListNode slow = val;
//移动n+1个单位
for(int i = 0; i <= n; i++){
fast = fast.next;
}
//fast移动到空结点以前,fast与slow一起移动
while(fast != null){
fast = fast.next;
slow = slow.next;
}
//删除目标结点
slow.next = slow.next.next;
//返回头结点
return val.next;
}
}
链表相交
思路:
- 如果链表相交,则链表A与链表B的相交部分必然在尾部,且长度相等。
- 将两个链表的尾部对齐,将长链表头部多出来的部分直接跳过,再依次判断链表A与链表B的结点是否相等,相等则返回结点,如果没有符合的结点,则返回null。
代码:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0;
int lenB = 0;
//A链表长度
while(curA != null){
curA = curA.next;
lenA++;
}
//B链表长度
while(curB != null){
curB = curB.next;
lenB++;
}
curA = headA;
curB = headB;
//如果B长度>A长度
if(lenB > lenA){
//交换len
int tmpLen = lenB;
lenB = lenA;
lenA = tmpLen;
//交换cur结点
ListNode tmpNode = curB;
curB = curA;
curA = tmpNode;
}
//gap是A与B相差的长度,使A与B对齐
int gap = lenA - lenB;
while(gap != 0){
curA = curA.next;
gap--;
}
while(curA != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
环形链表
思路:
- 判断是否有环,找到环的入口
- 如何判断是否有环:设置快慢双指针,快指针每次移动两个结点,而慢指针每次移动一个结点,如果有环的话,快慢指针终会在环中的某个位置相遇,说明有环。
- 如何找到环的入口
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){//有环
ListNode index1 = head;
ListNode index2 = fast;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}