双指针leetcode十五题
双指针:一般代表用多个指针指向不同得位置,通过指针得移动来解决问题得变动
1.两数之和2-输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
左右两个指针比较值的大小为之移动,有点像快速排序的思想
class Solution {
public int[] twoSum(int[] numbers, int target) {
int i=0,j=numbers.length-1;
while(i!=j){
if(numbers[i]+numbers[j]>target){
j--;
}else if(numbers[i]+numbers[j]<target){
i++;
}else{
return new int[]{i+1,j+1};
}
}
return null;
}
}
2.返回倒数第 k 个节点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
输入: 1->2->3->4->5 和 k = 2
输出: 4
class Solution {
public int kthToLast(ListNode head, int k) {
ListNode p = head;
for(int i=0;i<k;i++){
p = p.next;
}
while(p != null){
p = p.next;
head = head.next;
}
return head.val;
}
}
392. 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
示例 1:
s = "abc"
, t = "ahbgdc"
返回 true
.
示例 2:
s = "axc"
, t = "ahbgdc"
返回 false
.
后续挑战 :
如果有大量输入的 S,称作S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
## 思路
双指针,短字符串一个,长字符串一个,如果相等都往下移,不等,长的移动,如果短的超过长度说明都存在于长的里面,相反如果长的超过长度且短的没有那么匹配不上
class Solution {
public boolean isSubsequence(String s, String t) {
int a=0;int b=0;
while(a<s.length()&&b<t.length()){
if(s.charAt(a)!=t.charAt(b)){
b++;
}else{
a++;
b++;
}
}
return a==s.length();
}
}
214. 最短回文串
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
示例 1:
输入: "aacecaaa"
输出: "aaacecaaa"
示例 2:
输入: "abcd"
输出: "dcbabcd"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upNjtV0u-1598867720089)(C:\Users\李川\AppData\Roaming\Typora\typora-user-images\image-20200829181353796.png)]
思路: 指针左右位移找到字符串里最长的回文串,然后把回文串以外右边的字母逆序放在开头,这样就构成了最短回文串,如果没有找到的话,就以第一个字符以后逆序存放在开头
class Solution {
public String shortestPalindrome(String s) {
//利用双指针 找到第一个字符开始的最大长度的回文串
int left = 0;
char[] c=s.toCharArray();
int right = c.length - 1;
//记录一下最大回文串的长度
int index = right;
while (left < right)
{
if (c[left] == c[right])
{
left++;
right--;
}
else
{
left = 0;
index = index -1;
right = index;
}
}
String ans = "";
//倒着把剩下的字母加入原来的字符串
for (int i = c.length - 1; i > index; i--)
{
ans += c[i];
}
return ans+s;
}
}
532. 数组中的K-diff数对
难度简单90收藏分享切换为英文关注反馈
给定一个整数数组和一个整数 k, 你需要在数组里找到不同的 k-diff 数对。这里将 k-diff 数对定义为一个整数对 (i, j), 其中 i 和 j 都是数组中的数字,且两数之差的绝对值是 k.
示例 1:
输入: [3, 1, 4, 1, 5], k = 2
输出: 2
解释: 数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。
尽管数组中有两个1,但我们只应返回不同的数对的数量。
示例 2:
输入:[1, 2, 3, 4, 5], k = 1
输出: 4
解释: 数组中有四个 1-diff 数对, (1, 2), (2, 3), (3, 4) 和 (4, 5)。
示例 3:
输入: [1, 3, 1, 5, 4], k = 0
输出: 1
解释: 数组中只有一个 0-diff 数对,(1, 1)。
思路:存住元素的出现次数,如果次数大于一,k=0的时候那么说明这个元素肯定可以减出0,count++,如果k!=0,那么就去找当前key+k的value的值存不存在,存在就count+1
class Solution {
public int findPairs(int[] nums, int k) {
int count = 0 ;
Map<Integer,Integer> map = new HashMap<>() ;
if(k<0){
return count ;
}
for(int i=0;i<nums.length;i++){
map.putIfAbsent(nums[i],0) ;
map.put(nums[i],map.get(nums[i])+1) ;
}
for(int i:map.keySet()){
if(k==0){
if(map.get(i)>1){
count++ ;
}
}
else if(k!=0){
if(map.containsKey(i+k)){
count++ ;
}
}
}
return count ;
}
}
思路: 双指针移动,先排序,如果左指针起点,右指针减去左指针还大于k那么已经没有能满足的了,左指针右移,相反,如果右指针减去之后小于k,就要右移右指针,找能不能等的,
这里要计算这种情况[1,1,1,1,1] k=0,全是1的情况,这样我们计算完第一个满足的时候我们要记录前一个满足的值,如果下次相等直接跳过
解法二: 双指针
public int findPairs(int[] nums, int k) {
if(k < 0) return 0;
Arrays.sort(nums);
int start = 0, count = 0, prev = 0x7fffffff;
for (int i = 1; i < nums.length; i++) {
if(nums[i] - nums[start] > k || prev == nums[start]) {
if (++start != i) i--;
}else if (nums[i] - nums[start] == k) {
prev = nums[start++];
count++;
}
}
return count;
}
925. 长按键入
你的朋友正在使用键盘输入他的名字 name
。偶尔,在键入字符 c
时,按键可能会被长按,而字符可能被输入 1 次或多次。
你将会检查键盘输入的字符 typed
。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True
。
示例 1:
输入:name = "alex", typed = "aaleex"
输出:true
解释:'alex' 中的 'a' 和 'e' 被长按。
示例 2:
输入:name = "saeed", typed = "ssaaedd"
输出:false
解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。
示例 3:
输入:name = "leelee", typed = "lleeelee"
输出:true
示例 4:
输入:name = "laiden", typed = "laiden"
输出:true
解释:长按名字中的字符并不是必要的。
思路: 判断第一个字符串的指针和第二个字符串,如果都相等字母,那就都后移,如果不等的话,就判断,如果第二个字符串这个字母是第一次的不重复的直接return false,如果是重复的,那么就判断它和前一个字母是否相等,
class Solution {
public static boolean isLongPressedName(String name, String typed) {
if (name.length()==0 &&typed.length()==0)
return true;
else if (name.length()==0 ||typed.length()==0)
return false;
int i=0,j=0;
while (i<name.length()||j<typed.length()){
if (i<name.length()&&j<typed.length()&&name.charAt(i)==typed.charAt(j)){
i++;
j++;
}
else {
if (j>0 && j<typed.length()&&typed.charAt(j)==typed.charAt(j-1))
j++;
else
return false;
}
}
return i==name.length() && j==typed.length();
}
}
763. 划分字母区间
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
-
记录每个字母在S中最后出现的下标
-
从下标0开始尝试分段,左边界left是0
-
“同一个字母必须在同一片段中”,所以初始right是该段首字母最后出现的位置
-
动态遍历left到right,如果有字符最后出现的下标大于right,更新right
-
遍历完成后,一个分段就生成了。计数区间长度,更新left = right + 1,转3继续。
主要步骤: 就是记录了字母最后出现的下标,然后再遍历过程,我们从头到最后出现的下标这一段里面有没有字母最后出现的下标超出这个右边界,有就更新,没有就把这段字符串存住,然后再以这个字符串为头往下开始遍历
class Solution {
public List<Integer> partitionLabels(String S) {
if (null == S || 0 == S.length()) {
return null;
}
int len = S.length();
// 做映射表,记录每个字母最后出现的位置
int[] ma = new int[26];
for (int i = 0; i < len; ++i) {
ma[S.charAt(i) - 'a'] = i;
}
List<Integer> ans = new ArrayList<>();
int left = 0;
while (left < len) {
char curLeft = S.charAt(left);
// 最小右边界
int right = ma[curLeft - 'a'];
for (int i = left + 1; i < right; ++i) {
// 枚举当前分段中的字符,更新右边界
if (ma[S.charAt(i) - 'a'] > right) {
right = ma[S.charAt(i) - 'a'];
}
}
// 至此,一个新的片段生成了
ans.add(right - left + 1);
// 分析下一段
left = right + 1;
}
return ans;
}
}
19. 删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode slow = head;
ListNode fast = head;
for(int i=0;i<n;i++){
fast = fast.next;
}
while(fast!=null){
pre = pre.next;
slow = slow.next;
fast = fast.next;
}
pre.next = slow.next;
return dummy.next;
}
}
剑指 Offer 48. 最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路: 利用map存放元素出现的下标,然后一个左指针i代表左区间,j代表右区间,只要i,j范围内无重复我们就j++,如果重复了,马上把左边的i更新到重复字母的下标处,这样就能保证在i,j以内是无重复的
然后我们每次用j-i来保存最大的无重复的长度
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int i = -1, res = 0;
for(int j = 0; j < s.length(); j++) {
if(dic.containsKey(s.charAt(j)))
i = Math.max(i, dic.get(s.charAt(j))); // 更新左指针 i
dic.put(s.charAt(j), j); // 哈希表记录
res = Math.max(res, j - i); // 更新结果
}
return res;
}
}
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums
和 一个目标值 target
。找出 nums
中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
1.首先进行数组排序,时间复杂度 O(nlogn)O(nlogn)
2.在数组 nums 中,进行遍历,每遍历一个值利用其下标i,形成一个固定值 nums[i]
3.再使用前指针指向 start = i + 1 处,后指针指向 end = nums.length - 1 处,也就是结尾处
4.根据 sum = nums[i] + nums[start] + nums[end] 的结果,判断 sum 与目标 target 的距离,如果更近则更新结果 ans
5.同时判断 sum 与 target 的大小关系,因为数组有序,如果 sum > target 则 end–,如果 sum < target 则 start++,如果 sum == target 则说明距离为 0 直接返回结果
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int ans = nums[0] + nums[1] + nums[2];
for(int i=0;i<nums.length;i++) {
int start = i+1, end = nums.length - 1;
while(start < end) {
int sum = nums[start] + nums[end] + nums[i];
if(Math.abs(target - sum) < Math.abs(target - ans))
ans = sum;
if(sum > target)
end--;
else if(sum < target)
start++;
else
return ans;
}
}
return ans;
}
}
287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
前后指针移动寻找相等元素
class Solution {
public int findDuplicate(int[] nums) {
Arrays.sort(nums);
int left=0;int right=1;
while(right<nums.length){
if(nums[left]!=nums[right]){
left++;
right++;
}else{
return nums[right];
}
}
return 0;
}
}
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 **s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。**如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
思路 : 两个指针,右指针在sum《target值得时候一直右移动,如果大于等于了,说明j到极限了,这时候记录一下j-i得数组长度,然后左指针再前移动一个元素,再判断sum是不是还大于等于target,是得话,记录长度,再左指针移动,直到小于,然后才能移动右指针,在上述过程中,会不断得记录大于等于s得最小长度,最后返回最小长度
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int lo = 0, hi = 0, sum = 0, min = Integer.MAX_VALUE;
while (hi < nums.length) {
sum += nums[hi++];
while (sum >= s) {
min = Math.min(min, hi - lo);
sum -= nums[lo++];
}
}
return min == Integer.MAX_VALUE ? 0 : min;
}
}
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
**说明:**你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
思路: 就是计算出底部边长乘以两边柱子最矮得值,这就是最大区域,然后我们要判断下一个只要移动矮柱子就行了,因为移动了长柱子,遇到得更长也没用,因为矮柱子取决了接水得高度,如果遇见更小得,那更惨了
public int maxArea(int[] height) {
int res = 0;
int i = 0;
int j = height.length - 1;
while (i < j) {
int area = (j - i) * Math.min(height[i], height[j]); //算出区域值
res = Math.max(res, area); //存最大得值
if (height[i] < height[j]) { //移动比较小得一根柱子,保留大柱子
i++;
} else {
j--;
}
}
return res;
}
86. 分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode minLink = new ListNode(0);//记录小值链表的头
ListNode minP = minLink;//对小表操作用的指针
ListNode maxnLink = new ListNode(0);//记录大值链表的头
ListNode maxP = maxnLink;//同理
while(head!=null){
if(head.val < x){//找到小的值
minP.next = head;//放入minLink中,操作指针后移一位
minP = head;
}else{
maxP.next = head;//放入maxLink中,操作指针后移一位
maxP = head;
}
head = head.next;
}
//遍历完成后记得后一段链表的最后节点指向null;
maxP.next = null;
//两段拼接
minP.next = maxnLink.next;
return minLink.next;
}
}
80. 删除排序数组中的重复项 II
给定一个排序数组,你需要在**原地**删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在**原地修改输入数组**并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**“引用”**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length < 3){ //长度小于三肯定满足
return nums.length;
}
int curPos = 1;
for (int i = 2; i < nums.length ; i++) { 循环数组
if(nums[i] != nums[curPos-1]){ //i指针是往右第一个不重复得元素
nums[++curPos] = nums[i]; //curPos是
}
}
return curPos+1;
}
}