1.通配符匹配
题解:
其中,横轴为string s,纵轴为pattern p
这个表第(m,n)个格子的意义是:【p从0位置到m位置】这一整段,是否能与【s从0位置到n位置】这一整段匹配
也就是说,如果表格的下面这一个位置储存的是T(True):
说明,"adcb"和"a*b"这一段是可以匹配的,你不用管前面发生了什么,你只要知道,这两段是匹配的,这就是动态规划为什么很棒棒
描述:如果这一行是"*",可以从上一行的任意T出发,向下、右铲平这一行的任意道路
在讲解另一个狠角色"?"之前,我们再往下走两步,巩固一下之前的知识。
接下来我们在pattern里遇到了一个b,它是一个普通字母,所以只能从上一行某个T往右下角走到达,而且字母匹配才能记录T,于是我们发现可以走这两步:
描述:如果这一行是"?",从上一行某个T也只能向右下角走,但不必匹配字母就能记录T
示例:
class Solution {
public boolean isMatch(String ss, String pp) {
int n = ss.length(), m = pp.length();
// 技巧:往原字符头部插入空格,这样得到 char 数组是从 1 开始,而且可以使得 f[0][0] = true,可以将 true 这个结果滚动下去
ss = " " + ss;
pp = " " + pp;
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
// f(i,j) 代表考虑 s 中的 1~i 字符和 p 中的 1~j 字符 是否匹配
boolean[][] f = new boolean[n + 1][m + 1];
f[0][0] = true;
for (int i = 0; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (p[j] == '*') {
f[i][j] = f[i][j - 1] || (i - 1 >= 0 && f[i - 1][j]);
} else {
f[i][j] = i - 1 >= 0 && f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?');
}
}
}
return f[n][m];
}
}
2.解码方法
我们先来理解一下题目的解码规则,如样例所示,s = "226",可以分为两种情况:
1、将每一位数字单独解码,因此可以解码成"BBF"(2 2 6)。
2、将相邻两位数字组合起来解码(组合的数字范围在10 ~ 26之间),因此可以解码成"BZ"(2 26), "VF"(22 6)。
两种情况是或的关系,互不影响,将其相加,那么226共有3种不同的解码方式,下面来讲解动态规划的做法。
状态表示:f[i]表示前i个数字一共有多少种解码方式,那么,f[n]就表示前n个数字一共有多少种不同的解码方式,即为答案。
设定字符串数组为s[](数组下标从1开始),考虑最后一次解码方式,因此对于第i - 1和第i 个数字,分为两种决策:
1、如果s[i]不为0,则可以单独解码s[i],由于求的是方案数,如果确定了第i个数字的翻译方式,那么解码前i个数字和解码前i - 1个数的方案数就是相同的,即f[i] = f[i - 1]。(s[]数组下标从1开始)
2、将s[i]和s[i - 1]组合起来解码( 组合的数字范围在10 ~ 26之间 )。如果确定了第i个数和第i - 1个数的解码方式,那么解码前i个数字和解码前i - 2个数的方案数就是相同的,即f[i] = f[i - 2]。(s[]数组下标从1开始)
最后将两种决策的方案数加起来,因此,状态转移方程为: f[i] = f[i - 1] + f[i - 2]
。
class Solution {
public int numDecodings(String s) {
int n = s.length();
int[] f = new int[n + 10];
f[0] = 1;
for(int i = 1; i <= n;i ++)
{
if(s.charAt(i - 1) != '0') f[i] = f[i - 1]; //单独解码s[i - 1]
if(i >= 2)
{
int t = (s.charAt(i - 2) - '0') * 10 + s.charAt(i - 1) - '0';
if(t >= 10 && t <= 26) f[i] += f[i - 2]; //将s[i - 2] 和 s[i - 1]组合解码
}
}
return f[n];
}
}
3.反转链表 ||
这里首先讲解一下如何反转一个链表
public ListNode reverseList(ListNode head) { //1
ListNode prev = null; // 2
ListNode curr = head; // 3
while (curr != null) { //4
ListNode nextTemp = curr.next; //5
curr.next = prev; // 6
prev = curr; //7
curr = nextTemp; //8
}
return prev; //9
}
第一行代码图解
public ListNode reverseList(ListNode head) { //1
我们顺着题目描述意思,假设链表就有1、2、3个元素吧,后面还跟着一个null,又因为输入是ListNode head,所以这个即将要反转的链表如下:
ListNode prev = null; // 2
将null赋值给prev,即prev指向null,可得图如下:
ListNode curr = head;
将链表head赋值给curr,即curr指向head链表,可得图如下:
循环部分代码图解
while (curr != null) { //4
ListNode nextTemp = curr.next; //5
curr.next = prev; // 6
prev = curr; //7
curr = nextTemp; //8
}
循环部分是链表反转的核心部分,我们先走一遍循环,图解分析一波。
因为curr指向了head,head不为null,所以进入循环。先来看第5行:
ListNode nextTemp = curr.next; //5
把curr.next 赋值给nextTemp变量,即nextTemp 指向curr的下一节点(即节点2),可得图如下:
curr.next = prev; // 6
然后我们看执行到第7行
prev = curr; //7
把curr赋值给prev,prev指向curr,图解如下:
接着,我们执行到第8行:
curr = nextTemp; //8
把nextTemp赋值给curr,即curr指向nextTemp,图解如下:
至此,第一遍循环执行结束啦,回到循环条件,curr依旧不为null,我们继续图解完它。
执行完ListNode nextTemp = curr.next;
后:
执行完curr.next = prev;
后:
大概是这么个思路,依次循环。
接下来详细讲解本题:
- 第 1 步:先将待反转的区域反转;
- 第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ
接下来是这道题的完整代码:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
ListNode rightNode = pre;
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
ListNode leftNode = pre.next;
ListNode curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
}
private void reverseLinkedList(ListNode head) {
// 也可以使用递归反转一个链表
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
}