Zuo Chengyun 알고리즘 노트 요약 - 기본 개선

부스트 01(해시)

해시 함수 이해

  1. 해시 함수의 입력은 일반적으로 끝이 없어야 하며 제한이 없으며 출력은 특정 범위를 가질 수 있습니다.예를 들어 MD5 암호화 후 생성된 문자열은 2의 32승 - 1을 가질 수 있으며 16진수에서는 16이 필요합니다. .캐릭터.
  2. 동일한 입력은 동일한 출력에 해당하며 임의성은 없습니다.
  3. 출력 도메인은 제한되어 있고 입력 도메인은 크기 때문에 서로 다른 입력이 동일한 출력을 가질 가능성이 있으며, 이는 해시 충돌입니다.
  4. 이산성(Discreteness): 입력 값이 매우 유사하더라도 출력 값의 분포는 매우 무작위적입니다. 균일성과 이산성: 모든 입력 값을 해시 테이블에 입력하고 이 값을 원으로 묶으며 매번 발견되는 값의 개수는 기본적으로 동일합니다.

현재 가장 많이 나타나는 단어 빈도를 알아내기 위해 계산해야 하는 정수형 숫자는 40억 개입니다. 처음에는 결함이 있는 제품을 해시 테이블에 저장하려고 생각했지만 모든 값을 한꺼번에 해시 테이블에 저장한다면 키-값 세트에는 8바이트가 필요하고 필요한 총 메모리는 최소한 32G, 메모리가 버스트됩니다.

해결 방법: 먼저 해시 함수를 기반으로 이 숫자를 100개의 파일에 할당합니다(데이터 양이 많기 때문에 각 파일의 모든 유형의 숫자는 기본적으로 표시되고 균등하게 나뉩니다). 첫 번째 파일 크기 번호를 처리한 후 두 번째 파일 크기 번호가 처리됩니다. 이는 하나의 파일 크기인 0.32G만 차지합니다. 이 100개의 번호를 처리한 후에는 각 파일의 최대값인 100을 얻게 됩니다. 값을 선택한 다음 100개의 최대값 중에서 가장 큰 값을 선택합니다. 최대 단어 빈도입니다.

해시 테이블 구현

  1. 해시 테이블은 처음에는 배열 + 연결 리스트로 구성되어 있지만, 연결 리스트에 노드가 너무 많으면 레드-블랙 트리로 변합니다.
  2. 해시 테이블에 저장된 n개의 숫자에 대해 확장 비용은 각 숫자에 균등하게 분배되고 시간 복잡도는 N*logN / N = O(logN)이며 n이 특별히 크지 않은 경우 logN은 logK와 동일하므로 사용됩니다. 대략 O(1)
  3. 스토리지가 너무 많으면 JVM은 해시 테이블의 오프라인 확장을 수행합니다. 즉, 새 해시 테이블을 생성하고 거기에 데이터를 이동한 후 새로 생성된 해시 테이블을 이동한 후 사용합니다.

RandomPool 구조 leetcode 705 설계

[제목] 다음 세 가지 기능을 갖는 구조를 설계합니다.

insert(key): 키가 반복적으로 추가되지 않도록 구조체에 키를 추가합니다.

delete(key): 원래 구조에 있던 키를 제거합니다.

getRandom(): 동일한 확률로 구조의 모든 키를 무작위로 반환합니다.

[요구사항] Insert, delete, getRandom 메소드의 시간 복잡도는 모두 O(1)입니다.

문제 해결 아이디어: 이 질문은 0에서 해시 테이블을 생성하라고 요구하지 않습니다. 이 구조는 두 개의 해시 테이블(<key, value>, <index, key>, 크기)로 구성되며 키가 삽입될 때마다 첫 번째 맵은 키와 값을 삽입하고 다른 맵은 인덱스(에 따라)를 삽입합니다. 크기++) 및 키에 해당합니다. 동일한 확률로 무작위로 반환할 때 인덱스 값을 계산하고, 이 인덱스 값을 기준으로 키 값을 찾은 후 첫 번째 테이블에서 키를 삭제합니다. 삭제 후에는 후속 무작위 값이 무작위로 null 값에 도달하지 않도록 보장해야 합니다. 이전에 0~25개의 연속 값이 있었다면 22를 삭제한 후 다음 무작위 값에 도달할 때 null 값이 되기 때문입니다. 22. 이러한 null 값 값이 너무 많으면 시간 복잡도가 증가하므로 여기서는 현재 맵의 마지막 값을 삭제된 위치로 이동하여 구멍을 채우므로 무작위화 중에 null 값이 무작위로 얻어지지 않습니다.

블룸 필터

해시셋과 해시맵의 차이점은 값이 있느냐 없느냐, 그 외는 모두 동일하다는 것입니다. Bloom 필터 구조는 추가 및 확인 기능만 있고 삭제 기능은 없는 집합과 같습니다. Bloom 필터를 사용하면 100억 개의 URL을 블랙리스트에 추가하여 사용자가 이러한 불법 링크에 액세스하는 것을 방지할 수 있습니다. Bloom 필터는 메모리를 매우 절약할 수 있지만 자체 설계 문제로 인해 오탐 확률이 낮습니다.예를 들어 URL 블랙리스트의 경우 블랙리스트에 포함되지 않은 URL이 다음과 같이 잘못 보고될 수 있습니다 . 검정색 URL . 그러나 블랙리스트에 있는 URL은 실수로 흰색 URL로 보고되지 않습니다 . "한 명을 놔두기보다는 실수로 천 명을 죽이는 편이 낫습니다." 특정 최적화 후에는 이러한 오탐지가 매우 드물어 수백 분의 1에 달합니다. 수천.

Bloom 필터 설계: 비트 단위의 배열인 비트맵이 필요합니다. 이 구조를 구현하는 방법은 int 배열을 사용합니다. int 배열의 각 데이터는 4바이트이므로 32비트입니다. 배열 길이가 10인 경우 , 그러면 320비트를 나타낼 수 있습니다. 코드를 살펴보세요:

int[] arr = new int[10];//32bit * 10 -> 320 bits
//arr[0] int 0 ~31
//arr[1] int 32~63
//arr[2] int 64~95
int i = 178;//想取得178位的状态
int numIndex = 178 / 32;//得到178位所在int数组的数组下标
int bitIndex = 178 % 32;//取得178所在的int元素中第几位,注意,在二进制位中,从右到左才是由低到高,所以bit在第几位要从右数
int s = (    (arr[numIndex] >> (bitIndex))    & 1)//把178位所在数组上的int数字右移bitIndex位,注意,int数字在二进制下从右到左是从低位到高位。然后和1做与运算,如果这一bit位是1那s就是1,如果是0,那s就是0;

//1、把178位的状态改为1
arr[numIndex]  = arr[numIndex] | (1 << (bitIndex));//把1左移bitIndex位后和数组元素进行或运算,得到的结果相对于原int元素的二进制形态,bit位上的数字变为1。

//2、把178位的状态改为0
arr[numIndex]  = arr[numIndex] & (~  (1 << bitIndex)  );//1左移bitIndex位后再取反,得到只有bitIndex位上是0的二进制数字(其他都是1),然后和arr[numIndex]与运算,得到和原来的int数字相比,178位的bit变为0

//3、把178位的状态拿出来
i = 178;
//bit 0 1
int bit=(arr[i/32] >> (i%32)) & 1;

Bloom 필터는 큰 비트맵으로 길이가 m인 비트맵이 m/8바이트를 차지한다.

Bloom 필터는 각 데이터에 대해 k개의 해시 연산을 수행하며, 각 연산의 결과에 해당하는 위치를 1로 설정하고, k개의 위치를 ​​1로 설정한 후, 데이터를 질의할 때 다시 k개의 해시 연산을 수행한다. k 위치가 모두 1인지 확인합니다. 그렇다면 비트맵에 있는 데이터와 동일합니다. 여기에서 몇 가지 결함을 볼 수 있는데, 화이트리스트 URL에 대해 계산된 해시 값이 블랙리스트에 대한 해시 값과 동일하면 흰색 URL이 블랙 URL로 오인됩니다.

