算法:双指针题目练习

news/2024/9/22 23:09:19 标签: 算法

文章目录

  • 算法:双指针
    • 移动零
    • 复写零
    • 快乐数
    • 盛最多水的容器
    • 有效三角形的个数
    • 查找总价格为目标值的两个商品
    • 三数之和
    • 四数之和
  • 总结


算法:双指针

移动零

在这里插入图片描述

定义两个指针,slow和fast.用这两个指针把整个数组分成三块.
[0,slow]为非零元素,[slow+1,fast-1]为0元素,[fast,num.length]为未处理的元素.
初始情况下slow=-1,fast=0,因为此时还没有对数组进行处理.
用fast遍历整个数组:

  • 当fast指向的元素不为0时,此时让slow++,并交换fast与slow所指向的元素.交换完毕后fast++,继续向后处理元素.
  • 当fast指向的元素为0时,直接fast++.
class Solution {
    public void moveZeroes(int[] nums) {
        int slow = -1;
        int fast = 0;
        while(fast < nums.length) {
            if(nums[fast] != 0) {
                slow++;
                int tmp = nums[fast];
                nums[fast] = nums[slow];
                nums[slow] = tmp;
            }
            fast++;
        }
    }
}

复写零

在这里插入图片描述
这道题目如果没有要求空间复杂度为O(1)的话,那确实是简单题.直接申请一块数组,遍历,然后放进去,最后把数组的引用交换一下就行了.
但是实际上,这道题还是有点难度的,需要考虑的细节情况很多,自己上手写写就知道了.

解决这道题,最关键的就是找到数组中最后一个要被修改的数,我们可以使用双指针来找.

  • 定义两个指针slow和fast并初始化为0.使用slow向后遍历数组,当slow遇到0时,fast向后移动两步,slow向后移动一步.当slow指向的元素不为零时,fast和slow都向后移动一步.重复上述过程,直到fast大于数组长度.
  • 循环结束后,此时fast和slow多移动了一步,所以要减掉.在这里还需要考虑fast向后移动了两步的情况(slow指向了0元素,而此时fast已经位于数组的最后一个元素了),判断一下fast是否越界,如果越界,修改fast的指向,让fast指向数组的最后一个元素,因为这时slow指向的元素肯定是0,所以我们可以将数组的最后一位修改成0,之后让slow和fast向前移动一位.
  • 当程序运行到这里时,所有的特殊情况我们就都处理完了.接下来就是简单的移动和赋值了,这里就不再赘述了~
class Solution {
    public void duplicateZeros(int[] arr) {
        int fast = 0;
        int slow = 0;
        while(fast < arr.length) {
            if(arr[slow] == 0) fast++;
            fast++;
            slow++;
        }
        --slow;
        --fast;
        if(fast == arr.length) {
            fast = arr.length-1;
            arr[fast--] = arr[slow--];
        }
        while(slow >= 0) {
            if(arr[slow] == 0) {
                arr[fast--] = 0;
            }
            arr[fast--] = arr[slow--];
        }
    }
}

也可以看看宫水三叶大佬写的代码,大佬在力扣上写了题解,可以去看看.

class Solution {
    public void duplicateZeros(int[] arr) {
        int n = arr.length, i = 0, j = 0;
        while (j < n) {
            if (arr[i] == 0) j++;
            i++; j++;
        }
        i--; j--;
        while (i >= 0) {
            if (j < n) arr[j] = arr[i];
            if (arr[i] == 0 && --j >= 0) arr[j] = 0;
            i--; j--;
        }
    }
}

作者:宫水三叶
链接:https://leetcode.cn/problems/duplicate-zeros/solutions/1607062/by-ac_oier-zivq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

快乐数

