代码随想录算法训练营Day7

79 阅读13分钟

代码随想录算法训练营Day7

454.四数相加II

思路

参考两数相加求解,先求AB相加的所有可能值,存在哈希表中,再求CD相加的所有可能值,如果相反数存在于哈希表中,则哈希表中以其相反数为key的value就是可能的解的个数。

代码实现

// 定义哈希表节点
typedef struct HashNode {
    int key;
    int value;
    struct HashNode *next;
} HashNode;

// 定义哈希表
typedef struct HashTable {
    int size;
    HashNode **buckets;
} HashTable;
// 初始化哈希表
HashTable* createHashTable(int size) {
    HashTable *table = (HashTable *)malloc(sizeof(HashTable));
    table->size = size;
    table->buckets = (HashNode **)malloc(sizeof(HashNode*) * size);
    for (int i = 0; i < size; ++i) {
        table->buckets[i] = NULL;
    }
    return table;
}

// 创建新的哈希节点
HashNode* createHashNode(int key, int value) {
    HashNode *newNode = (HashNode *)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->value = value;
    newNode->next = NULL;
    return newNode;
}

// 计算哈希值(简单的模运算)
unsigned int hash(HashTable *table, int key) {
    return (unsigned int) key % table->size;
}

// 插入键值对到哈希表
void insert(HashTable *table, int key, int value) {
    int bucketIndex = hash(table, key);
    HashNode *newNode = createHashNode(key, value);
    newNode->next = table->buckets[bucketIndex];
    table->buckets[bucketIndex] = newNode;
}

// 搜索哈希表中的值
int search(HashTable *table, int key) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];
    while (node) {
        if (node->key == key) {
            return node->value;
        }
        node = node->next;
    }
    return -1; // 表示找不到
}

// 删除哈希表中的节点
void delete(HashTable *table, int key) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];
    HashNode *prev = NULL;
    while (node) {
        if (node->key == key) {
            if (prev) {
                prev->next = node->next;
            } else {
                table->buckets[bucketIndex] = node->next;
            }
            free(node);
            return;
        }
        prev = node;
        node = node->next;
    }
}

// 销毁哈希表
void destroyHashTable(HashTable *table) {
    for (int i = 0; i < table->size; ++i) {
        HashNode *node = table->buckets[i];
        while (node) {
            HashNode *temp = node;
            node = node->next;
            free(temp);
        }
    }
    free(table->buckets);
    free(table);
}


// 更新哈希表中的值
void update(HashTable *table, int key, int value) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];

    // 搜索特定的键
    while (node != NULL) {
        if (node->key == key) {
            node->value = value;  // 如果找到,则更新值
            return;
        }
        node = node->next;
    }

//     如果键不存在,可以选择插入新的键值对,也可以不做任何操作
     insert(table, key, value); // 取消注释以插入新的键值对
}


int fourSumCount(int* nums1, int nums1Size, int* nums2, int nums2Size, int* nums3, int nums3Size, int* nums4, int nums4Size) {
    int count = 0;
    HashTable* table = createHashTable(10000);
    for (int i = 0; i < nums1Size; i++) {
        for (int j = 0; j < nums2Size; j++) {
            //计算A和B的和
            int sum1 = nums1[i] + nums2[j];
            int sum1Count = search(table, sum1);
            if(sum1Count == -1){
                //如果没有记录过,则记录次数为1
                update(table, sum1, 1);
            }else{
                //如果已经有记录,则记录数加一
                update(table, sum1, sum1Count+1);
            }
        }
    }
    
    for (int i = 0; i < nums3Size; i++) {
        for (int j = 0; j < nums4Size; j++) {
            //计算C和D的和
            int sum2 = nums3[i] + nums4[j];
            //求需要的A+B值wanted
            int wanted = -sum2;
            int wantedCount = search(table, wanted);
            if(wantedCount != -1){
                // 如果哈希表中存在wanted,则存在纪录次数有多少次
                count += wantedCount;
            }
        }
    }
    return count;
}

image-20240105022043252

383. 赎金信

思路

和字母异位词的比较类似,只是赎金信的字母频度记录应该是杂志的字母频度记录的子集,而不需要完全相同,也就是说杂志里的字母要够拼成赎金信即可。

还是一样用哈希表统计赎金信和杂志不同字母的频度,根据记录赎金信的哈希表的key值查询记录杂志的对应的key值频度(即对应的value)是否大于等于录赎金信的哈希表中的value,如果所有的key都是,那就说明

杂志有足够的材料(字符数)来组成赎金信。

代码实现

