Java中没有Listnode类,所以要自己定义一个Listnode。(要会写)
这里面用了自引用。注意:类是没有输入输出的。
Public class Listnode{
int val;
Listnode next;
Listnode(int x){
val = x;
next = null;
}
}
构造函数中的参数,例如ListNode(int x)
,是用来在创建对象时初始化该对象的状态。这个输入是通过代码直接传递的,而不是通过用户交互。
160. 相交链表 - 力扣(LeetCode)
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
-
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
-
listA
- 第一个链表 -
listB
- 第二个链表 -
skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数 -
skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 — 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
/*
将终点设置在相交点,PA→headA,PB→headB,
PA走完A以后,再继续从headB开始,走到相交点;
PB走完B以后,再继续从headA开始,走到相交点;
无论是否相交,PA和PB走过的路径等长,当走到终点(相交点)时,返回PA/PB即可
*/
if (headA == null || headB == null){
return null;
}
ListNode PA = headA;
ListNode PB = headB;
while(PA != PB){
if(PA!= null) {
PA = PA.next;
}
else {
PA = headB;
}
if(PB != null) {
PB = PB.next;
}
else {
PB = headA;
}
}
return PA;
}
}
206. 反转链表 - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//定义ListNode类
// public class ListNode{
// int val;
// ListNode next;
// ListNode(int x){
// val = x;
// next = null;
// }
// }
class Solution {
public ListNode reverseList(ListNode head) {
/*
最后一个指针肯定指向null,反转以后,就是head为最后一个指针,指向null
所以要先定义一个null指针,再用另一个指针指向head,让head指向null
因此,这是双指针法
*/
ListNode Pre = null;
//Pre.next = null;//Pre一开始就是null,无法访问其成员变量
ListNode P = head;
/*循环终止条件是while (P != null) 而不是while (P.next != null)
如果使用 while (P.next != null),会有以下问题:
无法处理最后一个节点:while (P.next != null) 会在当前节点的下一个节点是 null 时停止,这意味着最后一个节点的反转不会被处理。
链表长度为1的情况:如果链表只有一个节点,P.next 一开始就是 null,循环体会根本不会执行,这样也会出错。
*/
while (P != null){
ListNode tmp = P.next;//tmp保存当前指针的指向,方便后面反转
P.next = Pre;//指针要反转
Pre = P;//值也要反转
P = tmp;//P继续前进
}
return Pre;//当遍历完链表后,Pre会指向反转后的新链表的头节点。
}
}
234. 回文链表 - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
/*
其实就是在反转链表的基础上,判断一下反转后的链表与原链表是否相等
不过不能在原链表基础上反转,要复制出一个新链表再反转
*/
ListNode copyHead = copyList(head);
ListNode P = copyHead;
ListNode Pre = null;
while (P != null) {
ListNode tmp = P.next;
P.next = Pre;
Pre = P;
P = tmp;
}
//遍历整个链表,确保所有节点的值都相等才返回true。
while (head !=null && Pre!= null){
if (head.val!= Pre.val){
return false;
}
head = head.next;
Pre = Pre.next;
}
return true;
}
// 复制链表的辅助函数
private ListNode copyList(ListNode head) {
if (head == null) {
return null;
}
ListNode newHead = new ListNode(head.val);
ListNode current = newHead;
ListNode originalCurrent = head.next;
while (originalCurrent != null) {
current.next = new ListNode(originalCurrent.val);
current = current.next;
originalCurrent = originalCurrent.next;
}
return newHead;
}
}
141. 环形链表 - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
/*
HashSet存储遍历过的ListNode,看是否会重复遇见
HashSet本质上是集合,不允许重复,不过它是通过hashcode和equals来判断元素是否相同的,
所以即使ListNode的值相同,也不算重复(内存地址不同)
*/
HashSet<ListNode> Seen = new HashSet();
while(head != null){
/*
Seen.add(head) 尝试将当前节点 head 添加到 HashSet 中。
HashSet 的 add 方法会返回一个布尔值:
如果当前节点 head 没有在 HashSet 中,它会被添加到 HashSet,并且 add 方法返回 true。
如果当前节点 head 已经在 HashSet 中,它不会被重新添加,add 方法返回 false。
因此,当 add 方法返回 false 时,表示当前节点已经被访问过,意味着链表中存在环,函数返回 true。
*/
//即使 Seen.add(head) 在 if 语句中作为条件,它也会执行 add 操作。
//条件判断表达式会首先计算 seen.add(head) 的结果,然后根据这个结果决定是否执行 if 块内部的代码。
if (! Seen.add(head)){
return true;
}
head = head.next;
}
return false;
}
}
HashSet存储遍历过的ListNode,看是否会重复遇见:
HashSet
底层是基于哈希表实现的。每次添加一个元素时,HashSet
会计算这个元素的哈希码(使用 hashCode
方法),然后将这个哈希码映射到哈希表中的一个位置。如果该位置已经有一个元素存在,HashSet
会进一步检查这些元素是否相等(使用 equals
方法)。如果哈希码相同并且 equals
方法认为两个元素相等,那么 HashSet
会认为这个元素已经存在,不会再次添加。
所以即使ListNode的值相同,也不算重复(内存地址不同)
在 Java 的 HashSet
中,没有 key-value 的概念。HashSet
是基于 HashMap
实现的,但是它只存储单一的元素,并不涉及 key-value 对。HashSet
内部其实是通过一个 HashMap
实现的,元素作为 HashMap
的 key,而 value
是一个常量 PRESENT
,它的值通常是 new Object()
。
其实就是在上一题的基础上,返回环形处的ListNode
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
/*
HashSet存储遍历过的ListNode,看是否会重复遇见
HashSet本质上是集合,不允许重复,不过它是通过hashcode和equals来判断元素是否相同的,
所以即使ListNode的值相同,也不算重复(内存地址不同)
*/
HashSet<ListNode> Seen = new HashSet();
while(head != null){
/*
Seen.add(head) 尝试将当前节点 head 添加到 HashSet 中。
HashSet 的 add 方法会返回一个布尔值:
如果当前节点 head 没有在 HashSet 中,它会被添加到 HashSet,并且 add 方法返回 true。
如果当前节点 head 已经在 HashSet 中,它不会被重新添加,add 方法返回 false。
因此,当 add 方法返回 false 时,表示当前节点已经被访问过,意味着链表中存在环,函数返回 true。
*/
//即使 Seen.add(head) 在 if 语句中作为条件,它也会执行 add 操作。
//条件判断表达式会首先计算 seen.add(head) 的结果,然后根据这个结果决定是否执行 if 块内部的代码。
if (! Seen.add(head)){
return true;
}
head = head.next;
}
return false;
}
}
21. 合并两个有序链表 - 力扣(LeetCode)(递归)
什么是递归函数?
递归函数是一种函数调用自身的编程技巧。
在递归函数中,函数通过不断调用自身来解决一个问题,直到达到基本情况(递归终止条件)并返回结果。
递归函数在解决一些问题时非常有用,特别是那些具有递归结构的问题,例如树、图等。通过使用递归函数,可以简化问题的表达和解决过程。 需要注意的是,在编写递归函数时,确保递归终止条件能够被满足,并且每次递归调用都能使问题规模减小,以避免无限递归和栈溢出等问题。此外,递归函数的性能可能不如迭代方式,因此在某些情况下,考虑使用迭代方法来替代递归。
递归的实现原理
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
递归算法三要素
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
递归的代码比较简单,但是想到用递归比较难
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
/*用递归做,怎么能想到递归呢
确定递归函数的参数和返回值:
参数:两个链表的比较的节点
返回值:合并后链表的头结点(值更小的那个节点)
确定终止条件:list1或list2空了
确定单层递归逻辑:
比较两个节点的值,若list1.val <= list2.val,mergeTwoLists,返回list1
否则,返回list2
*/
// 确定终止条件:list1或list2为空
if (list1 == null){
return list2;
}
if (list2 == null){
return list1;
}
// 确定单层递归逻辑:比较两个节点的值,递归合并剩下的部分
//如果 list1 的值小于等于 list2 的值,
//则 list1 的 next 指向合并后的剩余部分,即 mergeTwoLists(list1.next, list2),
//然后返回 list1。
if (list1.val <= list2.val){
list1.next = mergeTwoLists(list1.next, list2);
return list1;
}
else{
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
}
2. 两数相加 - 力扣(LeetCode)(递归)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
/*
每一个节点处都是两个值相加,若有进位,则高位的和加1
这是一个和原问题相似的(两数相加),规模更小的子问题(节点相加),所以非常适合用递归解决。
递归单层逻辑:
相加:sum = l1.val + l2.val (if (l2 != null || l1 != null)) + JinWei
在l1的基础上求和,最后也是返回l1: l1.val = sum % 10
是否产生进位:JinWei = sum / 10
递归参数和返回值:
递归参数: l1 l2 JinWei
返回值:l1
递归终止条件:
l1 == null && l2 == null
*/
return addTwo(l1, l2, 0);
}
//递归函数
public ListNode addTwo(ListNode l1, ListNode l2, int JinWei) {
// 递归终止条件:
// l1 == null && l2 == null
if (l1 == null && l2 == null){
return JinWei == 0 ? l1 : new ListNode(JinWei);
}
//在l1的基础上求和,l1一定不能为null,若l1为null,就把l2的值给l1,l2为null
if (l1 == null){
l1 = l2;
l2 = null;
}
//递归单层逻辑
int sum = l1.val + (l2 != null ? l2.val:0) + JinWei;
JinWei = sum / 10;
l1.val = sum % 10;
l1.next = addTwo (l1.next, l2 != null ? l2.next: null, JinWei);
return l1;
}
}
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)(通过dummy_head统一操作链表)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
/*
定位到目标节点object:
先从头遍历一遍,求出链表长度L,
再遍历一遍,object就是第L-n+1个节点。
如果删除了头结点怎么办:
为了方便操作,统一在头结点前加一个dummy_head,最后返回dummy_head.next
*/
ListNode dummy_head = new ListNode(0,head);
int L = getLength (head);
//查找object的前一个节点
//初始化
ListNode pre = dummy_head;
//注意:i< L-n,千万不能取等号!!!
for (int i = 0; i< L-n; i++){
pre = pre.next;
}
pre.next = pre.next.next;
return dummy_head.next;
}
//写一个求长度的函数
public int getLength(ListNode head){
int length = 0;
while (head != null){
length ++;
head = head.next;
}
return length;
}
}
24. 两两交换链表中的节点 - 力扣(LeetCode)
这道题做的时候一定要画图,交换的是A和B
但是具体交换过程中,A前面的节点和B后面的节点都会用到
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy_head = new ListNode(0,head);
int L = getLength(head);
ListNode prenode = dummy_head;
while (head !=null && head.next != null){
prenode.next = head;
ListNode A = head;
ListNode B = head.next;
//交换
prenode.next = B;
A.next = B.next;
B.next = A;
//前进两格
prenode = A;
head = A.next;
}
return dummy_head.next;
}
public int getLength(ListNode head){
int Length = 0;
while (head != null){
Length++;
head = head.next;
}
return Length;
}
}
其实这道题也可以用递归:这是一个和原问题相似的,规模更小的子问题,所以可以用递归解决。