Leetcode记录库解题方法篇之二:双指针

简介

七、双指针

结论

一些很主观的东西,归根到底可能还是自己的实力不够:

1 167. 两数之和 II - 输入有序数组

题目

思路描述

  • 由于初始数组是有序的,所以我们可以使用双指针,左右搜索。
  • 如果当前的两个索引值之和小于target,我们就右移左索引
  • 如果大于target,我们就左移右索引
  • 相等我们就返回。

代码实现
java代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] ans = new int[2];
        int idx = 0, n = numbers.length - 1, tmp;
        while (idx < n) {
            tmp = numbers[idx] + numbers[n];
            if (tmp == target) {
                ans[0] = idx + 1;
                ans[1] = n + 1;
                return ans;
            } else if (tmp < target) idx++;
            else n--;
        }

        return ans;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 拓展延伸

2 633. 平方数之和

题目

思路描述

  • 由于初始数组是有序的,所以我们可以使用双指针,左右搜索。
  • 如果当前的两个索引值之和小于target,我们就右移左索引
  • 如果大于target,我们就左移右索引
  • 相等我们就返回。

代码实现
java代码:

class Solution {
    public boolean judgeSquareSum(int c) {
        long sqrtC = (int)Math.sqrt(c);
        long a = 0, b = sqrtC, tmp = 0;
        while (a <= b) {
            tmp = a * a + b * b;
            if (tmp == c) return true;
            else if (tmp < c) a++;
            else b--;
        }
        
        return false;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 拓展延伸

3 345. 反转字符串中的元音字母

题目

思路描述

  • 双指针前后扫
  • 虽然想到了可以用 set 把原因字符存起来,然后判断 s 中的字符是不是元音,这样就不会看起来这么一大片了,也不会进行多次判断,可能速度也会快点。但是实际操作起来发现速度没有这样快。
  • 时间复杂度O(1/2 * n),空间复杂度O(2n)

代码实现
java代码:

class Solution {
    public String reverseVowels(String s) {
        int h = 0, t = s.length() - 1;
        char tmp;
        char[] strArray = s.toCharArray();
        while(h < t){
            if(strArray[h] == 'a' || strArray[h] == 'e' || strArray[h] == 'i' || strArray[h] == 'o' || strArray[h] == 'u' ||
            strArray[h] == 'A' || strArray[h] == 'E' || strArray[h] == 'I' || strArray[h] == 'O' || strArray[h] == 'U'){
                if(strArray[t] == 'a' || strArray[t] == 'e' || strArray[t] == 'i' || strArray[t] == 'o' || strArray[t] == 'u' ||
                strArray[t] == 'A' || strArray[t] == 'E' || strArray[t] == 'I' || strArray[t] == 'O' || strArray[t] == 'U'){
                    tmp = strArray[h];
                    strArray[h] = strArray[t];
                    strArray[t] = tmp;
                    h++;
                    t--;
                } else {
                    t--;
                }
            } else {
                h++;
            }
        }

        return new String(strArray);
    }
}
  • 使用 set 判断是不是元音,速度没有上面的直观写法快。
class Solution {
    public String reverseVowels(String s) {
        HashSet<Character> set = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
        int h = 0, t = s.length() - 1;
        char tmp;
        char[] strArray = s.toCharArray();
        while(h < t){
            if(set.contains(strArray[h])){
                if(set.contains(strArray[t])){
                    tmp = strArray[h];
                    strArray[h] = strArray[t];
                    strArray[t] = tmp;
                    h++;
                    t--;
                } else {
                    t--;
                }
            } else {
                h++;
            }
        }

        return new String(strArray);
    }
}
  • 这样写也可以起到set的作用
class Solution {
    public String reverseVowels(String s) {
        String vowels = new String("aeiouAEIOU");
        int h = 0, t = s.length() - 1;
        char tmp;
        char[] strArray = s.toCharArray();
        while(h < t){
            if (vowels.indexOf(strArray[h]) > -1) {
                if (vowels.indexOf(strArray[t]) > -1) {
                    tmp = strArray[h];
                    strArray[h] = strArray[t];
                    strArray[t] = tmp;
                    h++;
                    t--;
                } else {
                    t--;
                }
            } else {
                h++;
            }
        }

        return new String(strArray);
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 难道就没有办法直接在原始的字符串上做操作吗?只能新建一个?

4 680. 验证回文字符串 Ⅱ

题目

思路描述

  • 双指针前后扫,如果没有遇到不相同的字符直接返回true;
  • 第一次遇到不同的字符,就头后移一位 || 尾前移一位,重新开始判断,再次遇到不一样的字符就返回false
  • 如果没有遇到就返回true。
  • 时间复杂度O(n),空间复杂度O(1)

代码实现
java代码:

class Solution {
    public boolean validPalindrome(String s) {
        return validPalindrome(s, 0, s.length() - 1, 1);
    }

    private boolean validPalindrome(String s, int h, int t, int del){
        while(h < t) {
            // System.out.print(s.charAt(h));System.out.println(s.charAt(t));
            if (s.charAt(h) == s.charAt(t)) {
                h++;
                t--;
            } else if (del == 1){
                del = 0;
                return validPalindrome(s, h + 1, t, del) || validPalindrome(s, h, t - 1, del);
            } else return false;
        }

        return true;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 注意事项

5 88. 合并两个有序数组

题目

思路描述

  • 先把大数组中的数据移到后半部分,然后在归并两个有序数组到大数组的前部。
  • 时间复杂度O(m + n),空间复杂度O(1)

代码实现
java代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = 0, i1 = n, i2 = 0, m2 = m - 1;
        int tmp;
        while (m2 > -1) {
            nums1[m2 + n] = nums1[m2--]; 
        }

        while(i1 < m + n && i2 < n) {
            if (nums1[i1] < nums2[i2]) {
                nums1[i++] = nums1[i1++];
            } else {
                nums1[i++] = nums2[i2++];
            }
        }

        while (i1 < m + n) {
            nums1[i++] = nums1[i1++];
        }

        while (i2 < n) {
            nums1[i++] = nums2[i2++];
        }
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 注意事项

6 141. 环形链表

题目

思路描述

  • 快慢指针
  • 时间复杂度O(m + n),空间复杂度O(1)

代码实现
java代码:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;

        ListNode fast = head.next, slow = head;
        while (fast != slow && fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        if (fast == slow) return true;
        else return false;
    }
}

注意事项

  1. 循环条件和循环体的判断,再循环条件中一定要把fast.next.next之前的两个fast和fast.next都要判断了。
        while (fast != slow && fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

拓展延伸

  1. 注意事项

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

题目

思路描述

  • 快慢指针
  • 时间复杂度O(m + n),空间复杂度O(1)

代码实现
java代码:

class Solution {
    public String findLongestWord(String s, List<String> dictionary) {
        int maxl = 0, i, j;
        String res = "";
        for (String d : dictionary) {
            i = 0;
            j = 0;
            while (i < s.length() && j < d.length()) {
                if (s.charAt(i) == d.charAt(j)) {
                    j++;
                }
                i++;
            }

            if (j == d.length()) {
                if (maxl < j || (maxl == j && res.compareTo(d) > 0)) {
                    maxl = j;
                    res = d;
                }
            }
        }

        return res;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. 注意事项

8 剑指 Offer 57 - II. 和为s的连续正数序列

题目

思路描述

  • 一旦涉及到求一个范围、一段连续的元素的问题,考虑使用双指针
  • 时间复杂度O(n),空间复杂度O(1)

代码实现
java代码:

class Solution {
    public int[][] findContinuousSequence(int target) {
        int halftarget = target >> 1;
        int l = 1, r = 1, sum = 0;
        
        ArrayList<int[]> ans = new ArrayList<>();

        while (l <= halftarget) {
            if (sum == target) {
                int[] tmp = new int[r - l];
                for (int i = l, j = 0; i < r; i++, j++) {
                    tmp[j] = i;
                }
                ans.add(tmp);
                sum -= l;
                l++;
            } else if (sum < target) {
                sum += r;
                r++;
            } else {
                sum -= l;
                l++;
            }
        }

        return ans.toArray(new int[ans.size()][]);
    }
}

注意事项

  1. 注意事项

拓展延伸
1.

// Integer 范型的 List 转化成 int[] 的方法
list1.stream().mapToInt(Integer::valueOf).toArray()

9 42. 接雨水

题目

思路描述

  • 单调栈
  • 时间复杂度O(n),空间复杂度O(n)

代码实现
java代码:

class Solution {
    public int trap(int[] height) {
        int ans = 0, n = height.length;
        Deque<Integer> s = new LinkedList<>(); // 注意!!!里面放的时索引!!!
        for (int i = 0; i < n; i++) {
            while (!s.isEmpty() && height[i] > height[s.peek()]) { // 如果当前要进栈的高度大于栈顶,那么就可能形成一个凹槽:高 低 高
                int low = s.pop(); // 凹槽的底部高度,我们下面称它为“低”
                if (s.isEmpty()) { // 如果栈中此时没有元素了,那么也就构不成凹槽了,只有:低 高
                    break; // 中断
                }
                int l = s.peek(); // 如果栈中此时还有元素,查看这个元素的高度
                if (height[l] == height[low]) { // 如果这个元素的高度和低一样高,那么说明此时的凹槽张这样:(未知)低低高
                    continue; // 我们提前开启下一次循环,寻找左边的高度
                } else { // 如果这个元素的高度比:低 高
                    int w = i - l - 1; // 那么我们计算这个凹槽的宽度
                    int h = Math.min(height[i], height[l]) -  height[low]; // 计算这个凹槽的高度:两个较矮的高边 - 凹槽的低
                    ans += w * h; // 计算容量,累加
                }
            }
            s.push(i); // 新高度入栈
        }

        return ans;
    }
}

思路描述

代码实现
java代码:

class Solution {
    public int trap(int[] height) {
        int l = 0, r = height.length - 1, ans = 0;
        int lmax = height[0], rmax = height[r];
        while (l < r) {
            lmax = Math.max(lmax, height[l]); // 更新左边的高边
            rmax = Math.max(rmax, height[r]); // 更新右边的高边
            if (height[l] < height[r]) { // 如果左边现在的高度小于右边,那么现在至少出现了这个样一个凹槽 lmax...height[l] ... height[r],并且height[r]也是一定高于lmax的(自己想想,或者模拟以下,这个不太好用文字说明)
                ans += lmax - height[l]; // 求这个凹槽能称多少水
                l++;
            } else {
                ans += rmax - height[r];
                r--;
            }
        }

        return ans;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. a

10 76. 最小覆盖子串

题目

思路描述

  • 双指针,滑动窗口,计数法
  • 时间复杂度O(n),空间复杂度O(1)

代码实现
java代码:

class Solution {
    public String minWindow(String s, String t) {
        int slen = s.length(), tlen = t.length();
        if (slen < tlen) {
            return "";
        }

        int[] need = new int[128]; // 桶,用来记录目标串t中各个字符的数量,表示我们需要找到多少对应的字符来覆盖目标串
        for (int i = 0; i < tlen; i++) {
            need[t.charAt(i)]++;
        }

        int l = 0, r = 0, cnt = tlen; // cnt 是我们需要找到的目标字符数量
        int min = slen + 1, start = 0, end = 0;
        while (r < slen) {
            while (cnt != 0 && r < slen) {
                if (need[s.charAt(r)] > 0) { // 如果字符串s当前的字符和目标中的的一致
                    cnt--; // 找到了一个覆盖字符,需要的字符减一
                }
                need[s.charAt(r)]--; // 对应的 需要字符 的桶中数量减一,即使是不需要的字符也会减一
                r++; // 右移右指针
            }

            while (l < r && cnt == 0) { // 移动左指针缩小滑动窗口的大小
                if (need[s.charAt(l)] < 0) { // 如果当前的字符不是覆盖字符中的一个,说明可以向右移动左指针
                    need[s.charAt(l)]++; // 窗口缩小,need中的字符也要对应变化
                    l++; // 右移指针
                } else { // 如果当前扫描到的字符是覆盖字符中的一个,说明此时在缩小窗口的话,就不能够再覆盖目标串了
                    if (min > r - l) { // 判断当前的窗口大小和之前记录的窗口大小关系
                        min = r - l;
                        start = l;
                        end = r;
                    }
                    need[s.charAt(l)]++; // 窗口变小,没有被窗口覆盖的字符增多
                    l++; // 左指针右移
                    cnt++; // 需要的字符个数 + 1
                    break; // 此时已经窗口中的字符已经覆盖不了目标串了,再遍历也没有用,不如重新右移右指针,遍历未搜索的s中的字符
                }
            }
        }

        return s.substring(start, end);
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. a

11 2024. 考试的最大困扰度

题目

思路描述
在这里插入图片描述

代码实现
java代码:

// 字符串的问题,想到了用动态规划(行不行呢?好像不行,还得分情况记录状态),但是忘了滑动窗口
// 滑动窗口离不了双指针

class Solution {
    public int maxConsecutiveAnswers(String answerKey, int k) {
        int len = answerKey.length();
        if (len < 2) return len; // 特殊情况判断

        return Math.max(getMax(answerKey, k, 'T'), getMax(answerKey, k, 'F')); // 可能是连续的 T 也可能是连续的 F
    }

    private int getMax(String answerKey, int k, char flag) {
        int l = 0, r = 0; // 双指针
        int max = 1, cnt = 0, len = answerKey.length();
        while (r < len) {
            cnt += answerKey.charAt(r) == flag ? 0 : 1; // 记录窗口内和目标字符 flag 不同的字符个数,使用 k 次修改机会对其进行修改
            while (cnt > k) { // 如果当前窗口内记录的和 flag 字符不同的字符个数大于了 k,此时需要缩小滑动窗口了,一直到 cnt == k 停止
                cnt -= answerKey.charAt(l++) == flag ? 0 : 1;
            }
            max = Math.max(max, r - l + 1); // 更新最大长度
            r++;
        }

        return max;
    }
}

注意事项

  1. 注意事项

拓展延伸

  1. a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值