// 定义哈希表节点
typedef struct HashNode {
    int key;
    int value;
    struct HashNode *next;
} HashNode;

// 定义哈希表
typedef struct HashTable {
    int size;
    HashNode **buckets;
} HashTable;
// 初始化哈希表
HashTable* createHashTable(int size) {
    HashTable *table = (HashTable *)malloc(sizeof(HashTable));
    table->size = size;
    table->buckets = (HashNode **)malloc(sizeof(HashNode*) * size);
    for (int i = 0; i < size; ++i) {
        table->buckets[i] = NULL;
    }
    return table;
}

// 创建新的哈希节点
HashNode* createHashNode(int key, int value) {
    HashNode *newNode = (HashNode *)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->value = value;
    newNode->next = NULL;
    return newNode;
}

// 计算哈希值(简单的模运算)
unsigned int hash(HashTable *table, int key) {
    return (unsigned int) key % table->size;
}

// 插入键值对到哈希表
void insert(HashTable *table, int key, int value) {
    int bucketIndex = hash(table, key);
    HashNode *newNode = createHashNode(key, value);
    newNode->next = table->buckets[bucketIndex];
    table->buckets[bucketIndex] = newNode;
}

// 搜索哈希表中的值
int search(HashTable *table, int key) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];
    while (node) {
        if (node->key == key) {
            return node->value;
        }
        node = node->next;
    }
    return -1; // 表示找不到
}

// 删除哈希表中的节点
void delete(HashTable *table, int key) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];
    HashNode *prev = NULL;
    while (node) {
        if (node->key == key) {
            if (prev) {
                prev->next = node->next;
            } else {
                table->buckets[bucketIndex] = node->next;
            }
            free(node);
            return;
        }
        prev = node;
        node = node->next;
    }
}

// 销毁哈希表
void destroyHashTable(HashTable *table) {
    for (int i = 0; i < table->size; ++i) {
        HashNode *node = table->buckets[i];
        while (node) {
            HashNode *temp = node;
            node = node->next;
            free(temp);
        }
    }
    free(table->buckets);
    free(table);
}


// 更新哈希表中的值
void update(HashTable *table, int key, int value) {
    int bucketIndex = hash(table, key);
    HashNode *node = table->buckets[bucketIndex];

    // 搜索特定的键
    while (node != NULL) {
        if (node->key == key) {
            node->value = value;  // 如果找到,则更新值
            return;
        }
        node = node->next;
    }

//     如果键不存在,可以选择插入新的键值对,也可以不做任何操作
     insert(table, key, value); // 取消注释以插入新的键值对
}

int* getKeys(HashTable *table, int *keysSize) {
    int count = 0;
    
    // 首先,统计哈希表中总共有多少个键
    for (int i = 0; i < table->size; ++i) {
        HashNode *node = table->buckets[i];
        while (node != NULL) {
            count++;
            node = node->next;
        }
    }

    *keysSize = count; // 设置键的数量
    if (count == 0) return NULL; // 如果表中没有键,则返回NULL

    int *keys = (int *)malloc(sizeof(int) * count);
    int index = 0;

    // 收集键
    for (int i = 0; i < table->size; ++i) {
        HashNode *node = table->buckets[i];
        while (node != NULL) {
            keys[index++] = node->key;
            node = node->next;
        }
    }

    return keys;
}

bool canConstruct(char* ransomNote, char* magazine) {
    long ransomNoteLength = strlen(ransomNote);
    long magazineLength = strlen(magazine);
    if(ransomNoteLength > magazineLength){
        // 如果赎金信的长度比杂志还要长,杂志必然没有足够的素材拼成赎金信
        return false;
    }
    HashTable* table = createHashTable(26);
    HashTable* table2 = createHashTable(26);
    // 用其中一个哈希表记录赎金信的字母使用频度。
    for (int i = 0; i < ransomNoteLength; i++) {
        int idx = ransomNote[i] - 'a';
        int count = search(table, idx);
        if(count == -1){
            update(table, idx, 1);
        }else{
            update(table, idx, count+1);
        }
    }
    // 用另一个哈希表记录杂志的字母使用频度。
    for (int i = 0; i < magazineLength; i++) {
        int idx = magazine[i] - 'a';
        int count = search(table2, idx);
        if(count == -1){
            update(table2, idx, 1);
        }else{
            update(table2, idx, count+1);
        }
    }
    
    // 获取赎金信中的字母种类个数(哈希表中的key有多少种)
    int keySize;
    int *keys = getKeys(table, &keySize);
    //
    for (int i = 0; i < keySize; i++) {
        int key = keys[i];
        if(search(table2, key) == -1){
            // 如果有一个赎金信中需要的字母杂志里完全没有,必然不能组成赎金信
            return false;
        }else{
            if(search(table, key) > search(table2, key)){
                //如果赎金信里的某个字母数量比杂志里要多,则杂志也不能组成赎金信
                return false;
            }
        }
    }
    return true;
}

