15数据结构与算法刷题之【双指针】篇

简介: 15数据结构与算法刷题之【双指针】篇

剑指offer


剑指 Offer II 018. 有效的回文【简单】


题目链接: 剑指 Offer II 018. 有效的回文


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


思路:双指针判断法


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
    public boolean isPalindrome(String s) {
        int i = 0, j = s.length() - 1;
        while (i < j) {
            while (i < s.length() && !isSatisfy(s.charAt(i))) {
                i++;
            }
            while (j >= 0 && !isSatisfy(s.charAt(j))) {
                j--;
            }
            if (i >= j) {
                break;
            }
            if (transfer(s.charAt(i++)) != transfer(s.charAt(j--))) {
                return false;
            }
        }
        return true;
    }
    public boolean isSatisfy(char ch) {
        if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
            return true;
        }
        return false;
    }
    public char transfer(char ch) {
        if (ch >= 'A' && ch <= 'Z') {
            return (char)(ch + 32);
        }
        return ch;
    }
}



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


题目链接:剑指 Offer 21. 调整数组顺序使奇数位于偶数前面


题目内容:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。


思路:


1、双指针,从左右两端开始。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
    //1234
    //12345
    public int[] exchange(int[] nums) {
        int n = nums.length;
        int left = 0, right = nums.length - 1;
        while (left < right) {
            while (left < right && nums[left] % 2 == 1) {
                left++;
            }
            while (left < right && nums[right] % 2 == 0) {
                right--;
            }
            //若是成立那么就兑换
            if (left < right) {
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
            }
        }
        return nums;
    }
}


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


题目链接:剑指 Offer 57 - II. 和为s的连续正数序列


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


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


思路:


1、滑动窗口


复杂度分析:


时间复杂度:O(n),最大为2n。
空间复杂度:O(1)
class Solution {
    //滑动窗口
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();
        //指定一个区间
        for (int l = 1, r = 1, sum = 0; r < target; r++) {
            sum += r;
            //若是>当前的窗口,及时进行缩减窗口
            while (l < r && sum > target) {
                sum -= l++;
            }
            //若是当前窗口符合
            if (sum == target && r - l > 0) {
                int [] res = new int[r - l + 1];
                for (int i = l; i <= r; i++) {
                    res[i - l] = i;
                }
                list.add(res);
            }
        }
        //将list转换为int数组
        int[][] res = new int[list.size()][];
        for (int i = 0; i < list.size(); i++) {
            res[i] = list.get(i);
        }
        return res;
    }
}


剑指 Offer 48. 最长不含重复字符的子字符串【中等】


题目链接:剑指 Offer 48. 最长不含重复字符的子字符串


题目内容:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。


思路:


1、滑动窗口+哈希表


复杂度分析:时间复杂度O(n),空间复杂度O(n)


class Solution {
    public int lengthOfLongestSubstring(String s) {
        //哈希+滑动窗口
        Set<Character> set = new HashSet<>();
        char[] arr = s.toCharArray();
        int max = 0;
        for (int l = 0, r = 0; r < arr.length; r++) {
            //缩减窗口
            while (l < r && !set.isEmpty() && set.contains(arr[r])) {
                set.remove(arr[l]);//先进行移除再左指针移动
                l++;
            }
            set.add(arr[r]);
            max = Math.max(max, r - l + 1);
        }
        return max;
    }
}


优化一下,对于set集合我们可以使用int数组来替代【最优】:


class Solution {
    public int lengthOfLongestSubstring(String s) {
        //哈希+滑动窗口
        int[] buckets = new int[128];
        char[] arr = s.toCharArray();
        int max = 0;
        for (int l = 0, r = 0; r < arr.length; r++) {
            //缩减窗口
            while (l < r && buckets[arr[r]] > 0) {
                buckets[arr[l]]--;
                l++;
            }
            buckets[arr[r]]++;
            max = Math.max(max, r - l + 1);
        }
        return max;
    }
}



牛客网


合并两个有序的数组【简单】


题目链接:合并两个有序的数组


题目内容:给出一个有序的整数数组 A 和有序的整数数组 B ,请将数组 B 合并到数组 A 中,变成一个有序的升序数组。


思路1:合并数组。从大到小,针对A数组来从后往前填。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    public void merge(int A[], int m, int B[], int n) {
        int i = m - 1;
        int j = n - 1;
        int k = m + n - 1;
        while (i >= 0 && j >= 0) {
            if (A[i] > B[j]) {
                A[k--] = A[i--];
            }else {
                A[k--] = B[j--];
            }
        }
        //若是B还有剩余继续往前添加,若是B没有,那就直接结束了
        while (j >= 0) {
            A[k--] = B[j--];
        }
    }
}


