LeetCode面试经典150题梳理

link:https://leetcode.cn/studyplan/top-interview-150/

日期题号备注
2025.5.2288. 合并两个有序数组 - 力扣(LeetCode)通过双指针法从后向前合并来解决,避免覆盖nums1中的元素
2025.5.2327. 移除元素 - 力扣(LeetCode)

用快慢指针

slow用来标识有效元素的下标

fast用来遍历整个数组

slow和fast都从下标0开始

如果nums[fast] != val, 将fast的值复制到slow的位置,然后移动slow

2025.5.2426. 删除有序数组中的重复项 - 力扣(LeetCode)

用快慢指针

fast用来遍历数组

slow标识新元素的下标

需要时刻考虑fast指针是否越界的问题

2025.5.2580. 删除有序数组中的重复项 II - 力扣(LeetCode)

用快慢指针

长度小于2,直接返回,slow和fast都从下标2开始

slow指针表示修改后数组的下标,fast遍历整个数组

比较 nums[fast] == nums[slow-2]:true, continue; false,给slow赋值

2025.5.26169. 多数元素 - 力扣(LeetCode)

最简单的:摩尔投票法

初始化候选人和计数器

遍历数组元素,相同元素则计数器+1,不同则-1

当计数器归零时更换候选元素

最终候选人即为所求

错误点:在count变为0时,将候选人设置为当前元素,并将count重置为1

2025.5.27189. 轮转数组 - 力扣(LeetCode)

反转直接写for循环

需要将k取模,来处理k大于数组长度的情况

2025.5.28121. 买卖股票的最佳时机 - 力扣(LeetCode)

直接跳过,自己做出来的

类似动态规划的思想:

minPrice 记录当前最低的股票价格

maxProfit 记录已知的最大利润

2025.5.29122. 买卖股票的最佳时机 II - 力扣(LeetCode)

用贪心算法,捕捉所有上升趋势的机会,并且就在第一天买,第二天卖

135. 分发糖果 - 力扣(LeetCode)

贪心算法,维护两个数组

从左到右:确保右边孩子的糖果比左边孩子多

从右到左:确保左边孩子的糖果比右边孩子多

没考虑到的地方:当前评分不大于前一个时,应该将糖果数设为1,而不是保持前一个的值

两个数组中取最大值

2025.5.3055. 跳跃游戏 - 力扣(LeetCode)

动态规划(自己想到的):

  • 维护一个bool数组loc
  • loc[0] = true
  • 遍历nums数组,loc[i]为true,则把 i 到 i+nums[i] 的所有loc都置为true。 如果loc[len(nums)-1] == true了,return true
  • 如果遍历到某个位置loc[i]为false了,return false

贪心算法:

  • 维护一个maxReach
  • 遍历数组,maxReach = max(maxReach, nums[i] + i)
  • 如果 maxReach < i, return false;
  • 如果 maxReach > len(nums) - 1, return true;
2025.6.245. 跳跃游戏 II - 力扣(LeetCode)

(没想到)

用贪心算法:每一步都尽量跳得最远,这样才能保证跳跃次数最少。

end = 0, farthest = 0, jump = 0。

end: 当前这一跳的最远位置;farthest:当前跳跃范围内,通过每一步能到达的最远位置。

每当你走到end时(即i==end),说明你需要跳一次,此时jumps++,并把end更新为farthest。

只要能覆盖到最后一个元素,就可以结束了。

2025.6.3274. H 指数 - 力扣(LeetCode)

(想到了暴力解法)

从数组里面的最大值开始遍历 value

找到符合 citations[i] >= value 的个数 count

如果count == value, 返回value

不然继续

优化: 排序 + 一次遍历

先把数组排序,第i篇论文的引用数是citation[n-i]

从前向后找,找到第一个 citations[i] >= n-i,说明有 n-i 篇论文引用不少于 n-i 次

2025.6.4380. O(1) 时间插入、删除和获取随机元素 - 力扣(LeetCode)

用空间换时间

  • 定义一个map[int]int数组:key是插入的值,value是元素在keys数组中的索引
  • 插入元素的时候,把索引更新到value的位置
  • 删除元素的时候,先查找元素是否存在,如果存在,找到其索引,然后将Keys末尾的元素移到该索引的位置,更新map中该末尾元素的索引,最后从Keys中删除末尾元素
  • rand种子的设置需要挪到最前面

