https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/
思路一:栈
从链表的头节点开始,依次将每个节点压入栈内,然后依次弹出栈内的元素并存储到数组中。
时间复杂度: O ( n ) O(n) O(n):正向遍历一遍链表,然后从栈弹出全部节点,等于又反向遍历一遍链表
空间复杂度: O ( n ) O(n) O(n):额外使用一个栈存储链表的值
class Solution {
public int[] reversePrint(ListNode head) {
Stack<Integer> stk = new Stack<>();
while (head != null) {
stk.push(head.val);
head = head.next;
}
int[] res = new int[stk.size()];
for (int i = 0; i < res.length; i++) {
res[i] = stk.pop();
}
return res;
}
}
执行用时:2 ms, 在所有 Java 提交中击败了36.18%的用户
内存消耗:39.1 MB, 在所有 Java 提交中击败了57.54%的用户
思路二:递归
本质上和栈是一样的,先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出
时间复杂度: O ( n ) O(n) O(n):遍历链表,递归 n n n 次【没有栈的压入、弹出操作,实际时间要快了一点】
空间复杂度: O ( n ) O(n) O(n):系统递归需要使用 O ( n ) O(n) O(n) 的栈空间。
class Solution {
int[] res = null;
int index = 0;
public int[] reversePrint(ListNode head) {
recur(head,0);
return res;
}
public void recur(ListNode head, int i) {
if (head == null) {
res = new int[i];
return;
}
recur(head.next, i + 1);
res[index++] = head.val;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:39.9 MB, 在所有 Java 提交中击败了8.33%的用户
思想3:反转链表
时间复杂度依然是 O ( n ) O(n) O(n),原地转置空间复杂度可以做到 O ( 1 ) O(1) O(1),不详细写了,下面有反转链表的题目
https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/
LeetCode中所有的链表题目都是不带头结点的,所以添加一个 newHead
方便删除首个结点。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode pre = newHead;
while (head != null) {
if (head.val == val) {
pre.next = head.next;
break;
}
pre = pre.next;
head = head.next;
}
return newHead.next;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:37.6 MB, 在所有 Java 提交中击败了92.14%的用户
https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/
思路一:双指针
快慢指针同时出发,慢指针一步一个,快指针一步两个,快慢指针确定链表长度 n,倒着数第 k 个,即正着数 n-k 个
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode slow = head, fast = head;
int length = 0;
while (fast != null && fast.next != null) {
length++;
slow = slow.next;
fast = fast.next.next;
}
length = fast == null ? length * 2 : length * 2 + 1;
length -= k;
while (length-- > 0)
head = head.next;
return head;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:36.3 MB, 在所有 Java 提交中击败了54.22%的用户
思路二:双指针(更简洁)
快指针先出发,让它指向正数第 k 个结点,慢指针再出发,和快指针同步后移,当快指针达到链表尾部时,慢指针就是倒数第k个结点
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head, slow = head;
while (k > 0) {
fast = fast.next;
k--;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:36.3 MB, 在所有 Java 提交中击败了34.05%的用户
https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/
思路一:递归
递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret
,函数返回的过程中,让当前结点的下一个节点的 next
指向当前节点,让当前结点的 next
指针指向空
时间复杂度: O ( n ) O(n) O(n),其中 n 是链表的长度。需要遍历链表一次。
空间复杂度: O ( n ) O(n) O(n),递归调用的栈空间,最多为 n n n 层
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode ret = reverseList(head.next);
head.next.next = head;
head.next = null;
return ret;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:38.3 MB, 在所有 Java 提交中击败了35.71%的用户
思路二:原地转置
双指针遍历链表,将当前节点的 next
指针改为指向前一个节点。
时间复杂度: O ( n ) O(n) O(n),其中 n 是链表的长度。需要遍历链表一次。
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public ListNode reverseList(ListNode head) {
ListNode temp, pre = null;
while (head != null) {
temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:37.7 MB, 在所有 Java 提交中击败了98.59%的用户
https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/
思路一:迭代归并
时间复杂度:O(n+m) ,n 和 m 分别为两个链表的长度
空间复杂度:O(1),只需要常数的空间存放若干变量。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(); //便于最后找到链表的头
ListNode p = head;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
p.next = l1;
l1 = l1.next;
} else {
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
p.next = l1 != null ? l1 : l2;
return head.next;
}
}
执行用时:1 ms, 在所有 Java 提交中击败了98.55%的用户
内存消耗:38.8 MB, 在所有 Java 提交中击败了16.43%的用户
思路二:递归
时间复杂度:O(n+m) ,n 和 m 分别为两个链表的长度
空间复杂度:O(n + m)递归调用消耗的栈空间,n 和 m 分别为两个链表的长度。
class Solution {
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;
}
}
}
执行用时:1 ms, 在所有 Java 提交中击败了98.55%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了5.13%的用户
https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
思想一:哈希表
先遍历一遍链表,通过一个HashMap记录新旧节点的映射,再遍历一遍链表,通过旧链表当前节点的 random 指针找到对应的新链表的节点random指针要指向的地方
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N)
class Solution {
HashMap<Node, Node> map = new HashMap(); //做新旧结点映射
public Node copyRandomList(Node head) {
if (head == null) return null;
Node newhead = new Node(0);
Node p = head, new_p = newhead;
while (p != null) {
Node cur = new Node(p.val);
new_p.next = cur;
new_p = new_p.next;
map.put(p, cur);
p = p.next;
}
p = head;
new_p = newhead.next;
while (p != null) {
if (p.random != null)
new_p.random = map.get(p.random);
new_p = new_p.next;
p = p.next;
}
return newhead.next;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了43.74%的用户
我只想到了哈希表的解法,下面三种都是 官方题解
方法二:回溯
方法三:O(N)空间的迭代
方法二和三的最核心要点还是用哈希表建立新旧结点的映射,才得以实现,不详细写了
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N)
方法二:O(1)空间的迭代
遍历原来的链表并拷贝每一个节点,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表,然后遍历新组成的链表,这样就有办法把Random指针接上,之后就好办了
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Node ptr = head;
while (ptr != null) {
Node newNode = new Node(ptr.val);
newNode.next = ptr.next;
ptr.next = newNode;
ptr = newNode.next;
}
ptr = head;
while (ptr != null) {
ptr.next.random = (ptr.random != null) ? ptr.random.next : null;
ptr = ptr.next.next;
}
Node ptr_old_list = head; // A->B->C
Node ptr_new_list = head.next; // A'->B'->C'
Node head_old = head.next;
while (ptr_old_list != null) {
ptr_old_list.next = ptr_old_list.next.next;
ptr_new_list.next = (ptr_new_list.next != null) ? ptr_new_list.next.next : null;
ptr_old_list = ptr_old_list.next;
ptr_new_list = ptr_new_list.next;
}
return head_old;
}
}
https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/
二叉排序树的中序遍历是有序的,所以这必然是中序遍历的过程中串成双链表,还有第一个和最后一个结点,在全部遍历结束后再处理让它们相互对指形成循环链表
递归
时间复杂度: O ( n ) O(n) O(n),中序遍历链表
空间复杂度: O ( n ) O(n) O(n),递归栈空间
class Solution {
Node pre = null, newHead = null;
void inOrder(Node root) {
if (root == null) return;
inOrder(root.left);
if (newHead == null)
newHead = root;
else
pre.right = root;
root.left = pre;
pre = root;
inOrder(root.right);
}
public Node treeToDoublyList(Node root) {
if (root == null) return null;
inOrder(root);
pre.right = newHead;
newHead.left = pre;
return newHead;
}
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:37.6 MB, 在所有 Java 提交中击败了88.54%的用户
非递归
时间复杂度: O ( n ) O(n) O(n),中序遍历链表
空间复杂度: O ( n ) O(n) O(n),栈空间
class Solution {
public Node treeToDoublyList(Node root) {
if (root == null) return null;
Stack<Node> stack = new Stack<>();
Node head = null, p = root, prev = null;
while (!stack.isEmpty() || p != null) {
while (p != null) {
stack.push(p);
p = p.left;
}
p = stack.pop();
//记录链表首个节点
if (head == null) {
head = p;
} else {
prev.right = p;
}
p.left = prev;
prev = p;
p = p.right;
}
//要求循环链表,处理头节点与尾节点
head.left = prev;
prev.right = head;
return head;
}
}
https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/
思路一:求表长,同步遍历
分别用快慢指针法求出链表 A,B 的长度 l e n g t h A , l e n g t h B length_A,length_B lengthA,lengthB,假设 A 表较长,让 A 先走 l e n g t h A − l e n g t h B length_A-length_B lengthA−lengthB ,然后两表同步遍历,如果存在公共节点,一定会相遇
不知道哪个表长,实现起来很繁琐,算了
双指针法
两个指针 P1,P2,P1先遍历表A再遍历表B,P2遍历表B遍历表A,相遇时就是公共节点,两表没有公共节点时,pA和pB会同时遍历完两个表,同时为空,不会死循环
时间复杂度: O ( m + n ) O(m+n) O(m+n),两个链表的长度
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA, pB = headB;
//两表没有公共节点时,pA和pB会同时遍历完两个表,同时为空,不会死循环
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return headA;
}
}
执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:41.3 MB, 在所有 Java 提交中击败了48.48%的用户
https://leetcode-cn.com/problems/palindrome-linked-list/
思路一:双指针+栈
快慢指针,前半部分存入栈中,但有些边界问题要处理,代码看起来不整洁
时间复杂度: O ( n ) O(n) O(n),遍历一遍链表
空间复杂度: O ( n ) O(n) O(n) ,辅助栈空间
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
Stack<Integer> stk = new Stack<>();
stk.push(head.val);
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
stk.push(slow.val);
}
if (fast != null) slow = slow.next;
while (slow != null) {
if (slow.val != stk.pop()) return false;
slow = slow.next;
}
return true;
}
}
执行用时:2 ms, 在所有 Java 提交中击败了60.25%的用户
内存消耗:42.2 MB, 在所有 Java 提交中击败了30.66%的用户
思路二:双指针+反转链表
快慢指针找到中点,反转后半部分链表
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//结点个数为奇数、偶数需要分别处理
ListNode p = fast == null ? slow : slow.next, pre = null;
while (p != null) {
ListNode temp = p.next;
p.next = pre;
pre = p;
p = temp;
}
while (pre != null) {
if (head.val != pre.val) return false
head = head.next;
pre = pre.next;
}
return true;
}
}
执行用时:1 ms, 在所有 Java 提交中击败了99.95%的用户
内存消耗:41.1 MB, 在所有 Java 提交中击败了64.37%的用户