练题100天——DAY25:升序合并文件+相交链表+多数元素

今天又是3道题的一天,难度范围:★★~★★★★。

今天感觉格外有成就感:在不想敲的时候,告诉自己先写完这道题,最后完成了3道题;在第三题做不出来想放弃想问豆包的时候,问自己如果这是比赛是面试,哪里来的豆包,所以坚持思考,最终做了出来。我为自己感到骄傲。

一.升序合并文件 ★★★☆☆

题目

将两个文件A.txt和B.txt,各存放一行升序的字母,要求把这两个文件的信息按升序合并,保存到一个新文件C.txt中。

思路

这道题整体思路是:获取文件内容到字符串中→合并字符串内容→保存到新文件中。

1.获取文件内容 char* ReadFile(const char* fileName,int* outLen)

1.1 要想获取到文件的内容,那么文件必须已经存在且有内容,所以在这一步之前需要自己创建好文件并添加内容,当然也可以下一个SetFile函数来实现这一步。有了文件和内容之后,就可以通过fread来获取文件的内容。

1.2 利用字符串保存文件内容,需要提前知道文件的长度,所以需要先利用fseek和ftell求出文件的长度fileLen。

1.3 为结果字符串res申请fileLen+1空间,并初始化为0(不用在保存文件内容后手动添加字符串结束符)。利用fread将文件内容存入到字符串中。

2.对两个字符串进行合并排序 char* MergeSort(const char* strA, int lenA, const char* strB, int lenB)

2.1 字符串作为函数参数时,需要同时传递字符串的长度,由此可知,在获取文件内容的时候,还需要获取对应内容的长度,这里可以将长度变量的地址作为参数传递,即设置一个指针作为参数,通过对应指针的值就能获取到文件内容的长度

2.2 合并排序的基础是待合并的两个字符串已经有序,所以要提前利用一种算法先使读取的文件内容有序,我使用的是冒泡排序

2.3 接下来通过合并排序的思想实现即可:创建长度为lenA+lenB+1的结果字符串res,依次比较两个字符串的个字母,将较小的放入res

3.利用前几步创建的函数,实现将两个文件内容的升序合并到新文件中。先读取文件内容,再是内容有序,再利用合并函数合并到一个字符串中,写入指定文件。

代码
cpp 复制代码
//设定文件内容------也可以自己手动创建并写入
void SetFILE() {
	//将升序字母写入
	char strA[] = "hello";
	char strB[] = "world";

	FILE* fpA = fopen("A.txt", "w");
	assert(fpA != NULL);
	if (fpA == NULL) {
		fclose(fpA);
		return;
	}
	fwrite(strA, sizeof(char), strlen(strA), fpA);
	fclose(fpA);

	FILE* fpB = fopen("B.txt", "w");
	assert(fpB != NULL);
	if (fpB == NULL) {
		fclose(fpB);
		return;
	}
	fwrite(strB, sizeof(char), strlen(strB), fpB);
	fclose(fpB);
}
//读取文件内容+长度
char* ReadFile(const char* fileName,int* outLen) {
	assert(fileName != NULL);
	if (fileName == NULL) {
		return NULL;
	}
	FILE* fp = fopen(fileName, "r");
	assert(fp != NULL);
	if (fp == NULL) {
		*outLen = 0;
		return NULL;
	}
	//求文件的长度
	fseek(fp, 0, SEEK_END);
	long fileLen = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	char* res = (char*)calloc(sizeof(char), fileLen + 1);
	assert(res != NULL);
	if (res == NULL) {
		*outLen = 0;
		return NULL;
	}
	*outLen = (int)fread(res, sizeof(char), fileLen, fp);

	fclose(fp);
	return res;
}
//给字符串排序------冒泡排序
void StrBubbleSort(char* str,int len) {
	if (len == 0) {
		return;
	}
	int i = 0;
	int end = len - 1;
	while (end > 0) {
		while (i < len - 1) {
			if (str[i] > str[i+1]) {
				char temp = str[i];
				str[i] = str[i+1];
				str[i+1] = temp;
			}
			i++;
		}
		end--;
		i = 0;
	}
}
//合并排序
char* MergeSort(const char* strA, int lenA, const char* strB, int lenB,int* outLen) {
	assert(strA != NULL && strB != NULL);
	if (strA == NULL || strB == NULL) {
		*outLen = 0;
		return NULL;
	}
	
	int resLen = lenA + lenB + 1;
	char* res = (char*)calloc(sizeof(char), resLen);
	assert(res != NULL);
	if (res == NULL) {
		*outLen = 0;
		return NULL;
	}

	int i = 0, j = 0, k = 0;
	while (i < lenA && j < lenB) {
		if (strA[i] < strB[j]) {
			res[k++] = strA[i++];
		}
		else {
			res[k++] = strB[j++];
		}
	}
	while (i < lenA) res[k++] = strA[i++];
	while (j < lenB) res[k++] = strB[j++];

	*outLen = resLen;
	return res;
}
//合并排序两个文件内容
void MergeFile() {
	int lenA = 0;
	int lenB = 0;
	char* strA = ReadFile("A.txt", &lenA);
	char* strB = ReadFile("B.txt", &lenB);
	assert(strA != NULL && strB != NULL);
	if (strA == NULL || strB == NULL) {
		return;
	}
	printf("A.txt:%s\n", strA);
	printf("B.txt:%s\n", strB);
	//将字符串用升序排列
	StrBubbleSort(strA, lenA);
	StrBubbleSort(strB, lenB);

	int fileCLen = 0;
	char* res = MergeSort(strA, lenA, strB, lenB, &fileCLen);
	assert(res != NULL);
	if (res == NULL) {
		return;
	}

	FILE* fp = fopen("C.txt", "w");
	assert(fp != NULL);
	if (fp == NULL) {
		return;
	}
	fwrite(res, sizeof(char), fileCLen, fp);
	printf("升序合并结果:%s 已成功写入C.txt\n", res);
	fclose(fp);
	free(strA);
	free(strB);
	free(res);
}
int main() {
    SetFILE();
	MergeFile();
	return 0;
}

