双指针刷题笔记

记录DataWhale 相对比较随意

查找表 双指针

查找表问题,大多是通过构建一个查找表,而避免了在查找中再内层嵌套循环,从而降低了时间 复杂度。那么在这一部分,主要是利用双指针来优化时间复杂度,虽然好几个地方还是在用hashmap。

1. 两数之和

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍
        int[] res=new int[2];
       
        for(int i=0;i<nums.length-1;i++){
            for(int j=nums.length-1;i<j;j--){
                if(target==nums[i]+nums[j]){
                    res[0]=i;
                    res[1]=j;
                    break;
                }
            }//forj end     
        }//fori end
        return res;
    }//method end
}//class end
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            map.put(nums[i],i);
        }
        for(int i=0;i<nums.length;i++){
            int sub=target-nums[i];
            if(map.containsKey(sub)&&map.get(sub)!=i){
                return new int[]{i,map.get(sub)};
            }
        }
    throw new IllegalArgumentException("No two sum solution");
    }//method end
}//class end
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            int sub=target-nums[i];
            if(map.containsKey(sub)){
                return new int[]{i,map.get(sub)};
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }//method end
}//class end

15. 三数之和

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

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

三层for再去重复的暴力笨办法 这里就不写了

class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        int len = nums.length;
        List<List<Integer>> ans = new ArrayList();
        if(nums == null || len < 3) return ans;

        Arrays.sort(nums); // 排序

        for (int i = 0; i < len-2 ; i++) {//遍历数组 其实要留两个指针余量 所以len-2
            if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
            if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
            int L = i+1;
            int R = len-1;
            while(L < R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                    while (L<R && nums[L] == nums[L+1]) L++; // 去重
                    while (L<R && nums[R] == nums[R-1]) R--; // 去重
                    L++;
                    R--;
                }//if end
                else if (sum < 0) L++;
                else if (sum > 0) R--;
            }//while end
        }//fori end        
        return ans;
    }//method end
}// class end
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
    int len = nums.length;
    List<List<Integer>> res = new LinkedList<>(); 
    if(nums == null || len < 3) return res;

    Arrays.sort(nums); //排序

    for (int i = 0; i < len-2; i++) {
        //为了保证不加入重复的 list,因为是有序的,所以如果和前一个元素相同,只需要继续后移就可以
        if (i == 0 || (i > 0 && nums[i] != nums[i-1])) {
            //两个指针,并且头指针从i + 1开始,防止加入重复的元素
            int lo = i+1, hi = len-1, sum = 0 - nums[i];
            while (lo < hi) {
                if (nums[lo] + nums[hi] == sum) {
                    res.add(Arrays.asList(nums[i], nums[lo], nums[hi]));
                    //元素相同要后移,防止加入重复的 list
                    while (lo < hi && nums[lo] == nums[lo+1]) lo++;
                    while (lo < hi && nums[hi] == nums[hi-1]) hi--;
                    lo++; hi--;
                } else if (nums[lo] + nums[hi] < sum) lo++;
                else hi--;
           }//while end
        }//if i end
    }//for i end
    return res;
    }//method end
}//class end

18. 四数之和