Bloom 필터의 비트맵 크기가 m이고 해시 함수의 개수가 k라고 가정합니다.

m이 너무 작으면 몇 초 안에 모든 위치를 1로 설정하기 쉽고 오판하기 쉽습니다.

k가 너무 작은 경우(사실 해시 연산을 수행하는 것은 지문을 수집하는 것과 같습니다. 지문을 많이 수집할수록 본인임을 증명할 확률이 높아집니다. k가 너무 작으면 계산하기 쉽습니다. 다른 데이터의 동일한 숫자 해시 값을 사용하면 흰색 URL을 흰색 URL로 잘못 판단하기 쉽습니다.

하지만 k는 너무 클 수 없으며, 너무 크면 하나의 데이터를 계산하면 1이 많이 생성되고 그리드가 검게 변해 잘못된 판단을 내리게 됩니다.

요약: 오류율은 비트 배열이 증가함에 따라 감소하고 k 값이 증가함에 따라 처음에는 감소하다가 증가합니다.

n은 표본 크기를 나타내고 p는 오류율을 나타냅니다.

이 두 값을 기반으로 계산하려면 크기 m(비트 배열 크기, 즉 메모리)과 해시 함수 개수 k가 필요합니다.

공식:

여기에 이미지 설명을 삽입하세요.

위의 계산은 모두 이론적인 값이지만 실제로는 더 큰 메모리 공간을 신청할 수 있으면 오류율이 더 낮을 것입니다. 더 큰 메모리를 신청할 수 있는지 면접관에게 물어보세요. 그렇다면 실제 계산식은 다음과 같습니다. 오류율은 다음과 같습니다.

"k true"의 계산은 이전 공식을 기반으로 합니다. "m true"로 대체하면 됩니다.

여기에 이미지 설명을 삽입하세요.

일관된 해싱 원칙

서로 다른 개체의 데이터가 서로 다른 시스템에 저장되는 분산 저장소의 경우 해시 함수를 사용하여 데이터에서 서버로의 매핑 관계를 설정합니다.

배경: 단순 해쉬 함수를 사용할 경우 처음에 3개의 머신이 있다고 가정하면 해쉬 함수를 통해 3개의 머신에 데이터가 균등하게 분배되지만, 수평으로 머신을 추가하려면 모든 데이터를 재분배해야 한다. 이로 인해 전체적으로 대량의 데이터 마이그레이션이 발생하여 네트워크 통신 부담이 급격히 증가하고 심각한 경우 데이터베이스 다운타임이 발생할 수도 있습니다.

일관된 해시 알고리즘을 사용하면 시스템을 추가하거나 축소할 때 노드 간 데이터 마이그레이션이 두 노드로 제한되고 글로벌 네트워크 문제가 발생하지 않도록 할 수 있습니다.

일관된 해시 1.0: 해시 링이 필요하며 머신은 평균 3개의 노드에 배치되고 해시 함수에 따라 데이터가 분산됩니다. 링에 있는 데이터는 시계 방향으로 다음 머신에 속합니다. 새 노드를 추가해야 하는 경우 새 노드 전후의 데이터 저장 위치만 다시 조정하면 됩니다. 하지만 두 가지 문제가 있는데, 첫째, 노드를 추가하면 부하 불균형이 발생합니다. 둘째, 처음에 머신 수가 충분하지 않은 경우 해시 함수는 각 머신에 할당된 데이터가 균등하게 분산되도록 보장할 수 없습니다.

여기에 이미지 설명을 삽입하세요.

일관된 해시 2.0: 가상 노드 기술. 예를 들어, 원래 3개의 머신이 있었는데 각 머신에게 직접 1,000개의 복사본을 복사하도록 요청하고 이러한 가상 노드, 즉 가상 머신이 링을 가져오도록 하여 각 머신이 가져오는 링의 데이터는 다음과 같습니다 . , 각 머신의 복사본이 충분하기 때문입니다. 그리고 머신 성능에 따라 서로 다른 수의 노드를 복사할 수 있으며, 성능이 강하면 2,000개의 가상 노드가 할당되고, 성능이 약하면 1,000개의 가상 노드가 할당되어 능력 있는 사람이 더 많은 작업을 수행할 수 있습니다. 그리고 합리적으로 배분하세요. 새 시스템이 추가되면 자체 복사본 1,000개가 복사되고 마이그레이션되는 데이터는 많지 않습니다.

섬 문제

[제목] 행렬에는 0과 1의 두 가지 값만 있고 각 위치는 상하좌우 위치로 연결될 수 있습니다.1의 조각이 함께 연결된 경우 이 부분을 호출합니다. 섬. 행렬에 섬이 몇 개 있는지 알아보세요.

[예시] 001010 111010 100100 000000 이 행렬에는 3개의 섬이 있습니다.

[고급] 이 문제를 해결하기 위해 병렬 알고리즘을 설계하는 방법

기본 접근 방식: 재귀 + 순회. 모든 노드를 순회합니다. 숫자 1이 발견되면 결과에 1을 더하고 자신과 인접한 모든 1을 2로 설정하여 순회되었음을 나타냅니다. 모든 노드가 탐색될 때까지. 시간 복잡도는 O(N*M)입니다. 이는 각 노드가 위쪽, 아래쪽, 왼쪽 및 오른쪽 이웃을 여전히 n 레벨인 자신과 비교하면서 최대 4번만 통과하기 때문이 아닙니다. 코드 보기: Code03_Islands

고급 접근 방식:

병렬 알고리즘 설계: 그러나 우리의 테스트 문제와 인터뷰는 일반적으로 단일 CPU 및 단일 메모리 알고리즘을 사용합니다.병렬 알고리즘은 인터뷰 중에 프로세스만 설명하면 됩니다.

이 알고리즘은 통합 조회 세트를 알아야 합니다.

행렬을 왼쪽과 오른쪽으로 나누어 두 개의 CPU를 사용하여 왼쪽과 오른쪽에서 통계를 수행한다고 가정하면 각 초기 노드를 세트로 배치한 후 감염시키고 경계 위치를 기록하는 방식입니다. 노드가 감염되고 최종적으로 결과가 병합되며 결과 병합 시 왼쪽과 오른쪽의 노드가 감염되면 해당 초기 노드 세트가 병합되고 최종적으로 병합이 완료되며 남은 세트 수 섬의 개수입니다.

여기에 이미지 설명을 삽입하세요.

그리고 컬렉션을 검색해 보세요.

배경: k 알고리즘에서는 처음에 각 데이터에 대해 세트가 생성되며 두 가지 방법이 필요합니다. 하나는 동일한 세트인지 확인하는 isSameSet이고, 다른 하나는 두 세트를 병합하는 Union입니다. 그러나 두 가지 기본 데이터 구조가 있습니다. 하나는 이 두 메소드를 구현하기 위한 집합인 hashmap입니다. isSameSet 메소드는 두 세트가 동일한 맵인지 여부만 판별하면 됩니다. 시간 복잡도는 O(1)이지만 Union 시간 복잡도는 O(N)이므로 다른 맵의 데이터를 이전 맵으로 이동해야 합니다. 다른 하나는 연결리스트(linked list)인데, 연결리스트를 병합할 때 머리 부분과 꼬리 부분만 병합하면 O(1)이지만, 쿼리를 순회해야 하는 경우가 O(N)이다. 따라서 두 방법의 시간 복잡도를 O(1)에 가깝게 만드는 구조가 있습니다.

합집합 검색: 각 요소에 대해 처음에는 별도의 집합이 있고 각 요소는 아버지를 위쪽으로 가리키므로 아버지는 처음에 그 자신입니다.

isSameSet 메소드의 경우 각 요소가 위쪽을 가리키는지 확인해야 하는데, 즉 위쪽 지점이 빌 때까지 findHead 메소드를 호출하면 현재 집합의 대표 노드를 찾는다. 두 노드의 위치가 동일하다는 것은 두 노드가 동일하다는 것을 의미합니다. 노드는 동일한 세트에 있습니다. 대표 노드는 컬렉션을 병합할 때 사용해야 하는 현재 컬렉션의 크기를 저장해야 합니다.

Union 방식의 경우 현재 집합의 대표 노드를 찾은 후 대표 노드에 저장된 집합 크기 데이터를 기반으로 작은 집합을 결정해야 하며, 작은 집합의 대표 노드는 의 대표 노드를 가리켜야 합니다. 큰 세트를 처리하므로 두 세트가 병합되어 처리됩니다.

위에서 언급한 findHead 메소드의 경우 father가 있는지 찾아보고, 그렇다면 현재 노드를 father로 교체하고, 현재 노드의 father가 자신과 같다면 해당 노드의 대표 노드를 아는 것과 같다. 현재 세트(처음에는 각 노드의 아버지 포인터가 자신을 가리키기 때문입니다. 프로세스에서 대규모 컬렉션을 가리키지 않으면 항상 자신을 가리킵니다). 이렇게 하면 노드와 스토리지를 나타내는 세트 크기를 찾을 수 있습니다.

findHead 프로세스에서는 Union-Find 구조의 일부 최적화가 완료되어야 합니다. 위쪽을 가리키는 프로세스에서 거리가 너무 길면 findHead가 매번 너무 먼 길을 가기 때문에 경로는 다음과 같아야 합니다. 비용을 줄이기 위해 이 프로세스를 압축합니다. 나중에는 시간이 복잡해집니다. 압축하는 방법? 노드가 순회되면 해당 노드를 스택이나 다른 데이터 구조에 넣은 다음 대표 노드가 발견되면 각 노드를 차례로 팝업하고 이 노드의 아버지가 대표 노드를 가리키도록 하여 순회된 모든 노드가 노드는 findHead의 시간 복잡도가 O(1)로 감소됩니다. 최적화 프로세스는 시간이 많이 걸리지만 최적화된 findHead 메서드는 각 호출에 대해 O(1)의 평균 시간 복잡도를 갖습니다.

암호:

package class01;

import java.util.HashMap;
import java.util.List;
import java.util.Stack;

public class Code04_UnionFind {
     
     

	public static class Element<V> {
     
     //这个结构就是对数据包一层,让他有了地址,方便下边的表示,避免出现根据数字来判断两个元素是否相等而误判的情况
		public V value;

		public Element(V value) {
     
     
			this.value = value;
		}

	}

	public static class UnionFindSet<V> {
     
     //并查集
		public HashMap<V, Element<V>> elementMap;//保存对应的包装元素,方便用户查询
		public HashMap<Element<V>, Element<V>> fatherMap;//保存父元素
		public HashMap<Element<V>, Integer> rankMap;//只有代表节点需要使用(也就是每个集合的头节点),用来保存集合的大小

		public UnionFindSet(List<V> list) {
     
     
			elementMap = new HashMap<>();
			fatherMap = new HashMap<>();
			rankMap = new HashMap<>();
			for (V value : list) {
     
     //设置每个元素的包装、父亲为自己,集合大小为1
				Element<V> element = new Element<V>(value);
				elementMap.put(value, element);
				fatherMap.put(element, element);
				rankMap.put(element, 1);
			}
		}

		private Element<V> findHead(Element<V> element) {
     
     
			Stack<Element<V>> path = new Stack<>();//栈来储存路程中遍历到的节点
			while (element != fatherMap.get(element)) {
     
     //向上遍历直到代表结点
				path.push(element);//压栈
				element = fatherMap.get(element);
			}
			while (!path.isEmpty()) {
     
     //把栈里的节点父亲设置为代表结点
				fatherMap.put(path.pop(), element);
			}
			return element;//返回代表结点
		}

		public boolean isSameSet(V a, V b) {
     
     
			if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
     
     //看是否存在这两个元素,如果不存在直接返回
				return findHead(elementMap.get(a)) == findHead(elementMap.get(b));//看代表结点是否相同来判断是否属于同一个集合
			}
			return false;
		}

		public void union(V a, V b) {
     
     
			if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
     
     //如果两个元素不存在直接什么都不做
				Element<V> aF = findHead(elementMap.get(a));
				Element<V> bF = findHead(elementMap.get(b));
				if (aF != bF) {
     
     //不是同一个集合前提下
					Element<V> big = rankMap.get(aF) >= rankMap.get(bF) ? aF : bF;//判断大小集合
					Element<V> small = big == aF ? bF : aF;
					fatherMap.put(small, big);//设置小集合的代表结点指向大集合的代表结点
					rankMap.put(big, rankMap.get(aF) + rankMap.get(bF));//更新集合大小
					rankMap.remove(small);//删除小集合大小
				}
			}
		}

	}

}