复杂度

时间复杂度:整体时间复杂度由各个函数时间复杂度"相加"后的最高项决定。读取文件O(n);冒泡排序O(n²);合并O(m+k),其中m、k是两个字符串的长度,所以最终时间复杂度是O(n+n²+n+n)=O(n²)。

空间复杂度:文件读取中动态分配了两个字符串的内存(O (m + k));归并合并动态分配了结果字符串的内存(O (m + k)),所以最终空间复杂度是O(m+k),其中m、k是两个字符串的长度。

二.相交链表 ★★☆☆☆

题目

160. 相交链表 给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

我的思路

创建一个哈希集合,然后先将链表1装入哈希集合,遍历链表2,如果在哈希集合中找到链表2的节点,说明两个链表相交,返回相交的节点;反之循环结束,返回NULL

代码
cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> s1;
        //1.将链表存入哈希表
        while(headA != NULL){
            s1.insert(headA);
            headA=headA->next;
        }
        //2.在哈希表中找第二个链表节点
        while(headB != NULL){
            //能找到就相交
            if(s1.find(headB)!=s1.end()){
                return headB;
            }
            headB=headB->next;
        }
        //没找到就不相交
        return NULL;
    }
};

复杂度

m、n分别为两个链表的长度

时间复杂度:O(m+n)。将链表1复制到哈希集合上循环m次,在哈希集合中寻找链表2的节点最多循环n次,所以时间复杂度为O(m+n)。

空间复杂度:O(m)。哈希集合的空间大小------存储链表 A 的所有节点。

官方题解思路------双指针法

首先判断两个链表是否为空,有一个为空就直接返回NULL。

两者都不为空,创建两个临时变量分别指向两个链表的头结点,然后移动指针,当指针移动到最后时,使其指向另一条链表的头结点,这样以来,两者会因为移动的距离相等而指向同一块位置,返回此时的位置即可。

说明:当两个链表不相交时,走了一样的距离时都会指向NULL,所以此时刚好返回NULL;当两个链表相交时,最终会在相交节点处相等。

也可以去看官方题解的说明,很详细。

代码
cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==NULL || headB==NULL){
            return NULL;
        }
        ListNode* p=headA;
        ListNode* q=headB;
        while(p != q){
            //循环→两指针移动距离一样→相等则相交;不相等ze
            p= p==NULL ? headB :p->next;
            q= q==NULL ? headA : q->next;
        }
        return p;
    }
};

复杂度

时间复杂度:O(m+n)。最坏情况下,两个指针会遍历两个链表的长度m+n,要么在最后一个节点相交返回对应节点,要么不相交返回NULL。