class Solution {
    public List<List<Integer>> fourSum(int[] nums,int target){
        int len = nums.length;
        List<List<Integer>> res = new LinkedList<>();
        if(nums == null || len < 4) return res;

        Arrays.sort(nums); // 排序

        //多加了层循环
        for (int j = 0; j < len - 3; j++) {
            //防止重复的
            if (j == 0 || (j > 0 && nums[j] != nums[j - 1]))//nums[j] != nums[j - 1]去重复
                for (int i = j + 1; i < len - 2; i++) {
                    //防止重复的,不再是 i == 0 ,因为 i 从 j + 1 开始
                    if (i == j + 1 || nums[i] != nums[i - 1]) {//num[i] != num[i - 1] 去重复
                        int L = i+1;
                        int R = len-1;
                        int  sum = target - nums[j] - nums[i];
                        while (L < R) {
                            if (nums[L] + nums[R] == sum) {
                                res.add(Arrays.asList(nums[j], nums[i], nums[L], nums[R]));
                                while (L < R && nums[L] == nums[L + 1])
                                    L++;//去重复
                                while (L < R && nums[R] == nums[R - 1])
                                    R--;//去重复
                                L++;
                                R--;
                            } else if (nums[L] + nums[R] < sum)
                                L++;
                            else
                                R--;
                        }//while end
                    }//if i end
                }//fori end && if j end
        }// forj end 
        return res;
    }//method end
}//class end
class Solution {
    public List<List<Integer>> fourSum(int[] nums,int target){
        /*定义一个返回值*/
        List<List<Integer>> result=new ArrayList<>();
        /*当数组为null或元素小于4个时,直接返回*/
        if(nums==null||nums.length<4){
            return result;
        }
        /*对数组进行从小到大排序*/
        Arrays.sort(nums);
        /*数组长度*/
        int length=nums.length;
        /*定义4个指针k,i,j,h  k从0开始遍历,i从k+1开始遍历,留下j和h,j指向i+1,h指向数组最大值*/
        for(int k=0;k<length-3;k++){
            /*当k的值与前面的值相等时忽略*/
            if(k>0&&nums[k]==nums[k-1]){
                continue;
            }
            /*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值根本没戏*/
            int min1=nums[k]+nums[k+1]+nums[k+2]+nums[k+3];
            if(min1>target){
                break;
            }
            /*获取当前最大值,如果最大值比目标值小,说明后面越来越小的值根本没戏,忽略*/
            int max1=nums[k]+nums[length-1]+nums[length-2]+nums[length-3];
            if(max1<target){
                continue;
            }
            /*第二层循环i,初始值指向k+1*/
            for(int i=k+1;i<length-2;i++){
                /*当i的值与前面的值相等时忽略*/
                if(i>k+1&&nums[i]==nums[i-1]){
                    continue;
                }
                /*定义指针j指向i+1*/
                int j=i+1;
                /*定义指针h指向数组末尾*/
                int h=length-1;
                /*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值根本没戏,忽略*/
                int min=nums[k]+nums[i]+nums[j]+nums[j+1];
                if(min>target){
                    continue;
                }
                /*获取当前最大值,如果最大值比目标值小,说明后面越来越小的值根本没戏,忽略*/
                int max=nums[k]+nums[i]+nums[h]+nums[h-1];
                if(max<target){
                    continue;
                }
                /*开始j指针和h指针的表演,计算当前和,如果等于目标值,j++并去重,h--并去重,当当前和大于目标值时h--,当当前和小于目标值时j++*/
                while (j<h){
                    int curr=nums[k]+nums[i]+nums[j]+nums[h];
                    if(curr==target){
                        result.add(Arrays.asList(nums[k],nums[i],nums[j],nums[h]));
                        j++;
                        while(j<h&&nums[j]==nums[j-1]){
                            j++;
                        }
                        h--;
                        while(j<h&&i<h&&nums[h]==nums[h+1]){
                            h--;
                        }
                    }else if(curr>target){
                        h--;
                    }else {
                       j++;
                    }
                }
            }
        }
        return result;
    }
}

16. 最接近的三数之和

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int sub = Integer.MAX_VALUE; //保存和 target 的差值
        int sum = 0; //保存当前最接近 target 的三个数的和
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++)
                for (int k = j + 1; k < nums.length; k++) {
                    if (Math.abs((nums[i] + nums[j] + nums[k] - target)) < sub) {
                        sum = nums[i] + nums[j] + nums[k];
                        sub = Math.abs(sum - target);
                    }
                }
        }
        return sum;
    }
}
class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int len=nums.length;
        int sum=0;
        int sub=Integer.MAX_VALUE;
        Arrays.sort(nums);
        for(int i=0;i<len-2;i++){
            int L=i+1,R=len-1;
            while(L < R){
                if(Math.abs(nums[i]+nums[L]+nums[R]-target)<sub){
                    sub=Math.abs(nums[i]+nums[L]+nums[R]-target);
                    sum=nums[i]+nums[L]+nums[R];
                }
                if(nums[L]+nums[R]+nums[i]>target){
                    R--;
                }else{
                    L++;
                }

            }//while end
        }//fori end
        return sum;
    }//method end
}//class end