反转字符串【简单】


题目链接:反转字符串


题目内容:写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)。


思路:双指针来进行交换替换字符。


复杂度分析:


时间复杂度:O(n)。
空间复杂度:O(1)。
import java.util.*;
public class Solution {
    /**
     * 反转字符串
     * @param str string字符串 
     * @return string字符串
     */
    public String solve (String str) {
        int i = 0, j = str.length() - 1;
        char[] chars = str.toCharArray();
        while (i < j) {
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
            i++;
            j--;
        }
        return new String(chars);
    }
}



判断是否为回文字符串【中等】


题目链接:合并两个有序的数组


题目内容:给定一个长度为 n 的字符串,请编写一个函数判断该字符串是否回文。如果是回文请返回true,否则返回false。


思路1:使用双指针,一个从前开始,一个从后开始来进行遍历比较。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param str string字符串 待判断的字符串
     * @return bool布尔型
     */
    public boolean judge (String str) {
        int i = 0, j = str.length() - 1;
        while (i <= j) {
            if (str.charAt(i) != str.charAt(j)) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }
}



最长无重复子数组【中等】


题目链接:最长无重复子数组


题目内容:给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。子数组是连续的,


比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组。


思路:滑动窗口。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(n)
import java.util.*;
public class Solution {
    /**
     * 
     * @param arr int整型一维数组 the array
     * @return int整型
     */
    public int maxLength (int[] arr) {
        //滑动窗口
        Map<Integer, Integer> map = new HashMap<>();
        int res = 0;
        for (int left = 0, right = 0; right < arr.length; right++) {
            //这个if操作就是往map里面添加值的
            if (map.containsKey(arr[right])) {
                map.put(arr[right], map.get(arr[right]) + 1);
            }else {
                map.put(arr[right], 1);
            }
            //移动left指针
            while (map.get(arr[right]) > 1) {
                map.put(arr[left], map.get(arr[left++]) - 1);
            }
            //计算长度最大值
            res = Math.max(res, right - left + 1);
        }
        return res;
    }
}



盛水最多的容器【中等】


题目链接:盛水最多的容器


题目内容:给定一个数组height,长度为n,每个数代表坐标轴中的一个点的高度,height[i]是在第i点的高度,请问,从中选2个高度与x轴组成的容器最多能容纳多少水。


思路:定义左右指针,然后不断的缩短容量,移动位置。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param height int整型一维数组 
     * @return int整型
     */
    public int maxArea (int[] height) {
        int res = 0;
        int left = 0, right = height.length - 1;
        while (left < right) {
            //贪心策略
            res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
            //如果左边的高度<右边的高度,那么左边位置向右移动
            if (height[left] < height[right]) {
                left++;
            }else{
                right--;
            }
        }
        return res;
    }
}


接雨水问题【中等】


题目链接:接雨水问题


题目内容:给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个柱子高度图,计算按此排列的柱子,下雨之后能接多少雨水。(数组以外的区域高度视为0)。


思路1:双指针方案。定义左右各个最大高度,然后来进行比较平移,若是右边更高,那么就开始平移左边的并且进行计算。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    /**
     * max water
     * @param arr int整型一维数组 the array
     * @return long长整型
     */
    public long maxWater (int[] arr) {
        int i = 0, j = arr.length - 1;
        int maxL = 0;
        int maxR = 0;
        long res = 0;
        while (i < j) {
            //取出当前左右边最大的边界
            maxL = Math.max(maxL, arr[i]);
            maxR = Math.max(maxR, arr[j]);
            //若是右边>左边,移动左边位置
            if (maxR > maxL) {
                res += maxL - arr[i++];
            }else {
                res += maxR - arr[j--];
            }
        }
        return res;
    }
}



合并区间【中等】


题目链接:合并区间


题目内容:给出一组区间,请合并所有重叠的区间。请保证合并后的区间按区间起点升序排列。


思路:排序比较。①根据start来进行排序。②不断的进行判断star<=新添加集合中元素的end,就可以直接添加进去。


复杂度分析:


