【寻找one piece的算法之路】——双指针算法!他与她是否会相遇呢?

💐个人主页:初晴~

📚相关专栏:寻找one piece的刷题之路


什么是双指针算法 

双指针算法是一种常用的编程技巧,尤其在处理数组字符串问题时非常有效。这种方法的核心思想是使用两个指针遍历数据结构,这两个指针通常分别位于数据结构的起始位置或某些特定位置,通过移动这两个指针来达到解决问题的目的。双指针算法可以显著减少时间复杂度,使其从O(n2)O(n2)降低到O(n)O(n),甚至在某些情况下可以达到O(log⁡n)O(logn)。

常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针
对撞指针:⼀般⽤于顺序结构中,也称左右指针。
  • 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
  • 对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循 环),也就是:
  • left == right (两个指针指向同⼀个位置)
  • left > right (两个指针错开)
快慢指针:⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
这种⽅法对于处理环形链表数组⾮常有⽤。
其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想。
快慢指针的实现⽅式有很多种,最常⽤的⼀种就是:
在⼀次循环中,每次让慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢

一、复写零

题目链接

题目描述:

题目分析:

题目的意思就是遍历原数组,每遇到一次0,就将后面所有的数水平向右移动一位,并再插入一个0,最后仍丢弃溢出的数据,仍取原来的数组长度的数据。

分析:

如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆
盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两
步:
  1. 先找到最后⼀个复写的数

我们先初始化两个指针 cur = 0 dest = -1

cur < n 的时候,⼀直执⾏下⾯循环:
判断 cur 位置的元素:
如果是 0 的话, dest 往后移动两位;
否则, dest 往后移动⼀位。
判断 dest 时候已经到结束位置,如果结束就终⽌循环;
如果没有结束, cur++ ,继续判断。

注意:

要判断 dest 是否越界到 n 的位置:
如果越界,执⾏下⾯三步:
  1. n - 1 位置的值修改成 0
  2. cur 向移动⼀步;
  1. dest 向前移动两步。

  2.然后从后向前进⾏复写操作

cur 位置开始往前遍历原数组,依次还原出复写后的结果数组:

判断 cur 位置的值:
1. 如果是 0 dest 以及 dest - 1 位置修改成 0 dest -= 2
2. 如果⾮ 0: dest 位置修改成 0 dest -= 1
cur-- ,复写下⼀个位置

代码实现:

class Solution {
    public void duplicateZeros(int[] arr) {
        int len=arr.length,cur=0,dest=-1;
        while(cur<len){
            if(arr[cur]==0){
                dest+=2;
            }else{
                dest++;
            }
            if(dest==len-1)break;
            if(dest>len-1){
                arr[len-1]=0;
                cur--;
                dest=len-2;
                break;
            }
            cur++;
        }
        if(dest==cur)return;
        while(cur>=0){
            if(arr[cur]!=0){
                arr[dest--]=arr[cur--];
            }else{
                arr[dest--]=0;
                arr[dest--]=0;
                cur--;
            }
        }
    }
}


二、盛⽔最多的容器

题目描述:

题目分析:

我们可以设两个指针 left right 分别指向容器的左右两个端点,此时容器的容积 :
v = (right - left) * min( height[right], height[left])
容器的左边界为 height[left] ,右边界为 height[right]
为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」
如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:
  • 容器的宽度⼀定变⼩
  • 由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是可能会超过原来左边界的柱⼦⾼度,因此容器的容积可能会增⼤。
  • 如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的
由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继续去判断下⼀个左右边界。
  • 当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到 left right 遇。期间产⽣的所有的容积⾥⾯的最⼤值,就是最终答案。

代码实现:

class Solution {
    public int maxArea(int[] height) {
        int l=0,r=height.length-1,max=0,t;
        while(l<r){
            if(height[l]<height[r]){
                t=height[l]*(r-l);
                l++;
            }else{
                t=height[r]*(r-l);
                r--;
            }
            if(max<t)max=t;
        }
        return max;
    }
}


三、有效三⻆形的个数

题目链接

题目描述:

题目分析:

首先我们得知道,三条边能构成三角形的充要条件是:任意两边之和大于第三边。

不过事实上,我们只要保证两条短边和大于最长边即可。

因此,我们可以先将数组从小到大进行排序,接着用一个指针 j 固定一个「最⻓边」,然后在⽐这条边⼩的有序数组中找出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指针」来优化

