Skip to content

Latest commit

 

History

History
811 lines (675 loc) · 23 KB

双指针.md

File metadata and controls

811 lines (675 loc) · 23 KB

[TOC]

剑指 Offer 57. 和为s的两个数字

剑指 Offer 57. 和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
 

限制:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6

双指针

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while (i < j) {
            int s = nums[i] + nums[j];
            if (s < target) {
                i++;
            } else if (s > target) {
                j--;
            } else {
                return new int[] {nums[i], nums[j]};
            }
        }
        return new int[0];
    }
}

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

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

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
 

限制:

1 <= target <= 10^5

双指针

left 到 right之间的和sum

  • sum < target, right++
  • sum > target, left++
  • sum == target, 添加结果,left++,因为从left开始只有一个符号条件。
class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> res = new ArrayList<>();
        for (int left = 1, right = 2; left < right;) {
            int sum = (left + right) * (right - left + 1) / 2;
            if (sum == target) {
                int[] tmp = new int[right - left + 1];
                for (int i = left; i <= right; i++) {
                    tmp[i - left] = i;
                }
                res.add(tmp);
                left++;
            } else if (sum < target) {
                right++;
            } else {
                left++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。
 

提示:

1 <= nums.length <= 50000
1 <= nums[i] <= 10000

双指针

class Solution {
    public int[] exchange(int[] nums) {
        int i = 0, j = nums.length - 1;
        int length = j;
        while (i <= j) {
            while (i <= length && nums[i] % 2 == 1) {
                i++;
            }
            while (j >= 0 && nums[j] % 2 == 0) {
                j--;
            }
            if (i >= j) {
                break;
            }
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        return nums;
    }
}

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

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

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

二分查找

x + y = target, 先设numbers[i] = x,二分查找target - y ,找到即成功

class Solution {
public:
    int binsearch(vector<int>& numbers, int target)
    {
        int left = 0, right = numbers.size() - 1;
        int ans = -1;
        while(left <= right)
        {
            int mid = left + ((right - left) >> 1);
            if(numbers[mid] < target)
                left = mid + 1;
            else if(numbers[mid] > target)
                right = mid - 1;
            else
            {
                ans = mid;
                break;
            }
        }
        return ans;
    }
    vector<int> twoSum(vector<int>& numbers, int target) {
        vector<int> ans;
        for(int i = 0; i < numbers.size(); ++i)
        {
            int y = target - numbers[i];
            int j = binsearch(numbers, y);
            if(j != -1 && i != j)
            {
                ans.push_back(min(i+1, j+1));
                ans.push_back(max(i+1, j+1));
                break;
            }
        }
        return ans;
    }
};

双指针

由于数组是有序的,只需要双指针即可。一个left首指针,一个right尾指针,如果left + right 值大于 target 则 right左移动, 否则 left 右移。

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

5. 最长回文子串

5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。 示例 2:

输入: "cbbd" 输出: "bb"

双指针

遍历每一个索引,以这个索引为中心,利用“回文串”中心对称的特点,往两边扩散,看最多能扩散多远。

回文串在长度为奇数和偶数的时候,“回文中心”的形式是不一样的。

  • 奇数回文串的“中心”是一个具体的字符,例如:回文串 "aba" 的中心是字符 "b";

  • 偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 "abba" 的中心是两个 "b" 中间的那个“空隙”。

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int maxLen = 1;
        String res = s.substring(0, 1);
        // 中心位置枚举到 len - 2 即可
        for (int i = 0; i < len - 1; i++) {
            String oddStr = centerSpread(s, i, i);
            String evenStr = centerSpread(s, i, i + 1);
            String maxLenStr = oddStr.length() > evenStr.length() ? oddStr : evenStr;
            if (maxLenStr.length() > maxLen) {
                maxLen = maxLenStr.length();
                res = maxLenStr;
            }
        }
        return res;
    }
    private String centerSpread(String s, int left, int right) {
        // left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
        // right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
        int len = s.length();
        int i = left;
        int j = right;
        while (i >= 0 && j < len) {
            if (s.charAt(i) == s.charAt(j)) {
                i--;
                j++;
            } else {
                break;
            }
        }
        // 这里要小心,跳出 while 循环时,恰好满足 s.charAt(i) != s.charAt(j),因此不能取 i,不能取 j
        return s.substring(i + 1, j);
    }
}

11. 盛最多水的容器

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

双指针

首尾各一个指针,每次向内移动短板,可以得到最大值。

class Solution {
    public int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while (i < j) {
            res = height[i] < height[j] ? Math.max(res, (j - i) * height[i++]) : Math.max(res, (j - i) * height[j--]);
        }
        return res;
    }
}

