코드 카프리스 알고리즘 트레이닝 캠프 여섯째 날 | 해시 테이블 이론적 기초, 242. 유효 알파벳, 349. 두 배열의 교집합, 202. 행복한 숫자, 1. 두 숫자의 합

오늘 배운 기사 및 비디오 링크

해시 테이블 이론적 근거 기사 링크: 링크
242 기사 링크: 링크
242 비디오 설명 링크: 링크
349 기사 링크: 링크
349 비디오 설명 링크 : 링크
202 기사 링크: 링크
202 비디오 설명 없음
1 기사 링크: 링크
1 비디오 설명 링크: 링크

해시 테이블 이론적 근거

해시 테이블의 내부 구현 원리

공식 설명: 해시 테이블은 키 코드의 값에 따라 직접 액세스되는 데이터 구조입니다. (많은 사람들이 직관력이 없는 것으로 추정됨)

우리에게 익숙한 배열에서 해쉬 테이블에 대해 알아보자 사실 배열은 해쉬 테이블이다 .

해시 테이블의 키 코드는 배열의 인덱스 첨자이며 아래 그림과 같이 첨자를 통해 배열의 요소에 직접 액세스합니다. 해시 테이블의 적용: 일반적 으로 신속하게 여부를 결정하는
여기에 이미지 설명 삽입
데 사용됩니다. 집합에 요소가 나타납니다 .

예: 이 학교에 이름이 있는지 확인하려는 경우.

열거하면 시간 복잡도는 O(n)이지만 해시 테이블을 사용하면 시간 복잡도는 O(1)에 불과합니다.

운영 방법: 해시 테이블에 이 학교의 학생 이름만 입력하면 되며 쿼리 시 인덱스를 통해 학생이 학교에 있는지 여부를 직접 알 수 있습니다.

그렇다면 학생 이름은 해시 테이블과 어떤 관련이 있습니까?

이것은 해시 함수가 사용되는 곳입니다 - 학생 이름을 해시 테이블에 매핑합니다.

해시 함수

해쉬함수는 학생의 이름을 해쉬테이블의 인덱스에 직접 매핑한 후, 인덱스 첨자를 조회하여 해당 학생이 재학 중인지를 빠르게 알 수 있습니다.

해시 함수는 hashCode를 통해 이름을 값으로 변환합니다 ( 특정 인코딩을 사용하여 다른 데이터 형식을 다른 값으로 변환).
여기에 이미지 설명 삽입
그런데 hashCode로 얻은 값이 해시 테이블의 크기보다 큰 경우(tableSize보다 큰 경우) 어떻게 해야 할까요?

이때 매핑된 인덱스 값이 모두 해시 테이블에 속하도록 하기 위해 해당 값에 대해 모듈로 연산을 다시 수행하여 해시 테이블에 매핑될 수 있도록 합니다.

하지만 학생 수가 해시 테이블의 크기보다 크면 어떻게 될까요? 이때 같은 인덱스 첨자 위치에 여러 동급생 이름이 동시에 매핑되는 것은 불가피하다. 이를 통해 해시 충돌을 유도합니다 .

해시 충돌

여러 요소가 인덱스 첨자의 위치에 매핑되면 해시 충돌 입니다 .
여기에 이미지 설명 삽입
일반적으로 지퍼 방법과 선형 감지 방법의 두 가지 솔루션이 있습니다.

지퍼 방식

위 그림과 같이 Xiao Li와 Xiao Wang은 인덱스 1에서 충돌이 발생하고 충돌하는 요소는 연결 목록에 저장됩니다. 인덱스를 통해 Xiao Li와 Xiao Wang을 찾을 수 있습니다.
여기에 이미지 설명 삽입
(데이터의 크기는 dataSize, 해시 테이블의 크기는 tableSize)

이 방법은 적절한 연결 목록 크기를 선택해야 배열의 빈 값으로 인해 많은 양의 메모리가 낭비되지 않고 연결 목록이 너무 길어서 검색 시간이 늘어나지 않습니다.

선형 프로빙

이 방법을 사용하면 tableSize가 dataSize보다 큰지 확인해야 합니다. 충돌은 해시 테이블의 슬롯에 의해 해결됩니다.

세 가지 일반적인 해시 구조

  • 정렬
  • 세트(컬렉션)
  • 지도(매핑)

C++에서 set 및 map은 각각 다음 세 가지 데이터 구조를 제공합니다. 기본 구현과 장단점은 다음 표에 나와 있습니다.
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
옵션 사용:

  • 해시 문제를 해결하려면 최적의 쿼리 및 삭제 효율성 때문에 unordered_set이 선호됩니다 .
  • 컬렉션을 주문해야 하는 경우 세트를 사용하십시오.
  • 정렬된 데이터와 반복 데이터가 모두 필요한 경우 multiset을 사용하십시오.

요약: 요소가 집합에 나타나는지 여부를 빠르게 판단해야 하는 경우 해싱을 고려해야 합니다. (시간을 위해 공간을 희생)

242. 효과적인 아나그램

