欢迎来到dream_ready的博客,相信您对博主首页也很感兴趣o (ˉ▽ˉ;)
学生邮箱白嫖/免费安装JetBrains全家桶(IDEA/pycharm等) —— 保姆级教程
目录
双指针算法解析
1、力扣283.移动零
2、力扣1089复写零
3、力扣202快乐数
4、力扣11盛水最多的容器
5、力扣611有效三角形的个数
6、力扣179 查找总价为目标值的两个商品/ 原剑指Offer和为s的两个数字
7、力扣15三数之和
8、力扣18四数之和
双指针是一种思想,而不是说真的就是定义了两个指针,它和语言没有关系,比如C++,Java,Python等都可以使用双指针算法解题,而且是一种非常常见的算法
本篇博客适合所有语言学者阅读,因为算法是思想,每个题目除超详细的算法解析外后面还附赠了Java代码来供参考
常见的双指针有两种形式,一种是左右指针,一种是快慢指针
左右指针
快慢指针
题目链接:283.移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums =[0,1,0,3,12]
输出:[1,3,12,0,0]
示例 2:
输入: nums =[0]
输出:[0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
算法思路:
快排的思想:数组划分区间 - 数组分两块
在本题中,我们可以用一个 cur 指针来扫描整个数组,另一个 dest 指针用来记录非零数序列的最后一个位置。
根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。
在cur遍历期间,使 [0, dest] 的元素全部都是非零元素, [dest + 1, cur - 1] 的元素全是零, 这两部分属于处理过的区间,存放处理过的元素,单论处理过的区间的话,是符合题中结果的,而 [cur, n-1] 是待处理的区间,里面存放还未处理的元素
算法流程:
Java算法代码:
class Solution
{
public void moveZeroes(int[] nums)
{
for(int cur = 0, dest = -1; cur < nums.length; cur++)
if(nums[cur] != 0) // 仅需处理⾮零元素
{
dest++; // dest 先向后移动⼀位
// 交换
int tmp = nums[cur];
nums[cur] = nums[dest];
nums[dest] = tmp;
}
}
}
题目链接:力扣1089复写零
题目描述:
给你一个长度固定的整数数组 arr
,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。
注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。
示例 1:
输入:arr = [1,0,2,3,0,4,5,0] 输出:[1,0,0,2,3,0,0,4] 解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]
示例 2:
输入:arr = [1,2,3] 输出:[1,2,3] 解释:调用函数后,输入的数组将被修改为:[1,2,3]
提示:
1 <= arr.length <= 104
0 <= arr[i] <= 9
算法思路:
原地复写 + 双指针
如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆 盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两 步:
i. 先找到最后⼀个复写的数;
ii. 然后从后向前进⾏复写操作
值得注意如何找最后一个复写的数,咱可以这样想,就先按另外开辟数组的方法,一个指针原数组用,另一个指针新开辟的数组用,把新开辟的数组完成复写后,原数组的指向不就是咱需要的最后一个复写的数么,当然,此题不能另外开辟空间,咱可以用这种思想,不复写只移动来找到那个复写的数
算法流程:
Java算法代码:
class Solution
{
public void duplicateZeros(int[] arr)
{
int cur = 0, dest = -1, n = arr.length;
// 1. 先找到最后⼀个需要复写的数
while(cur < n)
{
if(arr[cur] == 0) dest += 2;
else dest += 1;
if(dest >= n - 1) break;
cur++;
}
// 2. 处理⼀下边界情况
if(dest == n)
{
arr[n - 1] = 0;
cur--; dest -= 2;
}
// 3. 从后向前完成复写操作
while(cur >= 0)
{
if(arr[cur] != 0) arr[dest--] = arr[cur--];
else
{
arr[dest--] = 0;
arr[dest--] = 0;
cur--;
}
}
}
}
题目链接:力扣202快乐数
题目描述:
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
提示:
1 <= n <= 231 - 1
题目分析:
简单证明:
算法思路:
补充知识:如何求一个数n每个位置上的数字的平方和
当然,用递归求也行
Java算法代码:
class Solution
{
public int bitSum(int n) // 返回 n 这个数每⼀位上的平⽅和
{
int sum = 0;
while(n != 0)
{
int t = n % 10;
sum += t * t;
n /= 10;
}
return sum;
}
public boolean isHappy(int n)
{
int slow = n, fast = bitSum(n);
while(slow != fast)
{
slow = bitSum(slow);
fast = bitSum(bitSum(fast));
}
return slow == 1;
}
}
题目链接:力扣11盛水最多的容器
题目描述:
定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1] 输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
算法思路:
Java算法代码:
class Solution
{
public int maxArea(int[] height)
{
int left = 0, right = height.length - 1, ret = 0;
while(left < right)
{
int v = Math.min(height[left], height[right]) * (right - left);
ret = Math.max(ret, v);
if(height[left] < height[right]) left++;
else right--;
}
return ret;
}
}
题目链接:力扣611有效三角形的个数
题目描述:
给定一个包含非负整数的数组 nums
,返回其中可以组成三角形三条边的三元组个数。
示例 1:
输入: nums = [2,2,3,4] 输出: 3 解释:有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3
示例 2:
输入: nums = [4,2,3,4] 输出: 4
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
算法思路:
排序+双指针
Java算法代码:
class Solution
{
public int triangleNumber(int[] nums)
{
// 1. 优化:排序
Arrays.sort(nums);
// 2. 利⽤双指针解决问题
int ret = 0, n = nums.length;
for(int i = n - 1; i >= 2; i--) // 先固定最⼤的数
{
// 利⽤双指针快速统计出符合要求的三元组的个数
int left = 0, right = i - 1;
while(left < right)
{
if(nums[left] + nums[right] > nums[i])
{
ret += right - left;
right--;
}
else
{
left++;
}
}
}
return ret;
}
}
题目链接:力扣179查找总价值为目标值的两个商品
题目描述:
购物车内的商品价格按照升序记录于数组 price
。请在购物车中找到两个商品的价格总和刚好是 target
。若存在多种情况,返回任一结果即可。
示例 1:
输入:price = [3, 9, 12, 15], target = 18 输出:[3,15] 或者 [15,3]
示例 2:
输入:price = [8, 21, 27, 34, 52, 66], target = 61 输出:[27,34] 或者 [34,27]
提示:
1 <= price.length <= 10^5
1 <= price[i] <= 10^6
1 <= target <= 2*10^6
算法思路:
注意到本题是升序的数组,因此可以⽤「对撞指针」优化时间复杂度。
算法流程:
Java算法代码:
class Solution
{
public int[] twoSum(int[] nums, int target)
{
int left = 0, right = nums.length - 1;
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum > target) right--;
else if(sum < target) left++;
else return new int[] {nums[left], nums[right]};
}
// 照顾编译器
return new int[]{0};
}
}
题目链接:力扣15三数之和
题目描述:
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
算法思路:
每次固定一个位置,其余位置用双指针求双数之和的方法求,注意边界情况复杂
Java算法代码:
class Solution
{
public List> threeSum(int[] nums)
{
List> ret = new ArrayList<>();
// 1. 排序
Arrays.sort(nums);
// 2. 利⽤双指针解决问题
int n = nums.length;
for(int i = 0; i < n; ) // 固定数 a
{
if(nums[i] > 0) break; // ⼩优化
int left = i + 1, right = n - 1, target = -nums[i];
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum > target) right--;
else if(sum < target) left++;
else
{
// nums[i] nums[left] num[right]
ret.add(new ArrayList(Arrays.asList(nums[i],
nums[left], nums[right])));
left++; right--; // 缩⼩区间继续寻找
// 去重:left right
while(left < right && nums[left] == nums[left - 1]) left++;
while(left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重:i
i++;
while(i < n && nums[i] == nums[i - 1]) i++;
}
return ret;
}
}
题目链接:力扣18四数之和
题目描述:
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和 d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8 输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
算法思路:
排序 + 双指针
Java算法代码:
class Solution
{
public List> fourSum(int[] nums, int target)
{
List> ret = new ArrayList<>();
// 1. 排序
Arrays.sort(nums);
// 2. 利⽤双指针解决问题
int n = nums.length;
for(int i = 0; i < n; ) // 固定数 a
{
// 三数之和
for(int j = i + 1; j < n; ) // 固定数 b
{
// 双指针
int left = j + 1, right = n - 1;
long aim = (long)target - nums[i] - nums[j];
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum > aim) right--;
else if(sum < aim) left++;
else
{
ret.add(Arrays.asList(nums[i], nums[j], nums[left++],
nums[right--]));
// 去重⼀
while(left < right && nums[left] == nums[left - 1])
left++;
while(left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重⼆
j++;
while(j < n && nums[j] == nums[j - 1]) j++;
}
// 去重三
i++;
while(i < n && nums[i] == nums[i - 1]) i++;
}
return ret;
}
}
前路漫漫,愿星光与您相伴!