image-20240106234548744

15.三数之和

思路

双指针法解决三数之和问题的步骤如下:

  1. 排序: 首先对数组进行排序,这是保证解无重复值的关键。
  2. 遍历: 使用一个循环遍历数组中的每个元素,作为三数之和中的第一个数。
  3. 双指针设置: 对于每个元素,设定两个指针,一个在当前元素之后的位置开始,另一个在数组末尾。
  4. 查找和调整: 当两个指针没有相遇时,计算当前三个数的和。如果和小于零,左指针向右移;如果和大于零,右指针向左移。如果和为零,记录这三个数。
  5. 去重: 在遍历和移动指针时,跳过重复的数,包括对当前固定值的重复数据跳过,以及对当前已发现解的跳过,以找到所有独特的三元组。

代码实现

这是我写的三数和解决算法,请问有没有什么问题?

void quickSort(int *arr, int n) {
    _quickSort(arr, 0, n - 1);
}

void _quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        _quickSort(arr, low, pi - 1);
        _quickSort(arr, pi + 1, high);
    }
}

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);

    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void swap(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    int answerCount = 0;
    // 首先需要对目标数组进行排序
    quickSort(nums, numsSize);
    int left;                                                       //左指针
    int right;                                                      //右指针
    int ansLength = 1000;
    int **answers = (int**)malloc(ansLength * sizeof(int*));         //答案是一个二维数组。
    *returnColumnSizes = (int*)malloc(sizeof(int) * ansLength);             //答案的每个数组的长度组成的数组,本题可以
    for (int i = 0; i < numsSize; i++) {
        //num[i]为本轮计算的固定值
        //如果第一个数就已经大于0了,显然不可能有满足条件的答案。
        if(nums[i] > 0){
            break;
        }
        //限制i > 0防止nums[i - 1]越界,同时保证第一个值一定可以被处理
        if(i > 0 && (nums[i] == nums[i - 1])){
            continue;
        }
        left = i + 1;                                               //左指针去固定值的后一位,不需要考虑i之前的值,因为即便不是第一轮,之前的轮次中肯定已经包含了取i之前的值为其中一个值的方案了
        right = numsSize - 1;                                       //right取最后一个值
        //在left和right不相遇,两个指针都指向一个有效值,还存在答案的可能性,如果相遇了,说明潜在的可能性已经没有了
        while (left < right) {
            // 计算
            int currentSum = nums[i] + nums[left] + nums[right];
            if(currentSum < 0){
                //如果当前三数和小于0,说明当前值不够大,只能把left向后移动取更大的值才可能保证发现满足条件且不重复的新方案
                //如果是想让right也变大,那就违反了方案不能重复的规定,或者发生越界
                left++;
            }else if (currentSum > 0){
                //如果当前三数和大于0,说明当前值不够小,只能把right向后移动取更小的值才可能保证发现满足条件且不重复的新方案
                right--;
            }else{
                //如果当前三数和大于0, 那么现在就找到了一个方案;
                //在添加新元素前检查是否超过当前分配的内存大小。
                if(answerCount == ansLength){
                    ansLength *= 2;
                    answers = (int**)realloc(answers, sizeof(int*) * ansLength);
                    *returnColumnSizes = (int*)realloc(*returnColumnSizes, sizeof(int) * ansLength);
                }
                int* answer = (int*)malloc(sizeof(int) * 3);
                answer[0] = nums[i];
                answer[1] = nums[left];
                answer[2] = nums[right];
                answers[answerCount] = answer;
                //记得写上这个方案的元素数为3
                (*returnColumnSizes)[answerCount] = 3;
                //方案数加一
                answerCount++;

                int currentLeftNum = nums[left];
                // 跳过当前和left对应值相同的元素知道遇到不一样的元素或者遇到right,否则不进入下一轮
                while (nums[left] == currentLeftNum && left < right) {
                    left++;
                }
                // 跳过当前和right对应值相同的元素知道遇到不一样的元素或者遇到left,否则不进入下一轮
                int currentRightNum = nums[right];
                while (nums[right] == currentRightNum && left < right) {
                    right--;
                }
            }
        }
        
    }
    *returnSize = answerCount;
    return answers;
}

image-20240108010610692

18.四数之和

思路