프로모션 02 (주문목록)

주문 목록

이는 요소가 기본적으로 해시 테이블을 기반으로 키별로 정렬되며 정렬 규칙을 사용자 정의할 수 있음을 의미합니다. 모든 연산은 O(logN) 수준입니다.

순서 목록 은 AVL 트리, SB 트리(크기-균형), 레드-블랙 트리, 건너뛰기 목록(SkipList) 으로 구분됩니다 . 네 가지 구현 간에 성능 차이는 거의 없습니다. 그 중 AVL, SB, 레드-블랙 트리는 하나의 카테고리로 구분되는데, 균형 조건이 다른 점만 제외하면 균형 이진 검색 트리와 모두 유사하며 점프 테이블은 하나의 카테고리로 구분됩니다. 처음 세 구조의 유일한 차이점은 균형을 판단하는 조건입니다. SB 트리는 일반적으로 다시 작성하기가 덜 어렵기 때문에 대회에서 선택됩니다.

균형 탐색 트리(Balanced Search Tree): 균형 조건을 만족하는 트리(예를 들어 왼쪽과 오른쪽 하위 트리의 높이 차이가 1을 초과하지 않음) 노드의 경우 왼쪽 하위 트리의 값은 항상 자신보다 작고, 오른쪽 하위 트리는 항상 자신보다 큽니다. 중복된 값이 있는 경우 일반적으로 동일한 위치에 배치합니다.

이진 트리 검색

삭제 작업: 순회 중 이전 노드를 기록합니다.(1) 삭제하려는 값의 왼쪽 및 오른쪽 자식이 비어 있는 즉 리프 노드인 경우 직접 삭제하면 됩니다. (2) 왼쪽, 오른쪽 자식이 모두 없으면 삭제하고 유일한 자식으로 직접 교체합니다. (3) 노드의 왼쪽 및 오른쪽 하위 트리를 모두 삭제하는 경우 왼쪽 하위 트리의 가장 오른쪽 노드 또는 오른쪽 하위 트리의 가장 왼쪽 노드로 교체합니다. 오른쪽 하위 트리의 가장 왼쪽 하위 트리에 오른쪽 하위 트리가 있으면 삭제된 값을 대체하기 전에 오른쪽 하위 트리를 상위 노드에 제공해야 합니다.

수정: 먼저 삭제한 후 추가

여기에 이미지 설명을 삽입하세요.

이진 트리 검색에는 왼쪽 회전과 오른쪽 회전 작업이 있는데, AVL은 이 두 작업을 어떻게 사용하는지 고려합니다.

왼손과 오른손

노드에 대한 연산을 한다는 것은 노드를 왼쪽으로 돌리고, 오른쪽 자식 노드를 루트 노드로 만들고, 오른쪽 자식 노드의 왼쪽 하위 트리를 이 노드로 옮기는 것을 의미합니다. 오른쪽 회전도 마찬가지다. 왼쪽 및 오른쪽 회전은 트리의 균형을 더 좋게 만들 수 있습니다.

