【Day5】每天三算法

题目

CC12 拆分词句

描述

给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。

例如:

给定s=“nowcode”;
dict=["now", "code"].
返回true,因为"nowcode"可以被分割成"now code".
思考

这道题,虽然标注为动态规划,但是我们可以用回溯的思想解决这道题

首先,我们先从 str 中,切割出前部一部分包含在 set 里,接着,剩下的部分重复前面的操作

如果发现后面无论切多少,都没有部分可以在 set 中,那么,我们就回溯到之前成功的部分,然后增加成功部分切分的字符创长度,并重复之前的操作,直到成功为止

public boolean wordBreak(String s, Set<String> dict) {
  return backTrack(s,dict,0,0);
}

public boolean backTrack(String str,Set<String> dict,int l,int r) {
  // 递归终止条件
  if (r>=str.length()) return true;
  boolean res = false;
  for (int i = l; i <str.length(); i++) {
    if (dict.contains(subString(str,l,i))) {
      res = res || backTrack(str,dict,i+1,i+1);
    }
  }
  return res;
}

// 包头包尾的 subString 函数
public static String subString(String str,int l,int r) {
  return str.substring(l,r+1);
}

不出意料的,回溯法成功超时了

其会超时的主要原因,还是以为重复计算

重复计算的例子如下:

输入: s = "AAAleetcodeB", wordDict = ["leet", "code","A", "AA", "AAA"]
for 循环中:
首次递归: s = 'A' + helper('AAleetcodeB'), 最终检查不符合;
二次递归: s = 'AA' + helper('AleetcodeB'), 最终检查不符合;
三次递归: s = 'AAA' + helper('leetcodeB'), 最终检查不符合;

可以发现,在上面的例子中,leetcodeB 是一直被重复计算的

我们如果能将计算过的结果保存起来,下次直接用,就能节省大量时间

记忆有两种办法可供参考:

  • 动态规划
  • 记忆化数组进行搜索

动态规划:

dp[i] 表示 str(0,i) 是否符合条件

状态转移方程如下:

dp[i] = 对于任意j dp[j] && dict.contains(subString(j+1,i))

题解

使用动态规划的题解

public class CC12_wordBreak {

    public static void main(String[] args) {
        CC12_wordBreak solu = new CC12_wordBreak();
        Set<String> set = new HashSet<>();
        set.add("now");
        set.add("code");
        //set.add("a");
        System.out.println(solu.wordBreak("nowcode",set));
    }
  
    public boolean wordBreak(String s, Set<String> dict) {
        // 状态转移方程 dp[i] = 对于任意j dp[j] && dict.contains(str.subString(j+1,i))
        boolean[] dp = new boolean[s.length() + 1];
        dp[0]=true;
        for (int i = 1; i <=s.length() ; i++) {
            for (int j = 0; j < i; j++) {
              	//  这里为什么是 j 而不是 j+1 开始的
              	// 是因为我们的 dp[i] 表示的是长度
              	// 而咱们的 j ,表示的是下标
              	// 所以下面的 dp[j] 表示的是到长度为 j的部分
              	// 但是最后一位下标还是 j-1
              	// 所以,咱们这边的 subString 就应该是从 (j-1)+1 即 j 开始的
                String curr = s.substring(j, i);
                if (dp[j] && dict.contains(curr)) {
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

CC9 找环形链表入口

描述

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

思考

判断是否有环很容易,我们使用快慢指针就可以解决

问题是怎么找环的入口

其实这是有诀窍的,当快慢指针相碰时,我们再新开一个指针,从起点触发,让 slow 指针和这个新指针同步以一步一格的速度前进,直到二者相碰,相碰的位置就是环的入口

具体推导可以看力扣的题解:链表环入口题解

那有人会问:上面的过程,万一忘记了怎么办?我们其实可以将它总结为一个小短语:快慢遇,二慢碰

题解

知道思路,代码就很好写了

public ListNode EntryNodeOfLoop(ListNode pHead) {
        if (pHead==null || pHead.next==null) return null;
        ListNode fast = pHead;
        ListNode slow = pHead;
        while (fast!=null && fast.next!=null) {
            fast=fast.next.next;
            slow=slow.next;
            // 快慢相遇,说明成环
            if (fast==slow) {
                ListNode tmp = pHead;
                while (tmp!=slow) {
                    tmp=tmp.next;
                    slow=slow.next;
                }
                return tmp;
            }
        }
        return null;
    }

CC16 分糖果

描述

有N个小朋友站在一排,每个小朋友都有一个评分

你现在要按以下的规则给孩子们分糖果:

  • 每个小朋友至少要分得一颗糖果
  • 分数高的小朋友要他比旁边得分低的小朋友分得的糖果多

你最少要分发多少颗糖果?

思考

这道题你上网找题解,都说是固定思路,左遍历+右遍历

左规则是 ,从左往右遍历,如果ratings[i]>ratings[i-1] ,那么第 i 个小孩的糖要比第 i-1 个小孩的多一个

右规则和做规则就有少许不同了,从右往左遍历,如果ratings[i]>ratings[i+1],这个时候我们不能无脑 +1,而是要判断第 i 个小孩原有的和第 i+1 个小孩 +1后的糖果数的比较,选最大的那个就行。我们不能因为走了一次右规则,就把左规则打破了吧

题解
public int candy (int[] ratings) {
        int[] candies = new int[ratings.length];
        for (int i = 0; i < candies.length; i++) {
            candies[i]=1;
        }
        for (int i = 1; i < candies.length; i++) {
            if (ratings[i]>ratings[i-1]) candies[i]=candies[i-1]+1;
        }
        for (int i = candies.length-2; i>=0; i--) {
            if (ratings[i]>ratings[i+1]) candies[i]=Math.max(candies[i+1]+1,candies[i]);
        }
        int count = 0;
        for (int candy : candies) {
            count+=candy;
        }
        return count;
    }

小结

不刷多了,一天就三题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值