设最⻓边枚举到 j 位置,区间 [left, right] 是 j 位置左边的区间(也就是⽐它⼩的区间):

  • 如果 nums[left] + nums[right] > nums[i]

说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成⽐ nums[j] ⼤的⼆元组

则满⾜条件的有 right - left

此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进⼊下⼀轮判断

  •  如果 nums[left] + nums[right] <= nums[i]

说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满⾜条件的⼆元组

left 位置的元素可以舍去, left++ 进⼊下轮循环

代码实现:

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


 四、三数之和

题目链接

题目描述:

题目分析:

这题其实与上一题十分相似,都是要找一个满足一定条件的三元组,因此同样,我们可以采用先排序,接着用一个指针 j 固定一个数 a,接着在之后的区间内,利用双指针算法找到两数之和等于-a即可

需要注意的是,题目要求进行「去重」操作:

  1. 找到⼀个结果之后, left right 指针要「跳过重复」的元素;
  2. 当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素

代码实现:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        int n=nums.length;
        for(int j=0;j<n;){
            if(nums[j]>0)break;
            int l=j+1,r=n-1;
            int target=-nums[j];
            while(l<r){
                int sum=nums[l]+nums[r];
                if(sum==target){
                    ans.add(new ArrayList<Integer>(Arrays.asList(nums[l],nums[r],                                         
                                                                                nums[j])));
                    if(nums[r]==nums[l])break;
                    l++;
                    r--;
                    while(l<r&&nums[r+1]==nums[r])r--;
                    while(l<r&&nums[l-1]==nums[l])l++;
                }else if(sum>target){
                    r--;
                }else{
                    l++;    
                }  
            }
            j++;
            while(j<n&&nums[j]==nums[j-1])j++;
        }
        return ans;
    }
}


五、四数之和

题目链接

题目描述:

题目描述:

这题的思想其实也与三数之和类似,我们只需要先排序,再用一个指针 i 固定一个数 a在这个数 a 的后⾯区间上,利⽤「三数之和」找到三个数,使这三个数的和等于 target - a 即可

代码实现:

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        int n=nums.length;
        long t,q;
        Arrays.sort(nums);
        List<List<Integer>> ans=new ArrayList<List<Integer>>();
        for(int i=0;i<n;){
            t=target;
            t-=nums[i];
            //if(t<0)return ans;
            for(int j=i+1;j<n;){
                q=t;
                q-=nums[j];
                int l=j+1,r=n-1;
                while(l<r){
                    int sum=nums[l]+nums[r];
                    if(sum==q){
                        List<Integer> list=new ArrayList<Integer>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[l]);
                        list.add(nums[r]);
                        ans.add(list);
                        l++;
                        r--;
                        while(l<r&&nums[l]==nums[l-1])l++;
                        while(l<r&&nums[r]==nums[r+1])r--;
                    }else if(sum>q)r--;
                    else l++;
                }
                j++;
                while(j<n&&nums[j]==nums[j-1])j++;
            }
            i++;
            while(i<n&&nums[i]==nums[i-1])i++;
        }
        return ans;
    }
}


 总结

双指针技巧的关键点

  1. 初始化:通常一个指针指向序列的开始位置,另一个指针指向序列的结束位置或某个特定位置。
  2. 移动规则:根据问题的具体情况定义指针的移动规则,如当条件不满足时向前或向后移动。
  3. 更新状态:在每次移动指针之后更新当前的状态,如累加、记录最大值等。
  4. 退出条件:当两个指针相遇或某个指针超出界限时结束循环。

双指针算法是一种简单而有效的算法技巧,通过维护两个指针的状态来简化问题的复杂度。合理地运用双指针法,可以帮助我们在处理数组和字符串问题时更加高效地达成目标。


那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

