面試題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;
}
}
遞歸
//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;
}
}
面試題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();
}
}