双指针经典题型~

本文探讨了双指针算法在有序数组查找、平方数和、合并数组、字符串操作、回文判断、链表环检测、最长字典序等问题中的应用,展示如何利用递增和递减指针策略优化解决经典问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双指针经典题型~

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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值