容器盛水问题

容器盛水问题


双指针

import java.util.*;


public class Solution {
    /**
     * max water
     * @param arr int整型一维数组 the array
     * @return long长整型
     */
    public long maxWater (int[] arr) {
        // write code here
        int i = 0, j = arr.length - 1;
        long sum = 0;
        // 找出左右边界的最小值作为水位高度
        int mark = Math.min(arr[i], arr[j]);
        while (i < j) {
            // 如果左边较低,则左边界向右遍历, 否则右边界向左移动
            if (arr[i] < arr[j]) {
                i++;
                // 如果当前标尺小于水位,则水量累加
                if (arr[i] < mark) {
                     sum += mark - arr[i];
                } else {
                    mark = Math.min(arr[i], arr[j]);
                }
            } else {
                j--;
                if (arr[j] < mark) {
                    sum += mark - arr[j];
                } else {
                    mark = Math.min(arr[i], arr[j]);
                }
            }
        }
        return sum;
    }
}

633. 平方数之和

633. 平方数之和

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c。

示例1:

输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5


示例2:

输入: 3
输出: False

双指针

首指针left = 0, 尾指针 right = sqrt(c)的整数,然后从两头遍历,left * left + right * right < c 则left++;否则,right--。防止越界特殊处理一下。

class Solution {
    public boolean judgeSquareSum(int c) {
        if (c == 0) {
            return true;
        }
        int left = 0;
        int right = (int)Math.sqrt(c);
        double eps = 1e-8;
        boolean flag = false;
        while (left <= right) {
            double tmp = c - left * left;
            if (Math.abs(tmp / right - right) < eps) {
                flag = true;
                break;
            } else if ((right - tmp / right) > eps) {
                right--;
            } else {
                left++;
            }
        }
        return flag;
    }
}

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

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

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

示例 1:

输入: "hello" 输出: "holle" 示例 2:

输入: "leetcode" 输出: "leotcede" 说明: 元音字母不包含字母"y"。

双指针

双指针,一个在头,一个在尾,两个都是元音就交换,遍历一遍即可

class Solution {
    boolean flag(char c) {
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
            return true;
        }
        return false;
    }
    public String reverseVowels(String s) {
        int left = 0, right = s.length() - 1;
        char[] chars = s.toCharArray();
        while (left < right) {
            boolean flag1 = flag(chars[left]), flag2 = flag(chars[right]);
            if (flag1 && flag2) {
                char c = chars[left];
                chars[left] = chars[right];
                chars[right] = c;
                left++;
                right--;
            } else if (flag1 && !flag2) {
                right--;
            } else if (!flag1 && flag2){
                left++;
            } else {
                right--;
                left++;
            }
        }
        return String.valueOf(chars);
    }
}

双指针优化

判断的次数少一点

class Solution {
public:
    string reverseVowels(string s) {
        
        unordered_map<char,int> map{{ 'a', 1}, {'e', 1}, {'i', 1},{'o', 1},{'u', 1},
                                    {'A', 1},{'E', 1},{'I', 1},{'O', 1},{'U', 1}};
        int i =0;
        int j = s.length()-1;
        char temp;
        while(i<j)
        {
            while(i<j&&map[s[i]]!=1) i++;
            while(i<j&&map[s[j]]!=1) j--;
            if(i<j)
            {
                temp = s[i];
                s[i] = s[j];
                s[j] = temp;
            }
            i++;j--;
        }
        return s;
    }
};

680. 验证回文字符串 Ⅱ

680. 验证回文字符串 Ⅱ

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1:

输入: "aba" 输出: True 示例 2:

输入: "abca" 输出: True 解释: 你可以删除c字符。 注意:

字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。

双指针

如果字符串的起始字符和结束字符相同(即 s[0]==s[s.length-1]),则内部字符是否为回文(s[1], s[2], ..., s[s.length - 2])将唯一地确定整个字符串是否为回文。

算法: 假设我们想知道 s[i],s[i+1],...,s[j] 是否形成回文。如果 i >= j,就结束判断。如果 s[i]=s[j],那么我们可以取 i++;j--。否则,回文必须是 s[i+1], s[i+2], ..., s[j] 或 s[i], s[i+1], ..., s[j-1] 这两种情况。 见代码中注释

class Solution {
    public boolean validPalindrome(String s) {
        int left = 0, right = s.length() - 1;
        int delIndex = -1;
        while (left <= right) {
            if (s.charAt(left) == s.charAt(right)) {
                left++;
                right--;
            } else {
                // 第一次不等,先删除左边的,left++
                if (delIndex == -1) { 
                    delIndex = left;
                    left++;
                // 第三次不等,直接返回false
                } else if (delIndex == s.length()) {
                    return false;
                // 第二次不等,left返回到第一次不等的位置,right也回到对应的位置,删除右边的
                } else {
                    left = delIndex;
                    right = s.length() - left - 2;
                    delIndex = s.length();
                }
            }
        }
        return true;
    }
}