제목보고 첫생각

제목 설명:
두 개의 문자열 s와 t가 주어지면 t가 s의 애너그램인지 확인하는 함수를 작성하십시오.

애너그램: 동일한 수의 동일한 문자이지만 동일한 순서는 아닙니다.

다음과 같은 아이디어가 있습니다.
단어의 문자가 다른 단어에 나타나는지 여부를 판단하기 위해 가장 먼저 떠오르는 것은 해시 테이블을 사용하는 것입니다. s의 문자 발생 횟수를 기록하는 배열을 정의합니다.

그리고 총 26개의 문자가 있고 a부터 z까지의 아스키 문자도 연속적이기 때문에 배열 레코드를 크기 26으로 정의하고 0으로 초기화합니다.

문자열 s를 순회하고 s[i] - 'a'가 있는 요소에 대해 +1 작업을 수행합니다 . 이러한 방식으로 s의 문자 발생 수를 셀 수 있습니다.

그런 다음 문자열 t를 탐색하고 t에 나타나는 문자 매핑 해시 테이블의 인덱스에 있는 값에 대해 -1 연산을 수행합니다.

마지막으로 레코드 배열의 요소가 0 이 아닌지 , 즉 둘 사이에 해당 문자 수가 다른지 확인 하고 false를 반환합니다.

레코드 배열의 모든 요소가 0 이면 문자열 s와 t가 애너그램이라는 의미이므로 true를 반환합니다.

코드 변덕을 읽은 후의 생각

생각은 나랑 똑같다

구현 중 발생하는 어려움

어려움 없음

코드

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        int record[26]={
    
    0};
        for(int i = 0;i < s.size();i++){
    
    
            record[s[i] - 'a']++;
        }
        for(int i = 0;i < t.size();i++){
    
    
            record[t[i] - 'a']--;
        }
        for(int i = 0;i < 26;i++){
    
    
            if(record[i] != 0){
    
    
                return false;
            }
        }
        return true;
    }
};

349. 두 배열의 교차점

제목보고 첫생각

제목 설명:

두 개의 배열 nums1과 nums2가 주어지면 교차점을 반환합니다. 출력의 각 요소는 고유 . 출력 결과의 순서를 무시할 수 있습니다 .

다음과 같은 아이디어가 있습니다.

참고: 출력 결과의 각 요소는 고유해야 합니다. 즉, 출력 결과가 중복 제거되며 출력 결과의 순서는 무시할 수 있습니다.

제목을 변경하여 값의 크기를 제한하고 배열을 해시 테이블로 사용할 수 있습니다.

코드 변덕을 읽은 후의 생각

영상에서는 set과 array 두 가지 방법이 있는데 배열을 예전에 써봤기 때문에 주로 set 사용법을 이해하고 있습니다.

집합의 응용 시나리오: 해시 값이 상대적으로 작고 분산되어 있고 범위가 큰 경우 이때 배열을 사용하면 공간 이 낭비 됩니다 . 이 경우 세트를 사용해야 합니다.

위의 내용에서 C++가 set에 사용할 수 있는 세 가지 데이터 구조를 제공한다는 것을 알 수 있습니다 .

  • 표준::세트
  • std::다중 집합
  • std::unordered_set

std::set 및 std::multiset의 기본 구현은 레드-블랙 트리이고 std::unordered_set의 기본 구현은 해시 테이블입니다.

제목은 데이터를 정렬할 필요가 없고 , 데이터가 반복되는 것을 허용 하지 않으므로 , 이 때 읽고 쓰는 데 unordered_set을 사용하는 것이 가장 효율적입니다 .

std::unordered_set의 기본 구현은 해시 테이블입니다.unordered_set을 사용하면 읽기 및 쓰기 효율성이 가장 높으며 데이터를 정렬할 필요가 없으며 데이터가 반복되는 것을 허용하지 않으므로 unordered_set이 선택됩니다.

아이디어는 아래 그림에 나와 있습니다.
여기에 이미지 설명 삽입

구현 중 발생하는 어려움

올바른 unordered_set템플릿은 아직 익숙하지 않으며 배워야 합니다.