여기에 이미지 설명을 삽입하세요.

AVL 트리

AVL 트리의 추가, 삭제, 수정 및 검색은 노드 운영 측면에서 이진 트리 검색과 다르지 않습니다. 하지만 노드를 추가할 때는 합류 후 위쪽으로 밸런스가 맞는지 확인하게 됩니다. 균형이 없으면 왼쪽이나 오른쪽으로 회전합니다. 노드를 삭제하는 경우에도 마찬가지이지만, 잔액 조회를 시작하는 위치는 대체 노드의 이전 노드이며, 위쪽으로 조회를 시작합니다.

잔액이 손상되었는지 구체적으로 확인하는 방법은 무엇입니까?

LL형: 현재 노드의 왼쪽 자식의 왼쪽이 너무 길어서 밸런스가 깨집니다.현재 노드를 오른쪽으로 회전시키면 됩니다.

RR형: 오른쪽 자식의 오른쪽이 너무 길어 균형이 깨져 현재 노드가 왼쪽으로 회전함

LR 유형: 왼쪽 자식의 오른쪽 자식이 너무 깁니다. 먼저 왼쪽 자식을 왼쪽으로 회전시키십시오. 즉, 왼쪽 자식의 오른쪽 자식을 왼쪽 자식의 위치로 이동시킨 다음 오른쪽 자식이 즉, Z가 오른쪽으로 회전하도록 합니다.
여기에 이미지 설명을 삽입하세요.

RL 유형: 오른쪽 자식의 왼쪽 자식이 너무 깁니다. C를 머리로 만들어 보십시오. 먼저 오른쪽 자식을 왼쪽으로 회전시킨 다음 왼쪽 자식의 왼쪽 자식을 머리쪽으로 이동시키십시오. 즉 Z를 시키십시오. 오른쪽으로 회전합니다.

AVL, SB, 레드-블랙트리의 추가, 삭제, 수정, 확인 방식은 동일하며, 차이점은 위반 여부를 판단하는 조건이 다르다는 점이다.

SB 트리

크기-균형 균형:
각 하위 트리의 크기는 형제 하위 트리의 크기보다 작지 않습니다. 즉,
각 삼촌 트리의 크기는 조카 트리의 크기보다 작지 않습니다.

여기에 이미지 설명을 삽입하세요.

그림과 같이 B가 F 트리와 G 트리보다 크거나 같은지 확인해야 합니다. 즉, 형제 트리 B와 C 사이의 간격이 (2+1)을 초과할 수 없습니다.

잔액이 파괴되었습니다:

LL: 현재 노드의 왼쪽 자식의 왼쪽 자식이 오른쪽 자식보다 큽니다. 현재 노드가 재귀되도록 합니다: (1) 오른쪽 회전 (2)m(T)(3)m(L). m 방법: 자식이 변경되는 것을 반복하고 오른쪽 회전 작업을 계속 수행하도록 합니다.

LR: 현재 노드의 왼쪽 자식의 오른쪽 자식이 오른쪽 자식보다 큽니다.

레드 블랙 트리

평형 조건:

(1) 각 노드는 빨간색이거나 검은색입니다.

(2) 헤드 노드와 리프 노드( 하단 널 노드 )

(3) 두 개의 빨간색 노드가 인접하지 않습니다.

(4) 임의의 노드에서 각 잎으로의 모든 경로에는 동일한 수의 검은색 노드가 포함됩니다.

헤드 노드에서 리프 노드까지의 전체 검정색 경로가 7이고 빨간색 및 검정색 경로가 14라고 가정합니다. 모든 리프 노드까지의 경로 차이가 2배를 넘지 않는지 확인합니다.

점프 테이블

키, 값, 하위 항목에 대한 포인터 목록이라는 세 가지 속성이 있습니다. 키는 비교할 수 있어야 합니다.

기본 노드: 기본적으로 포인터가 있으며 키는 가장 작으며 포인터만 확장할 수 있습니다.

새 노드가 추가되면 새 노드는 체를 기준으로 포인터 수를 결정합니다.

(1) 새 노드의 포인터 수가 기본 노드의 포인터 수보다 많으면 기본 노드는 동일한 포인터 수로 노드를 확장합니다. 그런 다음 기본 노드의 최상위 레벨부터 시작하여 새로 추가된 노드의 값보다 약간 큰 노드를 찾고, 그렇지 않은 경우 이 레이어의 포인터를 새 노드를 직접 가리킵니다. 그런 다음 새로 추가된 값보다 작거나 같은 가장 오른쪽 노드를 찾기 위해 하위 수준으로 이동한 다음 해당 포인터가 새 노드를 가리키도록 합니다. 가장 낮은 레벨까지 반복합니다. 그런 다음 기본 노드의 모든 포인터는 이 새 노드를 가리킵니다.

(2) 새 노드의 포인터 개수가 기본 포인터 개수보다 작거나 같으면 최상위 수준부터 시작하여 새 값보다 작거나 같은 가장 오른쪽 노드를 찾습니다. 그런 다음 현재 수준이 새 값의 특정 수준과 일치하는지 확인하고 일치하지 않으면 아무것도 하지 않고 하위 수준으로 이동합니다. 겹치는 포인터가 있으면 찾은 "가장 오른쪽 노드"의 포인터를 새 값을 가리킵니다. node를 누르고 계속해서 맨 아래 레이어로 내려갑니다.

스킵 테이블의 미묘한 점은 일부 값을 건너뛰고 새 노드가 있어야 할 위치를 찾을 수 있다는 것입니다. 예를 들어 아래 그림에서 70이 있는 위치에서 70보다 작거나 같은 가장 오른쪽 값을 찾습니다. 최상위 레벨에서 20을 찾으면 20부터 시작합니다. 70에는 5번째 레이어 노드가 없기 때문에 찾기 시작하고 20의 5번째 레이어에서 아래로 보기 시작하여 4번째 레이어로 이동하여 가장 오른쪽 노드를 찾습니다. 70 이상, 50 찾기, 4층 50에서 계속 아래를 내려다보면 3층으로 왔습니다. (레벨이 내려갈 때마다 일부 값은 생략됩니다.) 세 번째 레벨에서 70 이하의 새로운 값이 발견되지 않으면 두 번째 레벨로 이동합니다. 70이 첫 번째 레벨인 것을 발견하면, 그런 다음 첫 번째 레벨에 70을 놓고 0층으로 내려가서 70도 걸어 놓습니다.

복잡성 추정: 레이어 0에는 N개의 노드가 있고, 레이어 1에는 N/2개의 노드가 있으며, 레이어 2에는 N/4개의 노드가 있습니다. . . . 이러한 방식으로 각 검색은 현재 값의 거의 절반을 건너뛸 수 있으며 이는 전체 이진 트리 검색을 수행하는 것과 동일합니다. 시간 복잡도 O(logN)

여기에 이미지 설명을 삽입하세요.

부스트03(문자열)

하위 문자열은 순서가 지정되고 하위 시퀀스는 순서가 지정되지 않습니다.

KMP

질문: 문자열 str1과 str2, str1에 str2가 포함되어 있는지 여부, 그렇다면 str1에서 str2의 시작 위치를 반환합니다.

기본 방법은 str2와 str1의 각 문자를 시작점으로 비교하는 것이므로 시간 복잡도는 O(N*M)입니다. KMP 알고리즘은 이를 기반으로 일부 가속화를 수행했으며 전체적인 아이디어는 변경되지 않았습니다.