454. 四数相加 II


思路:

1)固定的四个数相加,每个数从A组元中选择一个,树状图画下去 就可得到
 关于程序上的计算,直接三个for嵌套肯定可以实现,O(N^3),但是不推荐,也不是我们想要的;

2) 四数之和 刚好 拆分两两相组  而且返回值只需要返回个数count
A + B = K; C+ D = -K;

拆分为子问题 A B两数组中 抽取a 和 b相加求和,得到的k 用hashmap存放 key为两数之和sum,value为sum的频次 
Map<Integer,Integer> getMapOf(int[] A,int[] B)

mapAB=getMapOf(A,B);
mapCD=getMapOf(C,D);

对两个map进行遍历,遇到k 和 -k匹配对的时候就对其value进行计数相加

最后返回 count


//另外的 上面过程简化
mapAB=getMapOf(A,B);
得到mapAB后,对CD进行操作时直接判断mapAB中是否包含-k即可进行计数操作
/*
上面思路2的简化版本,O(N^2),暂时找不到能优化下去的了
*/
class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int res = 0;
        Map<Integer, Integer> map1 = new HashMap<>();
        for (int a : A) {
            for (int b : B) {
                int sum1 = a + b;
                map1.put(sum1, map1.getOrDefault(sum1, 0) + 1);
            }
        }
        for (int c : C) {
            for (int d : D) {
                int sum2 = c + d;
                if(map1.containsKey(-sum2))  
                    res += map1.get(-sum2);
            }
        }
        return res;
    }//method end
}//class end

一个解析

49. 字母异位词分组

字母异位词 排序后 一致

我的第一个思路是hashmap,但是我们可以看看两次for 可以不

看看别人的 为什么不超时 详细通俗的思路分析,多解法

更新:他的也超时了,他说的复杂度O(NK),其实也可以说是O(NN),始终会先进入循环再判断是否使用,按照后续操作数来看,可以看做O(NK);我发现,O(NN) 就是会超时 我本以为是我我用字符串equals和 他用 hashmap做统计支持的差别 其实 都差不多超时

