双指针 & 贪心算法六题通关:从回文串到跳跃游戏(Python + C++)
双指针和贪心算法是笔试面试中常见的两类技巧。双指针常用于数组、字符串的线性扫描;贪心则用于最优子结构问题。本文整理6道经典题目,每道题包含:题目描述、解题思路、图解(文本示意)、Python代码、C++代码、复杂度分析。
📌 题目清单
| 题号 | 题目 | 核心考点 |
|---|---|---|
| 125 | 验证回文串 | 双指针 + 字符判断 |
| 680 | 验证回文串 II | 双指针 + 一次删除机会 |
| 455 | 分发饼干 | 贪心(最小胃口分配最小饼干) |
| 122 | 买卖股票的最佳时机 II | 贪心(累加正利润) |
| 55 | 跳跃游戏 | 贪心(维护最远可达) |
| 45 | 跳跃游戏 II | BFS / 贪心(最少步数) |
1. 验证回文串(LeetCode 125)
题目描述
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,忽略字母的大小写。
示例 :
输入:"A man, a plan, a canal: Panama" → 输出:true
输入:"race a car" → 输出:false
解题思路
- 使用双指针
left和right分别指向字符串开头和结尾。 - 跳过非字母数字字符,然后比较两个字符(统一转为小写)。
- 如果遇到不相等,返回 false;否则移动指针继续。
图解
s = "A man, a plan, a canal: Panama"
left=0 'A', right=30 'a' → 都转小写 'a' == 'a' → 移动
left=1 ' ', 跳过 → left=2 'm', right=29 'm' → 相等
... 直到 left>=right → true
Python代码
python
def isPalindrome(s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
# 跳过非字母数字字符
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
C++代码
cpp
class Solution {
public:
bool isPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right && !isalnum(s[left])) ++left;
while (left < right && !isalnum(s[right])) --right;
if (tolower(s[left]) != tolower(s[right])) return false;
++left;
--right;
}
return true;
}
};
复杂度分析
- 时间复杂度:O(n),每个字符最多被访问一次。
- 空间复杂度:O(1),仅使用常数个变量。
2. 验证回文串 II(LeetCode 680)
题目描述
给定一个非空字符串,判断最多删除一个字符后,能否成为回文串。
示例 :
输入:"aba" → 输出:true
输入:"abca" → 输出:true(删除 'b' 或 'c' 后得到 "aca" 或 "aba")
解题思路
- 使用双指针从两端向中间比较。
- 当遇到字符不相等时,分别尝试删除左边字符 (即判断
s[left+1:right+1])或删除右边字符 (即判断s[left:right])是否为回文。 - 如果任一为真,则返回 true;否则 false。
图解
s = "abca"
left=0 'a', right=3 'a' 相等 → left=1, right=2
left=1 'b', right=2 'c' 不相等
尝试删除b: 检查 s[2:3] = "c" 是回文 → true
或删除c: 检查 s[1:2] = "b" 是回文 → true
Python代码
python
def validPalindrome(s: str) -> bool:
def is_palindrome(l, r):
while l < r:
if s[l] != s[r]:
return False
l += 1
r -= 1
return True
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return is_palindrome(left + 1, right) or is_palindrome(left, right - 1)
left += 1
right -= 1
return True
C++代码
cpp
class Solution {
public:
bool validPalindrome(string s) {
auto isPal = [&](int l, int r) {
while (l < r) {
if (s[l] != s[r]) return false;
++l; --r;
}
return true;
};
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left] != s[right]) {
return isPal(left + 1, right) || isPal(left, right - 1);
}
++left; --right;
}
return true;
}
};
复杂度分析
- 时间复杂度:O(n),每个子串检查最多 O(n),总 O(n)。
- 空间复杂度:O(1)。
3. 分发饼干(LeetCode 455)
题目描述
假设你是一位家长,想要给你的孩子们一些小饼干。每个孩子 i 有一个胃口值 g[i],每块饼干 j 有一个尺寸 s[j]。如果 s[j] >= g[i],可以将饼干分配给孩子,孩子得到满足。每个孩子最多一块饼干。求最多能满足多少个孩子。
示例 :
输入:g = [1,2,3], s = [1,1] → 输出:1
输入:g = [1,2], s = [1,2,3] → 输出:2
解题思路(贪心)
- 将孩子胃口和饼干尺寸都排序。
- 使用双指针(或两个索引),尽可能用最小的满足胃口的饼干去满足胃口最小的孩子。
- 如果当前饼干满足当前孩子,则计数加1,移动两个指针;否则,只移动饼干指针找更大的饼干。
图解
g = [1,2,3], s = [1,1] 排序后相同
i=0(胃口1), j=0(饼干1): 满足 → count=1, i=1, j=1
i=1(胃口2), j=1(饼干1): 不满足 → j=2 越界结束 → count=1
Python代码
python
def findContentChildren(g, s):
g.sort()
s.sort()
i = j = 0
while i < len(g) and j < len(s):
if s[j] >= g[i]:
i += 1
j += 1
return i
C++代码
cpp
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int i = 0, j = 0;
while (i < g.size() && j < s.size()) {
if (s[j] >= g[i]) ++i;
++j;
}
return i;
}
};
复杂度分析
- 时间复杂度:O(m log m + n log n),排序开销。
- 空间复杂度:O(log m + log n) 或 O(1),取决于排序实现。
4. 买卖股票的最佳时机 II(LeetCode 122)
题目描述
给定一个数组 prices,其中 prices[i] 表示第 i 天的股票价格。你可以多次买卖股票(必须先卖出才能再次买入),求最大利润。
示例 :
输入:[7,1,5,3,6,4] → 输出:7
解释:在第2天买入(1),第3天卖出(5)利润4;第4天买入(3),第5天卖出(6)利润3;总利润7。
解题思路(贪心)
- 因为可以多次交易,只需累加所有上升段的差价。
- 遍历数组,如果
prices[i] > prices[i-1],则加上差值。
图解
prices: 7,1,5,3,6,4
差: -6, +4, -2, +3, -2
累加所有正数: 4+3=7
Python代码
python
def maxProfit(prices):
profit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
profit += prices[i] - prices[i-1]
return profit
C++代码
cpp
class Solution {
public:
int maxProfit(vector<int>& prices) {
int profit = 0;
for (int i = 1; i < prices.size(); ++i) {
if (prices[i] > prices[i-1]) {
profit += prices[i] - prices[i-1];
}
}
return profit;
}
};
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
5. 跳跃游戏(LeetCode 55)
题目描述
给定一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
示例 :
输入:[2,3,1,1,4] → 输出:true
输入:[3,2,1,0,4] → 输出:false
解题思路(贪心)
- 维护最远可达位置
maxReach。 - 遍历数组,如果当前位置
i超过maxReach,则无法到达。 - 否则更新
maxReach = max(maxReach, i + nums[i])。 - 如果
maxReach >= n-1,返回 true。
图解
nums = [2,3,1,1,4]
i=0: maxReach=2
i=1: maxReach=max(2,1+3)=4 >= 4 → true
Python代码
python
def canJump(nums):
max_reach = 0
n = len(nums)
for i in range(n):
if i > max_reach:
return False
max_reach = max(max_reach, i + nums[i])
if max_reach >= n - 1:
return True
return True
C++代码
cpp
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxReach = 0;
int n = nums.size();
for (int i = 0; i < n; ++i) {
if (i > maxReach) return false;
maxReach = max(maxReach, i + nums[i]);
if (maxReach >= n - 1) return true;
}
return true;
}
};
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
6. 跳跃游戏 II(LeetCode 45)
题目描述
给定一个非负整数数组,你最初位于第一个位置。每个元素代表在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达最后一个位置。假设你总是可以到达最后一个位置。
示例 :
输入:[2,3,1,1,4] → 输出:2(跳第一步到下标1,再跳3步到末尾)
解题思路(贪心 / BFS)
- 维护当前跳跃能到达的最远边界
end,以及下一步能到达的最远位置maxReach。 - 遍历数组(不包括最后一个元素),每步更新
maxReach。 - 当
i == end时,说明需要再跳一次,跳跃次数加1,并更新end = maxReach。
图解
nums = [2,3,1,1,4]
i=0: maxReach=2, i==end(0) → jumps=1, end=2
i=1: maxReach=max(2,1+3)=4
i=2: i==end(2) → jumps=2, end=4 → 结束
结果 jumps=2
Python代码
python
def jump(nums):
n = len(nums)
if n == 1:
return 0
jumps = 0
end = 0
max_reach = 0
for i in range(n - 1):
max_reach = max(max_reach, i + nums[i])
if i == end:
jumps += 1
end = max_reach
if end >= n - 1:
break
return jumps
C++代码
cpp
class Solution {
public:
int jump(vector<int>& nums) {
int n = nums.size();
if (n == 1) return 0;
int jumps = 0, end = 0, maxReach = 0;
for (int i = 0; i < n - 1; ++i) {
maxReach = max(maxReach, i + nums[i]);
if (i == end) {
++jumps;
end = maxReach;
if (end >= n - 1) break;
}
}
return jumps;
}
};
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
🎯 总结
| 题目 | 核心技巧 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 125. 验证回文串 | 双指针跳过非字母数字 | O(n) | O(1) |
| 680. 验证回文串 II | 双指针 + 一次删除尝试 | O(n) | O(1) |
| 455. 分发饼干 | 排序 + 双指针贪心 | O(m log m + n log n) | O(1) |
| 122. 买卖股票最佳时机 II | 累加所有上升差 | O(n) | O(1) |
| 55. 跳跃游戏 | 维护最远可达位置 | O(n) | O(1) |
| 45. 跳跃游戏 II | 贪心 + 记录边界 | O(n) | O(1) |
双指针常用于有序序列的线性比较;贪心则要求每一步局部最优能推出全局最优。这两类题目多画图、手动模拟,很容易掌握规律。