가속하기 전에 str2의 각 문자에 해당하는 정보를 계산해야 합니다. 이 정보는 자체와 관련이 없으며 앞에 있는 문자열과 관련됩니다. 이 정보: 문자열의 "최대 접두사 및 접미사" 그 앞에는 동일합니다." ” 문자열 “길이”. 문자 k 앞의 문자열이 "abbsabb"라고 가정하면 접두사 abb와 접미사 abb가 가장 큰 동일 부분이므로 이 정보는 3입니다. 그리고 같은 부분이 겹칠 수도 있습니다.예를 들어 문자열 "baabbaabb"의 정보는 5, "baabb"입니다. 그러나 최대 길이는 문자열의 전체 길이와 같을 수 없습니다 (예: "aaaa"). 이 정보는 4가 될 수 없습니다. 즉, 접두사와 접미사의 최대 길이가 문자열과 같을 수 없습니다.

str2에서 각 문자의 정보를 얻은 후 속도를 높일 수 있습니다.

여기에 이미지 설명을 삽입하세요.

그림과 같이 str1에서 i부터 시작하여 str2에서 "X" 이전의 내용과 "Y" 이전의 내용은 동일하지만 "x"와 "y"에 도달하면 동일하지 않다고 가정한다. y의 정보가 3이면 이전 문자열의 접두어가 해당 접미어와 같으므로, "x"가 str2의 접두어의 다음 문자와 직접 비교를 시작하도록 할 수 있습니다. 왜? (1) 접두사와 접미사가 동일하고 접미사 부분도 str1의 해당 부분과 같으면 접두사 부분은 str1의 접미사의 해당 부분과 동일하며 이는 접미사의 해당 부분과 동일합니다. "x" 앞의 값은 str2의 접두사 부분과 동일해야 하므로 접두사 비교를 건너뛸 수 있습니다. (2) 그림에서 i+1 j 부분은 왜 비교할 필요가 없나요? 즉, i+1 j 사이에서 비교가 시작될 때 str2와 같지 않은 이유는 무엇입니까? k부터 Large 접두사와 접미사까지의 거리에 있는 문자의 길이가 같지만 우리의 정보가 이미 가장 크기 때문에 k부터 비교하면 동일하다고 가정할 수 있습니다. 정보는 사실이 아니므로 i~j 사이에는 평등이 있을 수 없습니다.

i로 표시된 정보를 찾는 방법은 무엇입니까?

i의 정보를 요청할 때에는 0~i-2 구간의 접두어와 접미어인 i-1의 정보를 먼저 살펴보아야 하는데, 접두어 뒤의 문자 k가 i의 값과 같다면 i-1이면 i의 정보는 i-1 정보 + 1이고, 같지 않으면 k의 정보 접두어 뒤의 문자 m을 확인합니다. m이 i-1과 같으면 i의 정보는 k의 정보 + 1. 같지 않으면 찾을 때까지 이렇게 반복합니다. i-1과 같은 숫자입니다.

여기에 이미지 설명을 삽입하세요.

암호:

public static int getIndexOf(String s, String m) {
    
    
		if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
    
    
			return -1;
		}
		char[] str1 = s.toCharArray();
		char[] str2 = m.toCharArray();
		int i1 = 0;
		int i2 = 0;
		int[] next = getNextArray(str2);//获得信息数组 O(M)
		while (i1 < str1.length && i2 < str2.length) {
    
    //比对 直到str1到结尾或者str2到结尾 O(N)
			if (str1[i1] == str2[i2]) {
    
    //当前位置相等,比对下一位
				i1++;
				i2++;
			} else if (next[i2] == -1) {
    
    //str2指针的位置到了str2的0位置,那就说明当前以str1的指针位置为开头没有和str2相等的字符串,那么就要把str1指针向后移
				i1++;
			} else {
    
    //当前出现不相等的情况,把str2的指针向前移,移动到当前指针前字符串的前缀的后面一位。如果一直移动,直到移动到0位置,那就执行else if的那一种情况了。
				i2 = next[i2];
			}
		}
		return i2 == str2.length ? i1 - i2 : -1;//如果i2到达了结尾,那么说明存在相等部分,就把i1的位置减去i2大小,就是str1中str2开头的位置
	}

	public static int[] getNextArray(char[] ms) {
    
    
		if (ms.length == 1) {
    
    
			return new int[] {
    
     -1 };
		}
		int[] next = new int[ms.length];
		next[0] = -1;//数组0位置是-1,1位置是0,固定的。因为0位置前面没有东西,1位置前之有1个,但是不能等于字符串总长度
		next[1] = 0;
		int i = 2;
		int cn = 0;//代表前面数字的前缀的下一个数字k的下标
		while (i < next.length) {
    
    
			if (ms[i - 1] == ms[cn]) {
    
    //前一个数字等于前一个数字求出的前缀的后一个数字,则当前数字的信息是前一个数字信息加1
				next[i++] = ++cn;
			} else if (cn > 0) {
    
    //不等的情况下,还没走到0时,那就往前,看前缀字符串的前缀是不是存在上一个情况
				cn = next[cn];
			} else {
    
    //不等的情况下,走到了0还是没有相同数字,那么当前数字的信息就是0
				next[i++] = 0;
			}
		}
		return next;
	}

시간 복잡도: 배열 정보를 얻으려면 O(M)이 필요하고 문자열을 비교하려면 O(N)이 필요하지만 N>M이므로 시간 복잡도는 O(N)입니다.

마나처 알고리즘

질문: 문자열 str에서 가장 긴 회문 부분 문자열의 길이를 찾는 방법은 무엇입니까?

시간 복잡도 O(N) 완료를 달성하는 방법

고전적인 접근 방식:

여기에 이미지 설명을 삽입하세요.

각 문자에 대해 왼쪽과 오른쪽으로 확장하여 비교해 보세요. 홀수 상황과 짝수 상황을 처리하기 위해 문자 중간에 "#"과 같은 새로운 문자를 추가하여 홀수 상황과 짝수 상황을 결과 누락 없이 동일하게 처리할 수 있습니다. 시간 복잡도는 O(N2)입니다. 예를 들어, 모두 1인 문자열이 있는 경우 각 문자열은 n2인 모든 문자열과 비교되어야 합니다.

Manacher 알고리즘: 또한 고전적인 방법을 어느 정도 가속화합니다.

전제 지식:

회문 반경: 현재 문자의 왼쪽과 오른쪽으로 확장된 회문 문자열 길이의 절반

회문 직경: 현재 문자의 왼쪽과 오른쪽으로 확장된 회문 문자열의 길이

회문 반경 배열: 각 문자의 회문 반경을 기록하는 보조 배열을 만듭니다.

int R: 이전에 확장된 모든 위치 중에서 가장 오른쪽 회문 오른쪽 경계에 도달했습니다.

int C: 가장 오른쪽 회문 문자열의 중심점을 가져옵니다. R과 C가 함께 변경됩니다.

알고리즘 아이디어: 먼저 "#"으로 배열을 처리한 다음 R=-1 및 C=-1로 설정합니다. 그런 다음 i를 사용하여 현재 위치를 나타냅니다. 한 문자를 순회할 때마다 i에 있는 문자가 R의 어느 쪽에 있는지 판단한다.(1) R의 오른쪽에 있다면, 즉 가장 오른쪽 회문 문자열 내에 없으면 그 문자의 숫자를 격렬하게 비교한다. 문자를 찾기 위해 i의 왼쪽과 오른쪽 가장 큰 회문 문자열, 그리고 R을 업데이트합니다. (2) R의 왼쪽, 즉 지난번에 구한 회문 문자열 내라면 이 상황은 3가지 상황으로 나누어진다. 이 세 가지 상황은 주로 i에 대응하는 i'의 위치를 ​​살펴보는데, i'는 c를 중심으로 한 i의 대칭점이다.

(1) i'의 회문 문자열의 왼쪽 경계가 C에 대한 R의 대칭점 L 보다 클 때 , i의 회문 문자열의 길이는 i'의 회문 문자열의 길이와 같습니다. 왜냐하면 먼저, i and i' 대칭이고 회문 문자열도 대칭이므로 왼쪽과 오른쪽 문자열이 대칭입니다. (i'의 회문 길이는 이전에 계산되었습니다)