时间复杂度:O(nlogn)
空间复杂度:O(n)
import java.util.*;
/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
public class Solution {
    public ArrayList<Interval> merge(ArrayList<Interval> intervals) {
        if (intervals == null || intervals.size() == 0 || intervals.size() == 1) {
            return intervals;
        }
        //1、先对集合中的所有元素根据start来进行排序
        Collections.sort(intervals, (o1, o2)->o1.start - o2.start);
        //2、不断的拿intervals中的start来与list最后一个end来比较
        ArrayList<Interval> list = new ArrayList<>();
        list.add(intervals.get(0));
        for (int i = 1; i < intervals.size(); i++) {
            if (intervals.get(i).start <= list.get(list.size() - 1).end) {
                //list.get(list.size() - 1).end = intervals.get(i).end;
                //优化:针对[[1,4],[2,3]],所以需要进行比较
                list.get(list.size() - 1).end = Math.max(intervals.get(i).end, list.get(list.size() - 1).end);
            }else {
                list.add(intervals.get(i));
            }
        }
        return list;
    }
}



最小覆盖子串【中等】


题目链接:最小覆盖子串


题目内容:给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。


思路1:滑动窗口,先不断的进行窗口扩大,满足条件之后进行缩减,然后不断的使用两个指针进行移动比较。


复杂度分析:


时间复杂度:O(n*m)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    /**
     * 
     * @param S string字符串 
     * @param T string字符串 
     * @return string字符串
     */
    //滑动窗口
    public String minWindow (String S, String T) {
        //使用一个hash表,index下标表示对应的字符,value表示出现的次数,初始为0,若是有两个就是-2
        int[] hash = new int[128];
        for (int i = 0; i < T.length(); i++) {
            //可能会有重复的字符
            hash[T.charAt(i)]--;
        }
        //快慢指针来进行移动的
        int slow = 0, fast = 0;
        //左右指针用来记录最小子串的左右位置
        int left = -1, right = -1;
        //记录最小长度
        int min = S.length() + 1;
        for (;fast < S.length(); fast++) {
            //对字符所在位置下标+1
            hash[S.charAt(fast)]++;
            //如果当前已经覆盖所有的字符,那么进行缩减当前的窗口
            while (check(hash, T)) {
                if (min > (fast - slow + 1)) {
                    min = fast - slow + 1;
                    left = slow;
                    right = fast;
                }
                //窗口进行缩减
                hash[S.charAt(slow)]--;
                slow++;
            }
        }
        //没有找到情况
        if (left == -1 && right == -1) {
            return "";
        }
        //返回指定的字符串
        return S.substring(left, right + 1);
    }
    //check检查当前hash是否已经存在
    public boolean check(int[] hash, String T) {
        for (int i = 0; i < T.length(); i++) {
            if (hash[T.charAt(i)] < 0){
                return false;
            }
        }
        return true;
    }
}


上面代码在leetcode中超时,对其进行优化:主要是针对check()函数遍历做优化,更改为一个all的int型进行表示是否已经全部包含


class Solution {
    public String minWindow(String s, String t) {
        //hash表:128个字符(ascii码)。【>=0的表示符合t中的元素,<0的即为不符合元素】
        int[] hash = new int[128];
        for (int i = 0; i < t.length(); i++) {
            hash[t.charAt(i)]++;
        }
        //保存一个最小窗口值
        int min = s.length() + 1;
        //左右指针,用于最后进行返回
        int left = -1, right = -1;
        //当前符合条件的数量
        int all = t.length();
        //滑动窗口
        for (int slow = 0, fast = 0; fast < s.length(); fast++) {
            hash[s.charAt(fast)]--;
            //说明上面对某个字符操作,该字符是属于t中的
            if (hash[s.charAt(fast)] >= 0) {
                all--;
            }
            //若是all=0表示,已经找到了所在的范围
            if (all == 0) {
                while (hash[s.charAt(slow)] < 0) {
                    hash[s.charAt(slow++)]++;
                }
                //当前范围包含t的所有元素
                if (min > (fast - slow + 1)) {
                    min = fast - slow + 1;
                    left = slow;
                    right = fast;
                }
                //此时最左边的一定是在t中包含的,此时value值+1,以及all也进行+1
                hash[s.charAt(slow++)]++;
                all++;
            }
        }
        //没有符合条件的情况
        if (min == s.length() + 1) {
            return "";
        }
        //【left,right】
        return s.substring(left, right + 1);
    }
}



leetcode


合并区间【中等】(等同于牛客7)


题目链接:合并区间


题目内容:给出一组区间,请合并所有重叠的区间。请保证合并后的区间按区间起点升序排列。


思路:排序比较。①根据start来进行排序。②不断的进行判断star<=新添加集合中元素的end,就可以直接添加进去。