空间复杂度:O(1)。只有指针变量的空间,常数级。

三.多数元素 ★★★★☆

题目

169. 多数元素

给定一个大小为 n的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

说明

其实这道题的大体思路不难,就是记录每个数的个数,将个数>一半的输出,但是我卡在了怎么降低时间复杂度上。一开始我尝试用multiset(就是可以重复元素的哈希集合)自带的count函数,将count>len/2的输出,但是这个函数最坏情况下的时间复杂度就是O(n),它又是在给multiset赋值的循环中,所以时间复杂度为O(n²),在遇到很极端的用例时运行就会超时。

然后我想破脑袋,想到了哈希表可以存放两个元素,所以我把其中的key用来保存数组元素,value用来保存对应元素的个数,这样一来时间复杂度就下来了。

我的思路

利用哈希表:其中的key用来保存数组元素,value用来保存对应元素的个数。遍历数组,如果在哈希表中找到了对应元素,就将value++,同时判断value是否>len/2,大于直接退出循环,输出这个数;反之,令value为1,表示个数为1。

代码
cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int len=nums.size();
        if(len<=2){
            return nums[0];
        }
        int res=0;
        unordered_map<int,int> map;
        for(int i=0;i<len;i++){
            if(map.find(nums[i])!=map.end()){
                map[nums[i]]++;
                if(map[nums[i]]>len/2){
                    res=nums[i];
                    break;
                }
            }else{
                map[nums[i]]=1;
            }
        }
        return res;
    }
};

复杂度

时间复杂度:O(n)。

空间复杂度:O(n)。最坏情况下哈希表长度为n/2,因为一定存在多数元素,所以数组最多有n/2个元素。

官方题解

思路1------哈希表

我的思路和这个大差不差,但是在循环中有些许差异。

1.不用通过find查找后再修改value,直接对map[nums[i]]++; 即可,因为当nums[i]不存在时,nums[i]会自动插入,且value会自动初始化为0,然后自增。

2.不找个数大于len/2的数,而找个数最多的数:使用打擂台的方法,维护最大的值,即遍历哈希映射中的所有键值对,返回值最大的键。

代码
cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> counts;
        int majority = 0, cnt = 0;
        for (int num: nums) {
            ++counts[num];
            if (counts[num] > cnt) {
                majority = num;
                cnt = counts[num];
            }
        }
        return majority;
    }
};
复杂度

思路2------排序

将数组元素升序排列,因为多数元素是指个数>数组长度n的一半的元素,所以下标为n/2的元素一定是多数。

这个思路真的绝,果然最后编程考的还是数学思维。

代码
cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};
复杂度

时间复杂度:O(nlogn)。将数组排序的时间复杂度为 O(nlogn)。

空间复杂度:O(logn)。如果使用语言自带的排序算法,需要使用 O(logn) 的栈空间。如果自己编写堆排序,则只需要使用 O(1) 的额外空间。

官方还有三种方法,但我今天就到这了,下次再续。

思路3------随机化

思路4------分治

思路5------Boyer-Moore 投票算法

相关推荐
南_山无梅落4 小时前
8.Python3字典(dict):键值的增删改查_入门到进阶
python·算法
柒.梧.4 小时前
数据结构:二叉排序树,平衡二叉树,红黑树的介绍
数据结构·算法
Xの哲學4 小时前
Linux ALSA音频架构: 从内核驱动到应用开发的全面解析
linux·服务器·算法·架构·边缘计算
Queenie_Charlie4 小时前
小明统计数组
数据结构·c++·set
是毛毛吧4 小时前
数据结构与算法11种排序算法全面对比分析
数据结构·算法
郝学胜-神的一滴4 小时前
Separate Buffer、InterleavedBuffer 策略与 OpenGL VAO 深度解析
开发语言·c++·程序人生·算法·游戏程序·图形渲染
蒙奇D索大4 小时前
【数据结构】考研408 | B树收官:插入与删除的平衡艺术——分裂、合并与借位
数据结构·笔记·b树·考研·改行学it
长安er4 小时前
LeetCode 102/103/513 二叉树层序遍历(BFS)三类经典题解题总结
数据结构·算法·leetcode·二叉树·bfs·层序遍历
java修仙传4 小时前
力扣hot100:搜索插入位置
算法·leetcode·职场和发展