当前位置: 首页 > news >正文

LeetCode算法日记 - Day 30: K 个一组翻转链表、两数之和

目录

1. K 个一组翻转链表

1.1 题目解析

1.2 解法

1.3 代码实现

2. 两数之和

2.1 题目解析

2.2 解法

2.3 代码实现


1. K 个一组翻转链表

25. K 个一组翻转链表 - 力扣(LeetCode)

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

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

示例 1:

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

示例 2:

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

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

1.1 题目解析

题目本质
这是一个“分段原地翻转”的链表题:把链表按长度为 k 的小段切开——每段内部反转,段与段之间正确接回。不足 k 的尾巴保持原样。

常规解法
最直观的做法:
i)先数长度 n,只处理 n / k 个完整分段;
ii)每段用“头插法”/原地反转 k 次;
iii)把反转好的这段接回前后两端;
iiii)指针前移,处理下一段。

问题分析
难点不在“如何反转 k 个节点”,而在连边与指针推进

  • 反转会把这段内部 next 全改掉,导致与原链表左右两侧都断开

  • 每段结束都需要接回两条边

    • 左:上一段的尾 → 本段新头;

    • 右:本段新尾(原头) → 下一段起点;

  • 忘任意一条都会出错(尤其是“右边那条”会丢尾巴)。

复杂度预期:每个节点最多被访问/重连常数次,时间 O(n),空间 O(1)。

思路转折
为保证连接有序与时间 O(n):

  • 设立两个“钩子”指针:

    • dummyPrev 始终指向已处理前缀的最末尾(上一段的新尾);

    • start 指向当前段的原头,反转后变成新尾

  • 段内反转结束后,必须连两条边:

    • dummyPrev.next = prev(左边)

    • start.next = curr(右边)

  • 再把钩子前移:dummyPrev = start; start = curr;
    这样每段都“自洽”,最后自然正确。

1.2 解法

算法思想
对每个完整的长度为 k 的小段,进行原地反转;反转后把这段用两条边接回到大链表,再把“钩子”指针移到新位置开始下一段。
段内反转核心递推(头插法):

next = curr.next
curr.next = prev
prev = curr
curr = next

i)一次遍历统计长度 n,计算完整段数 count = n / k;

ii)设置哑节点 dummy 方便返回,初始化:

  • dummyPrev = dummy(上一段尾的“左钩子”)

  • start = head(当前段原头,“右钩子”)

iii)循环 count 次处理每段:

  • 段内反转:从 start 开始,做 k 次“头插法”;

    • 结束时:prev=本段新头,start=本段新尾,curr=下一段起点;

  • 段间连边(两条):

    • dummyPrev.next = prev(上一段尾 → 本段新头)

    • start.next = curr(本段新尾 → 下一段头)

  • 钩子前移:dummyPrev = start; start = curr

iiii)返回 dummy.next。

小技巧:k <= 1 或 count == 0 可直接返回 head。

易错点

  • 忘记右边那条边:start.next = curr,会导致尾巴丢失(尤其最后一段)。

  • 处理完一段后推进“钩子”:dummyPrev = start; start = curr。

  • 把 dummyPrev 和 start 当成同一角色(它们在段结尾短暂相等,但职责不同:dummyPrev 负责左连,start 负责右连)。

1.3 代码实现

