1.电话号码组合
这道题是典型的回溯方法,直接看图和代码理解就好了,没什么好说的。
class Solution {
//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
//这里也可以用map,用数组可以更节省点内存
String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
//注意边界条件
if(digits==null || digits.length()==0) {
return new ArrayList<>();
}
iterStr(digits, new StringBuilder(), 0);
return res;
}
//最终输出结果的list
List<String> res = new ArrayList<>();
//递归函数
void iterStr(String str, StringBuilder letter, int index) {
//递归的终止条件,注意这里的终止条件看上去跟动态演示图有些不同,主要是做了点优化
//动态图中是每次截取字符串的一部分,"234",变成"23",再变成"3",最后变成"",这样性能不佳
//而用index记录每次遍历到字符串的位置,这样性能更好
if(index == str.length()) {
res.add(letter.toString());
return;
}
//获取index位置的字符,假设输入的字符是"234"
//第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
//subString每次都会生成新的字符串,而index则是取当前的一个字符,所以效率更高一点
char c = str.charAt(index);
//map_string的下表是从0开始一直到9, c-'0'就可以取到相对的数组下标位置
//比如c=2时候,2-'0',获取下标为2,letter_map[2]就是"abc"
int pos = c - '0';
String map_string = letter_map[pos];
//遍历字符串,比如第一次得到的是2,页就是遍历"abc"
for(int i=0;i<map_string.length();i++) {
//调用下一层递归,用文字很难描述,请配合动态图理解
letter.append(map_string.charAt(i));
//如果是String类型做拼接效率会比较低
//iterStr(str, letter+map_string.charAt(i), index+1);
iterStr(str, letter, index+1);
letter.deleteCharAt(letter.length()-1);
}
}
}
2.k个一组反转链表
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode hair = new ListNode(0); // 创建一个哑节点(hair),其next指向头节点head
hair.next = head; // 哑节点的next指向链表的头节点
ListNode pre = hair; // pre作为前一个节点的指针,初始指向哑节点
while (head != null) { // 循环直到链表尾部
ListNode tail = pre; // tail作为当前k个节点组的尾部节点的前一个节点
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail.next; // 移动tail指针,查找尾部节点
if (tail == null) { // 如果剩余节点不足k个,则停止反转
return hair.next; // 返回反转后的链表头节点
}
}
ListNode nex = tail.next; // nex指向当前尾部节点的下一个节点,即下一组的头节点
ListNode[] reverse = myReverse(head, tail); // 调用myReverse函数反转当前k个节点
head = reverse[0]; // 反转后,head指向新的头部节点
tail = reverse[1]; // tail指向新的尾部节点
// 把子链表重新接回原链表
pre.next = head; // 将反转后的子链表接到前一个节点后面
tail.next = nex; // 将新尾部节点的next指向下一组的头节点
pre = tail; // 更新pre为当前尾部节点,为下一次循环做准备
head = tail.next; // 更新head为下一个待反转的子链表的头节点
}
return hair.next; // 返回反转后的链表头节点
}
public ListNode[] myReverse(ListNode head, ListNode tail) {
ListNode prev = tail.next; // prev指向尾部节点的下一个节点,即反转后的新尾部
ListNode p = head; // p作为当前遍历节点
while (prev != tail) { // 当prev未到达尾部节点时,继续反转
ListNode nex = p.next; // 保存当前节点的下一个节点
p.next = prev; // 将当前节点的next指向prev,实现反转
prev = p; // 更新prev为当前节点,向前移动
p = nex; // 更新p为下一个待反转的节点
}
return new ListNode[]{tail, head}; // 返回包含新尾部和新头部节点的数组
}
}
3.交错字符串
解题思路:
可定义 boolean[][] dp ,dp[i][j] 代表 s1 前 i 个字符与 s2 前 j 个字符拼接成 s3 的 i+j 字符,也就是存在目标路径能够到达 i ,j ;
状态方程:
边界 1:dp[0][0] = true;
边界 2:if i=0 : dp[0]dp[j] = s2[0-j) equals s3[0,j) 遇到 false 后面可以直接省略
边界 3:if j=0 : dp[i]dp[0] = s1[0-i) equals s3[0,i) 遇到 false 后面可以直接省略
其他情况,到达(i,j)可能由(i-1,j)点向下一步,选择 s1[i-1] 到达;也可能由 (i,j-1) 点向右一步,选择 s2[j-1] 到达;
dp[i,j] = (dp[i-1][j] &&s3[i+j-1] == s1[i-1]) || (dp[i][j-1] && s3[i+j-1] == s2[j-1])
class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
int m = s1.length(), n = s2.length();
if (s3.length() != m + n) return false;
// 动态规划,dp[i,j]表示s1前i字符能与s2前j字符组成s3前i+j个字符;
boolean[][] dp = new boolean[m+1][n+1];
dp[0][0] = true;
for (int i = 1; i <= m && s1.charAt(i-1) == s3.charAt(i-1); i++) dp[i][0] = true; // 不相符直接终止
for (int j = 1; j <= n && s2.charAt(j-1) == s3.charAt(j-1); j++) dp[0][j] = true; // 不相符直接终止
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = (dp[i - 1][j] && s3.charAt(i + j - 1) == s1.charAt(i - 1))
|| (dp[i][j - 1] && s3.charAt(i + j - 1) == s2.charAt(j - 1));
}
}
return dp[m][n];
}
}