/*测试用例ok 超出时间限制 复杂度O(N*N) 还是老实用hashmap吧*/
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        //所有输入均为小写字母。a~z 97~122
        /*
        1)hashmap or arrays 统计字符个数 来判断 是否为一类异或词
        2)字符串 按照字符数组排序后异或词结果一致
        */
        List<List<String>> res=new ArrayList<List<String>>();
        
        int len=strs.length;
        int[] isUse=new int[len];
        for(int i=0;i<len;i++)
            isUse[i]=0;
        for(int i=0;i<len;i++){
            if(isUse[i]!=0) continue;//使用过了的索引 跳过
            List<String> tmp=new ArrayList<String>();//每一层的异或词list
            tmp.add(strs[i]);
            isUse[i]=1;
            for(int j=i+1;j<len;j++){
                if(isUse[j]!=0) continue;//使用过了的索引 跳过
                // todo
                if(getStrByOrder(strs[i]).equals(getStrByOrder(strs[j]))){//getStrByOrder(strs[i]).equals(getStrByOrder(strs[j]))
                    tmp.add(strs[j]);
                    isUse[j]=1;
                }
            }//forj end
            if (tmp != null) {
                res.add(tmp); 
            }    
        }//fori end

        return res;
    }//method end
    //用于给单词 按字典顺序排序
    public String getStrByOrder(String str) {
        char[] ch=str.toCharArray();
        Arrays.sort(ch);
        return String.valueOf(ch);
    }//method end

    //此方法来源于上方链接解释,用hashmap做统计支持 判定字符串的异或相等性
    private boolean equals(String string1, String string2) {
    Map<Character, Integer> hash = new HashMap<>();
    //记录第一个字符串每个字符出现的次数,进行累加
    for (int i = 0; i < string1.length(); i++) {
        if (hash.containsKey(string1.charAt(i))) {
            hash.put(string1.charAt(i), hash.get(string1.charAt(i)) + 1);
        } else {
            hash.put(string1.charAt(i), 1);
        }
    }
     //记录第一个字符串每个字符出现的次数,将之前的每次减 1
    for (int i = 0; i < string2.length(); i++) {
        if (hash.containsKey(string2.charAt(i))) {
            hash.put(string2.charAt(i), hash.get(string2.charAt(i)) - 1);
        } else {
            return false;
        }
    }
    //判断每个字符的次数是不是 0 ,不是的话直接返回 false
    Set<Character> set = hash.keySet();
    for (char c : set) {
        if (hash.get(c) != 0) {
            return false;
        }
    }
    return true;
    }//method end



}//class end
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String, List<String>> hashMap = new HashMap<>();//根据String把元素放入对应的ListString
        List<String> anagramGroup;//组,存放字母一样的单词
        for (String curStr : strs) {
            String sortedStr = getStrByOrder(curStr);//每个单词按字母排序
            if (hashMap.containsKey(sortedStr)) {//存在该排序后单词的key,则取出value,将curStr插入
                anagramGroup = hashMap.get(sortedStr);
                anagramGroup.add(curStr);
            } else {//不存在该key 则新建一个list,将curStr add进去,在将该value即list 以sortedStr为key put进去
                anagramGroup = new ArrayList<>();
                anagramGroup.add(curStr);
                hashMap.put(sortedStr, anagramGroup);
            }
        }
        return new ArrayList<>(hashMap.values());
    }

    //用于给单词 按字典顺序排序
    public String getStrByOrder(String str) {
        char[] ch=str.toCharArray();
        Arrays.sort(ch);
        return String.valueOf(ch);
    }//method end
}

稍微简化一点代码行数

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String, List<String>> hash = new HashMap<>();
        for (int i = 0; i < strs.length; i++) {
            char[] s_arr = strs[i].toCharArray();
            Arrays.sort(s_arr);
            String key = String.valueOf(s_arr); 
            if (hash.containsKey(key)) {
                hash.get(key).add(strs[i]);
            } else {
                List<String> temp = new ArrayList<String>();
                temp.add(strs[i]);
                hash.put(key, temp);
            }
        }//fori end
        return new ArrayList<List<String>>(hash.values()); 
    }//method end
}//class end

447. 回旋镖的数量

思路: (1)遍历所有的点,让每个点作为两点距离的左端点。 (2)然后再遍历其他的点(作为右端点),统计左端点到所有其他点的距离出现的频次(用HashMap存储,Key为距离,Value为频次) (3)如果Value大于1,则将Value带入 n(n-1) 计算结果并累加到result中

注意点: (1)如果有一个点a,还有两个点 b 和 c ,如果 ab 和 ac 之间的距离相等,那么就有两种排列方法 abc 和 acb ; 如果有三个点b,c,d 都分别和 a 之间的距离相等,那么有六种排列方法,abc, acb, acd, adc, abd, adb; 如果有 n 个点和点 a 距离相等,那么排列方式为 n(n-1)。 (2)计算距离时不进行开根运算, 以保证精度。