코드

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for(int num : nums2){
    
    
            if(nums_set.find(num) != nums_set.end()){
    
    
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

질문 202. 행복한 숫자

제목보고 첫생각

제목 설명:

숫자 n이 행복한 숫자인지 판단하는 알고리즘을 작성하세요.

"행복한 숫자"는 다음과 같이 정의됩니다. 양의 정수의 경우 매번 각 위치의 숫자의 제곱의 합으로 숫자를 바꾼 다음 숫자가 1이 될 때까지 이 과정을 반복하거나 무한 루프가 될 수 있지만 절대 1로 바뀌지 않습니다. 1이 될 수 있다면 이 숫자는 행복한 숫자입니다.

n이 행복한 숫자이면 True를 반환하고 그렇지 않으면 False를 반환합니다.

다음과 같은 아이디어가 있습니다.

키: ** 루프가 무한이면 합산 과정에서 합계가 반복적으로 나타납니다**

따라서 합이 반복되는지 여부를 판단하기 위해 해시 방식을 사용하는데, 반복되면 false 를 반환하고 그렇지 않으면 1이 될 때까지 합을 찾는다.

unordered_set을 사용하여 합계가 반복되는지 여부를 확인할 수 있습니다 .

코드 변덕을 읽은 후의 생각

같은 생각

구현 중 발생하는 어려움

값의 각 자릿수에 대한 특이 연산에 익숙하지 않습니다.

코드

class Solution {
    
    
public:
    // 取数值各个位上的单数之和
    int getSum(int n) {
    
    
        int sum = 0;
        while (n) {
    
    
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
    
    
        unordered_set<int> set;
        while(1) {
    
    
            int sum = getSum(n);
            if (sum == 1) {
    
    
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
    
    
                return false;
            } else {
    
    
                set.insert(sum);
            }
            n = sum;
        }
    }
};

1. 두 숫자의 합

제목보고 첫생각

제목 설명:

정수 배열 nums와 대상 값 대상이 주어지면 합계가 배열의 대상 값인 두 정수를 찾아 해당 배열 첨자를 반환하십시오.

이 문제를 해결하기 어렵고 지도에 대해 잘 모릅니다.

코드 변덕을 읽은 후의 생각

이 질문은 우리가 순회한 요소를 저장하기 위한 컬렉션이 필요하며 배열을 순회할 때 요소가 순회되었는지 여부, 즉 이 컬렉션에 나타나는지 여부를 이 컬렉션에 묻습니다.

이 질문에서 우리는 요소가 순회되었는지 여부를 알아야 할 뿐만 아니라 이 요소에 해당하는 첨자 도 알아야 합니다. 저장할 키 값 구조 , 요소를 저장할 키저장할 값을 사용해야 합니다. 첨자 이므로 map 을 사용하는 것이 적절합니다 .

이전에 사용하던 세트를 사용하는 이유는 무엇입니까?

  • 배열의 크기는 제한적이며 요소가 적고 해시 값이 너무 크면 메모리 공간이 낭비됩니다.
  • 집합은 모음이고, 그 안에 있는 요소는 키일 수 있습니다 .두 숫자의 합에 대한 문제는 y가 존재하는지 여부를 판단할 뿐만 아니라 y의 첨자 위치를 기록해야 합니다. x와 y의 값을 반환해야 합니다. 따라서 set 도 사용할 수 없습니다.

맵은 키 값 저장 구조로, 키를 사용하여 값을 저장하고 값을 사용하여 값이 있는 위치에 첨자를 저장할 수 있습니다.

C++의 세 가지 유형의 맵 중에서 std::unordered_map 을 선택하십시오 . 이 질문에서는 키 순서가 필요하지 않으며 std::unordered_map 을 선택하는 것이 더 효율적이기 때문입니다 .

지도를 사용할 때 다음 두 가지 사항에 주의하십시오.

  • 지도는 무엇에 사용됩니까?
  • 맵의 키와 값은 무엇을 나타냅니까?

첫 번째 점과 관련하여 map의 목적은 우리가 방문한 요소를 저장하는 것입니다. 배열을 순회할 때 이전에 순회한 요소와 해당 첨자를 기록해야 현재 항목과 일치하는 항목을 찾을 수 있기 때문입니다. 요소(즉, 목표에 동일하게 추가)

두 번째 항목에 관해서는 이 질문에 대해 요소를 제공하고 이 요소가 나타나는지 확인하고 그렇다면 이 요소의 첨자를 반환해야 합니다.

요소의 출현 여부를 판단하기 위해서는 이 요소를 키로 사용하므로 배열의 요소를 키로 사용하고 키는 값에 해당하며 값은 첨자를 저장하는 데 사용됩니다.

따라서 맵의 저장 구조는 {key: 데이터 요소, 값: 배열 요소에 해당하는 첨자}입니다.

배열을 순회할 때 현재 순회한 요소와 일치하는 값이 있는지 보기 위해 지도를 쿼리하기만 하면 됩니다 . 그렇다면 일치하는 쌍을 찾은 것입니다. 그렇지 않으면 현재 순회한 요소를 지도에 넣습니다. 지도 상점은 우리가 방문한 요소입니다.

구현 프로세스는 다음과 같습니다.
여기에 이미지 설명 삽입

구현 중 발생하는 어려움

값의 각 자릿수에 대한 특이 연산에 익숙하지 않습니다.

코드

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
    
    
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
    
    
                return {
    
    iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {
    
    };
    }
};

오늘 수확

1. 해시 테이블의 기본 이론에 대한 이해

2. 세트 및 맵의 적용 시나리오를 파악합니다.

3. 아직 템플릿 라이브러리 사용이 미숙하고 앞으로 강화할 필요가 있습니다.

오늘 공부시간은 3시간

이 기사의 사진은 모두 Carl의 코드 카프리스에서 가져온 것이며 대단히 감사합니다.

추천

출처blog.csdn.net/m0_46555669/article/details/127060997