(2) i'의 회문열의 왼쪽 경계가 C에 대한 R의 대칭점 L보다 작을 때 , i의 회문열의 길이 는 Ri의 길이와 같습니다 . 그렇다면 왼쪽으로 조금 더, 조금 더 크게 하면 안 될까요? 왜냐하면 L의 왼쪽에 있는 점은 C의 대칭점을 기준으로 R의 오른쪽에 있는 점에 대해 비대칭이기 때문입니다.

(3) i'의 회문열의 왼쪽 경계가 C에 대한 R의 대칭점 L과 같고, i의 회문의 길이가 적어도 "Ri"의 길이일 때, 오른쪽 위치는 R측은 회문에 속하거나 별도의 폭력 판단이 필요합니다.

암호:

public static char[] manacherString(String str) {
     
     //在字符两边添加"#"
		char[] charArr = str.toCharArray();
		char[] res = new char[str.length() * 2 + 1];
		int index = 0;
		for (int i = 0; i != res.length; i++) {
     
     
			res[i] = (i & 1) == 0 ? '#' : charArr[index++];//奇数位填#
		}
		return res;
	}

	public static int maxLcpsLength(String str) {
     
     
		if (str == null || str.length() == 0) {
     
     
			return 0;
		}
		char[] charArr = manacherString(str);					
		int[] pArr = new int[charArr.length];//回文半径数组
		int C = -1;
		int R = -1;//为了代码处理方便,此时的R代表右边界的再往右一个字符
		int max = Integer.MIN_VALUE;
		for (int i = 0; i != charArr.length; i++) {
     
     //遍历每个字符
			pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1; //设置i位置的回文字符串至少是多少,当i>R时,先设置为1,至少为自身。当i小于等于右边界(因为此时R代表右边界右边的值)时,在R-i到i'回文长度中取最小值
			while (i + pArr[i] < charArr.length && i - pArr[i] > -1) {
     
     //在上一步的基础上进行暴力查询,相当于对四种情况都进行了暴力,不过i大于R和i<R两种情况本身不用暴力查询,这里也只用查询一次,其他两种情况i'<L和i==R本身就需要暴力
				if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
					pArr[i]++;
				else {
     
     
					break;
				}
			}
			if (i + pArr[i] > R) {
     
     //看看向右扩后是不是比R大,如果大,那就更新R
				R = i + pArr[i];
				C = i;
			}
			max = Math.max(max, pArr[i]);//更新max
		}
		return max - 1;//max代表带"#"的字符串的最大回文半径,最大回文半径减1就是不带"#"的字符串的回文直径,也就是最大回文字符串的长度
	}

프로모션 04 (슬라이딩 윈도우, 단조로운 스택)

슬라이딩 윈도우

이는 더블 엔드 큐입니다. 일반적으로 더블 엔드 큐의 가장 왼쪽이 헤드이고 가장 오른쪽이 테일이라고 믿어집니다. 왼쪽 포트와 오른쪽 포트 모두 오른쪽으로 미끄러질 수 있습니다. 데이터는 왼쪽과 오른쪽 끝 모두에서 들어오고 나갈 수 있습니다.

LinkedList는 Java, Deque deque = new LinkedList()의 이중 종료 대기열에도 사용됩니다. 헤드 삽입: addFirst(E e)테일 삽입: addLast(E e)포함 여부: contains(Object o)첫 번째 요소를 반환(삭제하지 않음)(head): element()或getFirst() peekFirst()마지막 요소를 반환(삭제하지 않음)(tail): getLast() peekLast()첫 번째 요소를 반환 및 삭제: poll() pollFirst() removeFirst()마지막 요소를 반환 및 삭제:pollLast() removeLast()

일반 큐의 선언은 다음과 같습니다: Queue queue = new LinkedList().

또한 Stack 클래스 외에도 Stack 사용을 위해 LinkedList를 선언할 수도 있습니다(Stack은 더 이상 사용되지 않음). Deque deque = new LinkedList()

슬라이딩 윈도우의 숫자를 큰 것에서 작은 것까지 제어하는 ​​문제에서 오른쪽 끝이 오른쪽으로 슬라이드할 때마다 이전 대기열의 가장 오른쪽 값이 현재 슬라이딩 값보다 작은지 여부를 판단합니다. , 가장 오른쪽 값이 팝업됩니다. 여전히 현재 값보다 작은 경우 자체보다 큰 값을 만날 때까지 다시 팝업한 후 오른쪽부터 대기열에 합류합니다. 그러면 가장 왼쪽 끝은 항상 현재 창의 최대값이 되며, 최대값을 얻으려면 왼쪽 끝에서 첫 번째 값을 팝업하면 됩니다.

leetcode 3은 슬라이딩 윈도우를 사용하여 반복되는 문자가 없는 가장 긴 부분 문자열을 찾습니다.

단조로운 스택

질문: 배열에서 그보다 큰 각 숫자에 가장 가까운 값을 찾으십니까?

배열에 중복된 값이 없다고 가정합니다.

스택의 맨 아래부터 스택의 맨 위까지 큰 것부터 작은 것 순으로 스택을 유지합니다(스택에 저장된 항목은 첨자입니다). 현재 스택에 푸시된 숫자가 스택 상단보다 작으면 순서를 유지할 수 있도록 직접 스택에 푸시하고, 스택 상단보다 큰 요소를 만나면 스택 상단을 팝합니다. 스택이 팝업되면 팝을 계산할 수 있습니다. value의 값이 다음보다 작으면 스택의 맨 위 요소가 현재 값보다 커질 때까지 계속 루프를 반복합니다. 모든 값을 스택에 푸시한 후 스택에 여전히 값이 남아 있으면 마지막으로 스택을 처리해야 합니다. 이때, 스택의 각 숫자에는 그보다 큰 값이 없으며, 그보다 큰 각 숫자에 가장 가까운 값이 그 아래의 값이 됩니다.

여기에 이미지 설명을 삽입하세요.

중복된 값의 상황을 고려할 때: 스택을 푸시하고 자신과 같은 값을 만나면 스택의 상단과 자신을 결합하고 두 개의 첨자를 같은 위치에 저장한 다음 현재 값이 이 두 값보다 큰지 확인합니다. 큰 경우 오른쪽의 가장 큰 값이 현재 값이고 왼쪽의 가장 큰 값이 그 아래의 값입니다.

업그레이드 05(트리 DP, 모리스 순회)

트리 DP 루틴

트리 dp 루틴을 사용하기 위한 전제조건: 문제 해결 목표가 S 규칙인 경우 각 노드를 헤드 노드로 하는 하위 트리의 S 규칙에 따른 모든 답으로 해결 프로세스를 설정하고 최종 답은 그 중에 있어야 합니다.

트리 dp 루틴의 첫 번째 단계: 노드 X를 헤드 노드로 하는 하위 트리에서 답의 가능성을 분석합니다. 이 분석은 X의 왼쪽 하위 트리, X의 오른쪽 하위 트리 및 전체 트리를 기반으로 합니다. X의 관점에서 가능성을 고려한다.

트리 DP 루틴의 두 번째 단계: 첫 번째 단계의 가능성 분석을 기반으로 필요한 모든 정보를 나열합니다.

트리 DP 루틴의 세 번째 단계: 두 번째 단계에서 정보를 병합하고 왼쪽 트리와 오른쪽 트리에 동일한 요구 사항을 제시하고 정보 구조를 작성합니다.

트리 DP 루틴의 네 번째 단계: 재귀 함수 설계 재귀 함수는 X를 헤드 노드로 다룰 때의 답입니다. 여기에는 재귀 기본 케이스 설계, 기본적으로 왼쪽 및 오른쪽 트리의 모든 정보를 직접 획득, 가능성 통합, 세 번째 단계의 정보 구조 반환이라는 네 가지 작은 단계가 포함됩니다.