88. 合并两个有序数组

88. 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 示例:

输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]

直接插入

从nums1中倒序找到nums2[i]的插入位置,然后插入,遍历所有nums2[i],即合并成功。 因为两个数组都是递增的,倒序找更快。不要小看这种方法,也是非常快的,而且空间复杂度为O(1).

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = 0, j = m - 1;
        int n1 = j;
        while (i < n) {
            while (j >= 0) {
                if (nums1[j] > nums2[i]) {
                    j--;
                } else {
                    break;
                }
            }
            for (int k = n1; k >= j + 1; k--) {
                nums1[k + 1] = nums1[k];
            }
            nums1[j + 1] = nums2[i];
            i++;
            n1++;
            j = n1;
        }
    }
}

双指针

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m - 1, p2 = n - 1, p = m + n - 1;
        while (p1 >= 0 && p2 >= 0) {
            nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];
        }
        // nums2可能有剩余,将剩余的添加到nums1中。
        for (int i = 0; i <= p2; i++) {
            nums1[i] = nums2[i];
        }
    }
}

15. 三数之和

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

双指针

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            // 后面的元素肯定不满足要求,都是大于0的
            if (nums[i] > 0) {
                return res;
            }
            // 遇到重复的跳过
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int cur = nums[i];
            int left = i + 1, right = len - 1;
            while (left < right) {
                int tmp = cur + nums[left] + nums[right];
                if (tmp == 0) {
                    List<Integer> list = Arrays.asList(cur, nums[left], nums[right]);
                    res.add(list);
                    // 剔除重复的
                    while (left < right && nums[left + 1] == nums[left]) {
                        ++left;
                    }
                    while (left < right && nums[right - 1] == nums[right]) {
                        --right;
                    }
                    // 同时移动left、right
                    ++left;
                    --right;
                } else if (tmp < 0) {
                    // 说明left太小,left前进
                    ++left;
                } else {
                    --right;
                }
            }
        }
        return res;
    }
}

75. 颜色分类

75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意: 不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2] 进阶:

一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 你能想出一个仅使用常数空间的一趟扫描算法吗?

排序双指针

class Solution {
    public void sortColors(int[] nums) {
        int p1 = 0, p2 = nums.length - 1, cur = 0;
        while(cur <= p2){
            if(nums[cur] == 1){
                cur++;
            }else if(nums[cur] == 0){
                int tmp = nums[p1];
                nums[p1++] = nums[cur];
                nums[cur++] = tmp;
            }else {
                int tmp = nums[p2];
                nums[p2--] = nums[cur];
                nums[cur] = tmp;
            }
        }
    }
}

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

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

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

示例 1:

输入: s = "abpcplea", d = ["ale","apple","monkey","plea"]

输出: "apple" 示例 2:

输入: s = "abpcplea", d = ["a","b","c"]

输出: "a" 说明:

所有输入的字符串只包含小写字母。 字典的大小不会超过 1000。 所有输入的字符串长度不会超过 1000。

暴力匹配1

先对d按首字母大小排序,然后直接匹配,找到匹配成功字符串最长的那个。

class Solution {
public:
    string findLongestWord(string s, vector<string>& d) {
        sort(d.begin(), d.end());
        int max = 0, ans = -1, i = 0;
        for(i = 0; i < d.size(); ++i)
        {
            int j = 0, n = d[i].length(), k = 0, flag = 0;
            while(j < s.length() && k < n)
            {
                if(s[j] == d[i][k])
                    j++, k++;
                else
                    j++;
                if(k == n)
                    flag = 1;
            }
            if(flag && max < n)
                max = n, ans = i;
        }
        return ans != -1 ? d[ans] : "";
    }
};

暴力匹配2

不用排序,每次记录匹配成功的字符串,跟下一次比较取最长且首字母最小的那个。

class Solution {
    boolean isSubString(String s1, String s2) {
        int j = 0;
        for (int i = 0; i < s1.length() && j < s2.length(); i++) {
            if (s1.charAt(i) == s2.charAt(j)) {
                j++;
            }
        }
        return j == s2.length();
    }
    public String findLongestWord(String s, List<String> d) {
        String max = "";
        for (String s1 : d) {
            if (isSubString(s, s1) && (max.length() < s1.length() || (max.length() == s1.length() && max.compareTo(s1) > 0))) {
                max = s1;
            }
        }
        return max;
    }
}