复杂度分析:


时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals == null || intervals.length == 0 || intervals.length == 1) {
            return intervals;
        }
        //1、进行排序
        Arrays.sort(intervals, (a, b)->a[0] - b[0]);
        //2、来进行排序
        List<int[]> res = new ArrayList<>();
        res.add(intervals[0]);
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] <= res.get(res.size() - 1)[1]) {
                res.get(res.size() - 1)[1] = Math.max(intervals[i][1], res.get(res.size() - 1)[1]);
            }else {
                res.add(intervals[i]);
            }
        }
        return res.toArray(new int[0][]);
    }
}



11. 盛最多水的容器【中等】


学习:leetcode题解


题目链接:11. 盛最多水的容器


题目内容:


给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。


找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。


返回容器可以储存的最大水量。


说明:你不能倾斜容器。


思路:


1、暴力


思路:通过暴力列举出所有面积的可能性再一一比对最终求得最大面积。


复杂度分析:时间复杂度O(n2)、空间复杂度O(1)


特征:实际是两个指针同时从左边出发来进行不对列举比较,每次按照指定的顺序同步移动导致每次需要计算面积,就会有额外许多不必要的计算。


/**
 * 解法1:暴力解法
 * @param height
 * @return
 */
public int maxArea(int[] height) {
    int maxArea = 0;
    for (int i = 0; i < height.length; i++) {
        for (int j = i + 1; j < height.length; j++) {
            //最大面积 vs 当前面积(高度最矮 * 两边距离)
            maxArea = Math.max(maxArea, Math.min(height[i],height[j]) * (j - i));
        }
    }
    return maxArea;
}


2、左右指针


特征:左右两边
模式识别:需要移动左右两头的问题可以考虑双指针
难点:如何移动指针?①相同情况下两边距离越远越好。②区域受限于短边。


思路:通过不断的移动左右指针位置来减少不必要的面积计算比较,移动的依据是根据当前指针指向的两个高度比较,较矮的一方进行移动,最终来减少不必要的比较!


复杂度分析:时间复杂度O(n)、空间复杂度O(1)


/**
 * 解法2:左右指针根据两个高度大小来移动
 * @param height
 * @return
 */
public int maxArea(int[] height) {
    int max = 0, l = 0, r = height.length - 1;
    while (l < r){
        max = max = Math.max(max, Math.min(height[l],height[r]) * (r - l));
        if (height[l] < height[r]){
            l++;
        }else {
            r--;
        }
    }
    return max;
}



文章知识点与官方知识档案匹配,可进一步学习相关知识

相关文章
|
2月前
|
算法 索引 容器
双指针算法详解
本文介绍了双指针算法及其应用。双指针算法是在数组或字符串中常用的高效技术,通过维护两个指针遍历数据结构以解决特定问题。根据指针移动方向,可分为同向双指针、相向双指针和快慢指针。同向双指针如移动零和复写零问题;快慢指针如快乐数问题;相向双指针如盛水最多的容器、有效三角形数量及多数之和等问题。通过合理运用双指针技巧,可简化代码并提高效率。
56 4
双指针算法详解
|
29天前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
12 0
|
1月前
|
算法 C++
【算法】双指针+二分(C/C++
【算法】双指针+二分(C/C++
|
4月前
【数据结构OJ题】复制带随机指针的链表
力扣题目——复制带随机指针的链表
51 1
【数据结构OJ题】复制带随机指针的链表
|
2月前
crash —— 如何知道哪些数据结构内嵌了指定的数据结构或者内嵌了指向指定数据结构的指针
crash —— 如何知道哪些数据结构内嵌了指定的数据结构或者内嵌了指向指定数据结构的指针
|
3月前
|
Python
【Leetcode刷题Python】138. 复制带随机指针的链表
LeetCode上题目“138. 复制带随机指针的链表”的Python解决方案,包括两种方法:一种是在每个节点后复制一个新节点然后再分离出来形成新链表;另一种是构建一个字典来跟踪原始节点与其副本之间的映射关系,从而处理新链表的构建。
20 1
|
3月前
|
算法 容器
【算法】双指针
【算法】双指针
|
3月前
【刷题记录】最大公因数,最小公倍数(辗转相除法、欧几里得算法)
【刷题记录】最大公因数,最小公倍数(辗转相除法、欧几里得算法)
|
3月前
|
算法 C++ 容器
【C++算法】双指针
【C++算法】双指针
下一篇
无影云桌面