주제: 포크 트리 노드 간 최대 거리 문제

이진 트리의 노드 a에서 시작하면 위로 올라갈 수도 있고 내려갈 수도 있지만 도중에 있는 노드는 한 번만 통과할 수 있으며, 노드 b에 도달할 때 경로에 있는 노드의 수를 a에서 b까지의 거리라고 합니다. 그러면 이진 트리의 두 노드 사이에는 거리가 없습니다. 거리가 있으면 전체 트리에서 최대 거리를 찾으세요.

알고리즘 아이디어: 임의의 노드에 대해 루트가 되는 전체 트리의 최대 경로는 이를 통과할 수도 있고 통과하지 못할 수도 있으며, 이 조건에 따라 상황을 나눌 수 있습니다. (1) 포함되지 않는 경우, 이를 루트로 하는 트리의 최대 거리는 왼쪽 하위 트리의 최대 거리 또는 오른쪽 하위 트리의 최대 거리와 같습니다.

(2) 포함할 때 거리는 가장 왼쪽 서브트리부터 그것까지의 거리와 같고, 그 다음 오른쪽 서브트리까지의 거리는 왼쪽 서브트리의 높이 + 오른쪽 서브트리의 높이와 같습니다. + 1.

그런 다음 아래에서 위로 각 노드에 대해 왼쪽 하위 트리와 오른쪽 하위 트리 정보를 가져오고 세 가지 사례 중 거리가 가장 큰 사례를 확인한 다음 Info 클래스로 돌아갑니다. Info 클래스에는 최대 거리와 높이라는 두 가지 속성이 포함되어 있습니다. 노드가 전체 트리의 루트인 경우 계산된 최대 거리는 전체 트리의 최대 거리입니다. 기본 케이스를 설정하고 높이가 0이고 최대 거리가 0인 Info 클래스를 반환하는 것을 기억하세요.

암호:


	public static class Node {
     
     
		public int value;//在本题没什么用,路径每经过一个节点就+1
		public Node left;
		public Node right;

		public Node(int data) {
     
     
			this.value = data;
		}
	}

	public static class ReturnType{
     
     
		public int maxDistance;
		public int h;//height
		
		public ReturnType(int m, int h) {
     
     
			this.maxDistance = m;;
			this.h = h;
		}
	}
	
	public static ReturnType process(Node head) {
     
     
		if(head == null) {
     
     
			return new ReturnType(0,0);
		}
		ReturnType leftReturnType = process(head.left);
		ReturnType rightReturnType = process(head.right);
		int includeHeadDistance = leftReturnType.h + 1 + rightReturnType.h;
		int p1 = leftReturnType.maxDistance;
		int p2 = rightReturnType.maxDistance;
		int resultDistance = Math.max(Math.max(p1, p2), includeHeadDistance);//求三种情况的最大距离
		int hitself  = Math.max(leftReturnType.h, leftReturnType.h) + 1;//求高度
		return new ReturnType(resultDistance, hitself);
	}

모리스 순회

시간 복잡도 O(N)과 추가 공간 복잡도 O(1)을 갖는 이진 트리를 순회하는 방법

원본 트리에 있는 많은 수의 자유 포인터를 활용함으로써 공간 절약 목적이 달성됩니다.

Morris가 세부 사항을 살펴봅니다.

현재 노드 cur에 오면 cur가 처음의 헤드 노드 위치에 온다고 가정합니다.

1) cur에 왼쪽 자식이 없으면 cur는 오른쪽으로 이동합니다(cur = cur.right).

2) cur에 왼쪽 자식이 있는 경우 왼쪽 하위 트리에서 가장 오른쪽 노드 MostRight를 찾습니다.

a.mostRight의 오른쪽 포인터가 null을 가리키는 경우 cur를 가리키도록 두고 cur는 왼쪽으로 이동합니다(cur = cur.left).

b.mostRight의 오른쪽 포인터가 cur를 가리키면 null을 가리키도록 두고 cur는 오른쪽으로 이동합니다(cur = cur.right).

3) cur가 비어 있으면 순회가 중지됩니다.

모리스 탐색의 본질

왼쪽 하위 트리가 없는 노드의 경우 한 번만 도착하고 왼쪽 하위 트리가 있는 노드의 경우 두 번만 도착하는 메커니즘을 설정합니다.

Morris 순회 시간 복잡도: O(N)

Morris 선주문, 중간주문, 주문후 순회

선주문 순회: 노드에 한 번만 도달할 수 있으면 도달할 때 직접 인쇄하고, 노드에 두 번 도달할 수 있으면 처음에 직접 인쇄합니다.

	public static void morrisPre(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
    
    
			cur2 = cur1.left;
			if (cur2 != null) {
    
    
				while (cur2.right != null && cur2.right != cur1) {
    
    
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
    
    //第一次来到cur,打印
					cur2.right = cur1;
					System.out.print(cur1.value + " ");
					cur1 = cur1.left;
					continue;
				} else {
    
    
					cur2.right = null;
				}
			} else {
    
    //如果没有左子树,直接打印
				System.out.print(cur1.value + " ");
			}
			cur1 = cur1.right;
		}
		System.out.println();
	}

순차 순회: 노드에 한 번만 도달할 수 있으면 도달할 때 직접 인쇄하고, 노드에 두 번 도달할 수 있으면 두 번째로 인쇄합니다.

public static void morrisIn(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
    
    
			cur2 = cur1.left;
			if (cur2 != null) {
    
    
				while (cur2.right != null && cur2.right != cur1) {
    
    
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
    
    
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
    
    
					cur2.right = null;
				}
			}
			System.out.print(cur1.value + " ");//没有左子树时直接打印;有左子树时,执行完上边的if,如果第一次来到cur直接continue,执行不到此处;第二次来到cur后,没有continue,执行本处打印
			cur1 = cur1.right;
		}
	}

후위 순회: 한 번만 도달할 수 있는 노드에 대해서는 인쇄가 수행되지 않고, 두 번 도달할 수 있는 노드에 대해 두 번째로 도달할 때 노드의 왼쪽 하위 트리의 오른쪽 경계가 인쇄됩니다. 전체 트리의 오른쪽 경계가 역순으로 인쇄됩니다. 역순으로 인쇄하려면 시간 복잡도 O(1)이 필요합니다.연결된 목록을 역순으로 인쇄한 다음 연결 목록을 복원합니다.

재귀 순회 또는 Morris 순회를 선택하는 방법은 무엇입니까?

이진 트리의 재귀 순회 및 모리스 순회 결과를 통합하기 위해 세 번째 순회가 필요한 경우 순회 방법이 최적의 솔루션입니다. 그렇지 않으면 Morris가 최적의 솔루션입니다.

개선사항 06(빅데이터 질문/자원 제한 질문)

문제 해결 아이디어:

이런 종류의 질문에는 면접관의 자격을 묻는 것이 필요합니다. 명확하게

1) 해시 함수는 데이터를 종류에 따라 균등하게 나눌 수 있다(보편적 방식).

2) 블룸 필터는 컬렉션을 생성하고 쿼리하는 데 사용되며 많은 공간을 절약할 수 있습니다.

3) 일관된 해싱으로 데이터 서버의 부하 관리 문제를 해결합니다.

4) Union-Find 구조를 사용하여 아일랜드 문제의 병렬 계산을 수행합니다.

이 섹션의 내용:

5) 비트맵은 특정 범위의 숫자 발생을 해결하고 많은 공간을 절약할 수 있습니다.

6) 분할된 통계 아이디어를 사용하여 많은 공간을 더욱 절약합니다.

7) 힙 및 외부 정렬을 사용하여 여러 처리 장치의 결과를 병합합니다.

비트맵은 특정 범위의 숫자 발생을 해결하고 많은 공간을 절약할 수 있습니다.

