LeetCode 简单链表总结(52个)
简单中的又代表性的题目,复习的时候直接看这个就行 节约时间
操作总结
-
快慢指针
- 设置一个fast指针 设置一个slow指针 fast指针的步长比slow的步长要长
-
翻转链表
- 设置pre=null temp = head cur = head 通过移动指针实现翻转
-
添加头节点
- 统一操作 让代码更加简洁
-
循环遍历 迭代法 基础操作 不多BB
思想总结
- 双指针
- 双指针分别指向不同的位置从而实现一些操作
- 快慢指针
- 应用当快指针指向尾节点时候,慢指针指向中间节点
- 判断是否有链表中的环
- 长度差
- 利用两个链表的长度差 进行处理
题目总结
19 删除链表的倒数第N个节点
- 双指针解决
- 先让一个指针向前走N然后两个指针同时向后走当一个指针到达链表的表尾的时候剩下的那个指针指向了倒数第N个利用了差值
- 翻转链表 正着操作删除
// 在实现的时候利用了头结点 同时维护一个pre指针便于删除
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode node = new ListNode(-1);
node.next = head;
ListNode pre = node,first = head,second = head;
for (int i = 0; i < n; i++) {
first = first.next;
}
while (first!=null){
first = first.next;
second = second.next;
pre = pre.next;
}
pre.next = second.next;
return node.next;
}
// LeetCode代码 思路差不多 他是没有维护pre 但是间距差了n-1
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode node = new ListNode(-1);
node.next = head;
ListNode first = node,second = node;
// 为了保证n合理 这里设置间距是n-1 则直接删除不用维护pre
for (int i = 1; i <= n+1; i++) {
first = first.next;
}
while (first!=null){
first = first.next;
second = second.next;
}
second.next = second.next.next;
return node.next;
}
141环形链表
- 快慢指针解决
- 设置两个指针fast和slow指针,快指针的步长为2,慢指针的步长为1,快慢指针一起走判断快慢指针是否为空,如果不为空继续判断是否快慢指针指向的是同一个位置,如果不同则继续如果相同则返回true否则返回false
public boolean hasCycle(ListNode slow) {
if (slow.next==null){
return false;
}
ListNode fast = slow.next;
while (fast!=null || slow!=null){
if (fast==slow){
return true;
}
fast = fast.next.next;
slow = slow.next;
}
return false;
}
203移除链表元素
- 循环遍历 注意第一个节点的处理 第一个节点是特殊的位置
public ListNode removeElements(ListNode head, int val) {
// 异常情况
if (head == null) {
return null;
}
ListNode cur = head, pre = head;
while (cur!=null){
if (cur.val==val){
// 第一个节点
if (pre==cur){
head = cur.next;
pre = head;
}else{
pre.next = cur.next;
}
}else {
pre = cur;
}
cur = cur.next;
}
return head;
}
}
// LeetCode 解决添加头节点将所有节点的操作统一 最后返回头结点的next 不用考虑第一个节点的问题 添加头结点统一操作
public ListNode removeElements(ListNode head, int val) {
ListNode tempHead = new ListNode(0);
tempHead.next = head;
ListNode prev = tempHead;
while (head != null) {
if (head.val == val) {
prev.next = head.next;
} else {
prev = prev.next;
}
head = head.next;
}
return tempHead.next;
}
206反转链表
- 基础操作 方法很多
// 递归解决 模拟最后一步的操作
public ListNode reverseList(ListNode head) {
// 递归出口
if (head == null || head.next == null ) {
return head;
}
// 递归调用
ListNode newHead = reverseList(head.next);
// 递归操作
head.next.next = head;
head.next = null;
return newHead;
}
// 迭代法 设置新的起始节点为null
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode node = null, temp = head.next;
while (head != null) {
head.next = node;
node = head;
head = temp;
temp = temp.next;
}
return node;
}
// LeetCode答案 一样是迭代法 一样的思路
public ListNode reverseList(ListNode curr) {
ListNode prev = null;
ListNode next = null;
while (curr != null) {
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
234回文链表
- 先使用双指针法找到链表的中间节点,然后将链表分为两个部分,利用翻转的方法将后半部分的链表翻转,再去同时遍历前后两个链表判断是否完全相同如果相同则返回true否则返回false
public boolean isPalindrome(ListNode head) {
// 特殊情况 一个或者空不用翻转
if (head==null||head.next==null) return true;
// 快慢指针 快指针指向了最后一个节点,慢指针指向中间节点(奇数偶数)
ListNode fast= head,slow = head;
while (fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
ListNode newHead = null;
ListNode cur = slow; //中间节点
// 翻转后半部分的链表
while(cur!=null){
ListNode temp = cur.next;
cur.next= newHead;
newHead = cur;
cur = temp;
}
// 忽略奇数个的多一个的情况
// 忽略奇数偶数的干扰
while (newHead!=null&&head!=null){
if (newHead.val!=head.val) return false;
newHead = newHead.next;
head = head.next;
}
return true;
}
237删除链表中的节点
- 曲线救国法 因为删除指针需要获取到当前指针的前一个指针,但是题目只给了当前指针,所以用当前节点的next的val替代当前节点,同时删除后面一个节点 满足题目,说明不包含最后一个节点的删除。
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
876链表的中间结点
- 快慢指针 不多BB
public ListNode middleNode(ListNode head) {
if (head==null||head.next==null) return head;
ListNode fast=head,slow=head;
while (fast!=null&&fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
// 中间节点
return slow;
}
###1290二进制链表转整数
- 先获取长度然后开始遍历,利用Pow函数求出十进制数,在循环的过程中len–
public int getDecimalValue(ListNode head) {
int len = 0;
ListNode node = head;
while (node!=null){
node = node.next;
len++;
}
node = head;
int sum = 0;
while (node!=null){
sum += node.val*Math.pow(10,len);
len--;
}
return sum;
}
// LeetCode 答案
题解中的是运用了反向运算操作,我们在获得二进制的时候是除于2取余数,要计算被除数则是要商乘于2加余数。
例如: 将数字 5 转换为二进制,我们的计算步骤如下:
5 / 2 = 2 ...... 1
2 / 2 = 1 ...... 0
将上面第1步中的除于2移动到等号右边,得到: 5 = (2 * 2)+ 1
以上式子(2 * 2)中,乘号右边的2又可以拆解为: 2 = (2 * 1)+ 0
代入原式子中: 5 = ( 2 * ( ( 2 * 1 ) + 0) ) + 1
public int getDecimalValue(ListNode head) {
int result = 0;
while(head!=null){
// 逆运算再加上余数
result = result*2;
result += head.val;
head = head.next;
}
return result;
}
面试题_两个链表的第一个公共节点
- 我的思路是先求出连个链表的长度差,然后根据长度差移动较长链表的指针使得两个链表在同一个位置开始移动,当两个指针都不为空的时候,判断是否两个指针指向了同一个节点,如果是则返回这个节点,如果没有交点则返回null。
- 下面提供三个方法 方法2 代码比较简洁 值得学习
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA, curB = headB;
int lenA = LinkListLen(curA);
int lenB = LinkListLen(curB);
int diff = lenA >= lenB ? lenA - lenB : lenB - lenA;
for (int i = 0; i < diff; i++) {
if (lenA >= lenB) {
curA = curA.next;
} else {
curB = curB.next;
}
}
while (curA != null && curB != null) {
if (curA == curB) { // 这里判断交点不是判断节点的值是否相等是判断节点的地址(引用是否相等)
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
public int LinkListLen(ListNode node) {
int lenth = 0;
while (node != null) {
node = node.next;
lenth++;
}
return lenth;
}
// Leecode 方法---1
// 也是长度差的思想 不过操作上更加巧妙 让原本指向A链表的指针指向B从B的开始继续循环
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode node1 = headA;
ListNode node2 = headB;
// 如果没有交点则node1==node2==null
while (node1!=node2){
node1 = node1 != null?node1.next : headB;
node2 = node2 != null?node2.next : headA;
}
return node1;
}
// LeetCode---2 也是利用长度差 和我的差不多 但是是在一个方法内实现的 没有求长度 利用一个临时指针找到长度差 改变较长链表指针的指向
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 异常判断
if (headA == null || headB == null)
return null;
ListNode A = headA;
ListNode B = headB;
// A B 向前走
while (A != null && B != null) {
A = A.next;
B = B.next;
}
// 下面的只会执行其中的一个
while (A != null) {
A = A.next;
headA = headA.next;
}
while (B != null) {
B = B.next;
headB = headB.next;
}
// headA 和 headB 同位置 重点
while (headA != null && headB != null) {
if (headA == headB) return headA;
headA = headA.next;
headB = headB.next;
}
return null;
}
面试题_移除重复节点
- 我的思路 两层循环暴力判断当前节点和剩下的节点的值是否相同如果相同则重复的节点 但是时间复杂度有点高O(n2)
- LeetCode 解法的话是利用了HashSet判断当前节点的值是否在HashSet中如果存在删除该节点不存在添加进HashSet
// public static ListNode removeDuplicateNodes(ListNode head) {
// ListNode pre = null;
// ListNode cur = head;
// Set<Integer> numSet = new HashSet<>();
// while (cur != null) {
// int num = cur.val;
// if (numSet.contains(num)) {
// if (pre != null) { // 第一个节点 多余
// pre.next = cur.next;
// cur = pre;
// }
// } else {
// numSet.add(num);
// }
// pre = cur;
// cur = cur.next;
// }
// pre = null;
// cur = null;
// return head;
// }
// LeetCode
// public static ListNode removeDuplicateNodes(ListNode head) {
// ListNode pre = null;
// ListNode cur = head;
// Set<Integer> numSet = new HashSet<>();
// while (cur != null) {
// int num = cur.val;
// if (numSet.contains(num)) {
// if (pre != null) { // 第一个节点 多余
// pre.next = cur.next;
// cur = pre;
// }
// } else {
// numSet.add(num);
// }
// pre = cur;
// cur = cur.next;
// }
// pre = null;
// cur = null;
// return head;
// }