NC刷题笔记11-双指针
BM87 合并两个有序的数组
描述
给出一个有序的整数数组 A 和有序的整数数组 B ,请将数组 B 合并到数组 A 中,变成一个有序的升序数组
数据范围:0≤n,m≤100,|A_i| <=100∣ |B_i| <= 100
注意:
1.保证 A 数组有足够的空间存放 B 数组的元素, A 和 B 中初始的元素数目分别为 m 和 n,A的数组空间大小为 m+n
2.不要返回合并的数组,将数组 B 的数据合并到 A 里面就好了,且后台会自动将合并后的数组 A 的内容打印出来,所以也不需要自己打印
3. A 数组在[0,m-1]的范围也是有序的
示例1
输入:[4,5,6],[1,2,3]
返回值:[1,2,3,4,5,6]
说明:A数组为[4,5,6],B数组为[1,2,3],后台程序会预先将A扩容为[4,5,6,0,0,0],B还是为[1,2,3],m=3,n=3,传入到函数merge里面,然后请同学完成merge函数,将B的数据合并A里面,最后后台程序输出A数组
示例2
输入:[1,2,3],[2,5,6]
返回值:[1,2,2,3,5,6]
思路:
1 双指针 合并数组 都放在A数组的末尾
import java.util. ;
public class Solution {
public void merge(int A[], int m, int B[], int n) {
int index=m+n-1,i=m-1,j=n-1;
while(i>=0&&j>=0){
if(A[i]>B[j]){
A[index]=A[i];
i--;
}else{
A[index]=B[j];
j--;
}
index--;
}
while(j>=0){
A[index]=B[j];
index--;
j--;
}
}
}
BM88 判断是否为回文字符串
思路1:
反转字符串比较
思路2:
双指针
import java.util. ;
public class Solution {
public boolean judge (String str) {
return str.equals(new StringBuilder(str).reverse().toString());
}
}
import java.util. ;
public class Solution {
public boolean judge (String str) {
int i=0,j=str.length()-1;
while(i<j){
if(str.charAt(i)!=str.charAt(j)) return false;
i++;
j--;
}
return true;
}
}
BM89 合并区间
描述
给出一组区间,请合并所有重叠的区间。请保证合并后的区间按区间起点升序排列。
数据范围:区间组数 0≤n≤2×10^5,区间内 的值都满足0^50≤val≤2×10^5
要求:空间复杂度 O(n),时间复杂度 O(nlogn)
进阶:空间复杂度 O(val),时间复杂度O(val)
示例1
输入:[[10,30],[20,60],[80,100],[150,180]]
返回值:[[10,60],[80,100],[150,180]]
示例2
输入:[[0,10],[10,20]]
返回值:[[0,20]]
思路:
1 按照区间第一个元素从小到大排序
2 遍历1到n-1个元素,判断是否可以合并区间
2.1 可以 设置i+1位置
2.2 不可以 添加进数组
时间复杂度:O(n) 空间复杂度:O(1)
import java.util. ;
/
Definition for an interval.
public class Interval {
int start;
int end;
Interval() { start = 0; end = 0; }
Interval(int s, int e) { start = s; end = e; }
}
/
public class Solution {
public ArrayList<Interval> merge(ArrayList<Interval> intervals) {
ArrayList<Interval> res=new ArrayList<>();
if(intervals.size()<2) return intervals;
Collections.sort(intervals,(a,b)->a.start-b.start);
for(int i=0;i<intervals.size()-1;i++){
if(intervals.get(i).end>=intervals.get(i+1).start){
intervals.set(i+1,new Interval(intervals.get(i).start,Math.max(intervals.get(i+1).end,intervals.get(i).end)));
}else{
res.add(intervals.get(i));
}
}
res.add(intervals.get(intervals.size()-1));
return res;
}
}
BM90 最小覆盖子串
描述
给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。
数据范围:0>∣S∣,∣T∣≤10000,保证s和t字符串中仅包含大小写英文字母
要求:进阶:空间复杂度 O(n) , 时间复杂度 O(n)
例如:
S ="XDOYEZODEYXNZ"S="XDOYEZODEYXNZ"
T ="XYZ"T="XYZ"
找出的最短子串为"YXNZ""YXNZ".
注意:
如果 s 中没有包含 t 中所有字符的子串,返回空字符串 “”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。
示例1
输入:"XDOYEZODEYXNZ","XYZ"
返回值:"YXNZ"
示例2
输入:"abcAbA","AA"
返回值:"AbA"
思路:
左闭右开 滑动窗口
1 排除掉特殊条件
2 用一个字符数组 记录所需的字符和个数
3 声明左指针、右指针、命中need的数量、字符开始的指针、最小长度
4 开始扩大窗口
4.1 判断当前位置是否命中need
命中
如果have < need count++
have++
r++
缩小窗口
左指针未命中need 直接缩小
左指针命中need 且 have==need 此时count--
不命中
r++
continue
class Solution {
public String minWindow(String s, String t) {
if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
return "";
}
//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
//ASCII表总长128
int[] need = new int[128];
int[] have = new int[128];
//将目标字符串指定字符的出现次数记录
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}
//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
while (right < s.length()) {
char r = s.charAt(right);
//说明该字符不被目标字符串需要,此时有两种情况
// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
// 2.循环已经开始一段时间,此处又有两种情况
// 2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
// 如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
// 2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
if (need[r] == 0) {
right++;
continue;
}
//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
if (have[r] < need[r]) {
count++;
}
//已有字符串中目标字符出现的次数+1
have[r]++;
//移动右指针
right++;
//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
while (count == t.length()) {
//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
if (right - left < min) {
min = right - left;
start = left;
}
char l = s.charAt(left);
//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
if (need[l] == 0) {
left++;
continue;
}
//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
if (have[l] == need[l]) {
count--;
}
//已有字符串中目标字符出现的次数-1
have[l]--;
//移动左指针
left++;
}
}
//如果最小长度还为初始值,说明没有符合条件的子串
if (min == s.length() + 1) {
return "";
}
//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
return s.substring(start, start + min);
}
}
BM91 反转字符串
描述
写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)
数据范围:0≤n≤1000
要求:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:"abcd"
返回值:"dcba"
示例2
输入:""
返回值:""
思路1:
调用API
思路2:
双指针
import java.util. ;
public class Solution {
public String solve (String str) {
return new StringBuilder(str).reverse().toString();
}
}
import java.util. ;
public class Solution {
public String solve (String str) {
char[] chars=str.toCharArray();
int l=0,r=str.length()-1;
while(l<r){
char c=chars[l];
chars[l]=chars[r];
chars[r]=c;
l++;
r--;
}
return new String(chars);
}
}
BM92 最长无重复子数组
描述
给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。
子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组
数据范围:0≤arr.length≤10 5,0<arr[i]≤10^5
示例1
输入:[2,3,4,5]
返回值:4
说明:[2,3,4,5]是最长子数组
示例2
输入:[2,2,3,4,3]
返回值:3
说明:[2,3,4]是最长子数组
示例3
输入:[9]
返回值:1
示例4
输入:[1,2,3,1,2,3,2,2]
返回值:3
说明:最长子数组为[1,2,3]
示例5
输入:[2,2,3,4,8,99,3]
返回值:5
说明:最长子数组为[2,3,4,8,99]
思路:
滑动窗口,记录值和索引,重复的话直接从重复位置的下一个位置开始滑动
import java.util. ;
public class Solution {
public int maxLength (int[] arr) {
if(arr==null|arr.length==0) return 0;
if(arr.length==1) return 1;
HashMap<Integer,Integer> map=new HashMap<>();
int left=0,right=0,max=1;
while(right<arr.length){
int r=arr[right];
if(!map.containsKey(r)){
map.put(r,right);
}else{
int rindex=map.get(r);
left=Math.min(rindex+1,right);
map=new HashMap<>();
for(int i=left;i<=right;i++){
map.put(arr[i],i);
}
}
right++;
max=Math.max(right-left,max);
}
return max;
}
}
BM93 盛水最多的容器
描述
给定一个数组height,长度为n,每个数代表坐标轴中的一个点的高度,height[i]是在第i点的高度,请问,从中选2个高度与x轴组成的容器最多能容纳多少水
1.你不能倾斜容器
2.当n小于2时,视为不能形成容器,请返回0
3.数据保证能容纳最多的水不会超过整形范围,即不会超过231-1
数据范围: 0<=height.length<=10^5 0<=height[i]<=10^4
如输入的height为[1,7,3,2,4,5,8,2,7],那么如下图:
示例1
输入:[1,7,3,2,4,5,8,2,7]
返回值:49
示例2
输入:[2,2]
返回值:2
示例3
输入:[5,4,3,2,1,5]
返回值:25
思路:
双指针 每次移动最小的那根边
import java.util. ;
public class Solution {
public int maxArea (int[] height) {
if(height.length<=1) return 0;
int l=0,r=height.length-1;
int maxVolum=0;
while(l<r){
int minSide=Math.min(height[l],height[r]);
maxVolum=Math.max(maxVolum,minSide (r-l));
if(height[l]==minSide){
l++;
}else{
r--;
}
}
return maxVolum;
}
}
BM94 接雨水问题
描述
给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个柱子高度图,计算按此排列的柱子,下雨之后能接多少雨水。(数组以外的区域高度视为0)
数据范围:数组长度 0< n< 2 10^5,数组中每个值满足 0 < val <10^9,保证返回结果满足 0 < val < 10^9
要求:时间复杂度 O(n)
示例1
输入:[3,1,2,5,2,4]
返回值:5
说明:数组 [3,1,2,5,2,4] 表示柱子高度图,在这种情况下,可以接 5个单位的雨水,蓝色的为雨水 ,如题面图。
示例2
输入:[4,5,1,3,2]
返回值:2
思路1 :
双指针:
1 先找到最高的柱子
2 分别找左右两边能存水的值
左边: 当前位置的存水值=左侧最高值-当前值
右边: 当前位置的存水值=右侧最高值-当前值
思路2:
遍历每一层,高度为1、2、3...
左右不为空的中间夹层的个数
import java.util. ;
public class Solution {
public long maxWater (int[] arr) {
//寻找最高的位置
int maxHeight=0,maxIndex=0;
for(int i=0;i<arr.length;i++){
if(arr[i]>maxHeight){
maxIndex=i;
maxHeight=arr[i];
}
}
int left=0,right=0,maxleftindex=0,maxrightindex=arr.length-1;
//寻找左边能存的水
for(int i=0;i<maxIndex;i++){
if(i==0) continue;
if(arr[i]<arr[maxleftindex]){
left+=arr[maxleftindex]-arr[i];
}else{
maxleftindex=i;
}
}
//寻找右边能存的水
for(int i=arr.length-1;i>maxIndex;i--){
if(i==arr.length-1) continue;
if(arr[i]<arr[maxrightindex]){
right+=arr[maxrightindex]-arr[i];
}else{
maxrightindex=i;
}
}
return left+right;
}
}