주제:

32비트 부호 없는 정수의 범위는 0~4,294,967,295이며, 이제 정확히 40억 개의 부호 없는 정수를 포함하는 파일이 있으므로 전체 범위에 한 번도 등장한 적이 없는 숫자가 있을 것입니다. 최대 1GB의 메모리를 사용할 수 있는데, 이전에 나타나지 않은 숫자를 모두 찾는 방법은 무엇입니까?

숫자 크기는 0~2의 32-1제곱이고 숫자는 40억개인데 2의 32제곱을 8바이트로 나누어 적용하면 즉, 500MB를 한번에 적용하면 모든 숫자를 셀 수 있는지 여부를 셀 수 있다. 등장했습니다.

[고급] 메모리 제한은 3KB인데 이전에 나오지 않았던 숫자만 찾으면 됩니다.

3KB는 3000/4≒512 int 부호 없는 정수를 저장할 수 있습니다. 그런 다음 크기 512의 int 배열을 생성합니다. 이 배열의 각 비트는 8388608 범위의 숫자가 나타난 횟수를 나타냅니다. 총 512 8388608은 40억입니다. 이것이 범위 통계입니다. 각 비트는 숫자만큼 해당 통계 범위에 한 번 나타나면 1이 추가됩니다.

번호에 대한

고급: 몇 가지 변수에만 신청할 수 있다고 가정하면 어떤 숫자가 나타나지 않았는지 어떻게 계산합니까?

0-2를 32-1로 나누고 모든 숫자를 세어 어느 쪽이 불만족스러운지 확인합니다. 예를 들어 0의 왼쪽 2의 32승이 만족되지 않으면 왼쪽을 다시 두 부분으로 나누고, 그런 다음 다시 세어 어느 쪽이 불만족스러운지 확인하고, 한 번도 나타나지 않은 숫자를 찾을 때까지 반복합니다.

여기에 이미지 설명을 삽입하세요.

100억 개의 URL이 포함된 대용량 파일이 있습니다. 각 URL이 64B를 차지한다고 가정합니다. 파일에서 중복된 URL을 모두 찾아보세요.

블룸 필터를 사용할 수 있지만 특정 오류율을 허용해야 합니다.

[보충] 어떤 검색업체에서는 하루에 엄청난 양의 사용자 검색어(수백억 개의 데이터)를 사용하는데, 매일 인기 단어 상위 100개를 찾을 수 있는 실현 가능한 방법을 설계해 주십시오.

데이터를 작은 파일로 나누고 각각의 작은 파일에 대해 큰 루트 힙을 유지하고 마지막으로 매번 모든 큰 루트 힙 중에서 가장 큰 루트 힙을 선택합니다. 100을 선택하면 됩니다.

질문: 32비트 부호 없는 정수의 범위는 0~4294967295입니다. 현재 40억 개의 부호 없는 정수가 있습니다. 최대 1GB의 메모리를 사용하여 두 번 나타나는 모든 숫자를 찾을 수 있습니다.

해시함수를 나누거나 비트맵을 사용하며 2비트는 정보를 나타내며, 00은 0번 나타난다는 뜻, 01은 1번 나타난다, 10은 2번 나타난다는 뜻이다. 숫자를 표현하려면 2비트가 필요하며, 2의 32승에 2를 곱하고 8로 나누어 바이트 수를 구하는데, 1G 미만이면 표현할 수 있습니다.

고급: 최대 3KB의 메모리를 사용할 수 있습니다. 이 40억 개의 정수의 중앙값을 찾는 방법은 무엇입니까?

범위 통계, 각 주기는 단어 빈도를 계산하여 중앙값보다 작은 범위의 총 단어 수를 찾은 다음 이 중앙값 앞의 숫자를 뺀 다음 다음 주기에서 동일한 부분으로 나눕니다. 뺀 숫자를 계산한 다음 계산을 시작합니다.

프로모션 07(동적 프로그래밍)

동적 프로그래밍에 대한 잔혹한 재귀

동적 프로그래밍: 재귀 -> 기억된 검색(캐시) -> 엄격한 테이블 구조(직접 테이블 그리기) -> 동적 프로그래밍의 개선된 버전 - 기울기 최적화 등 -> 고급 최적화

선형 동적 프로그래밍, 배낭 동적 프로그래밍, 간격 동적 프로그래밍, 트리 동적 프로그래밍…

동적 프로그래밍은 반복되는 계산을 줄이기 위한 무차별적인 시도일 뿐입니다.

이 기술은 대규모 루틴이므로 먼저 시행착오 아이디어를 사용하여 시간 복잡도에 대한 걱정 없이 문제를 해결하는 재귀 함수를 작성합니다.

이 과정은 대체불가능하고 정해진 루틴이 없으며 오직 개인의 지혜나 충분한 경험에만 의지할 수 있습니다.

그러나 시도한 버전을 동적 프로그래밍으로 최적화하는 방법에는 고정된 루틴이 있으며 일반적인 단계는 다음과 같습니다.

1) 재귀적 상태를 나타낼 수 있는 변수 매개변수, 즉 매개변수가 결정되면 반환값이 결정되는 변수를 찾아라. 매개변수는 가능한 한 적어야 하며 변경하기 쉽지 않아야 한다.

2) 가변 매개변수의 모든 조합을 테이블로 매핑합니다. 하나의 가변 매개변수는 1차원 테이블이고, 두 개의 가변 매개변수는 2차원 테이블입니다.…

3) 최종 답이 필요한 위치를 표에 표시하십시오.

4) 재귀 과정의 기본 사례에 따라 다른 위치에 의존할 필요가 없는 이 테이블의 가장 간단한 위치의 값을 채웁니다.

5) 재귀 과정의 비기본 사례 부분, 즉 분석 테이블의 공통 위치를 어떻게 계산하는지에 따라 이 테이블의 채우기 순서가 결정됩니다.

6) 표를 채우고 표에서 최종 답의 위치 값을 반환합니다. 이것은 엄격한 테이블 구조입니다.

동적 프로그래밍 요약:

먼저 '시행법'을 생각한 후 재귀적 과정을 작성하고, 암기 검색을 선택해 반복 계산을 줄일 수도 있고, 엄격한 테이블 구조로 동적 프로그래밍을 직접 수행할 수도 있습니다. 엄격한 테이블 구조 동적 프로그래밍은 테이블을 그리는 것으로 먼저 기본 사례에 따라 결정된 위치를 결정한 다음 일반 위치가 의존하는 알려진 위치를 결정하고 얻은 그리드를 기반으로 모든 그리드를 순서대로 계산합니다. 마지막으로 일부 질문은 기울기 등에 대해 최적화될 수 있습니다. 예를 들어, 동일한 행이나 열에서 이전에 얻은 결과와 현재 종속성을 더해 현재 위치에서 필요한 결과를 계산할 수 있으며 결과를 직접 계산할 수 있습니다. 의존성 값을 반복적으로 계산하지 않고. 그러나 경사 최적화에는 많은 내용이 포함되며 수학적 문제가 수반됩니다. 단계를 최적화하고 불필요한 계산을 줄이세요.

  1. 재귀를 생각할 때 전체 재귀 프로세스에 너무 많은 관심을 기울이지 않도록 주의하고 기본 사례와 하위 문제만 생각하면 나머지는 재귀적으로 직접 해결할 수 있습니다.
  2. 3차원 매개변수 목록은 3차원입니다. 먼저 가장 낮은 계층의 데이터 상황을 고려한 다음 위쪽의 다른 계층의 상황을 계산합니다. 본질은 여전히 ​​2차원이지만 다음 계층의 결과에 의존합니다. 여러 2차원 수준을 계산합니다.

Arrays.fill()은 배열을 특정 숫자로 채웁니다.

추천

출처blog.csdn.net/m0_63323097/article/details/129910375