DFS回溯剪枝|KMP通过数组记录减少判断子字符串|思路

KMP|DFS回溯剪枝

#1、NC149kmp

初步思路:

两层for循环,一个T的字符开始与

S的字符比较,挨个比较,遇到不同就continue当前T的字符,重复步骤=》效率太低,超时

eg:

T=ABSABABABD

S=ABABD

S!=A时,退回到A与S比较,发现不同;

继续比较A与A,B与B...A与D,不相等了了。

此时,D前面有两个AB,所以指向S的字符索引只往前移动两个字符,退回到AB|(这里)ABD,即第一个AB公共前缀后面的A这里。

继续A与A比较...

KMP的核心思想是:不必退回到开始的地方!比较过的地方就不用重复比较了!
用一个数组去记录应该退回到哪里合适

nextj - 1 表示在 S0...j-1 这个子字符串中,最长的相同前缀和后缀的长度。如果 Sj 和当前要比较的字符不匹配,这意味着 Sj 不能作为前缀的一部分,因此需要找到一个更短的前缀,使得 S0...next\[j-1] 和 Si... 能够匹配。

while 循环用于在 next 数组构建过程中,当当前字符 S.charAt(i) 与 S.charAt(j) 不匹配时,回溯找到 j 的下一个有效值。这个值是 S0...j-1 的最长相同前缀和后缀的长度。

为什么 while 在 if 前面:

当 S.charAt(j) != S.charAt(i) 时,说明当前 j 所对应的前缀无法与 i 位置的字符匹配。此时,需要减小 j 的值,直到找到一个匹配的前缀或者 j 减到 0。

while 循环确保即使在多次不匹配的情况下,j 也能正确地回溯到一个有效的值。如果使用 if 语句,那么在 j > 0 且 S.charAt(j) != S.charAt(i) 时,j 只会减少 1,这可能不会找到正确的前缀匹配。

if 条件的作用:当 S.charAt(j) == S.charAt(i) 时,说明当前字符匹配,j 可以安全地向前移动一位,因为 S0...j 和 Si...(从 i 开始的剩余字符串)有一个共同的字符可以匹配。

nexti = j; 的意义:

无论 S.charAt(j) 和 S.charAt(i) 是否匹配,nexti 都被设置为 j 的当前值。

如果字符匹配,j 已经增加了 1,nexti 反映了这个增加;

如果字符不匹配,j 通过 while 循环回溯到了正确的值,nexti 反映了这个回溯。

java 复制代码
 public int[] getNext(String S) {
        int l = S.length();
        // 获得next数组
        int[] next = new int[l];
        next[0] = 0; //没有公共前缀
        int j = 0;
        // AABAS
        // 01
        for (int i = 1; i < l; i++) {
            while (j > 0 && S.charAt(j) != S.charAt(i)) {
                j = next[j - 1]; //会退一步判断
                // :next[j - 1] 表示在 S[0...j-1] 这个子字符串中,最长的相同前缀和后缀的长度。如果 S[j] 和当前要比较的字符不匹配,这意味着 S[j] 不能作为前缀的一部分,因此需要找到一个更短的前缀,使得 S[0...next[j-1]] 和 S[i...] 能够匹配。
            }
            if (S.charAt(j) == S.charAt(i)) {
                j++;
            }
            next[i] = j; //验证到了这一步了。

        }
        return next;
    }

通过相同的思路使用next\[\]数组,

先while退回到合适的索引位置(针对S而言)

然后是if判断,S的索引++;

如果S的索引走到了S的末尾,那么说明已在T中找到等于S 的字串。

java 复制代码
 import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    public int kmp (String S, String T) {
        // write code here
        int sl=S.length();
        int tl=T.length();
        int count=0;
        int[] next=getNext(S);
        int j=0;
        for(int i=0;i<tl;i++)
        {
            while(j>0&&S.charAt(j)!=T.charAt(i))
            {
                j=next[j-1];
            }
            if(S.charAt(j)==T.charAt(i))
            {
                j++;
            }
            if(j==sl)
            {
                count++;
                j=next[j-1];//会退回去继续判断
            }

        }
        return  count;
    }
    public int[] getNext(String S) {
        int l = S.length();
        // 获得next数组
        int[] next = new int[l];
        next[0] = 0; //没有公共前缀
        int j = 0;
        // AABAS
        // 01
        for (int i = 1; i < l; i++) {
            while (j > 0 && S.charAt(j) != S.charAt(i)) {
                j = next[j - 1]; //会退一步判断
                // :next[j - 1] 表示在 S[0...j-1] 这个子字符串中,最长的相同前缀和后缀的长度。如果 S[j] 和当前要比较的字符不匹配,这意味着 S[j] 不能作为前缀的一部分,因此需要找到一个更短的前缀,使得 S[0...next[j-1]] 和 S[i...] 能够匹配。
            }
            if (S.charAt(j) == S.charAt(i)) {
                j++;
            }
            next[i] = j; //验证到了这一步了。

        }
        return next;
    }
}

