双指针问题

双指针在解决数组和链表这一类题目的时候是一种很好用的方法,本文将会结合leetcode上的一些题目来总结下双指针这一解题的方法

有序数组的平方

给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。

示例 1:

输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]

示例 2:

输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:

  • 1 <= A.length <= 10000
  • -10000 <= A[i] <= 10000
  • A 已按非递减顺序排序。

题目中提到的A已经是排序完成的数组,但是在平方之后,负数会变成正数,此时的序列可能不是正确排序的序列,对此,我们可以使用双指针,左指针从负数序列往后遍历,右指针从正数最大往前遍历,比较左右指针指针的数的平方值

    /**
     * 【双指针解法】
     * O(n) n:n是数组长度
     * @param A
     * @return
     */
    public int[] sortedSquares4(int[] A) {
        int left = 0;
        int right = A.length - 1;
        int cur = A.length - 1;
        int[] b = new int[A.length];
        while(left <= right){
            if(Math.pow(A[left],2) <= Math.pow(A[right],2)){
                b[cur--] = (int)Math.pow(A[right],2);
                right--;
            }else{
                b[cur--] = (int)Math.pow(A[left],2);
                left++;
            }
        }
        return b;
    }

第二种方法就是将数组每项平方后再排序

    /**
     * 使用函数式编程一行搞定
       o(N*logN)
     * @param A
     * @return
     */
    public int[] sortedSquares3(int[] A) {
        return Arrays.stream(A).map(x -> x*x).sorted().toArray();
    }

合并问题

1.合并两个有序数组

给定两个有序整数数组 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]

这个题目和前一道题目其实是很相似的,这道题目涉及多了一个数组的复制,根本思想还是通过双指针去遍历数组,比较指针指向的数组元素的大小,实现一边遍历,一边排序和合并

    /**
     * 【双指针】
     * @param nums1
     * @param m
     * @param nums2
     * @param n
     */
    public void merge2(int[] nums1, int m, int[] nums2, int n) {
        //m:nums1.length
        //n:nums2.length
        int i = m - 1;
        int j = n - 1;
        int p = m+n-1;
        while(i >=0 && j >= 0){
            nums1[p--] = nums1[i] < nums2[j] ? nums2[j--] : nums1[i--];
        }
        System.arraycopy(nums2,0,nums1,0,j+1);
    }

第二种双指针的做法有些不完美,就是这种做法只适用于数组元素都大于等于0,这种做法的思路就是把num1中数组元素为0的元素替换为nums2中的元素,然后再排序

    /**
     * 【双指针】
     * 思路:把nums1中的0替换掉在排序,并不完美
     * @param nums1
     * @param m
     * @param nums2
     * @param n
     */
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //m:nums1.length
        //n:nums2.length
        int j = 0;
        int i = 0;
        while(j < n){
            if(nums1[i] == 0){
                nums1[i] = nums2[j];
                j++;
                i++;
            }else{
                i++;
            }
        }
        Arrays.sort(nums1);
    }

2.求两个数组的交集(1)

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]

说明:

  • 输出结果中的每个元素一定是唯一的。
  • 我们可以不考虑输出结果的顺序。

