广东南电建设集团网站简约网站建设
一、跳跃游戏
题目描述:
给定一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
示例:
- 输入:
nums = [2,3,1,1,4]→ 输出:True - 输入:
nums = [3,2,1,0,4]→ 输出:False
---------------------------------------------------------------------------------------------------------------------------------
代码实现:
def can_jump(nums):max_reach = 0n = len(nums)for i in range(n):if i > max_reach: # 当前位置无法到达,直接失败return Falsemax_reach = max(max_reach, i + nums[i])if max_reach >= n - 1: # 已覆盖终点,提前成功return Truereturn max_reach >= n - 1 # 遍历结束后判断
详细解析:
- 核心变量:
max_reach记录当前能到达的最远索引,初始为 0(起点)。 - 遍历逻辑:
- 若当前索引
i超过max_reach,说明该位置不可达,直接返回False(因为无法从前面的位置跳到这里)。 - 计算从当前位置
i能跳到的最远距离i + nums[i],并更新max_reach为两者中的较大值(确保始终记录最远可达位置)。 - 若
max_reach已覆盖数组最后一个索引(n-1),说明可到达终点,提前返回True以优化效率。
- 若当前索引
- 边界处理:若遍历完所有位置后
max_reach仍未覆盖终点,则返回False(题目中大部分情况可提前判断,此处为兜底)。 - 贪心思想体现:无需记录具体路径,只需通过局部最优(每次更新最远可达距离)推导全局最优(是否能到终点),时间复杂度 O (n),空间复杂度 O (1)。
二、最长递增子序列(动态规划解法)
题目描述:
给你一个整数数组 nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)元素而不改变其余元素的顺序,且子序列严格递增。
示例:
输入:nums = [10,9,2,5,3,7,101,18] → 输出:4(最长子序列为 [2,3,7,101] 或 [2,5,7,18])
---------------------------------------------------------------------------------------------------------------------------------
代码实现:
def length_of_lis(nums):if not nums:return 0n = len(nums)dp = [1] * n # 每个元素至少自身是一个子序列for i in range(1, n):for j in range(i):if nums[j] < nums[i]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)
详细解析:
- 动态规划数组定义:
dp[i]表示以nums[i]为结尾的最长严格递增子序列的长度。初始值均为 1(每个元素自身构成长度为 1 的子序列)。 - 状态转移逻辑:
- 对于每个
i(从 1 到 n-1),遍历所有j < i:- 若
nums[j] < nums[i],说明nums[i]可接在nums[j]后面形成更长的子序列,因此dp[i]可更新为dp[j] + 1(与当前dp[i]取最大值,确保记录最长长度)。
- 若
- 对于每个
- 结果计算:遍历完所有
i后,dp数组中的最大值即为整个数组的最长递增子序列长度。 - 示例验证:以
nums = [2,5,3,7]为例:i=0:dp[0] = 1(初始值)。i=1(nums[1]=5):j=0时nums[0]=2 < 5,故dp[1] = dp[0]+1 = 2。i=2(nums[2]=3):j=0时nums[0]=2 < 3,故dp[2] = dp[0]+1 = 2(j=1时nums[1]=5 > 3,不更新)。i=3(nums[3]=7):j=0时dp[0]+1=2,j=1时dp[1]+1=3,j=2时dp[2]+1=3,故dp[3] = 3。
最终max(dp) = 3(对应子序列[2,5,7]或[2,3,7])。
- 复杂度:时间复杂度 O (n²)(两层循环),空间复杂度 O (n)(存储
dp数组)。
三、最长递增子序列(贪心 + 二分查找解法)
题目描述:
同第二题(题目一致,解法不同)。
---------------------------------------------------------------------------------------------------------------------------------
代码实现:
import bisectdef length_of_lis(nums):tails = []for num in nums:# 找到tails中第一个 >= num的位置,替换它idx = bisect.bisect_left(tails, num)if idx == len(tails):tails.append(num)else:tails[idx] = numreturn len(tails)
详细解析:
- 核心思路:维护一个
tails数组,其中tails[i]表示长度为i+1的严格递增子序列的最小尾元素。例如:tails[0]是长度为 1 的子序列的最小尾元素(即数组中最小的元素)。tails[1]是长度为 2 的子序列的最小尾元素(即能接在tails[0]后面的最小元素)。
这样做的目的是:尾元素越小,后续越容易添加更大的元素形成更长的子序列(贪心策略)。
- 二分查找的作用:
- 对于每个新元素
num,用bisect_left在tails中查找第一个大于等于num的位置idx(tails始终是严格递增的,因此可二分)。 - 若
idx等于tails的长度,说明num比所有现有尾元素都大,可直接追加到tails末尾(此时子序列长度 + 1)。 - 否则,将
tails[idx]替换为num(目的是用更小的尾元素更新该长度的子序列,为后续元素留更多可能)。
- 对于每个新元素
- 示例验证:
输入nums = [10,9,2,5,3,7,101,18]:num=10→tails为空,idx=0等于长度 0,追加 →tails = [10]。num=9→ 二分找到tails中第一个 ≥9 的位置是 0(tails[0]=10),替换 →tails = [9]。num=2→ 二分找到位置 0,替换 →tails = [2]。num=5→ 二分找到位置 1(超过tails长度 1),追加 →tails = [2,5]。num=3→ 二分找到tails中第一个 ≥3 的位置是 1(tails[1]=5),替换 →tails = [2,3]。num=7→ 二分找到位置 2(超过长度 2),追加 →tails = [2,3,7]。num=101→ 二分找到位置 3(超过长度 3),追加 →tails = [2,3,7,101]。num=18→ 二分找到位置 3(tails[3]=101 ≥18),替换 →tails = [2,3,7,18]。
最终tails长度为 4,即最长子序列长度为 4(与题目示例一致)。
- 关键说明:
tails数组本身不是最长子序列,但其长度等于最长子序列的长度(因为每次更新都对应子序列长度的潜在增长)。 - 复杂度:时间复杂度 O (n log n)(每个元素二分查找 O (log k),k 是当前
tails长度,总复杂度 O (n log n)),空间复杂度 O (n)(tails最长为 n)。
四、岛屿数量(DFS 解法)
题目描述:
给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。岛屿由相邻的陆地连接形成,相邻指水平或垂直方向(斜向不算)
示例:
输入:
grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3
-------------------------------------------------------------------------------------
答案:
def num_islands(grid):if not grid:return 0rows, cols = len(grid), len(grid[0])count = 0def dfs(i, j):# 越界或不是陆地,直接返回if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] != '1':returngrid[i][j] = '0' # 标记为已访问(淹没)# 递归遍历四个方向(上下左右)dfs(i+1, j) # 下dfs(i-1, j) # 上dfs(i, j+1) # 右dfs(i, j-1) # 左for i in range(rows):for j in range(cols):if grid[i][j] == '1':count += 1 # 发现新岛屿dfs(i, j) # 淹没整个岛屿,避免重复计数return count
详细解析:
- 核心问题:统计 “连通分量” 的数量(相邻陆地构成的独立区域)。
- DFS 函数作用:从坐标
(i,j)出发,递归 “淹没” 所有相连的陆地(将'1'改为'0'),确保每个岛屿只被计数一次。 - DFS 终止条件:
- 坐标越界(
i或j超出网格范围)。 - 当前位置不是陆地(
grid[i][j] != '1',可能是水或已被淹没的陆地)。
- 坐标越界(
- 主循环逻辑:
- 遍历网格中的每个单元格
(i,j)。 - 若遇到
grid[i][j] == '1',说明发现一个新岛屿,计数器count加 1。 - 立即调用
dfs(i,j)淹没该岛屿(将所有相连的'1'改为'0'),防止后续遍历再次计数。
- 遍历网格中的每个单元格
- 示例验证:
以上述示例为例:- 首次遇到
(0,0)为'1',count=1,启动 DFS 淹没左上角所有相连的'1'(第一、二行前两列变为'0')。 - 继续遍历,遇到
(2,2)为'1',count=2,启动 DFS 淹没中间岛屿。 - 最后遇到
(3,3)为'1',count=3,启动 DFS 淹没右下角岛屿((3,3)和(3,4)相连)。
最终count=3,与示例一致。
- 首次遇到
- 复杂度:时间复杂度 O (rows×cols)(每个单元格最多被访问一次),空间复杂度 O (rows×cols)(最坏情况全是陆地,递归栈深度为网格大小)。
