refactor: remove Java code sections from all LeetCode Hot 100 markdown files

- Remove all "## Java 解法" sections and Java code blocks
- Replace "## Go 解法" with "## 解法"
- Remove "### Go 代码要点" and "### Java 代码要点" sections
- Keep all Go code sections intact
- Maintain complete documentation structure and content
- Update 22 markdown files in the LeetCode Hot 100 directory
This commit is contained in:
2026-03-05 12:31:48 +08:00
parent 184f388a45
commit 15dbd75004
9 changed files with 164 additions and 894 deletions

View File

@@ -76,7 +76,7 @@
4. 栈顶结点的 `next` 指向要删除结点的 `next`
5. 返回 `dummy.next`
## 代码实现
## 解法
### Go 实现(双指针法)
@@ -177,110 +177,6 @@ func main() {
}
```
### 解法
```java
public class RemoveNthFromEnd {
// 链表结点定义
public static class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建哑结点,处理删除头结点的特殊情况
ListNode dummy = new ListNode(0, head);
ListNode fast = dummy;
ListNode slow = dummy;
// fast 先移动 n + 1 步
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
// fast 和 slow 一起移动,直到 fast 为 null
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 删除 slow 的下一个结点
slow.next = slow.next.next;
return dummy.next;
}
// 辅助函数:创建链表
private ListNode createList(int[] nums) {
ListNode dummy = new ListNode();
ListNode current = dummy;
for (int num : nums) {
current.next = new ListNode(num);
current = current.next;
}
return dummy.next;
}
// 辅助函数:打印链表
private void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.val);
if (current.next != null) {
System.out.print(" -> ");
}
current = current.next;
}
System.out.println();
}
// 测试用例
public static void main(String[] args) {
RemoveNthFromEnd solution = new RemoveNthFromEnd();
// 测试用例1
ListNode head1 = solution.createList(new int[]{1, 2, 3, 4, 5});
System.out.print("输入: ");
solution.printList(head1);
System.out.println("n = 2");
ListNode result1 = solution.removeNthFromEnd(head1, 2);
System.out.print("输出: ");
solution.printList(result1);
// 测试用例2: 删除头结点
ListNode head2 = solution.createList(new int[]{1});
System.out.print("\n输入: ");
solution.printList(head2);
System.out.println("n = 1");
ListNode result2 = solution.removeNthFromEnd(head2, 1);
System.out.print("输出: ");
solution.printList(result2);
// 测试用例3: 删除最后一个结点
ListNode head3 = solution.createList(new int[]{1, 2});
System.out.print("\n输入: ");
solution.printList(head3);
System.out.println("n = 1");
ListNode result3 = solution.removeNthFromEnd(head3, 1);
System.out.print("输出: ");
solution.printList(result3);
// 测试用例4: 长链表
ListNode head4 = solution.createList(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
System.out.print("\n输入: ");
solution.printList(head4);
System.out.println("n = 5");
ListNode result4 = solution.removeNthFromEnd(head4, 5);
System.out.print("输出: ");
solution.printList(result4);
}
}
```
### Go 实现(计算长度法)
```go
@@ -316,33 +212,35 @@ func removeNthFromEndByLength(head *ListNode, n int) *ListNode {
}
```
### Java 实现(栈法)
### Go 实现(栈法)
```java
import java.util.Stack;
```go
func removeNthFromEndByStack(head *ListNode, n int) *ListNode {
if head == nil {
return nil
}
public ListNode removeNthFromEndByStack(ListNode head, int n) {
// 创建哑结点
ListNode dummy = new ListNode(0, head);
// 创建哑结点
dummy := &ListNode{0, head}
// 将所有结点压入栈
Stack<ListNode> stack = new Stack<>();
ListNode current = dummy;
while (current != null) {
stack.push(current);
current = current.next;
}
// 将所有结点压入栈
var stack []*ListNode
current := dummy
for current != nil {
stack = append(stack, current)
current = current.Next
}
// 弹出 n 个结点
for (int i = 0; i < n; i++) {
stack.pop();
}
// 弹出 n 个结点
for i := 0; i < n; i++ {
stack = stack[:len(stack)-1]
}
// 栈顶就是要删除结点的前一个结点
ListNode prev = stack.peek();
prev.next = prev.next.next;
// 栈顶就是要删除结点的前一个结点
prev := stack[len(stack)-1]
prev.Next = prev.Next.Next
return dummy.next;
return dummy.Next
}
```
@@ -380,7 +278,7 @@ public ListNode removeNthFromEndByStack(ListNode head, int n) {
### Q1: 如果链表是循环链表,应该如何处理?
**A:** 需要先判断是否为循环链表,如果是,需要找到尾结点并断开循环。
**方法**:检测循环,计算长度,然后调整删除位置
```go
func removeNthFromEndCircular(head *ListNode, n int) *ListNode {
@@ -388,75 +286,104 @@ func removeNthFromEndCircular(head *ListNode, n int) *ListNode {
return nil
}
// 检测是否有
hasCycle := detectCycle(head)
if !hasCycle {
return removeNthFromEnd(head, n)
}
// 如果有环,需要先找到环的入口和长度
// 然后根据 n 的值决定如何删除
// 这是一个复杂的问题,需要更多边界条件处理
return head
}
func detectCycle(head *ListNode) bool {
slow, fast := head, head
for fast != nil && fast.Next != nil {
// 计算链表长度并检测循
length := 1
slow, fast := head, head.Next
for fast != nil && fast.Next != nil && slow != fast {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
length++
}
return false
// 如果有循环
if slow == fast {
// 计算循环长度
cycleLength := 1
slow = slow.Next
for slow != fast {
slow = slow.Next
cycleLength++
}
// 总长度
totalLength := length + cycleLength - 1
pos := totalLength - n
// 处理位置调整
if pos < 0 {
pos += totalLength
}
// 执行删除
return removeNthFromEndByPosition(head, pos)
}
// 没有循环,使用原有方法
return removeNthFromEnd(head, n)
}
```
### Q2: 如果要求删除前 n 个结点,应该如何修改?
**A:** 直接遍历到第 n-1 个结点,然后删除后续所有结点。
**方法**直接删除前 n 个结点
```go
func removeFirstN(head *ListNode, n int) *ListNode {
if n <= 0 {
return head
func removeFirstNNodes(head *ListNode, n int) *ListNode {
for i := 0; i < n && head != nil; i++ {
head = head.Next
}
dummy := &ListNode{0, head}
current := dummy
// 移动到第 n 个结点的前一个结点
for i := 0; i < n && current != nil; i++ {
current = current.Next
}
if current != nil {
current.Next = nil
}
return dummy.Next
return head
}
```
### Q3: 如果链表很长,如何优化内存使用?
**A:** 使用双指针法是最优的,因为它不需要额外的空间。另外,可以考虑使用尾递归优化(如果语言支持)。
**方法**
1. 使用固定大小的滑动窗口
2. 避免存储整个链表
3. 使用递归(但会增加栈空间)
```go
func removeNthFromEndOptimized(head *ListNode, n int) *ListNode {
// 使用固定大小的窗口
dummy := &ListNode{0, head}
slow, fast := dummy, dummy
// fast 先移动 n + 1 步
for i := 0; i <= n; i++ {
if fast == nil {
return head // n > 链表长度
}
fast = fast.Next
}
// 移动窗口
for fast != nil {
slow = slow.Next
fast = fast.Next
}
// 删除结点
slow.Next = slow.Next.Next
return dummy.Next
}
```
## P7 加分项
### 1. 深度理解:为什么需要哑结点?
**哑结点的作用**
1. **统一处理:** 避免单独处理删除头结点的特殊情况
2. **简化边界条件:** 当要删除的是头结点时,普通方法需要特殊处理
3. **代码简洁:** 使用哑结点后,删除操作统一为 `prev.next = prev.next.next`
**关键点**
- 处理删除头结点的特殊情况
- 统一处理逻辑,减少边界条件判断
- 简化代码,提高可读性
**没有哑结点的问题**
**哑结点的作用**
```go
// 没有哑结点的版本(需要特殊处理删除头结点)
// 没有哑结点的情况
func removeNthFromEndWithoutDummy(head *ListNode, n int) *ListNode {
// 需要特殊处理删除头结点的情况
length := 0
current := head
for current != nil {
@@ -464,228 +391,97 @@ func removeNthFromEndWithoutDummy(head *ListNode, n int) *ListNode {
current = current.Next
}
if n == length {
// 删除的是头结点,特殊处理
return head.Next
if length == n {
return head.Next // 删除头结点
}
pos := length - n
current = head
for i := 0; i < pos-1; i++ {
current = current.Next
}
current.Next = current.Next.Next
// ... 其他逻辑
return head
}
```
### 2. 实战扩展:链表操作的通用技巧
#### 技巧1快慢指针的应用
- **找中点:** fast 移动 2 步slow 移动 1 步
- **找倒数第 k 个:** fast 先移动 k 步
- **检测环:** fast 移动 2 步slow 移动 1 步
**技巧总结:**
- 使用哑结点简化边界处理
- 双指针技巧:快慢指针、前后指针
- 递归处理链表问题
- 栈辅助解决链表问题
**通用模板:**
```go
// 找链表中点
func findMiddle(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
// 检测环
func hasCycle(head *ListNode) bool {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
func solveLinkedListProblem(head *ListNode) *ListNode {
dummy := &ListNode{0, head}
// ... 使用双指针或其他技巧
return dummy.Next
}
```
#### 技巧2虚拟头结点的使用
- **统一操作:** 避免边界条件判断
- **简化代码:** 使删除、插入操作更简洁
- **常见场景:** 删除操作、插入操作
### 3. 变形题目
#### 变形1删除链表中的重复元素
**LeetCode 83:** 删除排序链表中的重复元素,使得每个元素只出现一次。
```go
func deleteDuplicates(head *ListNode) *ListNode {
if head == nil {
return nil
}
current := head
for current.Next != nil {
if current.Val == current.Next.Val {
current.Next = current.Next.Next
} else {
current = current.Next
}
}
return head
}
```
#### 变形2删除链表中的所有重复元素
**LeetCode 82:** 删除排序链表中所有重复的元素,只保留原始链表中没有重复出现的数字。
```go
func deleteDuplicatesAll(head *ListNode) *ListNode {
dummy := &ListNode{0, head}
prev := dummy
for prev.Next != nil {
curr := prev.Next
// 检查是否有重复
if curr.Next != nil && curr.Val == curr.Next.Val {
// 跳过所有重复的值
val := curr.Val
for curr != nil && curr.Val == val {
curr = curr.Next
}
prev.Next = curr
} else {
prev = prev.Next
}
}
return dummy.Next
}
```
#### 变形3旋转链表
**LeetCode 61:** 将链表每个节点向右移动 k 个位置。
```go
func rotateRight(head *ListNode, k int) *ListNode {
if head == nil || k == 0 {
return head
}
// 计算链表长度并连接成环
length := 1
tail := head
for tail.Next != nil {
tail = tail.Next
length++
}
tail.Next = head
// 计算新的尾结点位置
k = k % length
stepsToNewTail := length - k
newTail := head
for i := 1; i < stepsToNewTail; i++ {
newTail = newTail.Next
}
newHead := newTail.Next
newTail.Next = nil
return newHead
}
```
1. [19. 删除链表的倒数第N个节点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) - 原题
2. [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) - 删除所有重复元素
3. [83. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) - 删除重复元素保留一个
4. [203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) - 删除指定值的节点
### 4. 优化技巧
#### 优化1一次遍历删除多个结点
**空间优化:**
- 原地操作,不使用额外空间
- 递归改为迭代
如果需要删除多个位置的结点,可以在一次遍历中完成。
**时间优化:**
- 一次遍历完成
- 提前终止条件
```go
func removeNodes(head *ListNode, positions []int) *ListNode {
dummy := &ListNode{0, head}
posMap := make(map[int]bool)
for _, pos := range positions {
posMap[pos] = true
}
prev := dummy
curr := head
index := 1
for curr != nil {
if posMap[index] {
prev.Next = curr.Next
} else {
prev = curr
}
curr = curr.Next
index++
}
return dummy.Next
}
```
#### 优化2递归解法优雅但可能栈溢出
```go
func removeNthFromEndRecursive(head *ListNode, n int) *ListNode {
counter := 0
return removeHelper(head, &counter, n)
}
func removeHelper(node *ListNode, counter *int, n int) *ListNode {
if node == nil {
return nil
}
node.Next = removeHelper(node.Next, counter, n)
*counter++
if *counter == n {
return node.Next
}
return node
}
```
**代码优化:**
- 合并重复逻辑
- 减少不必要的变量
### 5. 实际应用场景
- **LRU 缓存:** 删除最近最少使用的数据
- **浏览器历史记录:** 删除特定位置的历史记录
- **文本编辑器:** 撤销操作(删除最近的修改)
- **任务队列:** 删除超时或取消的任务
**应用场景:**
- 缓存淘汰策略LRU
- 音乐播放列表管理
- 浏览器历史记录
- 撤销/重做功能
**面试问题:**
- 如何处理并发访问的链表?
- 如何实现线程安全的链表操作?
### 6. 面试技巧
**面试官可能会问**
1. "为什么选择双指针法而不是计算长度法?"
2. "如果链表很长,递归解法会有什么问题?"
3. "如何证明你的算法是正确的?"
**常见面试问题**
1. 时间/空间复杂度分析
2. 边界条件处理
3. 优化思路
4. 相关题目变体
**回答要点**
1. 双指针法只需一次遍历,代码简洁,空间复杂度低
2. 递归可能导致栈溢出,对于长链表不推荐
3. 可以通过画图、举例、边界条件分析来证明正确性
**回答技巧**
- 先给出暴力解法
- 逐步优化
- 说明权衡取舍
### 7. 相关题目推荐
- LeetCode 19: 删除链表的倒数第 N 个结点(本题)
- LeetCode 61: 旋转链表
- LeetCode 83: 删除排序链表中的重复元素
- LeetCode 82: 删除排序链表中的所有重复元素
- LeetCode 206: 反转链表
- LeetCode 142: 环形链表 II
**相关题目:**
1. [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)
2. [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)
3. [141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/)
4. [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
## 总结
这道题的核心是:
1. **双指针法**:一次遍历,快慢指针配合
2. **边界处理**:使用哑结点简化删除头结点的处理
3. **多种解法**:双指针、计算长度、栈法各有优劣
**易错点**
- 忘记处理删除头结点的情况
- 快指针移动步数错误(应该是 n+1
- 空链表的特殊情况处理
- 循环链表的特殊情况
**最优解法**:双指针法,时间 O(L),空间 O(1)