初始化map的方法:make(map[int]int)

判断key是否存在的方法: index, exists := this.Mapping[val]

删除key的方法: delete(this.Mapping, val)

设置随机种子:rand.Seed(time.Now().UnixNano()), keys[rand.Intn(len(keys))]

2025.6.5238. 除自身以外数组的乘积 - 力扣(LeetCode)

自己想到的:

  1. 维护两个数组:当前数左边/右边所有数的乘积
  2. 初始化数组:nums1 := make([]int, len(nums))
42. 接雨水 - 力扣(LeetCode)

动态规划:两个数组记录左右两边的最大值

当前网格储水量 = min(leftMax[i], rightMax[i]) - 当前高度

2025.6.613. 罗马数字转整数 - 力扣(LeetCode)

从左到右累加

  1. 当前字符 >= 后一个字符,result += current_value, i+1
  2. 当前字符 < 后一个字符, result += (next_value - current_value), i+2
  3. 初始化map的方法 mapping := map[byte]int{...}
2025.6.9134. 加油站 - 力扣(LeetCode)

想到了暴力解法, 拼接数组的逻辑:

  • a := nums[k:]
  • b := nums[:k]
  • result := append(a, b ...)

贪心算法:

  • 一次遍历:记录总油量totalGas和当前油量currentGas。currentGas不足时,重置起点为下一个加油站;totalGas不足时,无法完成任务
2025.6.1012. 整数转罗马数字 - 力扣(LeetCode)

贪心算法:

  •         维护一个从大到小的结构体数组, 每次减去可减去的最大数
type romanNumeral struct {
    value  int
    symbol string
}

var numerals = []romanNumeral{
    {1000, "M"},
    ......
}
  •       使用string builder来构建结果
var b strings.Builder
    for _, n := range numerals {
        for num >= n.value {
            b.WriteString(n.symbol)
            num -= n.value
        }
    }

2025.6.1158. 最后一个单词的长度 - 力扣(LeetCode)

自己想到的:

        先跳过末尾的所有空格,再直接遍历找出末尾最后一个单词的长度

2025.6.1214. 最长公共前缀 - 力扣(LeetCode)自己想到的:逐个比较每个字符的最长公共前缀
151. 反转字符串中的单词 - 力扣(LeetCode)自己想到的:
  • 原来有多个空格,转换后还有一个空格的解法:strings.Fields
  • 反转字符的解法:从后向前遍历
2025.6.1628. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

用双指针

slow: 用来标志第slow个字符被检查过是否符合了,取值 0 到 两个长度之差

比较的时候应该是slow+j和j的下标进行比较

2025.6.17125. 验证回文串 - 力扣(LeetCode)

先都转换成小写,在保留所有的小写字母,最后双指针判断是否是回文串

// 编译超时改进拼接字符串:

for _, ch := range s {

    if unicode.IsLetter(ch) || unicode.IsDigit(ch) {

        filtered = append(filtered, unicode.ToLower(ch))

    }

}

2025.6.18392. 判断子序列 - 力扣(LeetCode)自己想到的: 

双指针

167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)

想到了暴力解法,也可以用双指针

i=0,j = len(numbers) - 1: 两数之和<target,i++; 两数之和>target,j--;

2025.6.1911. 盛最多水的容器 - 力扣(LeetCode)

想到了贪心算法, 最大水量 = width * min(height)

没想到:从两边开始向中间挪最短的边,不断更新最大水量

2025.6.2015. 三数之和 - 力扣(LeetCode)

想到:先排序,在固定一个值后用双指针

没想到的:需要跳过重复的值,跳过后记得更新双指针

2025.6.23209. 长度最小的子数组 - 力扣(LeetCode)

没想到:可以用滑动窗口: left, right := 0, 0

向右移动right,直到 sum >= target, 设置一个变量记录result

移动左窗口来缩小滑动窗口;

继续移动right,重复上面的步骤

2025.6.273. 无重复字符的最长子串 - 力扣(LeetCode)

想到思路,实现卡了好几天:

也用滑动窗口,left,right

