代码随想录算法训练营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;
}
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;
}
15.三数之和
思路
双指针法解决三数之和问题的步骤如下:
- 排序: 首先对数组进行排序,这是保证解无重复值的关键。
- 遍历: 使用一个循环遍历数组中的每个元素,作为三数之和中的第一个数。
- 双指针设置: 对于每个元素,设定两个指针,一个在当前元素之后的位置开始,另一个在数组末尾。
- 查找和调整: 当两个指针没有相遇时,计算当前三个数的和。如果和小于零,左指针向右移;如果和大于零,右指针向左移。如果和为零,记录这三个数。
- 去重: 在遍历和移动指针时,跳过重复的数,包括对当前固定值的重复数据跳过,以及对当前已发现解的跳过,以找到所有独特的三元组。
代码实现
这是我写的三数和解决算法,请问有没有什么问题?
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;
}
18.四数之和
思路
四数之和的基本方案和三数之和类似,都可以使用双指针法, 在三数之和基础上再套一层for循环即可,两层固定数的边界和重复数据处理方案完全一致。需要注意的是Target变成了任意值,因此特殊情况剪枝条件要有变化。
"四数之和"问题的解决方法包括以下几个关键步骤:
- 排序数组:首先对数组进行排序,以便更容易处理重复元素和应用双指针技术。
- 双层循环:使用两层嵌套循环固定前两个数字。
- 双指针技术:对剩余部分的数组使用双指针技术,类似于三数之和的解法,寻找两个数,使得它们的和加上前两个固定的数等于目标值。
- 去重:在循环和双指针移动过程中,注意去除重复的组合,确保不重复计算相同的四元组。
代码实现
/**
* 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;
}