굳이 정답을 찾기 위해 모든 부분을 봐야하는가?
화살표 두 개에 의미를 부여해서 탐색 범위를 압축하는 방법
1. 1차원 배열 위에 2개의 포인터를 만드는 경우
1. 2개의 포인터가 모두 왼쪽에서 시작해 같은 방향으로 이동
R이 3칸 가면, L이 1칸 , 그럼 또 R이 또 감, L이 쫓아감
2. 2개의 포인터가 양 끝에서 서로를 향해 이동
2. 관찰을 통해서 문제에 등장하는 변수 2개의 값을 두 포인터로 표현하는 경우
꿀 팁 키워드
- 1차원 배열에서의 “연속 부분 수열”
- “순서를 지키며 차례대로”
- 곱의 최소
- A가 커지면 B가 작아지니까, 하나가 늘면 하나가 줄어드니까
연속된 수들의 부분합중에 그 합이 S 이상이 되는 것 중 가장 짧은 것의 길이
- 원소의 개수 N, 기준합 S,
static void pro(){
int R = 0, sum = 0, ans = n + 1; //ans=s를 넘는 값들 중 가장 짧은거 기록
for(int L = 1;L <= n;L++){
// L - 1 을 구간에서 제외하기
sum -= a[L - 1];
// R을 옮길 수 있을 때까지 옮기기 (합이 S보다 커지는 순간 멈춤)
while(R + 1 <= n && sum < S)
{
R++;
sum += a[R];
}
//[L...R]의 합, 즉 sum이 조건을 만족하면 정답 갱신
if(sum >= S){
ans = Math.min(ans, R - L + 1);
}
}
//ans값 보고 불가능 판단하기
if(ans == n + 1){//min이 갱신이 안됐다면
ans = 0;
}
System.out.println(ans);
}
알고리즘
가장 작은수(-99) 랑 가장 큰 수(98) 랑 더해도 음수인데, 그보다 덜 작은수(-1,-2), 덜 큰수랑 더하면 안더해도 음수다. -99 + 98은 이미 좋은 애랑 했다.
- 최소 + 최대 < 0
→ 최소 입장에서는 최선책을 만난 상태, 짝을 찾았으니 최소(-99) 삭제
![](https://velog.velcdn.com/images/seungyeonnnnnni/post/a35345a4-3c60-47a3-953e-99ab482c404d/image.png)
다음으로, 98이랑 -2랑 더하면 합이 양수 최대 입장에서, 굳이 덜 작은 수(-1)이랑 덜 큰수(4)를 더해야할까?
- 최소 + 최대 > 0
→ 최대 입장에서는, 최선택을 만난 상태, 짝을 찾았으니 최대(98) 삭제
![](https://velog.velcdn.com/images/seungyeonnnnnni/post/529101f2-249a-469a-bcf2-3311520eb022/image.png)
정렬 > L,R 계산 후에 이동 > O(N log N)
static void pro(){
Arrays.sort(A,1,N+1);
int best_sum = Intger.MAX_VALUE;
int v1 = 0, v2 = 0, L = 1, R = N;
while(L < R){
int sum = A[L] + A[R];
if(Math.abs(sum) < best_sum){
best_sum = Math.abs(sum);
v1 = A[L];
v2 = A[R];
}
if( sum > 0 ){
R--;
}else L++;
sb.append(v1).append(' ').append(v2);
System.out.println(sb);
}
수열에서 연속한 1개 이상의 수 뽑을 때 같은수가 여러번 등장하지 않는 경우의 수
정답의 최대치
- N + (N-1) + (N-2) … = 50억 → Long
static void input(){
N = sc.nextInt();
A = new int[N + 1];
for(int i=1;i<=N;i++){
A[i] = sc.nextInt();
}
cnt = new int[100000 + 1];
}
static void pro(){
long ans = 0;
for(int L=1,R=0; L<=N;L++){
// TODO : R을 옮길 수 있을 만큼 옮긴다
while( R+1<=N && cnt[A[R+1]] == 0){ // R을 옮겨도 N을 넘어가면 안되며,
// 옮긴 위치 숫자가 L~R사이에 있으면 안됨
R++;
cnt[A[R]]++;
}
// TODO : 정답을 갱신한다
ans += R - L + 1;
// TODO : L을 옮겨주면서 A[L]의 개수를 감소시킨다.
cnt[A[L]]--;
}
System.out.println(ans);
N개의 수 중에서 어떤수가 다른 수 두개의 합으로 나타낼 수 있을 때, 그 갯수
1️⃣ 타겟을 정한다 O(N) > 2️⃣ 나머지에서 2개 결정해서 만들어지나 확인 O(N^2)
1️⃣ 정렬을 한다 > 2️⃣ 타겟을 정한다 > 3️⃣ 나머지에서 다른수 2개로 (두 용액문제 풀이와 같음)
```java
static boolean func(int tartget_idx){
// target_idx 번쨰 원소가 서로 다른 두 수의 합으로 표현 되는가
int L = 1, R = N;
int target = A[target_idx];
while( L < R){
// TODO :
if(L == target_idx) L++;
else if(R == target_idx) R--;
else{
if(A[L] + A[R] == target) return true;
if(A[L] + A[R] > target) R--;//warning
else L++;
}
}
return false;
}
static void pro(){
// TODO : 최소, 최대를 빠르게 알기 위한 정렬
Arrays.sort(A,1,N+1);
int ans = 0;
for(int i=1;i<=N;i++){
// TOOD : i번쨰 원소가 서로 다른 두 수의 합으로 표현이 되는가
if(func(A[i]))
ans++;
}
System.out.println(ans);
}
```
최대 N개 종류의 알파벳을 가진 연속된 최대 문자열의 길이
static void add(char x){
cnt[x-'a']++;
if(cnt[x-'a'] == 1){ //처음 들어온거니까 kind++
kind++;
}
}
static void erase(char x){
cnt[x-'a']--;
if(cnt[x-'a'] == 0){ //방금 나간거니까 kind--
kind--;
}
}
static void pro(){
int len = A.length(), ans = 0;
for(int R=0,L=0;R<len;R++){
// TODO : R번째 문자를 오른쪽에 추가
add(A.charAt(R));
// TODO : 불가능하면, 가능할 때까지 L을 이동
while(kind > N){
erase(A.charAt(L++));
// TODO : 정답 갱신
ans = Math.max(ans,R - L + 1);
}
System.out.println(ans);
}