用一个map[byte]int来维护,int可以下标的值

left,right双指针:

  1.  mapping[right]存在就更新left 到重复字符的下一个位置
  2. 更新字符的最新索引
  3. 计算result
2025.6.3036. 有效的数独 - 力扣(LeetCode)

自己没有想到的:

用3个二维数组是否存在的状态

  • rows[i][j]:第i行中是否出现了数字j+1
  • cols[i][j]:第i列中是否出现了数字j+1
  • boxes[boxIndex][num]:第boxIndex个小方格里是否有数字num+1;boxIndex=(row/3)×3+(col/3)

byte 转int:intVal, _ := strconv.Atoi(string(val))

初始化数组: var rows [9][9]bool

2025.7.1383. 赎金信 - 力扣(LeetCode)

自己想到的

直接用一个数组维护magazine; index = char - 'a',

遍历ransomNote,存在的话val-1, 不存在return false

2025.7.254. 螺旋矩阵 - 力扣(LeetCode)

没想到,边界控制法

从左到右 -> 从上到下 -> 从右到左 -> 从下到上

更新位置的时候应该在if条件里面

从右到左应该检查上下边界

从下到上应该检查左右边界

2025.7.348. 旋转图像 - 力扣(LeetCode)

matrix[i][j] 和 matrix[n-1-i][n-1-j]互换顺序

上下的行互换顺序

沿着对角线互换顺序没想到:

for i := 0; i < n; i++ {
	for j := i+1; j < n; j++ {
		matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
    }
}

2025.7.473. 矩阵置零 - 力扣(LeetCode)

自己想到的:

先遍历一遍数组把需要置为0的行和列都存下来,然后直接赋0

2025.7.7205. 同构字符串 - 力扣(LeetCode)

没想到,维护两个mapping s2t 和 t2s

把t和s的值相互映射,确认是否有重复的映射关系

2025.7.8290. 单词规律 - 力扣(LeetCode)

自己想到的,还是用两个数组维护映射关系

p2s := make(map[byte]string)

s2p := make(map[string]byte)

p2s[char] != ""  s2p[str] > 0

2025.7.10242. 有效的字母异位词 - 力扣(LeetCode)

自己想到的,注意遍历string数组的方式:

mapping := make(map[rune]int)

for _, c := range s {...}

2025.7.1449. 字母异位词分组 - 力扣(LeetCode)

自己没想到

用计数法,记下26个字母每个字母出现的次数,然后拼接成一个string

相同key的就是字母异位

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

自己想到的

用一个map[int]int数组保存,key是当前值,value是下标

2025.7.17202. 快乐数 - 力扣(LeetCode)

思路没想到,代码自己写的

使用一个map集合来判断是否出现了重复的数字

出现了就不是快乐数字,没出现就是

2025.7.18219. 存在重复元素 II - 力扣(LeetCode)

用一个map[int]int来存储:key是nums[i],value 是i

如果map[nums[i]]已经存在:

if abs(i - j) <= k, 成立就return true

else map[nums[i]] = 新的i。

如果map[nums[i]]不存在, map[nums[i]] = i

继续循环,直到最后一个值还不成立,return false

math.Abs(float64(index - i)) <= float64(k)

2025.8.4128. 最长连续序列 - 力扣(LeetCode)

没想到:

把每个值都先存到一个map里,key是数组里出现的值,value是1

遍历数组,只从起始值开始遍历; 起始值的定义:value - 1 不再map里,在的话就不需要遍历

再用visited的map记录某个值是否被访问过

2025.8.5228. 汇总区间 - 力扣(LeetCode)

自己想到的:

循环遍历nums数组

判断 nums[i+1] == nums[i]+1:

不等于直接append

等于的话继续向后循环

2025.8.656. 合并区间 - 力扣(LeetCode)

有思路,没写出来:

先按照集合的第一个数字进行排序:

sort.Slice(result, func(i, j int) bool {

return result[i][0] < result[j][0]

})

贪心合并:不需要新建临时数组

2025.8.857. 插入区间 - 力扣(LeetCode)

没思路

1. 处理完全在新区间左侧的区间

2. 处理重叠区间

2.1 有重叠,开始合并

