Skip to content

Commit bec19e3

Browse files
committed
19
1 parent 41e2109 commit bec19e3

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
* [16. 3Sum Closest](leetCode-16-3Sum-Closest.md)
2020
* [17. Letter Combinations of a Phone Number](leetCode-17-Letter-Combinations-of-a-Phone-Number.md)
2121
* [18. 4Sum](leetCode-18-4Sum.md)
22+
* [19. Remove Nth Node From End of List](leetCode-19-Remov-Nth-Node-From-End-of-List.md)
2223
* [79. Word Search](leetCode-79-Word-Search.md)
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/19.jpg)
4+
5+
给定一个链表,将倒数第 n 个结点删除。
6+
7+
# 解法一
8+
9+
删除一个结点,无非是遍历链表找到那个结点前边的结点,然后改变下指向就好了。但由于它是链表,它的长度我们并不知道,我们得先遍历一遍得到它的长度,之后用长度减去 n 就是要删除的结点的位置,然后遍历到结点的前一个位置就好了。
10+
11+
```java
12+
public ListNode removeNthFromEnd(ListNode head, int n) {
13+
int len = 0;
14+
ListNode h = head;
15+
while (h != null) {
16+
h = h.next;
17+
len++;
18+
}
19+
//长度等于 1 ,再删除一个结点就为 null 了
20+
if (len == 1) {
21+
return null;
22+
}
23+
24+
int rm_node_index = len - n;
25+
26+
//如果删除的是头结点
27+
if (rm_node_index == 0) {
28+
return head.next;
29+
}
30+
31+
//找到被删除结点的前一个结点
32+
h = head;
33+
for (int i = 0; i < rm_node_index - 1; i++) {
34+
h = h.next;
35+
}
36+
37+
//改变指向
38+
h.next = h.next.next;
39+
return head;
40+
}
41+
```
42+
43+
时间复杂度:假设链表长度是 L ,那么就第一个循环是 L 次,第二个循环是 L - n 次,总共 2L - n 次,所以时间复杂度就是 O(L)。
44+
45+
空间复杂度:O(1)。
46+
47+
我们看到如果长度等于 1 和删除头结点的时候需要单独判断,其实我们只需要在 head 前边加一个空节点,就可以避免单独判断。
48+
49+
```java
50+
public ListNode removeNthFromEnd(ListNode head, int n) {
51+
ListNode dummy = new ListNode(0);
52+
dummy.next = head;
53+
int length = 0;
54+
ListNode first = head;
55+
while (first != null) {
56+
length++;
57+
first = first.next;
58+
}
59+
length -= n;
60+
first = dummy;
61+
while (length > 0) {
62+
length--;
63+
first = first.next;
64+
}
65+
first.next = first.next.next;
66+
return dummy.next;
67+
}
68+
```
69+
70+
# 解法二 遍历一次链表
71+
72+
上边我们遍历链表进行了两次,我们如何只遍历一次呢。
73+
74+
看了 [leetcode](https://leetcode.com/problems/remove-nth-node-from-end-of-list/solution/) 的讲解。
75+
76+
想象一下,两个人进行 100m 赛跑,假设他们的速度相同。开始的时候,第一个人就在第二个人前边 10m ,这样当第一个人跑到终点的时候,第二个人相距第一个人依旧是 10m ,也就是离终点 10m。
77+
78+
对比于链表,我们设定两个指针,先让第一个指针遍历 n 步,然后再让它俩同时开始遍历,这样的话,当第一个指针到头的时候,第二个指针就离第一个指针有 n 的距离,所以第二个指针的位置就刚好是倒数第 n 个结点。
79+
80+
```java
81+
public ListNode removeNthFromEnd(ListNode head, int n) {
82+
ListNode dummy = new ListNode(0);
83+
dummy.next = head;
84+
ListNode first = dummy;
85+
ListNode second = dummy;
86+
//第一个指针先移动 n 步
87+
for (int i = 1; i <= n + 1; i++) {
88+
first = first.next;
89+
}
90+
//第一个指针到达终点停止遍历
91+
while (first != null) {
92+
first = first.next;
93+
second = second.next;
94+
}
95+
second.next = second.next.next;
96+
return dummy.next;
97+
}
98+
```
99+
100+
时间复杂度:链表整个只遍历了一遍,第一个指针先到 n ,再从 n 到结束,也就是 L 次,而第二个指针和第一个指针是同时进行的。但其实时间复杂度和上一个没什么差别,本质上其实只是把两个循环合并到了一起而已,时间复杂度没有任何变化。所以是 O(L)。
101+
102+
空间复杂度:O(1)。
103+
104+
# 解法三
105+
106+
没看讲解前,和室友讨论下,如何只遍历一次链表。室友给出了一个我竟然无法反驳的观点,哈哈哈哈。
107+
108+
第一次遍历链表确定长度的时候,顺便把每个结点存到数组里,这样找结点的时候就不需要再遍历一次了,空间换时间???哈哈哈哈哈哈哈哈哈。
109+
110+
```java
111+
public ListNode removeNthFromEnd(ListNode head, int n) {
112+
List<ListNode> l = new ArrayList<ListNode>();
113+
ListNode h = head;
114+
int len = 0;
115+
while (h != null) {
116+
l.add(h);
117+
h = h.next;
118+
len++;
119+
}
120+
if (len == 1) {
121+
return null;
122+
}
123+
int remove = len - n;
124+
if (remove == 0) {
125+
return head.next;
126+
}
127+
//直接得到,不需要再遍历了
128+
ListNode r = l.get(remove - 1);
129+
r.next = r.next.next;
130+
return head;
131+
}
132+
```
133+
134+
时间复杂度:O(L)。
135+
136+
空间复杂度:O(L)。
137+
138+
#
139+
140+
利用两个指针先固定间隔,然后同时遍历,真的是很妙!另外室友的想法也很棒,哈哈哈哈哈。

0 commit comments

Comments
 (0)