【LeetCode每日一题合集】2023.7.31-2023.8.6(重排链表&贡献法)

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——动态规划 + 前缀和

https://leetcode.cn/problems/power-of-heroes/solutions/2359660/ying-xiong-de-li-liang-by-leetcode-solut-9k1g/

一定要搞清楚 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. 删除注释🚹🚹🚹🚹🚹

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回溯)

980. 不同路径 III

在这里插入图片描述
提示:
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. 合并两个有序链表

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. 两两交换链表中的节点

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wei *

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值