面试题06.从尾到头打印链表
安安思路:正向遍历链表,反转列表
#python 其实可以直接用head 不用p
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
list1 = []
p = head
while(p):
#print(p.val)
list1.append(p.val)
p = p.next
return list1[::-1]
//java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
ListNode p = head;
int count = 0;
while(p != null){
//System.out.println(p.val);
count++;
p = p.next;
}
int[] array = new int[count];
p = head;
while(p != null){
array[--count] = p.val;
p = p.next;
}
return array;
}
}
官方题解:栈 和自己的类似
//java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
int size = stack.size();
int[] print = new int[size];
for (int i = 0; i < size; i++) {
print[i] = stack.pop().val;
}
return print;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//java
class Solution {
public int[] reversePrint(ListNode head) {
LinkedList<Integer> stack = new LinkedList<Integer>();
while(head != null) {
stack.addLast(head.val);
head = head.next;
}
int[] res = new int[stack.size()];
for(int i = 0; i < res.length; i++)
res[i] = stack.removeLast();
return res;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
另一解法:递归
//java
class Solution {
ArrayList<Integer> tmp = new ArrayList<Integer>();
public int[] reversePrint(ListNode head) {
recur(head);
int[] res = new int[tmp.size()];
for(int i = 0; i < res.length; i++)
res[i] = tmp.get(i);
return res;
}
void recur(ListNode head) {
if(head == null) return;
recur(head.next);
tmp.add(head.val);
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#python
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
return self.reversePrint(head.next) + [head.val] if head else []
作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
面试题18.删除链表的结点
常规思路
//java anan 2020.3.18
//常规思路 找到结点并删除
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode h = new ListNode(); //新建一个头结点 题目给定的是不带头结点的链表
h.next = head;
ListNode p = head;
ListNode pre = h;
while(p != null && p.val != val){
pre = p;
p = p.next;
}
pre.next = p.next;
return h.next;
}
}
新思路:给定的是要删除结点的结点指针
下面这道题就是用的这种思路
面试题02.03.删除中间结点
//java
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
面试题22.链表中倒数第k个节点
安安思路:双指针 快慢指针
//java anan
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p = head;
ListNode q = head;
int n = 0;
while(p != null){
n++;
p = p.next;
if(n==k){
q = head;
}else if(n>k){
q = q.next;
}
}
return q;
}
}
//java 第一个指针先走k步 然后两个指针一起走
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p = head;
ListNode q = head;
for(int i = 0; i < k; i++){
p = p.next;
}
while(p != null){
p = p.next;
q = q.next;
}
return q;
}
}
注意的点:鲁棒性
//java
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(head == null || k <= 0){
return null;
}
ListNode p = head;
ListNode q = head;
for(int i = 0; i < k; i++){
if(p == null && i < k){
return null;
}
p = p.next;
}
while(p != null){
p = p.next;
q = q.next;
}
return q;
}
}
19.删除链表的倒数第N个节点
//java
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null || n <= 0){
return null;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode p = dummy;
ListNode q = dummy;
for(int i = 0; i < n+1; i++){
if(p == null){
return null;
}
p = p.next;
}
while(p != null){
p = p.next;
q = q.next;
}
q.next = q.next.next;
return dummy.next;
}
}
本题和上一题的区别:
1.上一题快指针需要先走n个节点 本题快指针需要走n+1个节点 为什么呢?
因为上一题找的是倒数第n个节点,定位到这个节点即可;但是本题要删除倒数第n个节点,所以需要定位的是倒数第n+1个节点,所以快慢指针的间隔是n+1
2.上一题不需要添加哑节点,本题却需要,为什么?
如果倒数第n个节点刚好是第1个节点,上一题直接返回即可,没有什么影响;但是本题却要删除,删除势必会影响到头结点,所以需要定义哑结点,删除第一个节点不需要单独考虑。
面试题22相关题目:求链表的中间节点
面试题24.反转链表(206)
修改next指针
//java
class Solution {
// public void show(ListNode head){
// ListNode p = head;
// while(p != null){
// System.out.print(p.val + " ");
// p = p.next;
// }
// System.out.println();
// }
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = null;
ListNode p = head;
ListNode post = p.next;
while(post != null){
p.next = pre;
//show(p);
pre = p;
p = post;
post = post.next;
}
p.next = pre;
return p;
}
}
(二刷)修改next指针
//anan 2020/8/3
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode pre = head;
ListNode cur = head;
ListNode suc = head.next;
while(cur != null){
cur.next = pre;
if(pre == head) pre.next = null;
pre = cur;
cur = suc;
if(suc != null) suc = suc.next;
}
return pre;
}
}
递归
//java anan 2020.3.18
class Solution {
// public void show(ListNode head){
// ListNode p = head;
// while(p != null){
// System.out.print(p.val + " ");
// p = p.next;
// }
// System.out.println();
// }
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode p = head;
if(head.next == null) return head;
else{
p = reverseList(head.next);
ListNode q = p;
while(q.next != null) q = q.next;
q.next = head;
head.next = null;
//show(p);
return p;
}
}
}
//java
/*
递归解法
这题有个很骚气的递归解法,递归解法很不好理解,这里最好配合代码和动画一起理解。
递归的两个条件:
终止条件是当前节点或者下一个节点==null
在函数内部,改变节点的指向,也就是 head 的下一个节点指向 head 递归函数那句
head.next.next = head
很不好理解,其实就是 head 的下一个节点指向head
*/
class Solution {
// public void show(ListNode head){
// ListNode p = head;
// while(p != null){
// System.out.print(p.val + " ");
// p = p.next;
// }
// System.out.println();
// }
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode res = reverseList(head.next);
head.next.next = head;
head.next = null;
//show(res);
return res;
}
}
学习的一个很骚的写法:
head.next.next = head
很不好理解,其实就是 head 的下一个节点指向head
92.反转链表II
安安思路:分成三段,按照反转整个链表进行,然后拼接
//java anan 2020.3.20
/*
1 -> 2 -> 3 -> 4 -> 5 m=2, n=4
定义两个指针,左指针和右指针
左指针指向要翻转的第一个结点的前一个结点 即指向1
右指针指向要反转的最后一个结点的下一个结点 即指向5
翻转234后进行拼接
*/
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if(m == n || head == null) return head; //考虑特殊情况,保证鲁棒性
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode left = new ListNode(0);
ListNode right = new ListNode(0);
ListNode p = dummy;
int count = 0;
//查找要翻转的部分
while(p != null){
if(count == m-1) left = p;
if(count == n){
right = p.next;
break;
}
count++;
p = p.next;
}
p.next = null; //注意找到要翻转的部分之后,要把最后一个结点的下一个结点置空
//进行翻转
ListNode rev = reverse(left.next);
//进行拼接
left.next = rev;
p = rev;
while(p.next != null) p = p.next;
p.next = right;
return dummy.next;
}
public ListNode reverse(ListNode head){
if(head.next == null) return head;
ListNode res = reverse(head.next);
head.next.next = head;
head.next = null;
return res;
}
}
大佬思路:递归
大佬原题解 强烈推荐去看,写的特别好
官方题解1:不改变链表结构,只交换链表结点的值
左指针设为全局变量,递归回溯时后指针前移,此时讲左指针后移,交换对应的值即可
新的技巧点:本题,只要左右指针移到两个的中间便需要停止递归回溯,直接返回去,但是该怎样直接结束返回去呢?题解里给了一个思路:设一个flag指针,只有在满足flag为初始值时才进行,需要返回时改变flag的值即可。
妙哉妙哉!
//Java anan写的 最后一步参考了题解
class Solution {
ListNode left;
boolean stop;
public void reverse(ListNode head, int m, int n){
if(m == 1) left = head;
if(n == 0) return ;
reverse(head.next, m-1, n-1);
if(left == head || head.next == left){
this.stop = true;
return ;
}
if(! this.stop){
int tmp;
tmp = left.val;
left.val = head.val;
head.val = tmp;
left = left.next;
}
}
public ListNode reverseBetween(ListNode head, int m, int n) {
left = head;
stop = false;
reverse(head, m, n);
return head;
}
}
官方题解2:依次修改结点指针的指向
和自己之前做反装整个链表时相似
24.两两交换链表中的节点
这两个解法都是自己写的
具体思路可以参见:某童鞋题解
解法1:迭代
//java anan
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null) return null;
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
ListNode p = head;
ListNode post;
while(p != null && p.next != null){
post = p.next.next;
//改变指针的方向
p.next.next = p;
pre.next = p.next;
p.next = post;
pre = p;
p = post;
}
return dummy.next;
}
}
解法2:递归
//java anan
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
ListNode ret = swapPairs(head.next.next);
ListNode res = head.next;
head.next.next = head;
head.next = ret;
return res;
}
}
面试题25.合并两个排序的链表(21)
自己之前的解法:类似于集合合并
//c 安安 2020.2
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode *p = l1;
struct ListNode *q = l2;
struct ListNode *n = NULL; //指向新链表的第一个结点
struct ListNode *r; //指向新链表的最后一个结点
struct ListNode *s; //指向新创建的结点
while(p && q){
s = (struct ListNode*)malloc(sizeof(struct ListNode));
if(p->val <= q->val){
s->val = p->val;
p = p->next;
}else{
s->val = q->val;
q = q->next;
}
s->next = NULL;
if(n == NULL){
n = s;
r = s;
}else{
r->next = s;
r = s;
}
// printf("新链表:");
// struct ListNode *m = n;
// while(m){
// printf("%d ", m->val);
// m = m->next;
// }
// printf("\n");
}
if(q){
p = q;
}
while(p){
s = (struct ListNode*)malloc(sizeof(struct ListNode));
s->val = p->val;
s->next = NULL;
p = p->next;
if(n == NULL){ //特殊例子: [] [0]
n = s;
r = s;
}else{
r->next = s;
r = s;
}
// printf("新新链表:");
// struct ListNode *m = n;
// while(m){
// printf("%d ", m->val);
// m = m->next;
// }
// printf("\n");
}
return n;
}
解法1:递归
//java anan 2020.3.20
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val <= l2.val){
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}else{
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
解法2:迭代 代码很漂亮
//java
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while(l1 != null && l2 != null){
if(l1.val <= l2.val){
prev.next = l1;
l1 = l1.next;
}else{
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
prev.next = (l1 == null ? l2:l1); //这一步漂亮的很
return prehead.next;
}
}
(二刷)迭代
//anan 2020.8.3
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode();
ListNode p = dummy;
while(l1 != null && l2 != null){
if(l1.val <= l2.val){
p.next = l1;
l1 = l1.next;
}else if(l1.val > l2.val){
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
if(l2 != null){
l1 = l2;
}
while(l1 != null){
p.next = l1;
p = p.next;
l1 = l1.next;
}
return dummy.next;
}
}
面试题35.复杂链表(带随机指针)的复制(138)
安安 哈希表
//自己的想法也是大多数人直接想到的想法,但是这中思想的时间复杂度为O(n2)
//因为要定位random指针 //所以官解(同剑指offer书中)用了哈希表(空间换时间)可以迅速定位random指针指向的节点
//java anan 2020.4.26
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
/*
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。
*/
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
//1.构造原链表中的random映射
HashMap<Integer, Integer> map = new HashMap<>(); //哈希表 用来存储节点random的关系 用下标表示
Node p=head; //p用来遍历原链表
for(int i=0; p != null; p=p.next, i++){
// System.out.print("本节点对象:" + p + " ");
// System.out.println("指向的random对象" + p.random);
Node p1=head; //p1用来遍历原链表
int j = 0;
for(; p.random != p1; p1=p1.next, j++){
;
}
//System.out.println("指向的random对象的下标位置" + j);
map.put(i, (p.random==null ? -1 : j)); //random为null的话,对应的键值赋为-1
}
//showHashMap(map);
//2.构造新链表
Node dummy = new Node(-1);
Node q=dummy; //q指向新链表的最后一个节点
//2.1.先把新链表创建起来
p = head;
for(int i=0; p != null; p=p.next, i++){
Node s = new Node(p.val); //新节点
q.next= s;
q = s;
}
// show(dummy.next);
// show(head);
//2.2.构建random指向
q=dummy.next; //q用来遍历新链表
for(int i=0; q != null; q=q.next, i++){
if(map.get(i) == -1){
q.random = null;
}else{
Node q1=dummy.next; //q1用来遍历新链表
for(int j = 0; j < map.get(i); q1=q1.next, j++){
;
}
q.random = q1;
}
}
return dummy.next;
}
public void show(Node head){
Node p=head;
while(p != null){
System.out.print(p.val + " ");
p = p.next;
}
System.out.println();
}
public void showHashMap(HashMap<Integer, Integer> map){
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.print(entry.getKey() + "--->");
System.out.println(entry.getValue());
}
}
}
官解1:DFS回溯(剑指offer提到了,但是没写代码)
public class Solution {
// HashMap which holds old nodes as keys and new nodes as its values.
HashMap<Node, Node> visitedHash = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// If we have already processed the current node, then we simply return the cloned version of
// it.
if (this.visitedHash.containsKey(head)) {
return this.visitedHash.get(head);
}
// Create a new node with the value same as old node. (i.e. copy the node)
Node node = new Node(head.val, null, null);
// Save this value in the hash map. This is needed since there might be
// loops during traversal due to randomness of random pointers and this would help us avoid
// them.
this.visitedHash.put(head, node);
// Recursively copy the remaining linked list starting once from the next pointer and then from
// the random pointer.
// Thus we have two independent recursive calls.
// Finally we update the next and random pointers for the new node created.
node.next = this.copyRandomList(head.next);
node.random = this.copyRandomList(head.random);
return node;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/copy-list-with-random-pointer/solution/fu-zhi-dai-sui-ji-zhi-zhen-de-lian-biao-by-leetcod/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
官解2:迭代 个人感觉和1差不多
官解3:交织链表(剑指offer)
1.扭曲链表
2.修改random指针
3.恢复next指针
//java 根据思路自己写的
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
//创建交织链表
Node p = head;
while(p != null){
Node s = new Node(p.val);
s.next = p.next;
p.next = s;
p = p.next.next;
}
//show(head);
//修改random指针
p = head;
while(p != null){
if(p.random == null) p.next.random = null;
else{
p.next.random = p.random.next;
}
p = p.next.next;
}
//修改next指针
Node p1 = head; //遍历原链表
Node p2 = head.next; //遍历新链表
Node returnNode = head.next;
while(p2.next != null){
p1.next = p1.next.next; //恢复原链表的next指针
p2.next = p2.next.next; //恢复新链表的next指针
p1 = p1.next;
p2 = p2.next;
}
p1.next = null;
p2.next = null;
return returnNode;
}
public void show(Node head){
Node p=head;
while(p != null){
System.out.print(p.val + " ");
p = p.next;
}
System.out.println();
}
}