🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
今天开始,算法专栏正式开始更新,欢迎订阅~~
目录
- 1. 概述
- 1.1 对撞指针
- 1.2 快慢指针
- 2. 移动零(难度:🟢1度)
- 3. 复写零(难度:🟡3度)
- 4. 快乐数(难度:🔵2度)
- 5. 盛水最多的容器(难度:🟡3度)
1. 概述
常见的双指针算法有两种:一种是对撞指针,一种是快慢指针.
1.1 对撞指针
对撞指针,也叫左右指针.一般用于顺序结构中.
- 对撞指针从两端向中间移动,一个指针从最右边开始,一个指针从最左边开始.
- 对撞指针的最终结果就是:
left == right
两个指针相遇或者left >= right
两个指针刚好错开,也可能在循环遍历的途中找到结果直接break掉.
1.2 快慢指针
又称为龟兔赛跑算法,基本思想就是在一个链表或者序列结构上定义两个移动速度不同的指针,一个走得快,一个走的慢,最常用的就是一个指针移动一位,另一个指针移动两位.
这种算法在解决环形结构的时候非常有用.如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想.
2. 移动零(难度:🟢1度)
OJ链接
- 题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
- 算法原理
本题最核心的原理就是:数组区间划分.
我们可以定义两个指针,一个是cur
,一个是dest
,dest
用来记录最后一个非零数字的位置,cur
用来遍历数组.在划分的时候,[0,dest]区间全部是非零元素,[dest+1,cur]区间中全部是0元素,[cur+1,array.length]区间是未处理的元素,这样就使用两个指针把这个数组分段为了三个区间. - 解决步骤
- 定义
cur = 0
定义dest = 0
,之后cur
向后移动. cur
移动到一个元素不为0的时候,交换dest
和cur
位置的元素.之后dest++
.cur
遇到一个为0的元素的时候,不做任何处理,cur
直接++.- 直到
cur
移动到数组的末尾,结束循环.
- 定义
- 代码编写
class Solution {public void moveZeroes(int[] nums) {int cur = 0;int dest = 0;while(cur < nums.length){if(nums[cur] == 0){cur++;}else{int tmp = nums[dest]; nums[dest] = nums[cur];nums[cur] = tmp;cur++;dest++;}}}
}
3. 复写零(难度:🟡3度)
OJ链接
- 题目描述
给你一个长度固定的整数数组 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]
- 算法原理
如果使用从前向后复写的思路来解决的话,这样就会使得0被复写的时候,后面一个未被复写的元素被覆盖掉.
所以我们采用从后向前复写的方法,但是如果从后向前复写的话,就必须最后一个被复写的对象.
找到最后一个被复写的对象,我们就可以采用从前向后模拟指针移动方式来找到. - 算法步骤
- 初始化
cur = 0
dest = 0
. - 找到最后一个复写的数字.
cur
遍历数组,当cur
遇到非0的数字的时候,dest
向后移动1位,当cur
遇到0的时候,cur
向后移动两位,直到dest
走到数组的末尾的时候,cur
停止遍历,此时cur
的位置就是最后一个要复写的数字.- 判断
dest
位置的情况,如果dest
位置在数组越界一个下标的位置的时候:我们就可以让n-1位置的值修改为0,之后dest-=2
,cur-=1
.之所以会出现越界的情况,是因为cur
最后一个遍历到的元可能是0,这就使得dest
元素向后移动了2个位置,导致数组越界. dest
从前向后移动,当cur
遇到非0的时候,dest
的位置就重写一次cur
位置的元素,如果遇到0,就重写两次0,直到dest
走到数组的首位.
- 初始化
- 代码编写
class Solution {public void duplicateZeros(int[] arr) {int cur = 0;int dest = -1;//找到最后一个复写的元素while(cur < arr.length){if(arr[cur] == 0){dest+=2;}else{dest++;}if(dest >= arr.length-1){break;}cur++;}//处理边界越界情况if(dest == arr.length){arr[arr.length-1] = 0;dest-=2;cur--;}//向前移动完成复写操作while(cur >= 0){if(arr[cur] != 0){arr[dest]=arr[cur];cur--;dest--;}else{arr[dest] = 0;dest--;arr[dest] = 0;dest--;cur--;}}}
}
4. 快乐数(难度:🔵2度)
OJ链接
- 题目描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
- 算法原理
为了⽅便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和」这⼀个操作记为x 操作
题⽬告诉我们,当我们不断重复x 操作的时候,计算⼀定会「死循环」,死的⽅式有两种:
▪ 情况⼀:⼀直在1 中死循环,即1 -> 1 -> 1 -> 1…
▪ 情况⼆:在历史的数据中死循环,但始终变不到1
由于上述两种情况只会出现⼀种,因此,只要我们能确定循环是在「情况⼀」中进⾏,还是在「情
况⼆」中进⾏,就能得到结果。
如果这道题不会出现死循环,这道题就会变得非常麻烦,难度至少到达4度,所以题目中规定一定会出现死循环其实是帮我们降低了难度.
这里我们使用使用快慢双指针算法,但是这里 的双指针并不是像上面数组的下标,更不是像链表中的两个引用对象,而是数字结果本身.
根据上述的题⽬分析,我们可以知道,当重复执⾏x 的时候,数据会陷⼊到⼀个「循环」之中。⽽「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的,也就是说他们总会
相遇在⼀个位置上。如果相遇位置的值是1 ,那么这个数⼀定是快乐数;如果相遇位置不是1的话,那么就不是快乐数
- 算法步骤
- 定义x函数,计算每次循环的结果.
- 定义
slow = n
.(第一个数字),定义fast = x(n)
.(第二个数字),之所以不定义0,是为了可以进得去循环. - 每次
slow
向后移动一个数字,fast
向后移动两个数字. - 直到两个指针相遇,即值相等.
- 判断相遇的位置是否等于1.
- 代码编写
class Solution {public int x(int n){int sum = 0;while (n != 0){sum += (n%10)*(n%10);n/=10;}return sum;}public boolean isHappy(int n) {int slow = n;int fast = x(n);while (slow != fast){slow = x(slow);fast = x(x(fast));}if (slow == 1){return true;}else{return false;}}
}
5. 盛水最多的容器(难度:🟡3度)
OJ链接
- 题目描述
给定一个长度为 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
- 算法原理
设两个指针left ,right 分别指向容器的左右两个端点,此时容器的容积:
v = (right - left) * min( height[right], height[left])
容器的左边界为height[left]
,右边界为height[right]
.
为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」。
如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:
◦ 容器的宽度⼀定变⼩。
◦ 由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的水面高度不确定,但是⼀定不会超
过右边的柱子高度,因此容器的容积可能会增大。
◦ 如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会
超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。
综上,我们只需要改变高度较小的一边即可.
由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以left++
跳过这个边界,继续去判断下⼀个左右边界。
当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到left
与right
相遇。期间产生的所有的容积里面的最大值,就是最终答案。 - 代码编写
class Solution {public int maxArea(int[] height) {int left = 0;int right = height.length-1;int max = 0;int v = 0; while (left <= right){v = Math.min(height[left],height[right])*(right-left);max = Math.max(v,max);if (height[left] < height[right]){left += 1;}else{right -= 1;}}return max;}
}