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:
@@ -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)
|
||||
@@ -97,49 +97,6 @@ func exist(board [][]byte, word string) bool {
|
||||
|
||||
### Java 实现
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
private boolean[][] visited;
|
||||
private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
|
||||
|
||||
public boolean exist(char[][] board, String word) {
|
||||
int m = board.length, n = board[0].length;
|
||||
visited = new boolean[m][n];
|
||||
|
||||
for (int i = 0; i < m; i++) {
|
||||
for (int j = 0; j < n; j++) {
|
||||
if (board[i][j] == word.charAt(0) && dfs(board, word, i, j, 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean dfs(char[][] board, String word, int i, int j, int k) {
|
||||
if (k == word.length()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length ||
|
||||
visited[i][j] || board[i][j] != word.charAt(k)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visited[i][j] = true;
|
||||
|
||||
for (int[] dir : directions) {
|
||||
if (dfs(board, word, i + dir[0], j + dir[1], k + 1)) {
|
||||
visited[i][j] = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
visited[i][j] = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
|
||||
@@ -116,56 +116,6 @@ func main() {
|
||||
|
||||
### Java 实现(回溯法)
|
||||
|
||||
```java
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Subsets {
|
||||
|
||||
public List<List<Integer>> subsets(int[] nums) {
|
||||
List<List<Integer>> result = new ArrayList<>();
|
||||
List<Integer> current = new ArrayList<>();
|
||||
backtrack(result, current, nums, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void backtrack(List<List<Integer>> result, List<Integer> current,
|
||||
int[] nums, int start) {
|
||||
// 将当前子集加入结果
|
||||
result.add(new ArrayList<>(current));
|
||||
|
||||
// 从 start 开始尝试包含每个元素
|
||||
for (int i = start; i < nums.length; i++) {
|
||||
// 选择当前元素
|
||||
current.add(nums[i]);
|
||||
// 递归处理下一个元素
|
||||
backtrack(result, current, nums, i + 1);
|
||||
// 撤销选择(回溯)
|
||||
current.remove(current.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static void main(String[] args) {
|
||||
Subsets solution = new Subsets();
|
||||
|
||||
// 测试用例1
|
||||
int[] nums1 = {1, 2, 3};
|
||||
System.out.println("输入: [1, 2, 3]");
|
||||
System.out.println("输出: " + solution.subsets(nums1));
|
||||
|
||||
// 测试用例2
|
||||
int[] nums2 = {0};
|
||||
System.out.println("\n输入: [0]");
|
||||
System.out.println("输出: " + solution.subsets(nums2));
|
||||
|
||||
// 测试用例3
|
||||
int[] nums3 = {1, 2};
|
||||
System.out.println("\n输入: [1, 2]");
|
||||
System.out.println("输出: " + solution.subsets(nums3));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(迭代法-位掩码)
|
||||
|
||||
@@ -192,26 +142,6 @@ func subsetsBitMask(nums []int) [][]int {
|
||||
|
||||
### Java 实现(迭代法-位掩码)
|
||||
|
||||
```java
|
||||
public List<List<Integer>> subsetsBitMask(int[] nums) {
|
||||
int n = nums.length;
|
||||
int total = 1 << n; // 2^n 个子集
|
||||
List<List<Integer>> result = new ArrayList<>();
|
||||
|
||||
for (int mask = 0; mask < total; mask++) {
|
||||
List<Integer> subset = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
// 检查第 i 位是否为 1
|
||||
if ((mask & (1 << i)) != 0) {
|
||||
subset.add(nums[i]);
|
||||
}
|
||||
}
|
||||
result.add(subset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(级联法)
|
||||
|
||||
|
||||
@@ -139,72 +139,6 @@ func main() {
|
||||
|
||||
### Java 实现(回溯法)
|
||||
|
||||
```java
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenerateParentheses {
|
||||
|
||||
public List<String> generateParenthesis(int n) {
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
backtrack(result, current, 0, 0, n);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void backtrack(List<String> result, StringBuilder current,
|
||||
int open, int close, int max) {
|
||||
// 终止条件:生成了 2n 个括号
|
||||
if (current.length() == 2 * max) {
|
||||
result.add(current.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加左括号:左括号数量小于 n
|
||||
if (open < max) {
|
||||
current.append('(');
|
||||
backtrack(result, current, open + 1, close, max);
|
||||
current.deleteCharAt(current.length() - 1); // 回溯
|
||||
}
|
||||
|
||||
// 添加右括号:右括号数量小于左括号数量
|
||||
if (close < open) {
|
||||
current.append(')');
|
||||
backtrack(result, current, open, close + 1, max);
|
||||
current.deleteCharAt(current.length() - 1); // 回溯
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static void main(String[] args) {
|
||||
GenerateParentheses solution = new GenerateParentheses();
|
||||
|
||||
// 测试用例1
|
||||
int n1 = 3;
|
||||
System.out.println("输入: n = " + n1);
|
||||
System.out.println("输出: " + solution.generateParenthesis(n1));
|
||||
|
||||
// 测试用例2
|
||||
int n2 = 1;
|
||||
System.out.println("\n输入: n = " + n2);
|
||||
System.out.println("输出: " + solution.generateParenthesis(n2));
|
||||
|
||||
// 测试用例3
|
||||
int n3 = 4;
|
||||
System.out.println("\n输入: n = " + n3);
|
||||
List<String> result3 = solution.generateParenthesis(n3);
|
||||
System.out.println("输出长度: " + result3.size());
|
||||
System.out.println("输出: " + result3);
|
||||
|
||||
// 验证卡特兰数
|
||||
System.out.println("\n卡特兰数验证:");
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
System.out.println("n = " + i + ", 组合数 = " +
|
||||
solution.generateParenthesis(i).size());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(动态规划)
|
||||
|
||||
@@ -234,30 +168,6 @@ func generateParenthesisDP(n int) []string {
|
||||
|
||||
### Java 实现(动态规划)
|
||||
|
||||
```java
|
||||
public List<String> generateParenthesisDP(int n) {
|
||||
List<List<String>> dp = new ArrayList<>();
|
||||
List<String> dp0 = new ArrayList<>();
|
||||
dp0.add("");
|
||||
dp.add(dp0);
|
||||
|
||||
for (int i = 1; i <= n; i++) {
|
||||
List<String> current = new ArrayList<>();
|
||||
for (int j = 0; j < i; j++) {
|
||||
List<String> leftList = dp.get(j);
|
||||
List<String> rightList = dp.get(i - 1 - j);
|
||||
for (String left : leftList) {
|
||||
for (String right : rightList) {
|
||||
current.add("(" + left + ")" + right);
|
||||
}
|
||||
}
|
||||
}
|
||||
dp.add(current);
|
||||
}
|
||||
|
||||
return dp.get(n);
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
|
||||
@@ -81,40 +81,8 @@ func lengthOfLongestSubstring(s string) int {
|
||||
|
||||
---
|
||||
|
||||
## Java 解法
|
||||
## 解法
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int lengthOfLongestSubstring(String s) {
|
||||
// 记录字符最后出现的位置
|
||||
Map<Character, Integer> charIndex = new HashMap<>();
|
||||
int maxLength = 0;
|
||||
int left = 0;
|
||||
|
||||
for (int right = 0; right < s.length(); right++) {
|
||||
char char = s.charAt(right);
|
||||
|
||||
// 如果字符已存在且在窗口内,移动左边界
|
||||
if (charIndex.containsKey(char) && charIndex.get(char) >= left) {
|
||||
left = charIndex.get(char) + 1;
|
||||
}
|
||||
|
||||
// 更新字符位置
|
||||
charIndex.put(char, right);
|
||||
|
||||
// 更新最大长度
|
||||
maxLength = Math.max(maxLength, right - left + 1);
|
||||
}
|
||||
|
||||
return maxLength;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Java 代码要点
|
||||
1. `HashMap` 记录字符索引
|
||||
2. `charAt()` 遍历字符串
|
||||
3. `Math.max()` 更新最大值
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -136,71 +136,6 @@ func main() {
|
||||
|
||||
### Java 实现(中心扩展法)
|
||||
|
||||
```java
|
||||
public class LongestPalindromicSubstring {
|
||||
|
||||
public String longestPalindrome(String s) {
|
||||
if (s == null || s.length() < 2) {
|
||||
return s;
|
||||
}
|
||||
|
||||
int start = 0, maxLen = 1;
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
// 奇数长度:以当前字符为中心
|
||||
int len1 = expandAroundCenter(s, i, i);
|
||||
// 偶数长度:以当前字符和下一个字符之间为中心
|
||||
int len2 = expandAroundCenter(s, i, i + 1);
|
||||
|
||||
int currentLen = Math.max(len1, len2);
|
||||
if (currentLen > maxLen) {
|
||||
maxLen = currentLen;
|
||||
start = i - (currentLen - 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
return s.substring(start, start + maxLen);
|
||||
}
|
||||
|
||||
private int expandAroundCenter(String s, int left, int right) {
|
||||
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
|
||||
left--;
|
||||
right++;
|
||||
}
|
||||
return right - left - 1;
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static void main(String[] args) {
|
||||
LongestPalindromicSubstring solution = new LongestPalindromicSubstring();
|
||||
|
||||
// 测试用例1
|
||||
String s1 = "babad";
|
||||
System.out.println("输入: " + s1);
|
||||
System.out.println("输出: " + solution.longestPalindrome(s1));
|
||||
|
||||
// 测试用例2
|
||||
String s2 = "cbbd";
|
||||
System.out.println("\n输入: " + s2);
|
||||
System.out.println("输出: " + solution.longestPalindrome(s2));
|
||||
|
||||
// 测试用例3: 单个字符
|
||||
String s3 = "a";
|
||||
System.out.println("\n输入: " + s3);
|
||||
System.out.println("输出: " + solution.longestPalindrome(s3));
|
||||
|
||||
// 测试用例4: 全部相同
|
||||
String s4 = "aaaa";
|
||||
System.out.println("\n输入: " + s4);
|
||||
System.out.println("输出: " + solution.longestPalindrome(s4));
|
||||
|
||||
// 测试用例5: 无回文
|
||||
String s5 = "abc";
|
||||
System.out.println("\n输入: " + s5);
|
||||
System.out.println("输出: " + solution.longestPalindrome(s5));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(动态规划)
|
||||
|
||||
@@ -246,41 +181,6 @@ func longestPalindromeDP(s string) string {
|
||||
|
||||
### Java 实现(动态规划)
|
||||
|
||||
```java
|
||||
public String longestPalindromeDP(String s) {
|
||||
if (s == null || s.length() < 2) {
|
||||
return s;
|
||||
}
|
||||
|
||||
int n = s.length();
|
||||
boolean[][] dp = new boolean[n][n];
|
||||
int start = 0, maxLen = 1;
|
||||
|
||||
// 初始化:所有单个字符都是回文串
|
||||
for (int i = 0; i < n; i++) {
|
||||
dp[i][i] = true;
|
||||
}
|
||||
|
||||
// 按长度递增的顺序遍历
|
||||
for (int length = 2; length <= n; length++) {
|
||||
for (int i = 0; i <= n - length; i++) {
|
||||
int j = i + length - 1;
|
||||
|
||||
if (s.charAt(i) == s.charAt(j)) {
|
||||
if (length == 2 || dp[i + 1][j - 1]) {
|
||||
dp[i][j] = true;
|
||||
if (length > maxLen) {
|
||||
maxLen = length;
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.substring(start, start + maxLen);
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(Manacher 算法)
|
||||
|
||||
|
||||
@@ -54,27 +54,6 @@ func largestRectangleArea(heights []int) int {
|
||||
|
||||
### Java 实现
|
||||
|
||||
```java
|
||||
public int largestRectangleArea(int[] heights) {
|
||||
Stack<Integer> stack = new Stack<>();
|
||||
int maxArea = 0;
|
||||
int n = heights.length;
|
||||
|
||||
for (int i = 0; i <= n; i++) {
|
||||
int h = (i == n) ? 0 : heights[i];
|
||||
|
||||
while (!stack.isEmpty() && h < heights[stack.peek()]) {
|
||||
int height = heights[stack.pop()];
|
||||
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
|
||||
maxArea = Math.max(maxArea, height * width);
|
||||
}
|
||||
|
||||
stack.push(i);
|
||||
}
|
||||
|
||||
return maxArea;
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
|
||||
@@ -153,84 +153,6 @@ func main() {
|
||||
|
||||
### Java 实现(回溯法)
|
||||
|
||||
```java
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LetterCombinations {
|
||||
|
||||
public List<String> letterCombinations(String digits) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (digits == null || digits.length() == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 数字到字母的映射
|
||||
String[] phoneMap = {
|
||||
"", // 0
|
||||
"", // 1
|
||||
"abc", // 2
|
||||
"def", // 3
|
||||
"ghi", // 4
|
||||
"jkl", // 5
|
||||
"mno", // 6
|
||||
"pqrs", // 7
|
||||
"tuv", // 8
|
||||
"wxyz" // 9
|
||||
};
|
||||
|
||||
StringBuilder current = new StringBuilder();
|
||||
backtrack(digits, 0, phoneMap, current, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void backtrack(String digits, int index, String[] phoneMap,
|
||||
StringBuilder current, List<String> result) {
|
||||
if (index == digits.length()) {
|
||||
result.add(current.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前数字对应的所有字母
|
||||
int digit = digits.charAt(index) - '0';
|
||||
String letters = phoneMap[digit];
|
||||
|
||||
for (int i = 0; i < letters.length(); i++) {
|
||||
// 选择当前字母
|
||||
current.append(letters.charAt(i));
|
||||
// 递归处理下一个数字
|
||||
backtrack(digits, index + 1, phoneMap, current, result);
|
||||
// 撤销选择(回溯)
|
||||
current.deleteCharAt(current.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static void main(String[] args) {
|
||||
LetterCombinations solution = new LetterCombinations();
|
||||
|
||||
// 测试用例1
|
||||
String digits1 = "23";
|
||||
System.out.println("输入: " + digits1);
|
||||
System.out.println("输出: " + solution.letterCombinations(digits1));
|
||||
|
||||
// 测试用例2
|
||||
String digits2 = "";
|
||||
System.out.println("\n输入: " + digits2);
|
||||
System.out.println("输出: " + solution.letterCombinations(digits2));
|
||||
|
||||
// 测试用例3
|
||||
String digits3 = "2";
|
||||
System.out.println("\n输入: " + digits3);
|
||||
System.out.println("输出: " + solution.letterCombinations(digits3));
|
||||
|
||||
// 测试用例4: 最长输入
|
||||
String digits4 = "9999";
|
||||
System.out.println("\n输入: " + digits4);
|
||||
System.out.println("输出长度: " + solution.letterCombinations(digits4).size());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go 实现(队列迭代法)
|
||||
|
||||
@@ -275,39 +197,6 @@ func letterCombinationsIterative(digits string) []string {
|
||||
|
||||
### Java 实现(队列迭代法)
|
||||
|
||||
```java
|
||||
public List<String> letterCombinationsIterative(String digits) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (digits == null || digits.length() == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String[] phoneMap = {
|
||||
"", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
|
||||
};
|
||||
|
||||
// 初始化队列
|
||||
List<String> queue = new ArrayList<>();
|
||||
queue.add("");
|
||||
|
||||
for (int i = 0; i < digits.length(); i++) {
|
||||
int digit = digits.charAt(i) - '0';
|
||||
String letters = phoneMap[digit];
|
||||
List<String> newQueue = new ArrayList<>();
|
||||
|
||||
// 取出队列中所有组合,与当前字母组合
|
||||
for (String combination : queue) {
|
||||
for (int j = 0; j < letters.length(); j++) {
|
||||
newQueue.add(combination + letters.charAt(j));
|
||||
}
|
||||
}
|
||||
|
||||
queue = newQueue;
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
|
||||
@@ -125,65 +125,6 @@ func main() {
|
||||
|
||||
### Java 实现
|
||||
|
||||
```java
|
||||
public class ContainerWithMostWater {
|
||||
|
||||
public int maxArea(int[] height) {
|
||||
int left = 0;
|
||||
int right = height.length - 1;
|
||||
int maxArea = 0;
|
||||
|
||||
while (left < right) {
|
||||
// 计算当前面积
|
||||
int width = right - left;
|
||||
int h = Math.min(height[left], height[right]);
|
||||
int area = width * h;
|
||||
|
||||
// 更新最大面积
|
||||
maxArea = Math.max(maxArea, area);
|
||||
|
||||
// 移动较短的指针
|
||||
if (height[left] < height[right]) {
|
||||
left++;
|
||||
} else {
|
||||
right--;
|
||||
}
|
||||
}
|
||||
|
||||
return maxArea;
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static void main(String[] args) {
|
||||
ContainerWithMostWater solution = new ContainerWithMostWater();
|
||||
|
||||
// 测试用例1
|
||||
int[] height1 = {1, 8, 6, 2, 5, 4, 8, 3, 7};
|
||||
System.out.println("输入: [1, 8, 6, 2, 5, 4, 8, 3, 7]");
|
||||
System.out.println("输出: " + solution.maxArea(height1)); // 期望输出: 49
|
||||
|
||||
// 测试用例2
|
||||
int[] height2 = {1, 1};
|
||||
System.out.println("\n输入: [1, 1]");
|
||||
System.out.println("输出: " + solution.maxArea(height2)); // 期望输出: 1
|
||||
|
||||
// 测试用例3: 递增序列
|
||||
int[] height3 = {1, 2, 3, 4, 5};
|
||||
System.out.println("\n输入: [1, 2, 3, 4, 5]");
|
||||
System.out.println("输出: " + solution.maxArea(height3)); // 期望输出: 6
|
||||
|
||||
// 测试用例4: 递减序列
|
||||
int[] height4 = {5, 4, 3, 2, 1};
|
||||
System.out.println("\n输入: [5, 4, 3, 2, 1]");
|
||||
System.out.println("输出: " + solution.maxArea(height4)); // 期望输出: 6
|
||||
|
||||
// 测试用例5: 包含0
|
||||
int[] height5 = {0, 2};
|
||||
System.out.println("\n输入: [0, 2]");
|
||||
System.out.println("输出: " + solution.maxArea(height5)); // 期望输出: 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
|
||||
Reference in New Issue
Block a user