双指针经典题型~
167. 两数之和 II - 输入有序数组
因为是递增的数组 所以当左右指针相加时,大于目标值则左指针向右,小于则右指针向左
/*
双指针
因为是递增的数组 所以当左右指针相加时,大于目标值则左指针向右,反之
*/
public static int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
int[] res = new int[2];
while(left < right){
if (numbers[left] + numbers[right] > target){
// 右指针太大 往左移
right--;
} else if (numbers[left] + numbers[right] < target){
left++;
} else {
res[0] = left + 1;
res[1] = right + 1;
break;
};
};
return res;
}
633. 平方数之和
public static boolean judgeSquareSum(int c) {
// 从[0, sqrt(c)] 数组中去找
int left = 0, right = (int)Math.sqrt(c);
while (left <= right){
int a = left * left + right * right;
if (a > c){
right--;
} else if (a < c) {
left++;
} else {
return true;
}
}
return false;
}
二题同样都是递增的数组 注意一下第二题是可以左右相等即可。
总结:1、排好序的数组 2、求的是数组中2个数直接的相加关系
88. 合并两个有序数组
逆向指针
思路:将nums1和nums2都从后向前对比,每次都放入num1的后面,直到2个nums都遍历完
当只有一个nums遍历完后,则通过判断单独再将另一个nums遍历完
解法一:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// 从后方开始比较 谁的大 谁放后面
// 不需要管数组是增还是降 因为都是按照一种规律排序
int left = m - 1, right = n - 1, mergeIdx = m + n - 1;
// 将2往1放 用2循环到底 1本身就在里面 不需要动
while (right >= 0){
if (left < 0){ // 说明1结束了 但2还没全部放进去 有剩余 将2全部放进去
nums1[mergeIdx--] = nums2[right--];
} else if (nums2[right] > nums1[left]){ // 组2大于组1,则将组2的数放在后面
nums1[mergeIdx--] = nums2[right--];
} else {
nums1[mergeIdx--] = nums1[left--];
}
}
}
}
解法二:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// 思路:将组1和组2的每个数对比后放入组1
int left = m - 1, right = n - 1;
int cur = m + n - 1;
while (right >= 0 || left >=0){
if (left == -1) { // 当其中一个数组遍历完后则将另一个数组也单独遍历完
nums1[cur--] = nums2[right--];
} else if (right == -1){
nums1[cur--] = nums1[left--];
} else if (nums1[left] > nums2[right]) {
nums1[cur--] = nums1[left--];
} else {
nums1[cur--] = nums2[right--];
}
}
}
}
反转类型
345. 反转字符串中的元音字母
// 定义元音的集合
private final static HashSet<Character> vowels = new HashSet<>(
Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
public String reverseVowels(String s) {
// 字符串->字符数组
char[] arr = s.toCharArray();
int left = 0, right = arr.length - 1;
// 指针运动:非元音或者交换
while (left < right){
char ci = arr[left];
char cj = arr[right];
if (!vowels.contains(ci)){ // 左非元音
left++;
} else if (!vowels.contains(cj)){ // 右非元音
right--;
} else { // 两个都是元音 交换后同时运动
arr[left++] = cj;
arr[right--] = ci;
}
}
return new String(arr);
}
- 时间复杂度为 O(N):只需要遍历所有元素一次
- 空间复杂度 O(1):只需要使用两个额外变量
学习的点:1、实现指针到自己的元音就停下,相互交换后就又开始找自个的下一个元音
680. 验证回文字符串 Ⅱ
class Solution {
public boolean validPalindrome(String s) {
int left = 0, right = s.length() - 1;
while(left < right){
if (s.charAt(left) != s.charAt(right)){
return isPalindrome(s, left, right-1) || isPalindrome(s, left+1, right);
}
left++;
right--;
}
return true;
}
public boolean isPalindrome(String s, int left, int right){
while(left < right){
if (s.charAt(left++) != s.charAt(right--)){
return false;
}
}
return true;
}
}
修改后:
public boolean validPalindrome(String s) {
for (int left = 0, right = s.length() - 1; left < right; left++, right--){
if (s.charAt(left) != s.charAt(right)){
return isPalindrome(s, left, right-1) || isPalindrome(s, left+1, right);
}
}
return true;
}
1、判断字符,而不需要内部交换,因此用charAt就行,不用变成数组通过索引取值
2、使用2个方法来求解,第二个方法可以调用2次来分别模拟 不同的情况
思路:将nums1和nums2都从后向前对比,每次都放入num1的后面,直到2个nums都遍历完
当只有一个nums遍历完后,则通过判断单独再将另一个nums遍历完
解法一:
解法二:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// 思路:将组1和组2的每个数对比后放入组1
int left = m - 1, right = n - 1;
int cur = m + n - 1;
while (right >= 0 || left >=0){
if (left == -1) { // 当其中一个数组遍历完后则将另一个数组也单独遍历完
nums1[cur--] = nums2[right--];
} else if (right == -1){
nums1[cur--] = nums1[left--];
} else if (nums1[left] > nums2[right]) {
nums1[cur--] = nums1[left--];
} else {
nums1[cur--] = nums2[right--];
}
}
}
}
快慢指针
使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇(一个追另一个)。
141. 环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
// 双指针 一个走一步 一个走两步 一定会遇到
if (head == null){
return false;
}
ListNode l1 = head, l2 = head.next;
while(l1 != null && l2 != null && l2.next != null){ // 说明怎么样都不会出现空结点
if (l1 == l2){
return true;
}
l1 = l1.next;
l2 = l2.next.next;
}
// 出现空结点 没有环
return false;
}
}
总结:1、环状的话说可以一直next下去不会为空 ,非环状则会遇到空 2、一个指针比另一个指针快,那么环状就一定会遇到
524. 通过删除字母匹配到字典里最长单词
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
String longestWord = ""; // 标记最长的子字符串用于返回
for (String target : dictionary){
int l1 = longestWord.length(), l2 = target.length();
// 将最长子字符串和最小字典序直接放到开始判断
if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)){
continue;
}
// 获取子字符串
if (isSubstr(s, target)){
longestWord = target;
}
}
return longestWord;
}
// 判断是否是子序列的方法
private boolean isSubstr(String s, String target) {
int left = s.length() - 1, right = target.length() - 1;
while (right >= 0){ // 子字符串往主字符串对比 如果主遍历完子还没有结束则不是子字符串
if (left < 0){
return false;
}
if (target.charAt(right) == s.charAt(left)){
right--;
left--;
} else {
left--;
}
}
return true;
}
}
通过双指针判断字典中的每个字符串是不是s的子序列,取最大的那个
private boolean isSubstr(String s, String target) {
int i = 0, j = 0;
while (i < s.length() && j < target.length()) {
if (s.charAt(i) == target.charAt(j)) {
j++;
}
i++;
}
return j == target.length();
}