力扣热门算法题 128. 最长连续序列,134. 加油站,143. 重排链表

58 篇文章 1 订阅
55 篇文章 0 订阅
本文详细解析了LeetCode中的三个问题:128.最长连续序列(O(n)算法),134.加油站(环形路径判断),以及143.重排链表(快慢指针法)。提供了Python和Java的解决方案。
摘要由CSDN通过智能技术生成

128. 最长连续序列,134. 加油站,143. 重排链表,每题做详细思路梳理,配套Python&Java双语代码, 2024.03.26 可通过leetcode所有测试用例

目录

128. 最长连续序列

解题思路

完整代码

Python

Java

134. 加油站

解题思路

完整代码

Python

Java

143. 重排链表

解题思路

完整代码

Python

Java


128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

解题思路

要在 O(n) 的时间复杂度内解决这个问题,我们可以使用哈希表来存储数组中的每个数字,以便快速判断一个数字是否存在于数组中。接下来,我们可以遍历数组,并对每个元素执行以下步骤:

  1. 检查当前数字是否是序列的开始:对于数组中的每个数字,检查其减一的结果是否也在数组中。如果不在,说明当前数字是一个序列的开始。

  2. 探索序列:从当前序列的开始数字出发,不断增加 1,探索序列的长度,同时在哈希表中查找增加后的数字是否存在。

  3. 更新最大长度:在探索过程中,不断更新找到的最长序列的长度。

  4. 返回最大长度:遍历结束后,返回找到的最长序列的长度。

这种方法之所以有效,是因为它确保每个数字最多被探索两次:一次是作为其他数字的后续部分,一次是作为序列的开始。因此,总的时间复杂度仍然保持在 O(n)。

完整代码

Python
class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        num_set = set(nums)
        longest_streak = 0

        for num in num_set:
            # 只有当 num - 1 不在集合中时,才考虑以 num 为起点,这样可以避免重复
            if num - 1 not in num_set:
                current_num = num
                current_streak = 1

                # 探索当前序列
                while current_num + 1 in num_set:
                    current_num += 1
                    current_streak += 1

                # 更新最大长度
                longest_streak = max(longest_streak, current_streak)

        return longest_streak
Java
class Solution {
public int longestConsecutive(int[] nums) {
    Set<Integer> num_set = new HashSet<Integer>();
    for (int num : nums) {
        num_set.add(num);
    }

    int longestStreak = 0;

    for (int num : num_set) {
        // 只有当 num - 1 不在集合中时,才考虑以 num 为起点
        if (!num_set.contains(num-1)) {
            int currentNum = num;
            int currentStreak = 1;

            // 探索当前序列
            while (num_set.contains(currentNum+1)) {
                currentNum += 1;
                currentStreak += 1;
            }

            // 更新最大长度
            longestStreak = Math.max(longestStreak, currentStreak);
        }
    }

    return longestStreak;
}

}

134. 加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

解题思路

要解决这个问题,我们可以遵循以下的思路:

  1. 总体判断:如果总的汽油量小于总的消耗量,那么无法绕环路行驶一周。这是因为,不论从哪里出发,汽油总是不够用的。

  2. 寻找起始位置:如果总的汽油量大于或等于总的消耗量,那么一定存在一个起始位置可以绕环路一周。我们可以从头开始遍历加油站,尝试每个加油站作为起点,并计算从该起点开始绕环路的过程中的累积净汽油量(当前加油站的汽油量减去到下一个加油站的消耗量)。如果在某个点累积净汽油量变为负数,说明不能从当前起点绕一周,需要尝试下一个加油站作为新的起点,并重置累积净汽油量。

  3. 维护当前净汽油量和总净汽油量:在遍历过程中,我们维护一个当前净汽油量来判断是否能从当前加油站走到下一个加油站。同时,我们也维护一个总净汽油量来判断是否总体上能绕环路一周。

  4. 返回结果:如果找到了可以作为起始点的加油站,返回其索引;如果遍历结束也没有找到,返回 -1。

完整代码

Python
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        if sum(gas) < sum(cost):
            return -1

        start, total_tank, curr_tank = 0, 0, 0
        for i in range(len(gas)):
            total_tank += gas[i] - cost[i]
            curr_tank += gas[i] - cost[i]
            # 如果当前累积净汽油量小于0,从下一个加油站重新开始
            if curr_tank < 0:
                start = i + 1
                curr_tank = 0

        return start if total_tank >= 0 else -1
Java
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalTank = 0;
        int currTank = 0;
        int startingStation = 0;
        for (int i = 0; i < gas.length; i++) {
            totalTank += gas[i] - cost[i];
            currTank += gas[i] - cost[i];
            // 如果当前累积净汽油量小于0,从下一个加油站重新开始
            if (currTank < 0) {
                startingStation = i + 1;
                currTank = 0;
            }
        }
        return totalTank >= 0 ? startingStation : -1;
    }
}

143. 重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:

输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

解题思路

  1. 找到链表的中点:使用快慢指针法找到链表的中点。快指针每次移动两步,慢指针每次移动一步,当快指针到达链表末尾时,慢指针将位于链表的中间位置。

  2. 反转链表的后半部分:从中点开始反转链表的后半部分。反转后,链表的后半部分将以逆序的形式出现。

  3. 合并链表的前半部分和反转后的后半部分:将链表的前半部分和反转后的后半部分交替合并,形成最终的链表。

完整代码

Python
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        if not head:
            return
        
        # 1. 找到中点
        slow, fast = head, head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        
        # 2. 反转后半部分
        prev, curr = None, slow
        while curr:
            next_temp = curr.next
            curr.next = prev
            prev = curr
            curr = next_temp
        
        # 3. 合并两个链表
        first, second = head, prev
        while second.next:
            first.next, first = second, first.next
            second.next, second = first, second.next
Java
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        if (head == null) return;
        
        // 1. 找到中点
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        
        // 2. 反转后半部分
        ListNode prev = null, curr = slow, temp;
        while (curr != null) {
            temp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = temp;
        }
        
        // 3. 合并两个链表
        ListNode first = head, second = prev;
        while (second.next != null) {
            temp = first.next;
            first.next = second;
            first = temp;
            
            temp = second.next;
            second.next = first;
            second = temp;
        }
    }
}

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

昊昊该干饭了

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

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

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

打赏作者

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

抵扣说明:

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

余额充值