(基于java) 算法笔记 —— 双指针算法

双指针算法的学习,简单笔记

1、算法解释

双指针的存在

  • 主要用于遍历数组,两个指针指向不同的元素,协同完成任务
  • 若两个指针指向同一数组、方向相同、不会相交,也称为滑动窗口

2、两数和问题

① LeetCode 167 两数之和 Ⅱ - 输入有序数组

解题思路

  • 给定的数组已经按照升序排列
  • 设定双指针,分别指向头和尾,令其所指的数字相加,判断是否等于目标
  • 若等于则返回;和大于目标则后指针向前移;和小于目标则前指针向后移
  • 注: 前指针需小于后指针

Java解答

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int size = numbers.length;
        int l_p = 0, r_p = size - 1;
        int sum = 0;
        while(l_p < r_p){
            sum = numbers[l_p] + numbers[r_p];
            if(sum == target){
                return new int[]{l_p+1, r_p+1};
            } else if(sum > target){
                r_p--;
            } else if(sum < target){
                l_p++;
            }
        }

        return null;
    }
}

3、归并两个有序数组

① LeetCode 88 合并两个有序数组

解题思路

  • 给定的两个数组都是升序排好
  • 由于不是存入新的数组,而是将数组2合并到数组1中,且数组1已经扩容
  • 设定两个指针,分别指向两个数组的末尾,比较所指的值大小
  • 将大的值放入数组1末尾
  • 已取过值的数组指针向前移动

Java解答

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m - 1, p2 = n - 1;
        int tail = m + n - 1;
        int cur;
        while (p1 >= 0 || p2 >= 0) {
            if (p1 == -1) {
                cur = nums2[p2--];
            } else if (p2 == -1) {
                cur = nums1[p1--];
            } else if (nums1[p1] > nums2[p2]) {
                cur = nums1[p1--];
            } else {
                cur = nums2[p2--];
            }
            nums1[tail--] = cur;
        }
    }
}

4、快慢指针问题

① LeetCode 142 环形链表 Ⅱ

解题思路——快慢指针(Floyd判圈法)

  • 设定两个指针,均从链表头部开始
  • 慢指针每次向后移动一个位置;快指针每次向后移动两个位置
  • 快指针可以走到尽头则没有环路;快指针可以无限走下去则有环路
  • 若存在环路,则快慢指针一定会相遇
  • 当快慢指针第一次相遇时,将快指针移动到链表开头,步长设为1
  • 当快慢指针再次相遇时的节点即为环路节点

Java解答

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                fast = head;
                while(fast!=slow){
                    fast=fast.next;
                    slow=slow.next;
                }
                return fast;
            } 
        }
        return null;
    }
}

5、滑动窗口问题

① LeetCode 76 最小覆盖字串

解题思路

  • 设定双指针作为窗口的左右边界,均指向给定字符串
  • 先统计目标字符串中各字符的出现次数
  • 固定左指针不动,移动右指针
  • 若右指针指向的字符,属于目标字符串,则判断该字符在窗口中出现的次数和在目标字符串中的次数
    • 若窗口中次数小于目标字符串中次数,则符合字符的计数加1,且窗口中该字符出现次数加1,右指针后移
    • 若不小于,则仅将窗口中该字符出现次数加1(可以多,不能少),右指针后移
  • 判断符合字符的计数是否等于目标字符串的长度
    • 若不等,则重复上面
    • 若相等,说明现在窗口中已经完全包括了目标字符串,考虑移动左指针
    • 若左指针指向的字符,属于目标字符串,则判断该字符在窗口中出现的次数和在目标字符串中的次数是否相等
      • 若相等,则符合字符的计数减1,然后会退出循环
      • 若不等,说明窗口中该字符次数比目标字符串中该字符次数多,将窗口该字符出现次数减1后,左指针继续右移
  • 右指针到达尽头后,停止循环,判断是否包含

举例图解
(基于java) 算法笔记 —— 双指针算法_第1张图片
(基于java) 算法笔记 —— 双指针算法_第2张图片
在这里插入图片描述
(基于java) 算法笔记 —— 双指针算法_第3张图片
Java解答