<think>嗯,用户的问题是关于如何将逆向最大匹配法(RMM)的代码修改为正向最大匹配算法(FMM)的实现,并提供完整的代码。首先,我需要回顾一下这两种算法的基本原理,然后看看代码需要调整哪些部分。 根据引用[1]和[3],正向最大匹配是从左到右扫描文本,每次取最长的可能字符进行匹配,而逆向最大匹配则是从右到左,取末端的字符进行匹配。所以,RMM和FMM的主要区别在于扫描方向和匹配字段的位置。 用户提供的逆向最大匹配代码可能涉及以下几个关键点:文本处理方向、匹配字段的截取方式、字典的逆序处理。例如,RMM在处理时可能需要将文本逆序,或者从末尾开始取词,而FMM则需要从开头开始,并且不需要逆序处理。 首先,需要检查逆向最大匹配的代码结构。通常,逆向算法会从字符串的末尾开始,每次取末尾的i个字符作为匹配字段,如果匹配失败,就减少一个字符。而正向算法则是从开头取i个字符,同样匹配失败则减少末尾的一个字符。 然后,代码中的循环方向可能需要调整。例如,逆向算法可能使用逆序遍历,而正向算法则需要正序遍历。此外,字典在逆向算法中可能需要逆序存储,而正向算法则不需要,直接使用原字典。 接下来,考虑分词的方向。比如,逆向算法可能先处理整个句子的逆序,而正向算法直接处理原句子。因此,在修改代码时,可能需要去除逆序处理步骤,改为正向处理。 另外,匹配字段的截取方式也需要改变。逆向算法可能从当前位置向后的子串,而正向算法应该从当前位置向前的子串。例如,在逆向中,可能取current_pos - max_len到current_pos的子串,而正向应该取current_pos到current_pos + max_len的子串,但要注意边界条件。 还需要调整匹配失败后的处理方式。逆向算法可能去掉前面的字符,而正向算法需要去掉后面的字符。例如,在逆向中,如果匹配失败,可能截断最后一个字符,而正向则是截断前面的字符,不过实际上应该是截断后面,因为正向是从左到右取最大可能的词,所以当max_len不匹配时,逐渐减少长度,即从最长可能开始,每次减少一个字符,直到找到匹配或长度为1。 可能还需要调整字典的加载方式。逆向算法的字典可能是逆序存储的,而正向算法需要原字典,所以需要确保字典中的词是正常顺序的。 最后,测试代码是否正确,例如用示例句子进行测试,检查分词结果是否符合预期。比如,使用句子“研究生命科学”,正向最大匹配应该分成“研究生 命 科学”,而逆向可能分成“研究 生命 科学”,但这里要看字典中的词是什么。 总结一下,修改步骤可能包括: 1. 改变扫描方向,从左到右处理文本。 2. 调整子串的截取方式,从当前位置开始取max_len长度的子串。 3. 匹配失败时,减少子串的长度,从右侧截断。 4. 使用正常顺序的字典,不需要逆序处理。 5. 调整循环中的指针移动方式,匹配成功后移动相应的长度。 现在,我需要根据这些思考步骤,将逆向代码转换为正向,并确保语法正确。同时,引用相关的参考资料来支持修改的正确性,比如引用[1]和[3]说明正向算法的原理。</think>### 逐步说明 将逆向最大匹配(RMM)改为正向最大匹配(FMM)需要修改以下核心部分: 1. **扫描方向**:RMM从右向左扫描,FMM从左向右扫描 2. **匹配字段选取**:RMM取末尾字符,FMM取起始字符 3. **词典处理**:RMM需要逆序词典,FMM使用原序词典 4. **指针移动**:RMM反向移动指针,FMM正向移动指针 ### 代码实现 ```python def fmm_segment(text, word_dict, max_len): result = [] index = 0 # 起始位置指针 text_length = len(text) while index < text_length: size = min(max_len, text_length - index) # 剩余长度计算 while size > 0: piece = text[index:index+size] # 正向截取字段 if piece in word_dict or size == 1: # 匹配成功或单字 result.append(piece) index += size # 正向移动指针 break size -= 1 # 减少匹配长度 return result # 测试示例 word_dict = {'研究', '研究生', '生命', '科学', '起源'} text = "研究生命科学" print(fmm_segment(text, word_dict, max_len=4)) # 输出:['研究生', '命', '科学'] ``` ### 关键修改点说明 1. **指针初始化**:`index=0`改为从左端开始[^3] 2. **字段截取方向**:`text[index:index+size]`实现正向截取[^1] 3. **词典验证**:直接使用原序词典验证,无需逆序处理[^4] 4. **终止条件**:`size==1`保证单字切分,避免无限循环[^3] ### 性能优化建议 1. 使用**Trie树**结构加速词典查找 2. 添加**未登录词处理**模块 3. 实现**动态窗口调整**机制
评论 44
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值