链表是一种线性数据结构,其中每个节点都包含数据部分和指向下一个节点的指针。对于单向链表来说,一旦你知道了第一个节点(也就是头节点),你就可以通过跟随每个节点的
next
指针找到链表中的所有其他节点,直到你到达一个next
指针为null
的节点,这表示你已经到达了链表的末尾。
这里有几个关键点需要注意:
访问顺序:在单向链表中,你只能从第一个节点开始,顺序地访问到链表的末尾。你不能随机访问链表中间的节点,除非你从头节点开始遍历。
头节点的重要性:头节点是链表的入口点。如果头节点的引用丢失,整个链表可能就会丢失,除非你有其他方式可以访问到链表中的某个节点。
链表的遍历:要访问链表中的所有节点,你需要从头节点开始,然后依次访问每个节点的
next
节点,直到next
为null
。链表的操作:在链表上执行的大多数操作,如插入、删除节点等,都需要从头节点开始,或者至少需要某个特定节点的前一个节点的引用。
双向链表:在双向链表中,每个节点还有一个指向前一个节点的指针,这允许你从任一节点开始,向两个方向遍历链表。
循环链表:在循环链表中,最后一个节点的
next
指针指向头节点,形成一个闭环。这意味着你可以从头节点开始,无限次地遍历整个链表。因此,对于单向链表来说,确实只要知道了第一个节点,就能通过逐个节点的
next
指针找到所有其他节点。这也是为什么在很多链表操作中,头节点的引用是非常重要的。
(1)给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
import java.util.HashMap;
import java.util.Map;
public class Solution {
//暴力破解
/*public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
throw new IllegalArgumentException("No two sum solution");
}*/
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums1 = {3, 7, 11, 15, 5};
int target1 = 18;
int[] result1 = solution.twoSum(nums1, target1);
System.out.println("[" + result1[0] + "," + result1[1] + "]");
}
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numToIndex = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (numToIndex.containsKey(complement)) {
return new int[]{numToIndex.get(complement), i};
}
numToIndex.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
(2)给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
package leetcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Solution {
Logger logger = LoggerFactory.getLogger(Logger.class);
public static void main(String[] args) {
Solution solution = new Solution();
//创建第一个链表l1 = [2,4,3]
ListNode l1 = new ListNode(2);
l1.next = new ListNode(4);
l1.next.next = new ListNode(3);
//创建第二个链表l2 = [5,6,4]
ListNode l2 = new ListNode(5);
l2.next = new ListNode(6);
l2.next.next = new ListNode(4);
ListNode result = solution.addTwoNumbers(l1, l2);
printList(result);
}
public static void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.val + "->");
current = current.next;
}
System.out.println("null");
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode result = new ListNode();
ListNode current = result;
int carry = 0;
while (l1 != null || l2 != null) {
int x = (l1 != null) ? l1.val : 0;
int y = (l2 != null) ? l2.val : 0;
int sum = x + y + carry;
carry = sum / 10;
current.next = new ListNode(sum % 10);
current = current.next;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
current.next = new ListNode(carry);
}
return result.next;
}
}
(283)给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
package leetcode;
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {0, 1, 0, 3, 12};
solution.moveZeroes(nums);
for (int num : nums) {
System.out.println(num);
}
}
public void moveZeroes ( int[] nums){
int insertPos = 0;//非0元素插入的位置
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[insertPos] = nums[i];
insertPos++;
}
}
while (insertPos < nums.length) {
nums[insertPos] = 0;
insertPos++;
}
}
}
(19)给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]示例 2:
输入:head = [1], n = 1 输出:[]示例 3:
输入:head = [1,2], n = 1 输出:[1]
package leetcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Solution {
Logger logger = LoggerFactory.getLogger(Logger.class);
public static void main(String[] args) {
Solution solution = new Solution();
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
int n = 2;
ListNode result = solution.removeNthFromEnd(head, n);
ListPrint(result);
}
public static void ListPrint(ListNode head) {
ListNode result = head;
while (result != null) {
System.out.print(result.val + ",");
result = result.next;
}
System.out.println("null");
}
public ListNode removeNthFromEnd(ListNode head, int n) {
//列表为空和n不符合则无操作
if (head == null || n <= 0) {
return head;
}
ListNode dummy = new ListNode(0);//创建一个哑结点
dummy.next = head;
//同时定义两个指针
ListNode first = dummy;
ListNode second = dummy;
//遍历链表first,first指针移动了n+1次
for (int i = 0; i < n + 1; i++) {
first = first.next;
}
//如果first为空,则删除的元素是链表的头部
if (first == null) {
head = head.next;
return head;
}
/*如果first不为空,就将first移动至链表结尾,
同时second同步移动,当first到达结尾时,second则是倒数第n+1位*/
while (first != null) {
first = first.next;
second = second.next;
}
//删除倒数第n个节点,将倒数n+1位的next指向倒数n-1位
second.next = second.next.next;
return dummy.next;
}
}
给定两个单链表的头节点
headA
和headB
,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。图示两个链表在节点
c1
开始相交:题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 输出:Intersected at '2' 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。 在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。 由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。
方法一:
这段代码是用来找到两个链表的交点的。我们可以将这个过程想象成两个人在两条不同的跑道上跑步,他们从各自的起点同时出发,当他们跑到各自跑道的尽头时,会转向对方的跑道继续跑。如果他们在某个点相遇,那么这个点就是两个跑道的交点。
以下是代码的通俗解释:
我们有两个链表,分别用
headA
和headB
表示它们的头节点。我们定义两个指针
a
和b
,它们分别指向headA
和headB
。然后我们开始一个循环,这个循环会一直进行,直到
a
和b
都指向null
,也就是说,直到两个指针都到达了各自链表的末尾。在循环中,我们首先检查
a
和b
是否指向了同一个节点。如果是,那么我们找到了交点,返回这个节点。如果
a
指向的节点是null
(也就是说,它已经到达了第一个链表的末尾),我们就让a
指向headB
,这样它就会开始沿着第二个链表运行。同样,如果
b
指向的节点是null
(也就是说,它已经到达了第二个链表的末尾),我们就让b
指向headA
,这样它就会开始沿着第一个链表运行。如果循环结束后都没有找到交点,我们就返回
null
。这个方法的关键在于,如果两个链表有交点,那么这两个指针最终一定会相遇在这个交点上。如果没有交点,那么这两个指针最终都会到达
null
,循环结束。这个方法的优点是它只需要一次遍历,不需要预先计算两个链表的长度,也不需要额外的存储空间。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode listA = headA;
ListNode listB = headB;
while (listA != null || listB != null) {
if (listA == listB) {
return listA;
}
listA = (listA == null) ? headB : listA.next;
listB = (listB == null) ? headA : listB.next;
}
return null;
}
}
方法二:
将不同起点的两个人移动到相同长度的跑道,再同时起跑,如果相遇说明有相交的跑道
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode currentA = headA;
ListNode currentB = headB;
int countA = 0;
int countB = 0;
while (currentA != null) {
countA++;
currentA = currentA.next;
}
while (currentB != null) {
countB++;
currentB = currentB.next;
}
if (countA > countB) {
for (int i = 0; i < countA - countB; i++) {
headA = headA.next;
}
} else {
for (int i = 0; i < countB - countA; i++) {
headB = headB.next;
}
}
while (headA != null || headB != null) {
if (headA == headB) {
return headA;
}
headA = headA.next;
headB = headB.next;
}
return null;
}
给你一个链表的头节点
head
,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回
true
。 否则,返回false
。示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
import java.util.HashSet;
import java.util.Set;
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
ListNode listA = new ListNode(3);
listA.next = new ListNode(2);
listA.next.next = new ListNode(0);
listA.next.next.next = new ListNode(-4);
listA.next.next.next.next = listA.next;
boolean b = solution.hasCycle(listA);
System.out.println(b);
}
/*输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。*/
public boolean hasCycle(ListNode head) {
Set<ListNode> seem = new HashSet<>();
ListNode current = head;
while (current != null) {
if (!seem.add(current)) {
return true;
}
current = current.next;
}
return false;
}
}
龟兔赛跑法
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode fast = head;
ListNode slow = head;
while (fast != null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
return true;
}
if (fast == null || fast.next == null) {
return false;
}
}
return false;
}
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val
和next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。实现
MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。示例:
输入 ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] [[], [1], [3], [1, 2], [1], [1], [1]] 输出 [null, null, null, null, 2, null, 3] 解释 MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1); myLinkedList.addAtTail(3); myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3 myLinkedList.get(1); // 返回 2 myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3 myLinkedList.get(1); // 返回 3
public class MyLinkedList {
int size;
Node head;
public MyLinkedList() {
size = 0;
head = new Node(0);//设置哑结点
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);
System.out.println(myLinkedList.get(1));
myLinkedList.deleteAtIndex(1);
}
public int get(int index) {
if (index < 0 || index >= size) return -1;
Node pred = head;
for (int i = 0; i < index + 1; i++) {
pred = pred.next;
}
return pred.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;
}
size++;
Node pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
Node newNode = new Node(val);
newNode.next = pred.next;
pred.next = newNode;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
size--;
Node pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
public static class Node {
int val;
Node next;
public Node(int val) {
this.val = val;
this.next = null;
}
}
}
中等
给你一个长度为
n
的链表,每个节点包含一个额外增加的随机指针random
,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由
n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next
指针和random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。例如,如果原链表中有
X
和Y
两个节点,其中X.random --> Y
。那么在复制链表中对应的两个节点x
和y
,同样有x.random --> y
。返回复制链表的头节点。
用一个由
n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。你的代码 只 接受原链表的头节点
head
作为传入参数。示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]示例 2:
输入:head = [[1,1],[2,1]] 输出:[[1,1],[2,1]]示例 3:
输入:head = [[3,null],[3,0],[3,null]] 输出:[[3,null],[3,0],[3,null]]
public Node copyRandomList(Node head) {
if (head == null) return null;
//第一次遍历,复制每个节点,按照顺序连接
Map<Node, Node> map = new HashMap<>();
Node copy = head;
while (copy != null) {
map.put(copy, new Node(copy.val));
copy = copy.next;
}
//第二次遍历,复制next和random指针
copy = head;//重用copy变量,从链表开头开始
while (copy != null) {
Node nodeCopy = map.get(copy);
nodeCopy.next = copy.next != null ? map.get(copy.next) : null;
nodeCopy.random = copy.random != null ? map.get(copy.random) : null;
copy = copy.next;
}
return map.get(head);
}
方法二:递归法
public Node copyRandomList(Node head) {
if (head == null || cachedNodes.containsKey(head)) {
return cachedNodes.get(head);
}
//为头结点创建一个复制节点储存在程序中
Node newNode = new Node(head.val);
cachedNodes.put(head,newNode);
newNode.next = copyRandomList(head.next);
newNode.random = copyRandomList(head.random);
//返回复制节点
return newNode;
}