在这里插入图片描述
这道题条件给的很全,如果题目没给"重复这个过程直到这个数变为1,也可能是无限循环但始终变不到1"这个条件.
那么就需要好好的想一想了,上面的条件是可以证明出来的.运用到了数学上的思想"鸽巢原理",这个放到最后再写吧,先写题解~

  • "将一个正整数替换为它每个位置上的数字的平方和"这个我们可以把它写成一个方法,方便调用,写法也很简单,这里就不写了.
  • 这道题最核心的地方,在于你怎么判断它无限循环永远到不了1,我们可以使用双指针来帮助我们判断,如果你之前做过环形链表,那么就很容易想到。
  • 定义两个指针slow和fast,并初始化为n,首先让fast先走一步(做一次替换,因为之后的循环内需要判断fast和slow是否相等).
  • 紧接着进入循环,循环条件为fast != 1 || fun(fast) != 1,因为之后fast要一次向后走两步,所以fast的当前值和fast的下一个值都要判断.循环内如果fast与slow相等,说明有环了,直接return false.如果不相等,slow向后走一步,fast向后走两步.
  • 如果fast走着走着跳出了循环(表示fast可以被替换成1),此时直接return true.
class Solution {
    public static int fun(int n) {
        int sum = 0;
        while(n > 0) {
            int tmp = n%10;
            sum += tmp*tmp;
            n/=10;
        }
        return sum;
    }
    public boolean isHappy(int n) {
        int slow = n;
        int fast = fun(n);
        while(fast != 1 || fun(fast) != 1) {
            if(fast == slow) {
                return false;
            }
            fast = fun(fun(fast));
            slow = fun(slow);
        }
        return true;
    }
}

鸽巢原理讲解:
以下是不太严谨的证明~
举个例子,比如说n = 9999999999.
n经过一次变换之后n = 9^2*10 = 810.也就是说变换的区间就在[1,810]这个范围内.
如果我们将n变换810次,最差的情况就是[1,810]内所有的数都出现了一次.如果n在此基础上再次变换,那么这个数肯定就会落在这个区间内,此时就形成了环路.

盛最多水的容器

在这里插入图片描述
写这道题时,我们就要分析分析了.
设盛水容量为v,底部变长为s,高为h.左指针为left指向最左边,右指针为right指向最右边,两个指针向对方移动.
根据题意,我们可得:

  • v = s * h;
  • s = right - left;
  • h = min(left,right);
    当我们移动指向的最高的线的指针时,有以下两种情况:
  • s 减小(两指针之间的距离变短了), 移动后指针的指向的线高于移动前的线,h 不变(因为盛水多少取决于最低的线),那么 v 减小
  • s 减小,移动后指针的指向的线低于移动前的线,h 不变或减小(因为盛水多少取决于最低的线,移动后的指针指向的线可能比另一个指针指向的线短,也可能在移动后仍然比另一个指针指向的线长),那么 v 减小.
    可以看到,当我们移动指向的最高的线的指针时,v并不会增大.

当我们移动指向的最低的线的指针时,有以下两种情况:

  • s 减小(两指针之间的距离变短了), 移动后指针的指向的线高于移动前的线,h 增大(因为盛水多少取决于最低的线),那么 v 的变化不能确定.
  • s 减小,移动后指针的指向的线低于移动前的线,h 减小(因为盛水多少取决于最低的线),那么 v 减小.

在这里插入图片描述

综合上述分析,我们可以得到,只有在移动指向的最低的线的指针,并且移动后,指针的指向的线,高于移动前的线时,v才有可能变大.

有了以上的分析,代码就很好写啦~

class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int v = 0;
        while(left < right) {
            int s = Math.min(height[left],height[right]);
            int tmp = s*(right-left);
            if(tmp > v) {
                v = tmp; 
            }
            if(height[left] == s) {
                left++;
            } else {
                right--;
            }
        }
        return v;
    }
}

有效三角形的个数

在这里插入图片描述
先给数组排个序.
从后向前固定一个i值(循环1),当left小于right时(循环2),让left和right指向的数相加,判断是否大于i指向的数.

  • 大于i,让sum+=right-left; right–;
  • 小于或等于i,让left向右移.
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

class Solution {
    public int triangleNumber(int[] nums) {
        int left =0;
        int right = 0;
        Arrays.sort(nums);
        int sum = 0;
        for(int i=nums.length - 1;i >= 2;i--) {
            right = i-1;
            left = 0;
            while(left < right) {
                if(nums[left]+nums[right] > nums[i]) {
                    sum += right -left;
                    right--;
                } else {
                    left++;
                }
            }
        }
        return sum;
    }
}

查找总价格为目标值的两个商品

在这里插入图片描述
感觉没什么可说的~

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

三数之和

