剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
思路:
1.方法一:使用栈,将节点依次存入栈,再依次取出,存入数组
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<>();
while(head != null) {
stack.push(head);
head = head.next;
}
int size = stack.size();
int[] num = new int[size];
size = 0;
while (!stack.isEmpty()) {
num[size++] = stack.pop().val;
}
return num;
}
}
2.方法二:递归,递归将元素加入ArrayList中,再反过来遍历一遍加入数组
class Solution {
ArrayList<Integer> al = new ArrayList<>();
public int[] reversePrint(ListNode head) {
if (head == null) return new int[0];
cursion(head);
int[] num = new int[al.size()];
int size = al.size();
for(int i = 0 ;i < size; i++) {
num[i] = al.get(size-i-1);
}
return num;
}
public void cursion(ListNode ln) {
if (ln == null) return;
al.add(ln.val);
cursion(ln.next);
}
}
160. 相交链表
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表
在节点 c1 开始相交。返回交点注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
思路:
1.方法一:哈希表存入节点,遍历两条链表,如果有重复节点则相交,时间复杂度(Om+n),空间复杂度(Om+n)
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> hashSet = new HashSet<>();
while (headA != null) {
hashSet.add(headA);
headA = headA.next;
}
while (headB != null) {
if (hashSet.contains(headB)) {
return headB;
}
headB = headB.next;
}
return null;
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
反转链表
方法一:两个指针pre和cur,cur指向当前节点,pre指向cur的前一个节点
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
方法二:递归,走到倒数第二个节点开始反转,cur是最后一个节点,反转后变成头结点
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head; //
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
思路:
方法一:递归,递归调用,判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
else if (l2 == null) return l1;
else if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
方法二:迭代,两个指针prehead.next指向头节点,cur 指向当前节点前一个节点
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode cur = prehead;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
}
else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return prehead.next;
}
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 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]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
思路:
原始代码:两遍遍历链表
public ListNode removeNthFromEnd(ListNode head, int n) {
int size = 0;
ListNode temp = head;
ListNode pre = head;
while (head != null) {
head= head.next;
size++;
}
head = temp;
int index = 1;
while (head != null) {
if (n == size) {
temp = head.next;
break;
}
else if (size - index + 1 == n){
pre.next = head.next;
break;
}
pre = head;
head = head.next;
index++;
}
return temp;
}
方法一:新增一个dummy节点,指向头结点,遍历链表统计长度,移动指针到要删除的节点的前一个节点,return dummy.next;
public ListNode removeNthFromEnd(ListNode head, int n) {
int size = 0;
ListNode dummy = new ListNode(-1,head);
ListNode temp = head;
ListNode cur = dummy;
while (temp != null) {
temp = temp.next;
size++;
}
for (int i = 1;i < size - n + 1 ;i++) {
cur = cur.next;
}
cur.next = cur.next.next;
return dummy.next;
}
方法二:栈,将节点依次加入栈中,然后出n个节点,将栈顶元素node.next = node.next.next;
public ListNode removeNthFromEnd(ListNode head, int n) {
Stack<ListNode> stack = new Stack<>();
ListNode dummy = new ListNode(-1,head);
ListNode temp = dummy;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
for (int i = 0; i < n; i++) {
stack.pop();
}
ListNode cur = stack.peek();
cur.next = cur.next.next;
return dummy.next;
}
方法三:增加一个dummy节点,dummy.next指向head,双指针yyds,一个快指针先走n步,然后两个指针一起走,直到快指针走到头
public ListNode removeNthFromEnd(ListNode head, int n) {
//Stack stack = new Stack<>();
ListNode dummy = new ListNode(-1,head);
ListNode fast = head;
ListNode slow = dummy;
for (int i = 0 ;i < n ;i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
思路:
方法一:迭代,dummy指针next指向head,left指向dummy.next, right指向dummy.next.next ; 指针交换,然后dummy往后走
public ListNode swapPairs(ListNode head) {
if (head == null) return null;
ListNode temp = new ListNode(-1,head);
ListNode dummy = temp;
while (dummy.next != null && dummy.next.next != null) {
ListNode left = dummy.next;
ListNode right = dummy.next.next;
dummy.next = right;
left.next = right.next;
right.next = left;
dummy = left;
}
return temp.next;
方法二:递归,分解出子过程,然后交给下一级去执行剩下重复的过程
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
ListNode temp = head.next;
head.next = swapPairs(temp.next);
temp.next = head;
return temp;
}
445. 两数相加 II
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
思路:用栈存入链表的节点,然后取出构造新的链表,
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<ListNode> stackOne = new Stack<>();
Stack<ListNode> stackTwo = new Stack<>();
while (l1 != null) {
stackOne.push(l1);
l1 = l1.next;
}
while (l2 != null) {
stackTwo.push(l2);
l2 = l2.next;
}
int preNum = 0;
ListNode tail = null;
while (!stackOne.empty() || !stackTwo.empty() || preNum != 0) {
int a = stackOne.isEmpty() ? 0 : stackOne.pop().val;
int b = stackTwo.isEmpty() ? 0 : stackTwo.pop().val;
int cur = (a + b + preNum);
preNum = cur / 10;
cur = cur % 10;
ListNode curNode = new ListNode(cur);
curNode.next = tail;
tail = curNode;
}
return tail;
}
}
234. 回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
方法一:使用双端对列,将元素都加入队列中,然后从两头出对,当只剩一个元素或者全部都出队为止,时间O(n),空间O(n)
class Solution {
public boolean isPalindrome(ListNode head) {
LinkedList<ListNode> ll = new LinkedList<>();
while(head != null) {
ll.addLast(head);
head = head.next;
}
while(ll.size() != 1 && !ll.isEmpty()) {
int num1 = ll.removeFirst().val;
int num2 = ll.removeLast().val;
if (num1 != num2) {
return false;
}
}
return true;
}
}
方法二:快慢指针,将前半段反转,比较两段
class Solution {
public static boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = null;
while(fast != null && fast.next != null) {
fast = fast.next.next;
ListNode temp = slow.next;
slow.next = pre;
pre = slow;
slow = temp;
}
if(fast != null) {
slow = slow.next;
}
while(pre != null && slow != null) {
if(pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
}
方法三:找到前半段尾部,然后将后半段翻转,比较,最后将后半段重新翻转回去
class Solution {
public static boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode firLinkedList = getFirstLinkedList(head);
ListNode secLinkedList = reverseLinkedList(firLinkedList.next);
boolean flag = true;
while(flag && secLinkedList != null ) {
if(head.val != secLinkedList.val) {
flag = false;
}
head = head.next;
secLinkedList = secLinkedList.next;
}
firLinkedList.next = reverseLinkedList(secLinkedList);
return flag;
}
public static ListNode getFirstLinkedList(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static ListNode reverseLinkedList(ListNode head) {
ListNode temp = null;
ListNode cur = head;
while (cur != null) {
ListNode node = cur.next;
cur.next = temp;
temp = cur;
cur = node;
}
return temp;
}
}
725. 分隔链表
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 1:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
root 的长度范围: [0, 1000].
输入的每个节点的大小范围:[0, 999].
k 的取值范围: [1, 50].
思路:遍历一遍链表统计长度,然后计算 int count = len / k ;int res = len % k ;ListNode数组要存入每节链表开头的节点
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode[] list = new ListNode[k];
ListNode cur = root;
int len = 0;
while (cur != null) {
len++;
cur = cur.next;
}
int count = len / k;
int res = len % k ;
for (int i = 0;root != null && i < k;i++) {
list[i] = root;
int size = count + (res-- > 0 ? 1 : 0);
for (int j =0;j < size - 1;j++) {
root = root.next;
}
ListNode temp = root.next;
root.next = null;
root = temp;
}
return list;
}
}
328. 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
思路:两条链表,一条是奇链,一条是偶链,然后将偶链接在奇链的后面
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode oddhead = head;//奇链头结点
ListNode evenhead = head.next;//偶链头结点
ListNode odd = head,even = evenhead;
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenhead;//将偶链表接在奇链表后面
return oddhead;
}
}
1.指针对于链表的操作来说太重要了,用好指针,链表就是起飞
2.对于倒序,栈是个很应该考虑的方式
3.链表与数组很大的差别在于,链表插入,删除实在太方便了
4.适当构造新节点,可以使链表操作更方便