当我们谈到leetcode,作为一个程序员来说,各位小伙伴应该也并不陌生把。这个网站成立于2011年,到如今也有10个年头了,在这个平台里面不仅包含各大互联网大厂的面试题目,还有一些针对各个算法的一些习题,供国内外的程序员来进行刷题,提升自己的能力。
现在基本上也是刷题必备,是一个准备面试非常有用工具。
其实对于很多小伙伴来说,都有这种疑惑。这也是千人千面,对于基础掌握比较好的同学来说,可以直接讲题库从头开始刷,也未尝不可。对于我这种基础比较薄弱,而且想不断的提升自己的能力的小伙伴来说,分章节来刷,是一个不错的选择。优势也很明显,能够反复练习这个内容,总结出属于自己的刷题套路,培养出自己的解题思维,这是对于每一个程序员是可望而不可即,是一个慢慢积累,打怪的过程。小曾陪大家一起开始刷LeetCode之旅!
双指针从广义上来说,是指用两个变量在线性结构上遍历而解决的问题。狭义上说,
首先判断是用左右指针还是快慢指针,这个可以根据数据结构来进行选择,如果给出的是一个数组,那么可以考虑用左右指针。左右指针在数组中实际是指两个索引值,⼀般初始化为 left = 0, right =nums.length - 1 。
滑动窗口算法框架:
int left = 0, right = 0;
while (right < s.size()) {`
#增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩⼩窗⼝
window.remove(s[left]);
left++;
}
}
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例:输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
题目分析:不含有重复字符
思路:用到了滑动窗口的算法,滑动窗⼝的右边界不断的右移,只要没有重复的字符,就持续向右扩大窗口边界。⼀旦出现了重复字符,就需要缩小左边界,直到重复的字符移出了左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度度,最终最大的值就是题目中的所求。
如何判断重复字符呢?
可以用常用的数据结构–哈希集合(HashSet、set) ,在左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往哈希集合中添加一个字符。
算法代码实现:
python版本
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
#哈希集合,记录字符是否出现过
occ = set()
n = len(s)
rk , ans = -1,0
for i in range(n):
#左指针向右移动一个,并删除一个字符
if i !=0:
occ.remove(s[i-1])
while rk+1 < n and s[rk+1] not in occ:
#不断移动右指针
occ.add(s[rk+1])
rk +=1
#第i个到rk 极长的无重复字符子串
ans = max(ans , rk - i + 1)
return ans
java版本
class Solution {
public int lengthOfLongestSubstring(String s) {
//hash 集合
Set occ = new HashSet();
int n = s.length();
int rk = -1,ans =0;
for (int i =0 ; i
分析时间复杂度:O(N) [左指针和右指针分别会遍历整个字符串一次]
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
题目分析:首先要清楚容纳水量的公式 ,水量 = 左右指针指向的数字的较小值 *左右指针的距离
题目思路:方案一、暴力法,两个for循环,然后选择最大容纳水量的即可。方案二、双指针法,设置双指针 i , j分别位于容器壁两端,左右指针对应的高度h[i],h[j],水槽的实际高度是由两个板子的短板决定的,每次选择两板高度h[i],h[j]中的短板,向中间修改一格,并且更新面积最大值result,直至i==j时返回最大值。
复杂度分析:
时间复杂度:O(N),双指针总计最多遍历整个数组一次
空间复杂度:O(1)
python版本
class Solution:
def maxArea(self, height: List[int]) -> int:
num = len(height)
x,y = 0,num -1
max_num =0
while xheight[y]:
y -=1
else:
x +=1
## 返回最大容纳水量
return max_num
java代码
class Solution {
public int maxArea(int[] height) {
int i = 0 ,j = height.length-1 ;
int maxA = 0;
while(i
给你一个包含 n 个整数的数组 nums判断 nums 中是否存在三个元素 a,b,c 使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]
分析:主要采用排序 + 双指针的方法
本题的难点在于如何去除重复解。
算法流程:
1、特判,对于数组长度 n,如果数组为 null 或者数组长度小于 3,返回 []。
2、对数组进行排序。
3、遍历排序后数组:
复杂度分析
时间复杂度:O(n^2)
数组排序 O(NlogN),遍历数组 O(n),双指针遍历 O(n),总体 O(NlogN)+O(n)∗O(n),O(n^2)
空间复杂度:O(1)
Python3 代码
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
#对数组进行排序
nums.sort()
#对于数组长度 n,如果数组为 null 或者数组长度小于 3,返回 []
if len(nums)<3 or nums[0]>0:
return []
i , n = 0,len(nums)
ans = []
#遍历排序
for i in range(n-2):
#对于重复元素:跳过,避免出现重复解
if i>0 and nums[i] == nums[i-1]: # 去重
continue
l ,r = i+1 ,n-1
#令左指针 L=i+1,右指针 R=n-1,当 L
java代码
class Solution {
public List> threeSum(int[] nums) {
List> lists = new ArrayList<>();
//排序
Arrays.sort(nums);
//双指针
int len = nums.length;
for(int i =0;i0) return lists;
if(i>0 && nums[i]==nums[i-1]) continue;
int curr =nums[i];
int L=i+1 ,R =len -1;
while(Llist = new ArrayList<>();
list.add(curr);
list.add(nums[L]);
list.add(nums[R]);
lists.add(list);
while(L
跟三数之和相比,就是多加了一个for循环,具体请参考下面代码
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
nums.sort()
ans = []
if not nums or len(nums)<4:
return []
for i in range(n-3):
if i>0 and nums[i]== nums[i-1]:
continue
for j in range(i+1,n-2):
if j>i+1 and nums[j]== nums[j-1]:
continue
l,r = j+1,n-1
while l
题目描述:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素
题目分析:去除重复项
思路:使用双指针的思想进行遍历
具体步骤:
1、一个指针 i 进行数组遍历,另外一个指针 j 指向有效数组的最后一个位置。
2、只有当 i 所指向的值和 j 不一致(不重复),才将 i 的值添加到 j 的下一位置。
python 3
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
n = len(nums)
j = 0
for i in range(n):
if nums[i] !=nums[j]:
j+=1
nums[j]=nums[i]
return j+1
java版本
class Solution {
public int removeDuplicates(int[] nums) {
int n =nums.length;
int j = 0;
for(int i=0;i
时间复杂度:O(N)
空间复杂度:O(1)
遇到相似题目有通用解法
为了让解法更具有一般性,我们将原问题的「最多保留 1 位」修改为「最多保留 k 位」。
对于此类问题,我们应该进行如下考虑:
由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留。
对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留。
举个,我们令 k=1,假设有样例:[3,3,3,3,4,4,4,5,5,5]
1. 设定变量 idx,指向待插入位置。idx 初始值为 0,目标数组为 []
2. 首先我们先让第 1 位直接保留(性质 1)。idx 变为 1,目标数组为 [3]
3. 继续往后遍历,能够保留的前提是与 idx 的前面 1 位元素不同(性质 2),因此我们会跳过剩余的 3,将第一个 4 追加进去。idx
变为 2,目标数组为 [3,4]
4.继续这个过程,跳过剩余的 4,将第一个 5 追加进去。idx 变为 3,目标数组为 [3,4,5]
5、当整个数组被扫描完,最终我们得到了目标数组 [3,4,5] 和 答案 idx 为 3。
注:这里借鉴了leetcode中宫水三叶的思路,感兴趣可以去看一看。
总结:「通用解法」是一种针对「数据有序,相同元素最多保留 k 位」问题更加本质的解法,该解法是从性质出发提炼的,利用了「数组有序 & 保留逻辑」两大主要性质
题目描述:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下
可以接 6 个单位的雨水(蓝色部分表示雨水)
**题目分析:**可以直接暴力方法求解,对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。
**关键:**找到左右两边最高的墙(max_left,max_right)
直观理解:计算存水量,主要分三种情况
java版本:用两个for循环
public int trap(int[] height) {
int ans = 0;
int size = height.length;
for (int i = 1; i < size - 1; i++) {
int max_left = 0, max_right = 0;
//找到左边最高
for (int j = i; j >= 0; j--) {
max_left = Math.max(max_left, height[j]);
}
// 找到右边最高
for (int j = i; j < size; j++) {
max_right = Math.max(max_right, height[j]);
}
// 通过找到两端最小的减去当前高度计算存水量
ans += Math.min(max_left, max_right) - height[i];
}
// 返回总的存水量
return ans;
}
上述的方法是非常直观,也很容易想到,但是时间复杂度是比较高的O(N^2)
采用双指针的优点:将时间复杂度降低到O(N),只遍历一遍。
具体思路:维护两个指针 left 和 right, 以及两个变量 leftMax 和 rightMax,
初始时 left = 0 =0 =0, right = n − 1 =n-1 =n−1, leftMax = 0 =0 =0, rightMax = 0 =0 =0 。 指针
left 只会向右移动, 指针 right 只会向左移动,在移动指针的过
程中维护两个变量 left M a x \operatorname{left} M a x leftMax 和 rightMax \operatorname{rightMax} rightMax 的值。
python3 代码
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
left, right = 0, len(height) - 1
leftMax = rightMax = 0
# 开始从左向右开始遍历
while left < right:
# 思路与上面一致,分别计算左、右边最大值
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])
if height[left] < height[right]:
ans += leftMax - height[left]
left += 1
else:
ans += rightMax - height[right]
right -= 1
return ans
java版本
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
后续题目会继续添加,同时我目前也在看labuladong 的算法小抄和halfrost的LeetCode刷题手册,对我受益匪浅,现在推荐给大家,大家一起刷题,共勉!
同时需要这些资料的小伙伴们,可以关注一下“研行笔录”微信公众号,回复leetcode即可,还不快来领取leetcode刷题大礼包。