在这里插入图片描述
自己写的代码,在最开始new ArrayList<List<Integer>>()的时候不会new,忘了.(后来发现不用这么麻烦写e)
还有我最开始是在最内层循环里面使用了list.contains(list2),然后超时了qwq.
后来去看题解,发现还能这样去重!
改了改就成现在这样了,虽然效率还是很低就是了.

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        Arrays.sort(nums);
        int left = 0;
        int right = nums.length - 1;
        for(int i = nums.length-1; i >= 2; i--) {
            if(i < nums.length - 1 && nums[i] == nums[i+1]) continue;
            right = i - 1;
            left = 0;
            while(left < right) {
                if(right != i-1 && right >= left +1 && nums[right] == nums[right+1]) {
                    right--;
                    continue;
                }
                int tmp = nums[left] + nums[right] + nums[i];
                if(tmp > 0) {
                    right--;
                } else if(tmp < 0) {
                    left++;
                } else {
                    ArrayList<Integer> list2 = new ArrayList<>();
                    list2.add(nums[left]); 
                    list2.add(nums[right]); 
                    list2.add(nums[i]); 
                    list.add(list2);
                    right--;
                }
            }
        }
        return list;
    }
}

看了讲解后:

  1. List<List<Integer>> list = new ArrayList<List<Integer>>();其实可以写成
    List<List<Integer>> list = new ArrayList<>(); < >里面可以啥都不写.
  2. 当num[i]<0时,此时最大的数都小于0了,肯定三数之和就不可能等于0了,直接跳过.
  3. 第一次见还能这样写: list.add(new ArrayList(Arrays.asList(nums[left],nums[right],nums[i])));
  4. 当三数和为0时,left和right可以都移动一步(我写的只有right移动一步).
  5. 当三数和为0并且left和right移动后,此时就可以开始去重了,不必在内层循环用if和continue去重(这样多判断了right != i-1),而且我只对right去重了,left还是要经过整个循环emmm,虽然也达到了去重的效果(歪打正着,我当时还在想为啥if和continue不能换成while?被自己蠢哭了qwq).其实没必要,直接同时去重就OK了.

虽然自己第一次写的代码能通过,但是可优化的地方还有很多.没有大佬写的代码优雅~

以下是改后的代码:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);
        int left = 0;
        int right = nums.length - 1;
        for (int i = nums.length - 1; i >= 2; i--) {
            if (nums[i] < 0)
                break;
            if (i < nums.length - 1 && nums[i] == nums[i + 1])
                continue;
            right = i - 1;
            left = 0;
            while (left < right) {
                int tmp = nums[left] + nums[right] + nums[i];
                if (tmp > 0) {
                    right--;
                } else if (tmp < 0) {
                    left++;
                } else {
                    list.add(new ArrayList<Integer>(Arrays.asList(nums[left],nums[right],nums[i])));
                    right--;
                    left++;
                    while (right > left && nums[right] == nums[right + 1]) {
                        right--;
                    }
                    while (right > left && nums[left] == nums[left - 1]) {
                        left++;
                    }
                }
            }
        }
        return list;
    }
}

四数之和

在这里插入图片描述
自己写出来的,中间改了好几次,终于过了~
跟上一题很像.
自己写的代码:

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);

        if (nums[nums.length - 1] < 0 && target > 0) {
            return list;
        }

        for (int i = 0; i < nums.length - 3; i++) {
            if (nums[i] > 0 && target <= 0) {
                break;
            }
            if (i != 0) {
                while (i < nums.length - 3 && nums[i] == nums[i - 1]) {
                    i++;
                }
            }
            for (int j = i + 1; j < nums.length - 2; j++) {
                if (j != i + 1) {
                    while (j < nums.length - 2 && nums[j] == nums[j - 1]) {
                        j++;
                    }
                }

                int left = j + 1;
                int right = nums.length - 1;
                while (left < right) {
                    long sum = nums[left] + nums[right] + nums[i] + nums[j];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        list.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[left], nums[right])));
                        left++;
                        right--;
                        while (left < right && nums[left] == nums[left - 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right + 1]) {
                            right--;
                        }
                    }

                }
            }
        }
        return list;
    }
}

看了讲解后:

  • 去重操作可以放在最后写