四数之和的基本方案和三数之和类似,都可以使用双指针法, 在三数之和基础上再套一层for循环即可,两层固定数的边界和重复数据处理方案完全一致。需要注意的是Target变成了任意值,因此特殊情况剪枝条件要有变化。

"四数之和"问题的解决方法包括以下几个关键步骤:

  1. 排序数组:首先对数组进行排序,以便更容易处理重复元素和应用双指针技术。
  2. 双层循环:使用两层嵌套循环固定前两个数字。
  3. 双指针技术:对剩余部分的数组使用双指针技术,类似于三数之和的解法,寻找两个数,使得它们的和加上前两个固定的数等于目标值。
  4. 去重:在循环和双指针移动过程中,注意去除重复的组合,确保不重复计算相同的四元组。

代码实现

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

 void quickSort(int *arr, int n) {
    _quickSort(arr, 0, n - 1);
}

void _quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        _quickSort(arr, low, pi - 1);
        _quickSort(arr, pi + 1, high);
    }
}

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);

    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void swap(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes) {
    int answerCount = 0;
    // 首先需要对目标数组进行排序
    quickSort(nums, numsSize);
    int left;                                                       //左指针
    int right;                                                      //右指针
    int ansLength = 1000;
    int **answers = (int**)malloc(ansLength * sizeof(int*));         //答案是一个二维数组。
    *returnColumnSizes = (int*)malloc(sizeof(int) * ansLength);      //答案的每个数组的长度组成的数组
    for (int i = 0; i < numsSize; i++) {
        // 剪枝处理 ,如果当前的值是正数,并且已经比target大了,那么后续一定不会有合适方案了
        if (nums[i] > target && nums[i] >= 0) {
            break;
        }
        //限制i > 0防止nums[i - 1]越界,同时保证第一个值一定可以被处理
        if(i > 0 && (nums[i] == nums[i - 1])){
            continue;
        }
        //内层循环固定值的的起始位置为外层固定值的后一位,为了避免重复用值
        for (int j = i + 1; j < numsSize; j++) {
            // 2级剪枝处理
            if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) {
                break;
            }
            //限制j > i + 1防止nums[j - 1]越界,同时保证第一个值一定可以被处理
            if(j > i + 1 && (nums[j] == nums[j - 1])){
                continue;
            }
            left = j + 1;                                               //左指针去固定值的后一位,不需要考虑i之前的值,因为即便不是第一轮,之前的轮次中肯定已经包含了取i之前的值为其中一个值的方案了
            right = numsSize - 1;                                       //right取最后一个值
            //在left和right不相遇,两个指针都指向一个有效值,还存在答案的可能性,如果相遇了,说明潜在的可能性已经没有了
            while (left < right) {
                // 此处需要强转为long,否则容易越界
                long currentSum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                if(currentSum < target){
                    //如果当前四数和小于target,说明当前值不够大,只能把left向后移动取更大的值才可能保证发现满足条件且不重复的新方案
                    //如果是想让right也变大,那就违反了方案不能重复的规定,或者发生越界
                    left++;
                }else if (currentSum > target){
                    //如果当前四数和大于target,说明当前值不够小,只能把right向后移动取更小的值才可能保证发现满足条件且不重复的新方案
                    right--;
                }else{
                    //如果当前四数和等于于target, 那么现在就找到了一个方案;
                    //在添加新元素前检查是否超过当前分配的内存大小。
                    if(answerCount == ansLength){
                        ansLength *= 2;
                        answers = (int**)realloc(answers, sizeof(int*) * ansLength);
                        *returnColumnSizes = (int*)realloc(*returnColumnSizes, sizeof(int) * ansLength);
                    }
                    int* answer = (int*)malloc(sizeof(int) * 4);
                    answer[0] = nums[i];
                    answer[1] = nums[j];
                    answer[2] = nums[left];
                    answer[3] = nums[right];
                    answers[answerCount] = answer;
                    //记得写上这个方案的元素数为4
                    (*returnColumnSizes)[answerCount] = 4;
                    //方案数加一
                    answerCount++;
                    int currentLeftNum = nums[left];
                    // 跳过当前和left对应值相同的元素知道遇到不一样的元素或者遇到right,否则不进入下一轮
                    while (nums[left] == currentLeftNum && left < right) {
                        left++;
                    }
                    // 跳过当前和right对应值相同的元素知道遇到不一样的元素或者遇到left,否则不进入下一轮
                    int currentRightNum = nums[right];
                    while (nums[right] == currentRightNum && left < right) {
                        right--;
                    }
                }
            }
        }
    }
    *returnSize = answerCount;
    return answers;
}

image-20240108013735686