/*** 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 ListNode reverseKGroup(ListNode head, int k) {if (head == null || k <= 1) return head; // 边界:k=1 或空链表无需处理// 1) 统计长度,确定完整分组数int n = 0;ListNode curr = head;while (curr != null) {n++;curr = curr.next;}int count = n / k;if (count == 0) return head; // 不足一组,直接返回// 2) 准备哑节点与“钩子”指针ListNode dummy = new ListNode(0);dummy.next = head;               // 哑节点用于返回结果ListNode dummyPrev = dummy;      // 左钩子:已处理前缀的尾ListNode start = head;           // 右钩子:当前分组原头(反转后成为新尾)// 3) 逐组处理for (int i = 0; i < count; i++) {// 3.1 段内反转 k 次ListNode prev = null;curr = start; // 从本组原头开始反转for (int j = 0; j < k; j++) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next; // 正确推进到未反转部分}// 此时:prev=本组新头,start=本组新尾(原头),curr=下一段起点// 3.2 段间连边(两条都要!)dummyPrev.next = prev; // 左边:上一段尾 -> 本组新头start.next = curr;     // 右边:本组新尾(原start) -> 下一段起点// 3.3 钩子前移,开始下一组dummyPrev = start; // 新尾成为已处理前缀的尾start = curr;      // 下一组原头}// 4) 返回结果return dummy.next;}
}

复杂度分析

  • 时间复杂度:O(n),每个节点被常数次访问/重连。

  • 空间复杂度:O(1),只用到若干指针。

2. 两数之和

1. 两数之和 - 力扣(LeetCode)

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

2.1 题目解析

题目本质
把“找两数和为 target”的问题,转成“对当前数 x,另一个数要等于 need = target - x;只要我能在 O(1) 时间判断 need 是否已出现,就能立即返回答案”,如果查询不到则设为 -1 充当哨兵值,只需要判断哨兵值就可以知道 哈希表吃否存在 need。这就是补数查找

常规解法
双重循环暴力:枚举每对 (i,j),检查 nums[i] + nums[j] == target,返回 [i,j]。

问题分析
暴力解时间 O(n^2);当 n 接近 1e4 时,就不够优雅。并且做了大量重复比较。

思路转折
用哈希表记录“值 → 索引”。一边遍历一边查补数:
遍历到 i 时,先算 need = target - nums[i]:

  • 如果 need 已经在表里,直接返回 [idx(need), i];

  • 否则把当前数 nums[i] 的“值→索引”存进表,继续。
    这样每个元素只出表一次,时间 O(n)、空间 O(n)。
    小建议:先查再放,避免把自己配给自己(“不能使用两次相同的元素”)。

2.2 解法

算法思想
单遍扫描 + 哈希:

need=target−nums[i],    if need∈map⇒return [map[need],i]

否则 map[nums[i]] = i 继续。

i)创建 Map<Integer,Integer> map,表示“值 → 索引”。

ii)从左到右遍历 i:

  • 计算 need = target - nums[i];

  • 若 map 中存在 need,返回 [map.get(need), i];

  • 否则 map.put(nums[i], i)。

iii)按题意“必有解”,理论上不会走到返回空数组。

易错点

  • 返回而不是索引(容易错)。

  • 先 put 再 查 会把自己配给自己(违背“不用两次相同元素”)。应先查后放

  • 用 getOrDefault(tmp, 0) 判断存在性会误判:索引 0 合法。若用哨兵,选 -1;

  • 极端数据溢出:比较时用 need = target - nums[i](已避免溢出加法)

  • 有重复值时,哈希中应保存最早出现的那个索引吗?一遍扫天然满足:先遇到的先放,后遇到的与之匹配直接返回即可。

2.3 代码实现

import java.util.HashMap;
import java.util.Map;class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer,Integer> hash = new HashMap<>();for (int i = 0; i < nums.length; i++) {int need = target - nums[i];int j = hash.getOrDefault(need, -1); // -1 不与合法索引冲突if (j != -1) return new int[]{j, i};hash.put(nums[i], i);}return new int[0];}
}

复杂度分析

  • 时间复杂度:O(n),每个元素最多哈希查/存一次。

  • 空间复杂度:O(n),最坏把 n-1 个元素放入哈希表。

http://www.xdnf.cn/news/1437157.html

相关文章:

  • 基于Springboot和Vue的前后端分离项目
  • playwright+python UI自动化测试中实现图片颜色和像素对比
  • milvus使用
  • Hard Disk Sentinel:全面监控硬盘和SSD的健康与性能
  • Python学习-day4
  • 2026届长亭科技秋招正式开始
  • 算法 --- 模拟
  • NLP学习系列 | Transformer代码简单实现
  • Zephyr如何注册设备实例
  • [Java]PTA:jmu-Java-01入门-取数字浮点数
  • 自学嵌入式第三十三天:网络编程-UDP
  • Day19(前端:JavaScript基础阶段)
  • 分布式中防止重复消费
  • Spring Security的@PreAuthorize注解为什么会知道用户角色?
  • 开悟篇Docker从零到实战一篇文章搞定
  • 基于Python毕业设计推荐:基于Django的全国降水分析可视化系统
  • 战略咨询——解读81页中小企业企业战略规划方案【附全文阅读】
  • go-mapus最简单的离线瓦片地图协作
  • C++后端开发重点知识点
  • Adafruit_nRF52_Bootloader 使用 uf2
  • Spring Cloud Config 核心原理
  • 【C++】编写通用模板代码的重要技巧:T()
  • CICD的持续集成与持续交付和Zabbix
  • 【C++】15. ⼆叉搜索树
  • 室内定位---apriltag 视觉定位demo
  • (四)Python控制结构(条件结构)
  • deepseek7b本地部署技巧,新手也能玩得转
  • 下载 | Win11 官方精简版,系统占用空间极少!(8月更新、Win 11 IoT物联网 LTSC版、适合老电脑安装使用)
  • Flink RuntimeContext和FunctionContext:状态计算的核心桥梁
  • Linux中断实验