72.编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:插入一个字符,删除一个字符,替换一个字符。
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
- 动规。首先题目说了可以对word1或者word2操作,只要变成相同的就可以,没有要求变成两个字符串的公共子串,那么删除操作和填入操作是等价的,对word1删除等价于对word2添加。
- 不考虑删除实际上只有三种操作:①在word1添加②在word2添加③替换word1或者word2字符。这里说明一下,③中把1中的字符替换成2的字符和把2的字符替换成1的字符是等价的;而①和②是不等价的,比如"ab"和"a"你只能在word2添加字符b而不能在word1添加。
- dp[i][j]表示前word1前i个字符和word2前j个字符的编程距离,如果word1[i]不等于word2[j],那么就可以通过word1或者word2尾元素进行上述三种操作变成相同的,因此状态转移方程如下:dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)。
需要注意的是,如果word1[i]等于word2[j],那么相同元素就不需要动了,状态转移到字符串前移一个元素dp[i][j]=dp[i-1][j-1]。这种方法对于需要在头元素进行操作的情况也是适用的,举个例子"aaabcdefggg"和"bcdef",计算时dp[i-1][j]会前溯到dp[i-3][j]把不等的去掉,实际上dp[i-1][j]的结果就是已经考虑了把word1尾指针前移的最优解。这种dp写法是基于对尾元素进行操作的,也可以写成对首元素操作,那样的话遍历方向要从字符串后往前。
105.从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
- 一开始的想法是通过判断先序遍历节点的后驱在中序遍历中的索引下标是不是在该节点之前,以此来确定一个节点的左孩子,但是要想直接找到右孩子情况非常复杂。(还要区分当前节点是左孩子还是右孩子,其左孩子的右孩子是不是为空…)。
- 关于树的算法从原子操作的角度思考非常重要,树的定义本来就是一种递归,子树也是一个树,左子树和右子树拼接起来就是一颗新树。中序序列中一个节点的左边全是左子树,右边全是右子树,通过左子树的区间长度就可以找到前序序列中的左子树区间。基于这种思想可以用迭代方法解决这个问题:
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<preorder.length;i++)
hashmap.put(inorder[i],i);
return build(preorder,inorder,0,inorder.length-1,0,inorder.length-1);
}
public TreeNode build(int[] preorder, int[] inorder,int pl,int pr,int il,int ir)
{
if(il>=inorder.length||pl>pr||il>ir)
return null;
int index=hashmap.get(preorder[pl]);
int len=index-il,len2=ir-index;
TreeNode root=new TreeNode(preorder[pl]);
if(pl==pr)
return root;
root.left=build(preorder,inorder,pl+1,pl+len,il,index-1);
root.right=build(preorder,inorder,pl+len+1,pl+len+len2,index+1,ir);
return root;
}
151.翻转字符串里的单词
给你一个字符串 s ,逐个翻转字符串中的所有 单词 。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。
输入:s = “a good example”
输出:“example good a”
- 用首尾双指针,先把首尾多余的空格去掉,然后从后往前遍历把一个个单词添加进结果里,每次注意保存单词最后一个字母。
104.二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
1.略
76.最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
- 双指针法,每一轮循环先让right指针先向右移动,直至指针区间包含模式串,再让left开始向右移动直至left+1到right区间(首次)不包含模式串,此时双指针之间即为一个覆盖子串。但是这道题有一个比较麻烦的地方,模式串中的字符可以重复,因此难点在于两个地方,如何判断在right指针移动中首次包含字串和left移动中首次不包含子串。我的做法如下:
- 创建一个HashMap tmodel构建在模式串中每个字符和出现次数的映射,另一个HashMap del来存放两个指针区间的模式串字符和对应在指针区间出现次数的映射关系。再设置一个ArrayList full容器,存放当前还需要匹配找到的字符串,初始list容器把模式串的字符全部填入(可以重复)。主串每找到一个模式串含有字符就在del中记录出现次数加一,并从list中移除该元素(如果有),当full容器全部排空则说明首次匹配。
- 左指针移动时,每找到一个模式串含有的元素就在del中对应字符次数减一,当该字符在del中出现次数等于tmodel中的次数时,则表明若删去该字符,指针区间不包含子串。此时要记得在full中添加该字符(下次right右移找到该字符,即找到新的覆盖子串)。
见鬼的是,只有一个主串长度比较离谱的用例没通过,推测是容器扩容失败溢出了。
public String minWindow(String s, String t) {
Map<Character,Integer> tmodel=new HashMap<>(),del=new HashMap<>();
List<Character> full=new ArrayList<>();
int i=0,resl=0,resr=s.length();
while(i<t.length())
{
if(!tmodel.containsKey(t.charAt(i)))
tmodel.put(t.charAt(i),1);
else
tmodel.put(t.charAt(i),tmodel.get(t.charAt(i))+1);
full.add(t.charAt(i));
i++;
}
int left=0,right=0;
while(left<s.length()&&right<s.length())
{
while(right<s.length())
{
char c=s.charAt(right);
if(tmodel.containsKey(c))
{
if(full.contains(c))
full.remove(Character.valueOf(c));
if(!del.containsKey(c))
del.put(c,1);
else
del.put(c,del.get(c)+1);
}
if(full.size()==0)
break;
right++;
}
if(right==s.length())
break;
while(left<s.length())
{
char c=s.charAt(left);
if(tmodel.containsKey(c))
{
if(del.get(c)==tmodel.get(c))
{
if(right-left<resr-resl)
{
resl=left;
resr=right;
}
del.put(c,del.get(c)-1);
full.add(c);
left++;
right++;
break;
}
del.put(c,del.get(c)-1);
}
left++;
}
}
if(resr<s.length())
return s.substring(resl,resr+1);
return s.substring(0,0);
}
110.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
输入:root = [3,9,20,null,null,15,7]
输出:true
- 本质上求二叉树的深度,判断左子树深度和右子树深度之差是否大于1。
31.下一个排列
给你一个整数数组 nums
,找出 nums
的下一个排列。必须原地修改,只允许使用额外常数空间。整数数组的下一个排列是指其整数的下一个字典序更大的排列。
输入:nums = [1,2,3]
输出:[1,3,2]输入:nums = [3,2,1]
输出:[1,2,3]
- 扫描两边整个数组,第一遍从后往前找,找到第一个非降序的位置,第二遍扫描该非降序数组,找到比首元素大的数中最小的元素,使其与首元素交换,并使首元素后的数组首尾两两交换。
今日总结
刷题