今天又是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. 相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 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)。只有指针变量的空间,常数级。
三.多数元素 ★★★★☆
题目
给定一个大小为 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) 的额外空间。
官方还有三种方法,但我今天就到这了,下次再续。