改后代码:

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);

        if (nums[nums.length - 1] < 0 && target > 0) {
            return list;
        }

        for (int i = 0; i < nums.length - 3;) {
            if (nums[i] > 0 && target <= 0) {
                break;
            }
            for (int j = i + 1; j < nums.length - 2;) {
                int left = j + 1;
                int right = nums.length - 1;
                while (left < right) {
                    int sum = nums[left] + nums[right] + nums[i] + nums[j];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        list.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[left], nums[right])));
                        left++;
                        right--;
                        while (left < right && nums[left] == nums[left - 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right + 1]) {
                            right--;
                        }
                    }

                }
                j++;
                while (j < nums.length - 2 && nums[j] == nums[j - 1]) {
                    j++;
                }
            }
            i++;
            while (i < nums.length - 3 && nums[i] == nums[i - 1]) {
                i++;
            }
        }
        return list;
    }
}

总结

使用双指针:

  1. 一般是在数组中使用,通常需要对数组进行排序.
  2. 数据要有单调性.
  3. 使用双指针可以把时间复杂度降一维.O(n3)变O(n2);

http://www.niftyadmin.cn/n/5670959.html

相关文章

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 目录 Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 一、简单介绍 二、单例模式 (Singleton Pattern) 1、什么时候使用单例模式 2、单例模式的好处 3、使用单例模式的…

插入与冒泡排序(C++)

\一、插入排序 1 简介 插入排序&#xff0c;也称为直接插入排序&#xff0c;其排序思想和我们平时打扑克牌时排序类似。 2 算法步骤 将第一个元素看作已排序序列&#xff0c;第二个到最后一个看作未排序序列。 第二个元素&#xff0c;与之前已排序号的序列进行对比&#x…

基于SpringBoot+WebSocket实现地图上绘制车辆实时运动轨迹图

实现基于北斗卫星的车辆定位和轨迹图的Maven工程&#xff08;使用模拟数据&#xff09;&#xff0c;我们将使用以下技术&#xff1a; Spring Boot&#xff1a;作为后端框架&#xff0c;用来提供数据接口。Thymeleaf&#xff1a;作为前端模板引擎&#xff0c;呈现网页。Leaflet…

【Linux篇】网络编程基础(笔记)

目录 一、服务器模型 1. C/S 模型 2. P2P模型 二、服务器编程框架 1. I/O处理单元 2. 逻辑单元 3. 网络存储单元 4. 请求队列 三、网络编程基础API 1. socket 地址处理 API &#xff08;1&#xff09;主机字节序和网络字节序 &#xff08;2&#xff09;通用socket地…

开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序

源码简介&#xff1a; 一款给力的开源PHP导航网源码&#xff0c;它不仅外观精美简约&#xff0c;还是个网址导航收录网站/QQ技术导航程序哦&#xff01; 在信息爆炸的时代&#xff0c;找网页就像大海捞针一样难。但是有了像PHP 导航网这样的神器&#xff0c;一切都变得简单了…

Java中stream流及Collectors的常见用法详细汇总!!!

目录 1. Stream流的优势 2. 常用用法 2.1 中间操作 2.1.1filter() 2.1.2 map() 2.1.3 sorted() 2.1.4 distinct() 2.1.5 limit() 2.1.6 skip() 2.2 终端操作 2.2.1 foreach() 2.2.2 collect() 2.2.3 reduce() 2.2.4 count() 2.2.5 anyMatch() 2.3 查找和匹配…

去耦合的一些建议

尽量少用全局变量&#xff0c;以减少状态共享和潜在的副作用。 模块化设计&#xff1a;将代码分成小模块&#xff0c;每个模块独立实现特定功能&#xff0c;减少模块之间的相互依赖。 封装&#xff1a;将数据和操作封装在类中&#xff0c;控制对内部状态的访问&#xff0c;避…

js进阶——作用域闭包

1. 作用域与闭包的基础概念 1.1 作用域 (Scope) 在 JavaScript 中&#xff0c;作用域定义了变量、函数的可访问性。根据变量声明的位置不同&#xff0c;作用域有三种主要类型&#xff1a; 全局作用域&#xff1a;在代码的任何地方都可以访问。函数作用域&#xff1a;只在声明…