2.2 继续检查后续区间是否还与合并后的区间重叠

2.3 新区间不与任何现有区间重叠,直接添加

3. 处理完全在新区间右侧的区间

2025.8.11452. 用最少数量的箭引爆气球 - 力扣(LeetCode)

没思路:

1. 按右端点排序所有气球

        初始化箭数为1,第一支箭位置为第一个气球的右端点

2. 遍历后续气球:

        如果当前气球左端点 > 当前箭位置,需要新箭

        新箭位置设为当前气球的右端点

3. 返回总箭数

2025.8.1320. 有效的括号 - 力扣(LeetCode)

使用栈,goland应该是不支持栈的,使用切片模拟

// 入栈(push)

stack = append(stack, 1) // [1]

stack = append(stack, 2) // [1, 2]

// 查看栈顶元素(peek)

if len(stack) > 0 {

top := stack[len(stack)-1]

fmt.Println("栈顶元素:", top) // 输出: 栈顶元素: 2

}

// 出栈(pop)

if len(stack) > 0 {

stack = stack[:len(stack)-1] // [1]

}

遇到左边的括号( { [, push到栈里

遇到右边的括号,pop出来

2025.8.1471. 简化路径 - 力扣(LeetCode)

自己做出来的(加粗问的ai)

按照/ 或者 //分组: 先按照/分割,然后过滤掉空的值

把每个值放到切片里

如果是., 直接忽略

如果是..,弹出第一个元素, 没得弹出的时候continue

2025.8.22155. 最小栈 - 力扣(LeetCode)用切片模拟 自己写出来的
2025.8.25150. 逆波兰表达式求值 - 力扣(LeetCode)

用栈解决。

遇到数字就放入栈,遇到计算符号就弹出两个数字

错误的点:

1. 把当前计算出的结果再压回栈中

2. 判断是不是数字 strconv.Atoi。strconv.Atoi 的 err 就能判断是不是运算符

链表
2025.8.27141. 环形链表 - 力扣(LeetCode)

使用快慢指针:快的一次走两步,慢的一次走一步,每次走的时候都要判断能不能走

如果两个指针相遇了 -〉 有环

如果不能走,到尽头了 -〉 没环

2025.8.292. 两数相加 - 力扣(LeetCode)

新建一个链表来存储结果

设置一个进位符,每当相加的结果超过10的时候,进位符 = 1; 否则0

2025.9.1合并两个有序链表直接做出来的
2025.9.4138. 随机链表的复制 - 力扣(LeetCode)

想错思路了,正确做法:

1. 遍历原链表,复制每个节点,并建立原节点到新节点的映射

2. 再次遍历原链表,复制`Next`和`Random`指针

2025.9.592. 反转链表 II - 力扣(LeetCode)

自己没有想到: 定位区间 -> 反转区间 -> 连接区间

找left-1的节点

反转逻辑:

cur := pre.Next

for i := 0; i < right - left; i++{

temp := cur.Next

cur.Next = temp.Next

temp.Next = pre.Next

pre.Next = temp

}

返回新链表头

2025.9.9https://leetcode.cn/problems/remove-nth-node-from-end-of-list/?envType=study-plan-v2&envId=top-interview-150

长度5,倒数第2个节点,需要走到第(5-2)个节点, 走(5-2-1)步

如果删除的是第一个节点,需要特殊处理

2025.9.10https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/submissions/661241271/?envType=study-plan-v2&envId=top-interview-150

自己想到的解法只能删除相邻重复节点中的一个

设置dummy节点

如果发现重复值,记录重复的值,删除所有值为val的节点

如果没有重复,移动prev指针

2025.9.25https://leetcode.cn/problems/rotate-list/?envType=study-plan-v2&envId=top-interview-150

1. 把首位节点相连

2. ListNode的长度是length

3. k = k % length;

4. 首节点移动(length - k -1),cur 断开

没想到的:

1. k == 0 的时候,不要继续

2025.9.28https://leetcode.cn/problems/partition-list/submissions/666774439/?envType=study-plan-v2&envId=top-interview-150

自己想到了用两个切片存less & more,然后按顺序重建链表

优化:两个虚拟头 less 和 greater,一次遍历把原链表的节点“摘下”接到对应链表尾部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值