复杂度都是O(N*N) 没想到啥能降低的 才疏学浅

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int result=0;
        for(int i=0;i<points.length;i++){
            //存储点i到所有其他点的距离出现的频次(Key为距离,Value为频次)
            Map<Integer, Integer> map = new HashMap<>();
            for(int j=0;j<points.length;j++){
                if(i!=j){
                    int dis = dis(points[i], points[j]);
                    if(map.containsKey(dis))
                        map.put(dis,map.get(dis)+1);
                    else
                        map.put(dis,1);
                }
            }
            Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
            for (Map.Entry<Integer, Integer> entry : entries) {
                if(entry.getValue()>1)
                    //如果存在距离相等的两点的数量大于一,则可以组队(回旋镖)
                    //如果存在2个则2种排列方法,存在3个则6种排列方法,存在n个则n(n-1)中排列方法
                    result=result+entry.getValue()*(entry.getValue()-1);
            }
        }
        return result;
    }
    //计算两元组距离
    public static int dis(int[] left, int[] right){
        return (int) (Math.pow(left[0]-right[0],2)+Math.pow(left[1]-right[1],2));
    }
}
class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int res = 0;
        //O(n^2)
        for(int i = 0; i < points.length;i++){
            Map<Integer, Integer> record = new HashMap<>();
            for(int j = 0; j < points.length; j ++){
                if( j != i )
                    if(record.containsKey(distance(points[i], points[j]))){
                        record.put(distance(points[i], points[j]),record.get(distance(points[i], points[j])) + 1);
                    }
                    else
                        record.put(distance(points[i], points[j]), 1);
            }//for  j end
            for(int k : record.values()){
                if(k >= 2)//这里其实可以不加这句,因为k=1或k=0,结果都是0,加上可以减少一定的计算量。
                    res += k * (k - 1);
            }      
        }//for i end
        return res;
    }//method end
    private int distance(int[] x, int[] y){
        return (x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1]);
    }//method end
}

149. 直线上最多的点数

题目把我看懵了 在我的认知中 两点确定一条直线 再判断其他点在直线上与否 这显然不能这样模拟解决 复杂度绕死

也不能像人一样 画出来 一眼看吧

用查找表的思想 至少也是两点确定一条直线 有N*(N-1)个两点确定的直线 再判断其他点是否在直线上和直线重合问题?

引发问题 谁是key,直线方程y=kx+b,计算得出k和b作为key吗?

所以key为二维数组double/float <k,b> 还是一个length=2的list,value存放直线上的点数 最后return max

/*
1)斜率的求算中,有时会出现直线为垂直的情况,故需要对返回的结果进行判断,如果分母为0,则返回inf,但是这里还需要避免相同元素 时,不能直接计算
*/
class Solution {
    public int maxPoints(int[][] points) {
        int len=points.length;
        if (len < 3) return len;

        int res = 0;
        //遍历每个点
        for (int i = 0; i < len; i++) {
            int duplicate = 0;//考虑可能会有相同的点,duplicate记录重复次数
            int max = 0;//保存经过当前点的直线中,最多的点
            HashMap<String, Integer> map = new HashMap<>();
            for (int j = i + 1; j < len; j++) {
                //求出分子分母
                int x = points[j][0] - points[i][0];
                int y = points[j][1] - points[i][1];
                if (x == 0 && y == 0) {//重复计数
                    duplicate++;
                    continue;
                }
                //进行约分
                int gcd = gcd(x, y);
                x = x / gcd;
                y = y / gcd;
                String key = x + "#" + y;//可以考虑改StringBuilder
                map.put(key, map.getOrDefault(key, 0) + 1);
                //getOrDefault(key, 0)有key就用key对应的value,没有就使用defaultValue,这里的defaultValue传入值为0
                max = Math.max(max, map.get(key));
            }//forj end
            //1 代表当前考虑的点,duplicate 代表和当前的点重复的点
            res = Math.max(res, max + duplicate + 1);
        }//fori end
        return res;
    }//method end

    private int gcd(int a, int b) {//获取最大公约数
        while (b != 0) {
            int temp = a % b;
            a = b;
            b = temp;
    }
    return a;
    }//method end  

}//class end

车轮滚滚 时间