文章目录
一、滑动窗口系列
tip: 滑动窗口,可以借队列,双指针呈现。数组,字符串,链表这些线性结构容易考察,解法比较巧妙。窗口内的数需要满足哪些条件?重点,窗口左右边界移动的条件。
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-dong-chuang-kou-by-powcai/
//下面是相关题目
//3. 无重复字符的最长子串
//线性结构,字串问题,考虑滑动窗口,双指针
//Map用来干嘛?去重,题目要求不重复子串
//Map k是字符串中的某位字符 v是该字符在索引中的位置
//确定左右窗口移动的条件。
//左窗口,若当前字符已存在于哈希表,左窗口移动到原位置的下一个
//右窗口,不断向后移动(遍历序列的指针)
//关键代码,左窗口移动条件
public int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0) return 0;
HashMap<Character,Integer> map = new HashMap<>();
int left=0,maxLen=0;
for(int i=0;i<s.length();i++){
char ch = s.charAt(i);
if(map.containsKey(ch)){
left = Math.max(left,map.get(ch)+1);//重点,左窗口移动条件
}
map.put(ch,i);
if(i-left+1 > maxLen){
maxLen = i-left+1;
}
}
return maxLen;
}
//30. 串联所有单词的子串
//76. 最小覆盖子串
public String minWindow(String s, String t) {
if(t == null || t.length() == 0) return "";
int[] need = new int[128];
for(int i=0;i<t.length();i++){
char ch = t.charAt(i);
need[ch]++;
}
//若need[i]为0,说明该窗口i元素刚好满足
//若need[i]为负数,说明该窗口i元素已经超标,可移除
int left=0,count=t.length(),start=0;
int min = Integer.MAX_VALUE;
for(int i=0;i<s.length();i++){
char ch = s.charAt(i);
if(need[ch] > 0){
count--;
}
need[ch]--;
if(count == 0){//窗口满足条件
//当前窗口所满足的最小串
while(left<i && need[s.charAt(left)] < 0){//左窗口移动条件
need[s.charAt(left)]++;//每窗口变化,一定要更新相对的条件表
left++;
}
//在满足最小串的前提下,更新
if(i-left+1 < min){
start=left;
min=i-left+1;
}
//注意,再次移动左窗口,当前窗口不在满足条件
need[s.charAt(left)]++;//每窗口变化,一定要更新相对的条件表
left++;
count++;//窗口不满足,count也要修改
}
}
return min == Integer.MAX_VALUE ? "" : s.substring(start,start+min);
}
//159. 至多包含两个不同字符的最长子串
//209. 长度最小的子数组
//239. 滑动窗口最大值
//为当前窗口维护一个单调递减队列
//维护一个单调递减队列,窗口变化(左右边界的变化),单调队列就更新
//左窗口移动,单调队列出队:
// 若队首元素是窗口左边界内的值,就不操作;否则,删除单调队列的队首
//右窗口移动,单调队列入队:
// 若插入的新值大于队尾元素,则删除队尾元素,直到队尾不再比新值小,新值入队列
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length-k+1];
Deque<Integer> que = new ArrayDeque<>();
int idx=0;
for(int i=0;i<nums.length;i++){
int left = i-k+1;//左边界下标
//左边界
while(!que.isEmpty() && left > que.peek()){
que.poll();
}
//右边界
while(!que.isEmpty() && nums[i] > nums[que.peekLast()]){
que.pollLast();
}
que.offer(i);
if(left >= 0){
res[idx++]=nums[que.peek()];
}
}
return res;
}
//567. 字符串的排列
//632. 最小区间
//727. 最小窗口子序列
二、双子针
415. 字符串相加
demo:
输入:num1 = “11”, num2 = “123”
输出:"134"
tip:双指针,重点在,当前位,进位carry,两个变量。
public String addStrings(String num1, String num2) {
String str = "";
int n1=num1.length()-1,n2=num2.length()-1,carry=0;
//指针放在字符串末尾n1,n2
//注意遍历终止条件,n1>=0 || n2>=0 || carry!=0
while(n1>=0 || n2>=0 || carry!=0){
if(n1>=0) carry+=num1.charAt(n1--)-'0';
if(n2>=0) carry+=num2.charAt(n2--)-'0';
str = carry%10+str;//当前位
carry /= 10;//进位
}
return str;
}
三、栈与队列
20.有效括号
public boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for(int i=0;i<s.length();i++){
char ch = s.charAt(i);
if(ch == '('){
stack.push(')');
}else if(ch == '{'){
stack.push('}');
}else if(ch == '['){
stack.push(']');
}else if(!stack.isEmpty() && ch == stack.peek()){
stack.pop();
}else{
return false;
}
}
return stack.isEmpty();
}
四、动态规划
5.最长回文串
解法1,动态规划
public String longestPalindrome(String s) {
//dp[i][j] i是左边界,j是右边界,dp[i][j]表示该区间的子串是否为回文串
//dp[i][j]=dp[i+1][j-1]
int max=0;
int left=0;
boolean[][] dp = new boolean[s.length()][s.length()];
for(int i=s.length()-1;i>=0;i--){
for(int j=i;j<s.length();j++){
if(s.charAt(i) == s.charAt(j)){
if(j-i<=1){
dp[i][j]=true;
}else if(dp[i+1][j-1]){
dp[i][j]=true;
}
if(dp[i][j] && j-i+1>max){
max = j-i+1;
left = i;
}
}
}
}
return s.substring(left,left+max);
}
解法2,中心扩散,双指针
int start=0,max=0;
public String longestPalindrome(String s) {
if (s == null || s.length() == 0) {
return "";
}
for (int i = 0; i < s.length(); i++) {
extend(s, i, i, s.length()); // 以i为中心
extend(s, i, i + 1, s.length()); // 以i和i+1为中心
}
return s.substring(start,start+max);
}
void extend(String s, int i, int j, int n) {
while (i >= 0 && j < n && s.charAt(i) == s.charAt(j)) {
if(j-i+1 > max){
max = j-i+1;
start=i;
}
i--;
j++;
}
}