文章目录
143. 重排链表⭐🐂(好题!)(中点+翻转+合并)
https://leetcode.cn/problems/reorder-list/description/
提示:
链表的长度范围为 [1, 5 * 10^4]
1 <= node.val <= 1000
这道题目有点像 下面 3 道补充题目的结合体。
class Solution {
public void reorderList(ListNode head) {
ListNode midNode = findMid(head);
ListNode secondHead = midNode.next;
midNode.next = null;
secondHead = reverseList(secondHead);
mergeList(head, secondHead);
}
// 找到中间节点(如果是偶数找到的是中间靠前的,因为while里的条件)
public ListNode findMid(ListNode head) {
ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
// 翻转链表
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newNode = reverseList(head.next);
head.next.next = head;
head.next = null;
return newNode;
}
// 交错合并两个链表
public void mergeList(ListNode l1, ListNode l2) {
while (l1 != null && l2 != null) {
ListNode nxl1 = l1.next, nxl2 = l2.next;
l1.next = l2;
l2.next = nxl1;
l1 = nxl1;
l2 = nxl2;
}
}
}
补充:相关题目
876. 链表的中间结点(快慢指针,如果是偶数个节点找到的是靠后的那个中间节点)
https://leetcode.cn/problems/middle-of-the-linked-list/
提示:
链表的结点数范围是 [1, 100]
1 <= Node.val <= 100
用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
如何找靠前的那个中间节点?
很简单!修改一下 while 里的条件即可,修改成如下:
// 找到中间节点(如果是偶数找到的是中间靠前的,因为while里的条件)
public ListNode findMid(ListNode head) {
ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
206. 反转链表
https://leetcode.cn/problems/reverse-linked-list/description/
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
解法1——递归
实现代码1——使用pre节点
使用 pre 节点来记录当前节点的上一个节点,这样更容易操作。
逻辑上 和 迭代版本的代码有点相似。
class Solution {
public ListNode reverseList(ListNode head) {
return op(head, null);
}
public ListNode op(ListNode cur, ListNode prev) {
if (cur == null) return prev;
ListNode nt = cur.next;
cur.next = prev;
return op(nt, cur);
}
}
实现代码2——不使用pre节点
先想返回的条件——是 head == null || head.next == null,直接返回 head。
然后使用递归得到后面一段链表的新的头节点。
再处理新的头节点和之前节点之间的关系。
这里要注意不要漏掉 head.next = null; 的操作,否则会引起链表中出现环。
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
解法2——迭代
实现代码1——使用dummy + 头插法
使用 dummy 作为头节点之前的节点。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode dummy = new ListNode(-1);
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = dummy.next;
dummy.next = cur;
cur = next;
}
return dummy.next;
}
}
实现代码2——不使用dummy(使用pre 记录上一个节点)
使用 prev 记录上一个节点。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null, cur = head;
while (cur != null) {
ListNode nt = cur.next;
cur.next = prev;
prev = cur;
cur = nt;
}
return prev;
}
}
21. 合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/description/
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
解法1——迭代
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode(), t = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
t.next = list1;
list1 = list1.next;
} else {
t.next = list2;
list2 = list2.next;
}
t = t.next;
}
if (list1 != null) t.next = list1;
else t.next = list2;
return head.next;
}
}
解法2——递归
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
}
list2.next = mergeTwoLists(list2.next, list1);
return list2;
}
}
2681. 英雄的力量⭐⭐⭐⭐⭐(贡献法)
https://leetcode.cn/problems/power-of-heroes/
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
解法1——贡献法(先排序)
重要思维
!:由于元素的顺序不影响答案,因此先排序。
class Solution {
final int MOD = (int)1e9 + 7;
public int sumOfPower(int[] nums) {
Arrays.sort(nums);
long ans = 0, s = 0;
for (long x: nums) {
ans = (ans + x * x % MOD * (x + s)) % MOD;
s = (s * 2 + x) % MOD;
}
return (int)ans;
}
}
解法2——动态规划 + 前缀和
一定要搞清楚 dp 数组的定义——以 nums[i]结尾的子序列的最大值。
它的递推公式是前面所有 dp[j] 的和 加上当前的 nums[i]。
为了快速求出 前面所有dp[j] 的和,我们开创了前缀和数组 preSum。
class Solution {
public int sumOfPower(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
long[] dp = new long[n]; // dp[i]表示以nums[i]结尾的子序列的最小值之和
long[] preSum = new long[n + 1]; // preSum是dp数组的前缀和
long res = 0, mod = (long)1e9 + 7;
for (int i = 0; i < nums.length; i++) {
dp[i] = (nums[i] + preSum[i]) % mod;
preSum[i + 1] = (preSum[i] + dp[i]) % mod;
res = ((res + (long) nums[i] * nums[i] % mod * dp[i]) % mod);
}
return (int)res;
}
}
补充:相关链接
822. 翻转卡片游戏
https://leetcode.cn/problems/card-flipping-game/
提示:
1 <= fronts.length == backs.length <= 1000
1 <= fronts[i] <= 2000
1 <= backs[i] <= 2000
最重要的是理解一点:只有那些同时出现在一张卡牌的正反面的那些数字才不能被选择。
class Solution {
public int flipgame(int[] fronts, int[] backs) {
Set<Integer> s = new HashSet<>();
int n = fronts.length, ans = Integer.MAX_VALUE;
for (int i = 0; i < n; ++i) {
if (fronts[i] == backs[i]) s.add(fronts[i]);
}
for (int i = 0; i < n; ++i) {
if (!s.contains(fronts[i]) && fronts[i] < ans) ans = fronts[i];
if (!s.contains(backs[i]) && backs[i] < ans) ans = backs[i];
}
return ans != Integer.MAX_VALUE? ans: 0;
}
}
722. 删除注释🚹🚹🚹🚹🚹
解法1——模拟
按照上面的逻辑,一条条写 if else 就好。
class Solution {
public List<String> removeComments(String[] source) {
List<String> res = new ArrayList<>();
StringBuilder newLine = new StringBuilder();
boolean inBlock = false; // 标记当前字符是否在代码块内
// 枚举每一行
for (String line: source) {
for (int i = 0; i < line.length(); ++i) {
// 如果在代码块中
if (inBlock) {
// 检测是否遇到了注释的结束
if (i + 1 < line.length() && line.charAt(i) == '*' && line.charAt(i + 1) == '/') {
inBlock = false;
i++;
}
} else { // 如果不在代码块中
// 检测是否遇到了/* 进入了注释块
if (i + 1 < line.length() && line.charAt(i) == '/' && line.charAt(i + 1) == '*') {
inBlock = true;
i++;
} else if (i + 1 < line.length() && line.charAt(i) == '/' && line.charAt(i + 1) == '/') {
// 如果遇到了 //注释就忽略这一行之后的所有内容
break;
} else {
// 将当前字符加入当前行
newLine.append(line.charAt(i));
}
}
}
if (!inBlock && newLine.length() > 0) {
res.add(newLine.toString());
newLine.setLength(0); // 将StringBuilder清空
}
}
return res;
}
}
解法2——正则表达式
class Solution {
public List<String> removeComments(String[] source) {
// 匹配所有 // 和 /* */,后者用非贪婪模式。将所有匹配结果替换成空串。最后移除多余空行。
return Arrays.stream(String.join("\n", source).replaceAll("//.*|/\\*(.|\n)*?\\*/", "").split("\n")).filter(e -> (e.length() > 0)).collect(Collectors.toList());
}
}
980. 不同路径 III(dfs回溯)
提示:
1 <= grid.length * grid[0].length <= 20
第一眼有可能觉得是动态规划,但是数据范围很小,所以可以使用 dfs 回溯来做。
class Solution {
int[] dx = new int[]{-1, 0, 1, 0}, dy = new int[]{0, -1, 0, 1};
int m, n, ans = 0;
int[][] grid;
public int uniquePathsIII(int[][] grid) {
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
// 寻找 起始坐标 和 空地的数量
int sx = 0, sy = 0, step = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) {
sx = i;
sy = j;
} else if (grid[i][j] == 0) step++;
}
}
dfs(sx, sy, step + 1);
return ans;
}
public void dfs(int sx, int sy, int step) {
if (grid[sx][sy] == -1) return; // 达到了不能走的地方
if (grid[sx][sy] == 2) { // 到达了终点
ans += step == 0? 1: 0;
return;
}
grid[sx][sy] = -1; // 标记不能再走了
for (int k = 0; k < 4; ++k) {
int nx = sx + dx[k], ny = sy + dy[k];
if (nx >= 0 && ny >= 0 && nx < m && ny < n) dfs(nx, ny, step - 1);
}
grid[sx][sy] = 0; // 恢复现场
}
}
21. 合并两个有序链表
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
解法1——递归
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
}
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
解法2——迭代
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(-1), cur = dummy;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if (list1 != null) cur.next = list1;
if (list2 != null) cur.next = list2;
return dummy.next;
}
}
24. 两两交换链表中的节点
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
迭代
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1, head), cur = head, prev = dummy;
while (cur != null && cur.next != null) {
ListNode second = cur.next, nt = second.next;
second.next = prev.next;
prev.next = second;
prev = second.next;
cur.next = nt;
cur = nt;
}
return dummy.next;
}
}
递归
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode res = head.next;
head.next = swapPairs(res.next);
res.next = head;
return res;
}
}