5.13 二轮 链表+栈
This commit is contained in:
parent
60f7c9d91f
commit
67697ee1a8
36
src/main/java/linkedlist/DeleteDuplicates.java
Normal file
36
src/main/java/linkedlist/DeleteDuplicates.java
Normal file
@ -0,0 +1,36 @@
|
||||
package linkedlist;
|
||||
/**
|
||||
* 题目: 82. 删除排序链表中的重复元素 II (deleteDuplicates)
|
||||
* 描述:给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
|
||||
|
||||
示例 2:
|
||||
输入:head = [1,2,3,3,4,4,5]
|
||||
输出:[1,2,5]
|
||||
|
||||
* 链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/
|
||||
*/
|
||||
public class DeleteDuplicates {
|
||||
public ListNode deleteDuplicates(ListNode head) {
|
||||
if (head == null) {
|
||||
return head;
|
||||
}
|
||||
|
||||
ListNode dummy = new ListNode(0, head);
|
||||
|
||||
ListNode cur = dummy;
|
||||
while (cur.next != null && cur.next.next != null) {
|
||||
//cur及之前的元素一定是已经处理好的,如果它的下一个和下下个元素相同,那就删
|
||||
if (cur.next.val == cur.next.next.val) {
|
||||
int x = cur.next.val;
|
||||
//while循环删到没有这个值的元素为止
|
||||
while (cur.next != null && cur.next.val == x) {
|
||||
cur.next = cur.next.next;
|
||||
}
|
||||
} else {
|
||||
cur = cur.next;
|
||||
}
|
||||
}
|
||||
|
||||
return dummy.next;
|
||||
}
|
||||
}
|
95
src/main/java/linkedlist/ReverseBetween.java
Normal file
95
src/main/java/linkedlist/ReverseBetween.java
Normal file
@ -0,0 +1,95 @@
|
||||
package linkedlist;
|
||||
/**
|
||||
* 题目: 92. 反转链表 II (reverseBetween)
|
||||
* 描述:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
|
||||
|
||||
示例 2:
|
||||
输入:head = [1,2,3,4,5], left = 2, right = 4
|
||||
输出:[1,4,3,2,5]
|
||||
|
||||
* 链接:https://leetcode.cn/problems/reverse-linked-list-ii/
|
||||
*/
|
||||
//不会一次遍历来翻转
|
||||
public class ReverseBetween {
|
||||
private void reverseLinkedList(ListNode head) {
|
||||
// 也可以使用递归反转一个链表
|
||||
ListNode pre = null;
|
||||
ListNode cur = head;
|
||||
|
||||
while (cur != null) {
|
||||
ListNode next = cur.next;
|
||||
cur.next = pre;
|
||||
pre = cur;
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
//遍历两次,先找两个切断的点left和right,再对内部翻转,再拼接
|
||||
public ListNode reverseBetween2(ListNode head, int left, int right) {
|
||||
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
|
||||
ListNode dummyNode = new ListNode(-1);
|
||||
dummyNode.next = head;
|
||||
|
||||
ListNode pre = dummyNode;
|
||||
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
|
||||
// 建议写在 for 循环里,语义清晰
|
||||
for (int i = 0; i < left - 1; i++) {
|
||||
pre = pre.next;
|
||||
}
|
||||
|
||||
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
|
||||
ListNode rightNode = pre;
|
||||
for (int i = 0; i < right - left + 1; i++) {
|
||||
rightNode = rightNode.next;
|
||||
}
|
||||
|
||||
// 第 3 步:切断出一个子链表(截取链表)
|
||||
ListNode leftNode = pre.next;
|
||||
ListNode curr = rightNode.next;
|
||||
|
||||
// 注意:切断链接
|
||||
pre.next = null;
|
||||
rightNode.next = null;
|
||||
|
||||
// 第 4 步:同第 206 题,反转链表的子区间
|
||||
reverseLinkedList(leftNode);
|
||||
|
||||
// 第 5 步:接回到原来的链表中
|
||||
pre.next = rightNode;
|
||||
leftNode.next = curr;
|
||||
return dummyNode.next;
|
||||
}
|
||||
//头插法反转
|
||||
public ListNode reverseBetween(ListNode head, int left, int right) {
|
||||
// 1. 创建哨兵节点,简化边界处理(当 left = 1 时,也能统一处理)
|
||||
ListNode dummyNode = new ListNode(-1);
|
||||
dummyNode.next = head;
|
||||
|
||||
// 2. pre 最终要指向待反转区间的前一个节点:第 left−1 个节点
|
||||
ListNode pre = dummyNode;
|
||||
// 走 left−1 步,停在第 left−1 个节点上
|
||||
for (int i = 0; i < left - 1; i++) {
|
||||
pre = pre.next;
|
||||
}
|
||||
|
||||
// 3. cur 指向第 left 个节点,也就是待反转区间的第一个节点
|
||||
ListNode cur = pre.next;
|
||||
// next 用于临时保存 cur.next 节点
|
||||
ListNode next;
|
||||
|
||||
// 4. 头插法:将 [left+1, right] 之间的节点依次“摘下”并插入到 pre 之后
|
||||
// 需要执行 right−left 次操作,才能把区间内所有节点都移动到前面
|
||||
for (int i = 0; i < right - left; i++) {
|
||||
// 4.1 取出要移动的节点
|
||||
next = cur.next;
|
||||
// 4.2 将 next 从原位置摘除:让 cur 跳过 next
|
||||
cur.next = next.next;
|
||||
// 4.3 将 next 插到 pre 之后(即已反转部分的头部)
|
||||
next.next = pre.next;
|
||||
pre.next = next;
|
||||
//(cur 不动,next 每次都变成下一个要插入的节点)
|
||||
}
|
||||
|
||||
// 5. 返回新的链表头
|
||||
return dummyNode.next;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package greedy;
|
||||
package range;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
100
src/main/java/stack/SimplifyPath.java
Normal file
100
src/main/java/stack/SimplifyPath.java
Normal file
@ -0,0 +1,100 @@
|
||||
package stack;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目: 71. 简化路径 (simplifyPath)
|
||||
* 描述:给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为 更加简洁的规范路径。
|
||||
*
|
||||
* 在 Unix 风格的文件系统中规则如下:
|
||||
*
|
||||
* 一个点 '.' 表示当前目录本身。
|
||||
* 此外,两个点 '..' 表示将目录切换到上一级(指向父目录)。
|
||||
* 任意多个连续的斜杠(即,'//' 或 '///')都被视为单个斜杠 '/'。
|
||||
* 任何其他格式的点(例如,'...' 或 '....')均被视为有效的文件/目录名称。
|
||||
* 返回的 简化路径 必须遵循下述格式:
|
||||
*
|
||||
* 始终以斜杠 '/' 开头。
|
||||
* 两个目录名之间必须只有一个斜杠 '/' 。
|
||||
* 最后一个目录名(如果存在)不能 以 '/' 结尾。
|
||||
* 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.' 或 '..')。
|
||||
* 返回简化后得到的 规范路径 。
|
||||
|
||||
示例 1:
|
||||
输入:path = "/home/"
|
||||
输出:"/home"
|
||||
解释:
|
||||
应删除尾随斜杠。
|
||||
|
||||
* 链接:https://leetcode.cn/problems/simplify-path/
|
||||
*/
|
||||
public class SimplifyPath {
|
||||
public String simplifyPath(String path) {
|
||||
Deque<String> stack = new ArrayDeque<>();
|
||||
// 按 '/' 分割,连续斜杠会产生空字符串
|
||||
for (String dir : path.split("/")) {
|
||||
if (dir.isEmpty() || dir.equals(".")) {
|
||||
// 当前目录或多余的斜杠,跳过
|
||||
continue;
|
||||
}
|
||||
if (dir.equals("..")) {
|
||||
// 返回上一级目录
|
||||
if (!stack.isEmpty()) {
|
||||
stack.pop();
|
||||
}
|
||||
} else {
|
||||
// 有效目录名,入栈
|
||||
stack.push(dir);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建结果路径
|
||||
if (stack.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
while (!stack.isEmpty()) {
|
||||
result.insert(0, stack.pop());
|
||||
result.insert(0, "/");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
public String simplifyPath2(String path) {
|
||||
Deque<String> stack = new ArrayDeque<>();
|
||||
StringBuilder segment = new StringBuilder();
|
||||
// 在末尾加一个 '/',方便最后一段也能被处理
|
||||
String p = path.endsWith("/") ? path : path + "/";
|
||||
|
||||
for (int i = 0; i < p.length(); i++) {
|
||||
char c = p.charAt(i);
|
||||
if (c == '/') {
|
||||
if (segment.length() > 0) {
|
||||
String dir = segment.toString();
|
||||
if ("..".equals(dir)) {
|
||||
if (!stack.isEmpty()) {
|
||||
stack.pop();
|
||||
}
|
||||
} else if (!".".equals(dir)) {
|
||||
stack.push(dir);
|
||||
}
|
||||
segment.setLength(0);
|
||||
}
|
||||
} else {
|
||||
segment.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建规范路径
|
||||
if (stack.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
StringBuilder res = new StringBuilder();
|
||||
while (!stack.isEmpty()) {
|
||||
res.insert(0, "/" + stack.pop());
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package greedy;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import range.FindMinArrowShots;
|
||||
|
||||
public class FindMinArrowShotsTest {
|
||||
|
||||
|
16
src/test/java/stack/SimplifyPathTest.java
Normal file
16
src/test/java/stack/SimplifyPathTest.java
Normal file
@ -0,0 +1,16 @@
|
||||
package stack;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SimplifyPathTest {
|
||||
|
||||
@Test
|
||||
public void simplifyPath() {
|
||||
String path = "/home/";
|
||||
SimplifyPath solution = new SimplifyPath();
|
||||
String res=solution.simplifyPath(path);
|
||||
System.out.println(res);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user