class Solution {
    public String minWindow(String s, String t) {
        int sLen = s.length();
        int tLen = t.length();
        if(sLen == 0 || tLen == 0 || sLen < tLen) return "";

        char[] charArrayS = s.toCharArray();
        char[] charArrayT = t.toCharArray();

        int[] winFreq = new int[128];
        int[] tFreq = new int[128];
        for(char c : charArrayT){
            tFreq[c]++;
        }

        int distance = 0;
        int minLen = sLen + 1;
        int begin = 0;

        int left = 0;
        int right = 0;
        // [left, right)
        while (right < sLen) {    // 窗口右边界到尽头
            if (tFreq[charArrayS[right]] == 0){
                right++;
                continue;
            }

            if (winFreq[charArrayS[right]] < tFreq[charArrayS[right]]) {
                distance++;
            }
            winFreq[charArrayS[right]]++;
            right++;

            while (distance == tLen) {
                if (right - left < minLen) {
                    minLen = right - left;
                    begin = left;
                }

                if (tFreq[charArrayS[left]] == 0) {
                    left++;
                    continue;
                }

                if (winFreq[charArrayS[left]] == tFreq[charArrayS[left]]) {
                    distance--;
                }
                winFreq[charArrayS[left]]--;
                left++;
            }
        }

        if (minLen == sLen + 1){
            return "";
        }
        return s.substring(begin, begin + minLen);
    }
}

6、练习

① LeetCode 633 平方数之和

解题思路

  • 先将目标值求根,作为右指针的边界,左指针则从0开始
  • 左指针平方加上右指针平方,与目标值比较
    • 若平方和大,则右指针减1;若平方和小,则左指针加1

Java解答

class Solution {
    public boolean judgeSquareSum(int c) {
        long left = 0;
        long right = (long) Math.sqrt(c);
        while(left <= right){
            long sum = left * left + right * right;
            if(sum == c){
                return true;
            } else if(sum > c){
                right--;
            } else if(sum < c){
                left++;
            }
        }
        return false;
    }
}

② LeetCode 680 验证回文字符串 Ⅱ

解题思路

  • 将字符串转换为字符数组
  • 设定左右两个指针,分别指向数组首尾
  • 判断左右指针所指的是否相同
    • 若相同,则左指针后移,右指针前移
    • 若不同,则移动其中一个(即删除一个字符),根据是否相同来返回真/假(仅一次删除机会)

Java解答

class Solution {
    public boolean validPalindrome(String s) {
        int i = 0, j = s.length() - 1;
        char[] chars = s.toCharArray();
        while (i < j && chars[i] == chars[j]) {
            i++;
            j--;
        }
        // 至此有两种情况:
        // ① i>=j s本身就是回文串
        // ② i
        //   要么删s[i],判断s[i+1,j]是否是回文串
        //   要么删s[j],判断s[i,j-1]是否是回文串
        return i >= j || isPalindrome(chars, i + 1, j) || isPalindrome(chars, i, j - 1);
    }

    private boolean isPalindrome(char[] chars, int i, int j) {
        while(i<j && chars[i]==chars[j]) {
            i++;
            j--;
        }
        return i>=j;
    }
}

③ LeetCode 524 通过删除字母匹配到字典里最长单词

解题思路

  • 先判断字典中的单词是否能由目标字符串获得
    • 主要通过String.indexOf(char, int);
  • 对于能获得的字典单词,通过判断各单词的长度
    • 对于相同长度单词,通过String.compareTo(String);判断各单词的字典顺序

Java解答

class Solution {
    public String findLongestWord(String s, List<String> dictionary) {
        String res = "";
        for(String str : dictionary){
            if(isSub(s,str)){
                // compareTo()顺序比较两个字符串的单个字符的ASCII码,不相同则返回该两个字符的ASCII差值
                if(str.length() > res.length() || (str.length() == res.length() && str.compareTo(res) < 0)){
                    res = str;
                }
            }
        }
        return res;
    }

    boolean isSub(String target, String s){
        int index = -1;
        for(int i = 0; i < s.length(); i++){
            // 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
            index = target.indexOf(s.charAt(i),index+1);
            if(index == -1){
                return false;
            }
        }
        return true;
    }
}

你可能感兴趣的:(算法学习,指针,算法,leetcode)