双指针思路:

  • 两数组排序
  • i指针指向nums1,j指针指向nums2,k指针指向相同元素
  • i ,j指针指向的元素进行比较,找出相同的元素
    /**
     * 【双指针】
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersection4(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int len1 = nums1.length;
        int len2 = nums2.length;
        //指向nums1
        int i = 0;
        //指向nums2
        int j = 0;
        //指向交集
        int k = 0;
        //存放相同元素
        int[] num = new int[len1 > len2 ? len1 : len2];
        while(i < len1 && j < len2){
            if(nums1[i] < nums2[j]){
                i++;
            }else if(nums1[i] > nums2[j]){
                j++;
            }else{
                //保证元素只出现一次
                if(k == 0 || num[k-1] != nums1[i]){
                    num[k++] = nums1[i];
                }
                i++;
            }
        }
        //优化数组长度,避免空间浪费
        int[] concon=new int[k];
        for(int x=0;x<k;x++){
            concon[x]=num[x];
        }
        return concon;
    }

3.求两个数组的交集(2)

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

说明:

  • 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
  • 我们可以不考虑输出结果的顺序。

思路:

  • 排序
  • 找出相等的数放入集合
    public int[] intersect1(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        List<Integer> list = new ArrayList<>();
        for (int i = 0, j = 0; i < nums1.length && j < nums2.length; ) {
            if (nums1[i] < nums2[j]) {
                i++;
            } else if (nums1[i] > nums2[j]) {
                j++;
            } else {
                list.add(nums1[i]);
                i++;
                j++;
            }
        }
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            res[i] = list.get(i);
        }
        return res;
    }

也可以使用映射的hashmap解决这一问题

思路:

  • 先用map将在nums1中各元素出现的次数和值存起来
  • 遍历nums2过程中,如果在map中有该key,就添加到list中,将对应的出现次数-1,直到次数 = 0
    /**
     * 使用hashMap
     *
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersect(int[] nums1, int[] nums2) {
        //统计重复次数
        int k = 0;
        Map<Integer, Integer> map = new HashMap<>();
        //将nums数值和出现的次数存入map中
        for (int i = 0; i < nums1.length; i++) {
            if (map.containsKey(nums1[i])) {
                map.put(nums1[i], map.get(nums1[i]) + 1);
            } else {
                map.put(nums1[i], 1);
            }
        }
        //找出重复数,存到list,并减少相应的重复次数
        List<Integer> list = new LinkedList<>();
        for (int j = 0; j < nums2.length; j++) {
            if (map.containsKey(nums2[j]) && map.get(nums2[j]) > 0) {
                list.add(nums2[j]);
                map.put(nums2[j], map.get(nums2[j]) - 1);
            }
        }
        //最后,将list中的值放入数组中
        int count = list.size();
        int[] aux = new int[count];
        for (int i = 0; i < count; i++) {
            aux[i] = ((LinkedList<Integer>) list).poll();
        }
        return aux;
    }

反转问题

1.反转字符数组

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

示例 1:

输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

使用双指针,前指针和后指针,交换指针指向的字符,直到前后指针相遇

    public void reverseString(char[] s) {
        int i = 0;
        int j = s.length - 1;
        while(i < j){
            char tmp = s[j];
            s[j] = s[i];
            s[i] = tmp;
            i++;
            j--;
        }
    }

2.验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: "A man, a plan, a canal: Panama"
输出: true

示例 2:

输入: "race a car"
输出: false

思路:先将字符串转为统一形式,然后前后指针分别从前往后,从后往前遍历,只要不同,立马false掉

    /**
     * 验证回文串
     * 【双指针】
     * @param s
     * @return
     */
    public boolean isPalindrome(String s) {
        //只考虑字母和数字字符,可以忽略字母的大小写。
        s = s.toLowerCase().replaceAll("[\\p{Punct}\\p{Space}]+", "").trim();
        int i = 0;
        int j = s.length() - 1;
        while(i < j){
            //只要不相符,立马false掉
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }else {
                i++;
                j--;
            }
        }
        return true;
    }

3.回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

思路:

  • 快慢指针找到链表中间节点
  • 反转链表后半部分
  • 比较反转后的后半部分和前半部分是否一样
    public class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
        }
    }


    /**
     * 回文链表
     * @param head
     * @return
     */
    public boolean isPalindrome(ListNode head) {

        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        //快慢指针找到链表中点
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //反转后半部分
        slow = reverse(slow.next);
        //比较前后部分
        while(slow != null){
            if(slow.val != head.val){
                return false;
            }else{
                slow = slow.next;
                head = head.next;
            }
        }

翻转链表

//递归实现
public ListNode reverse(ListNode head){
    if (head == null || head.next == null) {
        return head;
    }
    ListNode next = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return next;
}

//非递归实现
public ListNode reverse(ListNode head){
    if (head == null || head.next == null) {
        return head;
    }
    ListNode pre = null;
    ListNode cur = head;
    while(cur != null){
        ListNode node = cur.next;
        cur.next = pre;
        pre = cur;
        cur = node;
    }
    return pre;
}

此外,刚刚提到的快慢指针除了可以找链表中间节点,还可以用来判断链表中是否有环

4.环形链表

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null && fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值