1143.最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace” ,它的长度为 3 。
- 动规。一般来讲题目问什么动态规划的dp数组的含义就定什么,多为长度。这道题里dp[i][j]表示text1前i个字符串和text2前j个字符串的最长公共子序列的长度。分两种情况:
- text1.charAt(i)不等于text2.charAt(j)时,dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j],dp[i-1][j-1]),其中dp[i-1][j]表明当前text1[i]不参与构成最长公共子序列。
- text1.charAt(i)等于text2.charAt(j)时,dp[i][j]=dp[i-1][j-1]+1,把当前text1[i]纳入最长公共子序列。这种写法其实按照公共子序列右对齐的形式。
93.复原IP地址
给定一个只包含数字的字符串 s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址。
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
- dfs+回溯+剪枝。当前字符串合法(两位以上不含有前导0且数值小于255)则加入结果,加入结果前要判断剪枝,如果当前字符串后面的字符串长度不在剩余未插入IP块数量一倍到三倍数量范围之内,那么直接continue(注意这里不能直接return回溯调整前一个IP块字符串,因为可能当前字符串长为1的时候不满足,但是当长为2的时候就满足,剪枝是剪去当前这个字符串的可能性,而不是当前这个IP块的可能性)。
- 当前字符串含有前导0或者是当前字符串长超过3(一个IP块最多只有三位)则回溯到前一个IP块,回溯前还要在容器path保存的IP地址中取出当前字符串,path.remove(num)。
- 每次递归都要修改当前字符串的下标index和当前IP块号。
public void addIP(List<String> res,List<Integer> path,String s,int num,int index)
{
if(num==4)
{
if(index==s.length())
res.add(new String(String.valueOf(path.get(0))+"."+String.valueOf(path.get(1)) +"."+String.valueOf(path.get(2))+"."+String.valueOf(path.get(3))));
return ;
}
for(int i=0;i<3;i++)
{
if((s.length()-index-i-1<3-num||s.length()-index-i-1>3*(3-num)))
continue ;
else
{
int sum=0,j=0;
while(j<=i)
{
sum=sum*10+(s.charAt(index+j)-'0');
j++;
}
if(sum>255)
return ;
if(s.charAt(index)=='0')
{
sum=0;
path.add(sum);
addIP(res,path,s,num+1,index+1);
path.remove(num);
return ;
}
path.add(sum);
addIP(res,path,s,num+1,index+i+1);
path.remove(num);
}
}
}
129.求根节点到叶子节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。每条从根节点到叶节点的路径都代表一个数字:例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。计算从根节点到叶节点生成的 所有数字之和 。
输入:root = [4,9,0,5,1]
输出:1026
- dfs。自顶向下递归。
113.路径总和Ⅱ
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
- dfs+回溯。
239.滑动窗口最大值
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
- Deque是LinkedList类实现的一个双端队列的接口,Deque<Integer> deque=new LinkedList<>()。
-
首元素 首元素 尾元素 尾元素 抛出异常 特殊值 抛出异常 特殊值 插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e) 删除 removeFirst() pollFirst() removeLast() pollLast() 检查 getFirst() peekFirst() getLast() peekLast()
- 一开始看到滑动窗口先想到动规,但是这题动规的开销比较大。开辟O( n 2 n^2 n2)空间数组来保存状态。dp[i][j]表示下标从i到j的最大值。则dp[i][j]=Math.max(dp[i][j-1],dp[i+1][j]),可以初始化第0行和第n列以及dp[i][i],然后需要从下往上,从左往右遍历来维护dp。
- 固定窗口+最大值自然要能想到大根堆。其实就是一个有序的双端队列,这里队列存放的是数组的索引下标,每次窗口滑动进来一个元素出去一个元素。有两个问题需要考虑:
-
每次窗口移动时,堆顶元素最大值要考虑在不在当前窗口内,因此每次窗口滑动若当前队列的最大值的索引下标小于当前窗口的左边界则移出。
-
插入新元素时,要保证插入后队列保持有序。如果每次插入都是从小(队尾)到大(头)找到相应的位置插入,那么排序时间O(logn)(PriorityQueue底层基于堆实现),总的时间复杂度O(nlogn)。
实际上每次插入时,队列中比待插入元素小的都可以直接出队,因为插入元素肯定是在当前窗口的右边界,索引下标在比它小的元素的右边,所以该元素进队后肯定是轮不到比它小的元素作为最大元素出队的。因此可以直接出队,每个元素只进入一次队列出一次队列(被访问就要出),因此排序时间O(1),总时间O(n)。
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> queue=new LinkedList<>();
int[] res=new int[nums.length-k+1];
for(int i=0;i<nums.length;i++)
{
while(!queue.isEmpty()&&queue.peek()<i-k+1) queue.poll();
while(!queue.isEmpty()&&nums[queue.peekLast()]<nums[i]) queue.pollLast();
queue.offer(i);
if(i>=k-1)
res[i-k+1]=nums[queue.peek()];
}
return res;
}
41.缺失的第一个正数
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
输入:nums = [3,4,-1,1]
输出:2
-
利用鸽巢原理,遍历第一遍,如果当前位置的数不等于当前索引下标,则把这个数放到对应索引的位置nums[nums[i]]=nums[i],不停的交换(如果发现nums[i]对应的坑位已经放了正确的数,那么说明nums[i]是重复的,直接置为-1),直到当前位置的数越出数组范围或者当前位置填放了正确的数。
第二次遍历如果从索引1开始,坑位正确,那么一直往后找直到nums[i]!=i即为答案。否则返回1。时间复杂度O(n)。
public int firstMissingPositive(int[] nums) {
if(nums.length==1)
{
if(nums[0]==1) return 2;
else return 1;
}
for(int i=0;i<nums.length;i++)
{
if(nums[i]<0||nums[i]==i)
continue;
while(nums[i]>=0&&nums[i]<nums.length&&i!=nums[i]) //交换
{
if(nums[nums[i]]==nums[i])
nums[i]=-1;
else
{
int temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
}
if(nums[1]==nums[1])
{
int i=1;
while(i<nums.length&&nums[i]==i) i++;
if(nums[0]==nums.length&&i==nums.length) return nums.length+1;
else return i;
}
return 1;
}
22.括号生成
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的 括号组合。
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
- dfs+回溯。左括号数量大于右括号数量才能添加右括号。注意可以剪枝排除全是左括号情况(剩余全是右括号时不足以填满左括号)。
今日总结
刷题