第二次出现的j=nextj-1; 不是在 "j 往前走的时候已经经历过了" 的位置停止,而是在每次找到匹配或确定不匹配后,为下一次可能的匹配做准备。将 j 设置为 nextj-1 允许我们在 T 的下一个字符处继续搜索。这是因为 S0...next\[j-1] 已经是一个匹配的前缀,我们可以从这个前缀的末尾开始,尝试与 T 中的下一个字符匹配。

避免重复匹配:如果我们将 j 重置为 0,那么我们会在 T 中重复计算已经匹配的 S 的部分。使用 nextj-1 确保我们不会重复计算,并且可以继续从 T 的下一个字符开始搜索。

优化搜索过程:通过这种方式,KMP 算法可以在找到一次匹配后,快速跳到可能的下一个匹配位置,而不必从头开始搜索,从而提高搜索效率。

#2、DFS回溯剪枝法

想到了树,不同的子节点,深度遍历下去有不同的路径结果。

分析题意:

X.X.X.X 每个X∈0,255, 要求不同出现0M,M∈0,9

剩余X的个数1<=字符串的长度<=剩余X的个数3(因为X是1到3位数组成)

树有不同的子节点,子节点的子节点...

即树的不同层代表着字符串s的剩余长度,即当前走到了s的哪一位了(索引位置cur)。

java 复制代码
  public void dfs(String s,int cur)
      {
        if(tmp.size()==4&& cur==s.length())
        {
            res.add(tmp.get(0)+"."+tmp.get(1)+"."+tmp.get(2)+"."+tmp.get(3));
        }
        //jianzhi 
        if(s.length()-cur>3*(4-tmp.size()))//每段大于3
        {
            return;
        }
        if(s.length()-cur<4-tmp.size())//小于1
        {
            return;
        }
        int num=0;
        //当前节点的操作,即X.X.X.X中的一个X   
        //X:从s的某个位置(索引cur)开始,i∈[cur,cur+2],且i<s.length()
        for(int i=cur;i<cur+3&&i<s.length();i++)//一个一个数字的判断
        {//0,1,2
            num=num*10+(s.charAt(i)-'0');//数字
            //百十个位
            if(num<0||num>255)//数字越界了就不要了,

            {
                break;
            }
            //
            tmp.add(s.substring(cur,i+1));//截取【0,255】之间的数字
            //判断写一个位置上可能的X
            dfs(s,i+1);//继续判断
            tmp.remove(tmp.size()-1);//为啥?
            // 回溯允许算法"回到"上一个状态,重新考虑不同的数字组合。
            if(num==0)
            {
                break;//只能0.不能02|09这些
            }


        }
      }
java 复制代码
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @return string字符串ArrayList
     */
     ArrayList<String> res=new ArrayList<>();
    ArrayList<String> tmp=new ArrayList<>();
     
    public ArrayList<String> restoreIpAddresses (String s) {
        // write code here
        // 限制每一个[1,3]位置以及如果是3,那么数字小于255,否则,挪给别的,
        // >1则不能以0开头
        // 根据位数判断
        dfs(s,0);
        return res;

      }
      public void dfs(String s,int cur)
      {
        if(tmp.size()==4&& cur==s.length())
        {
            res.add(tmp.get(0)+"."+tmp.get(1)+"."+tmp.get(2)+"."+tmp.get(3));
        }
        //jianzhi 
        if(s.length()-cur>3*(4-tmp.size()))//每段大于3
        {
            return;
        }
        if(s.length()-cur<4-tmp.size())//小于1
        {
            return;
        }
        int num=0;
        for(int i=cur;i<cur+3&&i<s.length();i++)//一个一个数字的判断
        {//0,1,2
            num=num*10+(s.charAt(i)-'0');//数字
            if(num<0||num>255)//数字越界了就不要了,

            {
                break;
            }
            tmp.add(s.substring(cur,i+1));//截取【0,255】之间的数字
            dfs(s,i+1);//继续判断
            tmp.remove(tmp.size()-1);//为啥?
            // 回溯允许算法"回到"上一个状态,重新考虑不同的数字组合。
            if(num==0)
            {
                break;//只能0.不能02|09这些
            }


        }
      }
}
相关推荐
江畔柳前堤6 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore7 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考8 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX9 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面10 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号10 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽10 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI10 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者12 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者12 小时前
【征程6】校准量化中HistogramObserver解析
算法